diff --git a/services/nginx/app/objects/orders_o.php b/services/nginx/app/objects/orders_o.php index d80c5766..81b6b8e0 100644 --- a/services/nginx/app/objects/orders_o.php +++ b/services/nginx/app/objects/orders_o.php @@ -906,6 +906,68 @@ class orders_o extends db return (bool)$count; } + /** + * Get the timestamp of the most recent completed wash for a license plate. + * Used by the front page to show the "last washed" hint when a plate is + * scanned (DHL trailer pick-up use case, TRU-78 / DRIFT 17). + * + * Only orders that have at least one non-deleted order item are + * considered (mirrors the contract used by customer_vehicles_o:: + * getLastOrderByPlate() so the timestamp is always backed by a real wash). + * + * @param string $reg_1 The license plate to look up + * @return string|null MySQL datetime string of the most recent qualifying + * order's `created_at`, or null when the plate has + * never been washed. + */ + public function getLastWashTimestampForPlate(string $reg_1): ?string + { + $normalized_reg_1 = trim($reg_1); + if ($normalized_reg_1 === '') { + return null; + } + + $orders = self::getFieldsWhere([ + 'reg_1' => $normalized_reg_1, + 'deleted_at' => null, + ], [ + 'id', + ]); + + // Walk the orders newest-first and return the first one that actually + // has at least one non-deleted order item. + $candidate_ids = array_reverse(array_map(static function ($row) { + return (int)($row['id'] ?? 0); + }, $orders)); + + foreach ($candidate_ids as $order_id) { + if ($order_id <= 0) { + continue; + } + + $has_items = (new order_items_o())->getFieldsWhere([ + 'order_id' => $order_id, + 'deleted_at' => null, + ], ['id']); + + if (count($has_items) === 0) { + continue; + } + + $details = self::getFieldsWhere([ + 'id' => $order_id, + 'deleted_at' => null, + ], ['created_at']); + + $created_at = $details[0]['created_at'] ?? null; + if (is_string($created_at) && $created_at !== '') { + return $created_at; + } + } + + return null; + } + public function getFixedPricingTransactions(int $customer_number, false $asArray, string|null $dateFrom = null, string|null $dateTo = null): array { // Get the fixed pricing transactions for a customer diff --git a/services/nginx/app/routes/plateScansRoute.php b/services/nginx/app/routes/plateScansRoute.php index 35873f48..ca9582f2 100644 --- a/services/nginx/app/routes/plateScansRoute.php +++ b/services/nginx/app/routes/plateScansRoute.php @@ -120,11 +120,21 @@ class plateScansRoute 'type' => (int)$tmp_scan_vehicle['type'], ]; } + // TRU-78 / DRIFT 17: enrich each scan with the + // timestamp of the most recent completed wash for + // that plate so the POS landing page can show + // "last washed" at a glance when DHL trailers are + // being picked up. + $plate_value = (string)$scan['plate']; + $tmp_scan_last_wash = [ + 'last_wash' => (new orders_o())->getLastWashTimestampForPlate($plate_value), + ]; // Return the object as an array return [ ...$scan, ...$tmp_scan_customer, ...$tmp_scan_seen_before, + ...$tmp_scan_last_wash, ]; }, $number_plate_scans->forceRestrictFilters( diff --git a/services/nginx/app/tests/Unit/Orders/OrderLastWashTimestampForPlateTest.php b/services/nginx/app/tests/Unit/Orders/OrderLastWashTimestampForPlateTest.php new file mode 100644 index 00000000..041e7277 --- /dev/null +++ b/services/nginx/app/tests/Unit/Orders/OrderLastWashTimestampForPlateTest.php @@ -0,0 +1,41 @@ +toBeTrue(); + + $ordersCode = preg_replace('/\s+/', ' ', (string)file_get_contents($ordersFile)); + + expect($ordersCode)->toContain('public function getLastWashTimestampForPlate(string $reg_1): ?string'); + + // The helper must look at non-deleted orders with non-deleted order items, + // matching the contract used by customer_vehicles_o::getLastOrderByPlate(). + expect($ordersCode)->toContain("'reg_1' => $normalized_reg_1"); + expect($ordersCode)->toContain("'deleted_at' => null"); + expect($ordersCode)->toContain("'order_id' => $order_id"); + expect($ordersCode)->toContain('return $created_at;'); + expect($ordersCode)->toContain('return null;'); +}); + +it('plateScansRoute enriches GET /numberplatescans with last_wash per scan (TRU-78)', function (): void { + $routeFile = app_path('routes/plateScansRoute.php'); + $ordersFile = app_path('objects/orders_o.php'); + + expect(is_file($routeFile))->toBeTrue(); + expect(is_file($ordersFile))->toBeTrue(); + + $routeCode = preg_replace('/\s+/', ' ', (string)file_get_contents($routeFile)); + $ordersCode = preg_replace('/\s+/', ' ', (string)file_get_contents($ordersFile)); + + // The route already loads orders_o and customer_vehicles_o; verify the + // new enrichment is wired in the GET /numberplatescans handler. + expect($routeCode)->toContain("\$this->get('/numberplatescans', function () {"); + expect($routeCode)->toContain("'last_wash'"); + expect($routeCode)->toContain('getLastWashTimestampForPlate'); + expect($routeCode)->toContain("'last_wash' => (new orders_o())->getLastWashTimestampForPlate"); + expect($routeCode)->toContain('$tmp_scan_last_wash'); + + // The helper definition must live in orders_o so the enrichment is real, + // not a stub. + expect($ordersCode)->toContain('public function getLastWashTimestampForPlate(string $reg_1): ?string'); +});