Last active
November 30, 2020 20:25
-
-
Save illmatix/f9e24a20446a5b95fb13f628c136246b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| <?php | |
| namespace App\Observers\CalculatedTimesheetHours; | |
| use Carbon\Carbon; | |
| use Illuminate\Database\Eloquent\ModelNotFoundException; | |
| use App\Models\CalculatedTimesheetHours; | |
| use App\Models\EmployeeBillingRates; | |
| use App\Models\OvertimeRules; | |
| use App\Repositories\CalculatedTimesheetHours\CalculatedTimesheetHoursRepositoryInterface; | |
| use App\Repositories\OvertimeRules\OvertimeRulesRepositoryInterface; | |
| use App\Repositories\PayPeriods\PayPeriodsRepositoryInterface; | |
| use App\Repositories\SpecialOvertimeRules\SpecialOvertimeRulesRepositoryInterface; | |
| /** | |
| * Class CalculatedTimesheetHoursUpdate | |
| * @package App\Observers\CalculatedTimesheetHours | |
| */ | |
| class CalculatedTimesheetHoursUpdate | |
| { | |
| /** | |
| * @var string $fieldTicketVoidedApplicationStatusName | |
| */ | |
| protected $fieldTicketVoidedApplicationStatusName = 'Void'; | |
| // Repository Interfaces. | |
| private $payPeriods; | |
| private $overtimeRules; | |
| private $specialOvertimeRules; | |
| private $calculatedTimesheetHours; | |
| // References. | |
| private $currentPayPeriod; | |
| private $currentTimesheetEntry; | |
| // Operation from observer | |
| private $op = 'create'; | |
| // Counters for comparing historic data. | |
| private $dailyHours = 0; | |
| private $weeklyHours = 0; | |
| private $monthlyHours = 0; | |
| // Standard and overtime hours. | |
| private $standardHours = []; | |
| private $overtimeHours = []; | |
| // Entries to be created. | |
| private $entries = []; | |
| /** | |
| * CalculatedTimesheetHoursUpdate constructor. | |
| * | |
| * @param PayPeriodsRepositoryInterface $payPeriods | |
| * @param OvertimeRulesRepositoryInterface $overtimeRules | |
| * @param SpecialOvertimeRulesRepositoryInterface $specialOvertimeRules | |
| * @param CalculatedTimesheetHoursRepositoryInterface $calculatedTimesheetHours | |
| */ | |
| public function __construct( | |
| PayPeriodsRepositoryInterface $payPeriods, | |
| OvertimeRulesRepositoryInterface $overtimeRules, | |
| SpecialOvertimeRulesRepositoryInterface $specialOvertimeRules, | |
| CalculatedTimesheetHoursRepositoryInterface $calculatedTimesheetHours | |
| ) { | |
| $this->payPeriods = $payPeriods; | |
| $this->overtimeRules = $overtimeRules->getAllActive(); | |
| $this->specialOvertimeRules = $specialOvertimeRules | |
| ->selectAllForSubscription() | |
| ->with('employeeRoles') | |
| ->first(); | |
| $this->calculatedTimesheetHours = $calculatedTimesheetHours; | |
| } | |
| /** | |
| * @param $timesheetEntry | |
| * @param string $op | |
| */ | |
| public function processTimesheetEntry($timesheetEntry, $op = 'create') | |
| { | |
| $this->op = $op; | |
| // Set current timesheet and pay period so other methods can use it. | |
| $this->currentTimesheetEntry = $timesheetEntry; | |
| $this->currentPayPeriod = $this->payPeriods->getWithinDate($this->currentTimesheetEntry->entry_date); | |
| // Parse the date with carbon to allow switching format. | |
| $fieldTicketEntryDate = Carbon::parse($this->currentTimesheetEntry->entry_date); | |
| // 1 based day of the week value. | |
| $fieldTicketWeekDay = $fieldTicketEntryDate->format('w') - 1; | |
| // 0 based day of the week value | |
| $weekStartDay = $this->specialOvertimeRules->first()['week_start']; | |
| // Get historic data to determine current overtime and standard time amounts. | |
| $entriesInMonth = $this->getMonthData(); | |
| $lastDate = null; | |
| foreach ($entriesInMonth as $entryInMonth) { | |
| $entryDate = Carbon::parse($entryInMonth->date); | |
| // 1 based day of the week value. | |
| $entryWeekDay = $entryDate->format('w') - 1; | |
| // Determine if historic item is on the first day of the week. | |
| if ($entryWeekDay == $weekStartDay) { | |
| $this->weeklyHours = 0; | |
| } | |
| // Only reset the day counter if checking a new date. | |
| // This handles overtime and standard time on the same date. | |
| if (empty($lastDate) == false && $entryDate->format('Y-m-d') !== $lastDate) { | |
| $this->dailyHours = 0; | |
| } | |
| // Skip items after ticket date. processing of these items will happen later. | |
| if ($entryDate > $fieldTicketEntryDate) { | |
| continue; | |
| } | |
| $this->checkHoursAgainstOvertimeRules((integer)$entryInMonth->hours, $entryInMonth->date, true); | |
| $lastDate = $entryDate->format('Y-m-d'); | |
| } | |
| $this->checkHoursAgainstOvertimeRules((integer)$this->currentTimesheetEntry->quantity, $this->currentTimesheetEntry->entry_date); | |
| $this->recalculateExistingEntries(); | |
| $this->handleTransaction(); | |
| } | |
| /** | |
| * Calculated standard hours and over time hours from a single timesheet entry. | |
| * | |
| * @param int $hours | |
| * @param $date | |
| * @param bool $isExistingEntry | |
| */ | |
| private function checkHoursAgainstOvertimeRules(int $hours, $date, bool $isExistingEntry = false) | |
| { | |
| // increment hour counts for each period that may have a rule. | |
| $this->monthlyHours += $hours; | |
| $this->weeklyHours += $hours; | |
| $this->dailyHours = $hours; | |
| // If the item isn't historic overtime determine which rules the hours fall into. | |
| if ($isExistingEntry == false) { | |
| // initialize the standard hours array | |
| if (isset($this->standardHours[$date]) == false) { | |
| $this->standardHours[$date] = 0; | |
| } | |
| $this->overtimeHours[$date] = []; | |
| foreach ($this->overtimeRules as $overtimeRule) { | |
| // Set default value for this overtime rate multiplier. | |
| $this->overtimeHours[$date][$overtimeRule->overtime_rates] = !empty($this->overtimeHours[$overtimeRule->overtime_rates]) | |
| ? $this->overtimeHours[$overtimeRule->overtime_rates] | |
| : 0; | |
| // Determine if monthly rule is set and at limit. | |
| if (empty($overtimeRule->monthly_hours) == false && $this->monthlyHours >= $overtimeRule->monthly_hours) { | |
| $this->overtimeHours[$date][$overtimeRule->overtime_rates] += $hours; | |
| continue; | |
| } | |
| // Determine if weekly rule is set and at limit. | |
| if (empty($overtimeRule->weekly_hours) == false && $this->weeklyHours >= $overtimeRule->weekly_hours) { | |
| $this->overtimeHours[$date][$overtimeRule->overtime_rates] += $hours; | |
| continue; | |
| } | |
| // Determine if daily rule is set and at limit. | |
| if (empty($overtimeRule->daily_hours) == false && $this->dailyHours >= $overtimeRule->daily_hours) { | |
| $this->overtimeHours[$date][$overtimeRule->overtime_rates] += $hours - $overtimeRule->daily_hours; | |
| $this->standardHours[$date] += $overtimeRule->daily_hours; | |
| continue; | |
| } | |
| if ($hours <= $overtimeRule->daily_hours) { | |
| $this->standardHours[$date] += $hours; | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * Recalculated existing entries after the field ticket date to conform with the overtime rules. | |
| */ | |
| private function recalculateExistingEntries() | |
| { | |
| $fieldTicketDate = Carbon::parse($this->currentTimesheetEntry->entry_date); | |
| $existingEntries = CalculatedTimesheetHours | |
| ::where('date', '>', $fieldTicketDate) | |
| ->where('employees_id', '=', $this->currentTimesheetEntry->employeeTimesheet->employees_id) | |
| ->get(); | |
| foreach ($this->overtimeRules as $overtimeRule) { | |
| foreach ($existingEntries as $existingEntry) { | |
| $existingEntry->is_ot = false; | |
| $existingEntry->overtime_rate = '1.00'; | |
| if ((empty($overtimeRule->monthly_hours) == false && $this->monthlyHours >= $overtimeRule->monthly_hours) | |
| || (empty($overtimeRule->weekly_hours) == false && $this->weeklyHours >= $overtimeRule->weekly_hours) | |
| || (empty($overtimeRule->daily_hours) == false && $this->dailyHours >= $overtimeRule->daily_hours)) { | |
| $existingEntry->is_ot = true; | |
| $existingEntry->overtime_rate = $overtimeRule->overtime_rates; | |
| } | |
| $existingEntry->save(); | |
| } | |
| } | |
| } | |
| /** | |
| * Create entries ready for inserting into the database. | |
| */ | |
| private function handleTransaction() | |
| { | |
| if ($this->op == 'update') { | |
| $this->update(); | |
| } elseif ($this->op == 'delete') { | |
| $this->delete(); | |
| } else { | |
| $this->create(); | |
| } | |
| } | |
| /** | |
| * Update existing item with new values. | |
| */ | |
| private function update() | |
| { | |
| // Get changed Attributes. | |
| $changedAttributes = $this->currentTimesheetEntry->getDirty(); | |
| // Find the existing Entries for the date. | |
| $entryDate = Carbon::parse($this->currentTimesheetEntry->entry_date); | |
| $entryDateYmd = $entryDate->format('Y-m-d'); | |
| $entryDatedMY = $entryDate->format('d-M-Y'); | |
| $existingEntries = CalculatedTimesheetHours | |
| ::where('date', '=', $entryDateYmd) | |
| ->where('employees_id', '=', $this->currentTimesheetEntry->employeeTimesheet->employees_id); | |
| foreach ($existingEntries->get() as $existingEntry) { | |
| if (array_key_exists('quantity', $changedAttributes)) { | |
| foreach ($this->overtimeRules as $overtimeRule) { | |
| if ((empty($overtimeRule->monthly_hours) == false && $this->monthlyHours >= $overtimeRule->monthly_hours) | |
| || (empty($overtimeRule->weekly_hours) == false && $this->weeklyHours >= $overtimeRule->weekly_hours) | |
| || (empty($overtimeRule->daily_hours) == false && $this->dailyHours >= $overtimeRule->daily_hours)) { | |
| $existingEntry->hours = $this->currentTimesheetEntry->quantity; | |
| } else { | |
| if ($existingEntry->is_ot == false && $existingEntry->hours !== $this->standardHours[$entryDatedMY]) { | |
| $existingEntry->hours = $this->standardHours[$entryDatedMY]; | |
| } | |
| if ($existingEntry->is_ot == true | |
| && array_key_exists($existingEntry->overtime_rate, $this->overtimeHours[$entryDatedMY]) | |
| && $existingEntry->hours !== $this->overtimeHours[$entryDatedMY][$existingEntry->overtime_rate]) { | |
| $existingEntry->hours = $this->overtimeHours[$entryDatedMY][$existingEntry->overtime_rate]; | |
| } | |
| } | |
| } | |
| } | |
| if (array_key_exists('employee_roles_id', $changedAttributes)) { | |
| $existingEntry->employee_roles_id = $this->hasSeparatedEmployeeRoles($changedAttributes['employee_roles_id']); | |
| } | |
| if (array_key_exists('employee_billing_rates_id', $changedAttributes)) { | |
| $existingEntry->employee_billing_rates_id = $changedAttributes['employee_billing_rates_id']; | |
| } | |
| if (array_key_exists('pay_rate', $changedAttributes)) { | |
| $existingEntry->pay_rate = $changedAttributes['pay_rate']; | |
| } | |
| $this->saveEntry($existingEntry); | |
| } | |
| } | |
| /** | |
| * Delete existing timesheet entries. | |
| */ | |
| private function delete() | |
| { | |
| // fill in placeholder | |
| } | |
| /** | |
| * Create a new timesheet entry. | |
| */ | |
| private function create() | |
| { | |
| // Pull current billing rate for employee using the data attached to the timesheet. | |
| $employeeBillingRates = $this->getEmployeeBillingRates(); | |
| // Throw error if no billing rates can be found. | |
| if (empty($employeeBillingRates) == true) { | |
| $message = 'Cannot find employee billing rates by charge_units_id, employee_roles_id, pay_rate '; | |
| $message .= '[' . $this->currentTimesheetEntry->charge_units_id | |
| . ', ' . $this->currentTimesheetEntry->employee_roles_id | |
| . ', ' . $this->currentTimesheetEntry->pay_rate . ']'; | |
| throw new ModelNotFoundException($message); | |
| } | |
| $employeeBillingRates = $employeeBillingRates->first(); | |
| // If there are standard hours to track populate the entry data. | |
| foreach ($this->standardHours as $dateKey => $quantity) { | |
| if ($quantity == 0) { | |
| continue; | |
| } | |
| $date = Carbon::parse($dateKey); | |
| $this->entries[] = $this->populateNewEntry( | |
| $employeeBillingRates->employee_billing_rates_id, | |
| $date, | |
| $quantity, | |
| $this->currentTimesheetEntry->pay_rate, | |
| 1, | |
| false | |
| ); | |
| } | |
| // If there are overtime hours to track populate the entry data. | |
| foreach ($this->overtimeHours as $dateKey => $overtimeGroup) { | |
| $date = Carbon::parse($dateKey); | |
| foreach ($overtimeGroup as $overtimeRate => $quantity) { | |
| if ($quantity == 0) { | |
| continue; | |
| } | |
| $this->entries[] = $this->populateNewEntry( | |
| $employeeBillingRates->employee_billing_rates_id, | |
| $date, | |
| $quantity, | |
| $this->currentTimesheetEntry->pay_rate, | |
| $overtimeRate, | |
| true | |
| ); | |
| } | |
| } | |
| // Save new entries that have been created. | |
| foreach ($this->entries as $entry) { | |
| $this->saveEntry($entry); | |
| } | |
| } | |
| /** | |
| * @param $employee_billing_rates_id | |
| * @param $date | |
| * @param $hours | |
| * @param $pay_rate | |
| * @param $overtime_rate | |
| * @param bool $is_ot | |
| * | |
| * @return CalculatedTimesheetHours | |
| */ | |
| private function populateNewEntry($employee_billing_rates_id, $date, $hours, $pay_rate, $overtime_rate, $is_ot = false): CalculatedTimesheetHours | |
| { | |
| $calculatedTimesheetHours = new CalculatedTimesheetHours(); | |
| $calculatedTimesheetHours->employees_id = $this->currentTimesheetEntry->employeeTimesheet->employees_id; | |
| $calculatedTimesheetHours->employee_roles_id = $this->hasSeparatedEmployeeRoles($this->currentTimesheetEntry->employee_roles_id); | |
| $calculatedTimesheetHours->employee_billing_rates_id = $employee_billing_rates_id; | |
| $calculatedTimesheetHours->pay_periods_id = $this->currentPayPeriod->pay_periods_id; | |
| $calculatedTimesheetHours->date = $date; | |
| $calculatedTimesheetHours->hours = $hours; | |
| $calculatedTimesheetHours->pay_rate = $pay_rate; | |
| $calculatedTimesheetHours->overtime_rate = $overtime_rate; | |
| $calculatedTimesheetHours->is_ot = $is_ot; | |
| return $calculatedTimesheetHours; | |
| } | |
| /** | |
| * @param CalculatedTimesheetHours $calculatedTimesheetHours | |
| */ | |
| private function saveEntry(CalculatedTimesheetHours $calculatedTimesheetHours) | |
| { | |
| $calculatedTimesheetHours->save(); | |
| } | |
| /** | |
| * @return mixed | |
| */ | |
| private function getMonthData() | |
| { | |
| $employee_id = $this->currentTimesheetEntry->employeeTimesheet->employees_id; | |
| // Determine the month and year from the field ticket. | |
| $fieldTicketEntryDate = Carbon::createFromFormat('d-M-Y', $this->currentTimesheetEntry->entry_date); | |
| $fieldTicketMonth = $fieldTicketEntryDate->format('M'); | |
| $fieldTicketYear = $fieldTicketEntryDate->format('Y'); | |
| // Create new date string from ticket date using the subscription defined start of month. | |
| $monthStartString = $fieldTicketMonth . ' ' . ($this->specialOvertimeRules->first()['month_start'] + 1) . ' ' . $fieldTicketYear; | |
| $monthStart = Carbon::CreateFromFormat('M d Y', $monthStartString); | |
| // Define the range for the subscription defined "month". | |
| $from = $monthStart; | |
| $to = $monthStart->copy()->addMonth()->subDay(); | |
| $getByEmployeeIdInMonth = $this->calculatedTimesheetHours->getByEmployeeIdInMonth($employee_id, $from, $to); | |
| return $getByEmployeeIdInMonth->get(); | |
| } | |
| /** | |
| * Get active billing rate for specific role, and pay rate. | |
| * Pay rate will be synced with the role on the timesheet by this point. | |
| * | |
| * @return Collection|EmployeeBillingRates[] | |
| */ | |
| private function getEmployeeBillingRates() | |
| { | |
| $employeeBillingRates = EmployeeBillingRates | |
| ::where('employee_billing_rates.charge_units_id', $this->currentTimesheetEntry->charge_units_id) | |
| ->where('employee_roles_id', $this->currentTimesheetEntry->employee_roles_id) | |
| ->where('pay_rate', $this->currentTimesheetEntry->pay_rate) | |
| ->get(); | |
| return $employeeBillingRates; | |
| } | |
| /** | |
| * @param $employee_roles_id | |
| * | |
| * @return int|null | |
| */ | |
| private function hasSeparatedEmployeeRoles($employee_roles_id) | |
| { | |
| $check = $this->specialOvertimeRules | |
| ->employeeRoles | |
| ->where('employee_roles_id', '=', $employee_roles_id); | |
| return $check->isNotEmpty() ? $check->first()->employee_roles_id : null; | |
| } | |
| /** | |
| * Check if the field ticket is void. | |
| * | |
| * @param FieldTickets $timesheetEntry | |
| * | |
| * @return bool | |
| */ | |
| public function fieldTicketActive($timesheetEntry): bool | |
| { | |
| $applicationStatus = $timesheetEntry->fieldTicket->fieldTicketStatus->applicationStatus; | |
| if ($applicationStatus->is_void) { | |
| return false; | |
| } | |
| if ($applicationStatus->name == $this->fieldTicketVoidedApplicationStatusName) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment