From 30ed01e717cd7a8f12e9db6d43c4aae7ac4ba8b8 Mon Sep 17 00:00:00 2001 From: Jeppe Bundgaard Date: Wed, 13 May 2026 12:19:07 +0200 Subject: [PATCH] Refactor transaction handling and improve shift time logic Introduced a new `getAllTransactionIds` utility function to better handle filtering of transaction IDs. Replaced outdated `start`/`end` time properties with `checkIn`/`checkOut` objects for shift records, alongside added validation in tests to exclude shifts without punches. Enhanced invoicing tests to ensure flags remain visible even for excluded transactions. --- .../classes/workfeed_shift_time_resolver.php | 14 +++---- .../DepartmentWeatherWorkfeedHoursTest.php | 37 ++++++++++++++++--- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/services/nginx/app/classes/workfeed_shift_time_resolver.php b/services/nginx/app/classes/workfeed_shift_time_resolver.php index 64983ff0..ced27cc2 100644 --- a/services/nginx/app/classes/workfeed_shift_time_resolver.php +++ b/services/nginx/app/classes/workfeed_shift_time_resolver.php @@ -22,13 +22,12 @@ final class workfeed_shift_time_resolver } $actual_start = self::firstDateTimeFromPaths($record, [ + 'checkIn.time', + 'checkIn', 'actualStart', 'actualStartTime', 'clockIn', 'clockInTime', - 'start', - 'startTime', - 'from', 'approval.originalStart', ]); $scheduled_end = self::firstDateTimeFromPaths($record, [ @@ -38,17 +37,14 @@ final class workfeed_shift_time_resolver 'to', ]); $actual_only_end = self::firstDateTimeFromPaths($record, [ + 'checkOut.time', + 'checkOut', 'actualEnd', 'actualEndTime', 'clockOut', 'clockOutTime', ]); - $current_end = self::firstDateTimeFromPaths($record, [ - 'end', - 'endTime', - 'to', - ]); - $saved_actual_end = $actual_only_end ?? $current_end; + $saved_actual_end = $actual_only_end; if ($actual_start === null || $scheduled_end === null || $saved_actual_end === null) { return null; diff --git a/services/nginx/app/tests/Unit/Workfeed/DepartmentWeatherWorkfeedHoursTest.php b/services/nginx/app/tests/Unit/Workfeed/DepartmentWeatherWorkfeedHoursTest.php index 94861641..42d9052e 100644 --- a/services/nginx/app/tests/Unit/Workfeed/DepartmentWeatherWorkfeedHoursTest.php +++ b/services/nginx/app/tests/Unit/Workfeed/DepartmentWeatherWorkfeedHoursTest.php @@ -23,22 +23,26 @@ it('calculates workfeed employee hours for the hour slot based on overlap', func $shifts = [ (object)[ 'departmentID' => 'dep_1', - 'start' => '2026-03-24T13:00:00+00:00', + 'checkIn' => (object)['time' => '2026-03-24T13:00:00+00:00'], + 'checkOut' => (object)['time' => '2026-03-24T14:00:00+00:00'], 'end' => '2026-03-24T14:00:00+00:00', ], (object)[ 'departmentID' => 'dep_1', - 'start' => '2026-03-24T13:30:00+00:00', + 'checkIn' => (object)['time' => '2026-03-24T13:30:00+00:00'], + 'checkOut' => (object)['time' => '2026-03-24T15:00:00+00:00'], 'end' => '2026-03-24T15:00:00+00:00', ], (object)[ 'departmentID' => 'dep_other', - 'start' => '2026-03-24T13:00:00+00:00', + 'checkIn' => (object)['time' => '2026-03-24T13:00:00+00:00'], + 'checkOut' => (object)['time' => '2026-03-24T14:00:00+00:00'], 'end' => '2026-03-24T14:00:00+00:00', ], (object)[ 'departmentID' => 'dep_1', - 'start' => '2026-03-24T14:00:00+00:00', + 'checkIn' => (object)['time' => '2026-03-24T14:00:00+00:00'], + 'checkOut' => (object)['time' => '2026-03-24T13:00:00+00:00'], 'end' => '2026-03-24T13:00:00+00:00', ], ]; @@ -93,12 +97,14 @@ it('calculates workfeed employee hours across multiple departments for one hour $shifts = [ (object)[ 'departmentID' => 'dep_1', - 'start' => '2026-03-24T13:00:00+00:00', + 'checkIn' => (object)['time' => '2026-03-24T13:00:00+00:00'], + 'checkOut' => (object)['time' => '2026-03-24T14:00:00+00:00'], 'end' => '2026-03-24T14:00:00+00:00', ], (object)[ 'departmentID' => 'dep_2', - 'start' => '2026-03-24T13:00:00+00:00', + 'checkIn' => (object)['time' => '2026-03-24T13:00:00+00:00'], + 'checkOut' => (object)['time' => '2026-03-24T14:00:00+00:00'], 'end' => '2026-03-24T14:00:00+00:00', ], ]; @@ -108,6 +114,25 @@ it('calculates workfeed employee hours across multiple departments for one hour expect($hours)->toBe(2.0); }); +it('does not count shifts without punches when checkIn/checkOut are null', function (): void { + $route = new moduleWeatherAPIRoute(); + $slot = new DateTime('2026-03-24T13:00:00+00:00'); + + $shifts = [ + (object)[ + 'departmentID' => 'dep_1', + 'checkIn' => null, + 'checkOut' => null, + 'start' => '2026-03-24T13:00:00+00:00', + 'end' => '2026-03-24T14:00:00+00:00', + ], + ]; + + $hours = weather_route_invoke_private($route, 'calculateWorkfeedEmployeeHoursForHour', [$shifts, 'dep_1', $slot]); + + expect($hours)->toBe(0.0); +}); + it('counts overtime minutes when a saved shift end extends past the approved original end', function (): void { $route = new moduleWeatherAPIRoute(); $slot = new DateTime('2026-03-23T18:00:00+00:00');