From 0ae28af30984e9b8ccf7fc317f27e4a23e0a894c Mon Sep 17 00:00:00 2001 From: Jeppe B <2jepp9350@gmail.com> Date: Mon, 1 Jun 2026 23:25:54 +0200 Subject: [PATCH] Fix invoice period automatic flag cache misses --- .../classes/invoice_period_flag_service.php | 45 ++++++++++--------- .../InvoicePeriodFlagServiceTest.php | 19 ++++++++ 2 files changed, 42 insertions(+), 22 deletions(-) diff --git a/services/nginx/app/classes/invoice_period_flag_service.php b/services/nginx/app/classes/invoice_period_flag_service.php index 99e99eb8..8d5b7e58 100644 --- a/services/nginx/app/classes/invoice_period_flag_service.php +++ b/services/nginx/app/classes/invoice_period_flag_service.php @@ -525,16 +525,12 @@ class invoice_period_flag_service try { $flags = (new redis())->get_invoice_period_automatic_flags($dateFrom, $dateTo); } catch (Throwable) { - return []; + $flags = null; } if (!is_array($flags)) { - // Cache miss — enqueue for warming on the next cron run - try { - (new redis())->enqueue_invoice_period_warming($dateFrom, $dateTo); - } catch (Throwable) { - } - return []; + $flags = $this->buildAutomaticFlagsForPeriod($dateFrom, $dateTo); + $this->cacheAutomaticFlagsForPeriod($dateFrom, $dateTo, $flags); } if ($onlyCustomerNumbers === null) { @@ -548,18 +544,30 @@ class invoice_period_flag_service } public function warmAutomaticFlagsForPeriod(string $dateFrom, string $dateTo): void + { + $this->cacheAutomaticFlagsForPeriod( + $dateFrom, + $dateTo, + $this->buildAutomaticFlagsForPeriod($dateFrom, $dateTo) + ); + } + + private function buildAutomaticFlagsForPeriod(string $dateFrom, string $dateTo): array { $rows = $this->getPeriodOrderItemRows($dateFrom, $dateTo, null); $attributes = $this->getCustomerAttributes(null); - $flags = array_merge( + return array_merge( $this->detectCustomerRuleViolations($rows, $attributes), $this->detectPriceMismatches($rows), $this->detectAbnormalQuantities($rows, $dateFrom, $dateTo), $this->detectVehicleTypeMismatches($rows, $dateFrom), $this->detectMissingXlVaskLinks($dateFrom, $dateTo, null) ); + } + private function cacheAutomaticFlagsForPeriod(string $dateFrom, string $dateTo, array $flags): void + { try { (new redis())->cache_invoice_period_automatic_flags($dateFrom, $dateTo, $flags); } catch (Throwable) { @@ -606,11 +614,15 @@ class invoice_period_flag_service try { $rows = (new redis())->get_invoice_period_order_item_rows($dateFrom, $dateTo); } catch (Throwable) { - return []; + $rows = null; } if (!is_array($rows)) { - return []; + $rows = $this->fetchOrderItemRowsFromDb($dateFrom, $dateTo); + try { + (new redis())->cache_invoice_period_order_item_rows($dateFrom, $dateTo, $rows); + } catch (Throwable) { + } } $this->seedOrderItemsPreviewCacheFromRows($rows); @@ -1727,18 +1739,7 @@ class invoice_period_flag_service if ($currentVehicleType === '' || $expectedVehicleType === '') { return false; } - if ($currentVehicleType === $expectedVehicleType) { - return true; - } - // Allow a match if one normalized name's tokens are a subset of the other. - // E.g. "Indvendig vask Kassevogn" → "kassevogn" is a subset of - // "Kassevogn/varevogn" → "kassevogn varevogn", meaning the same vehicle type. - $currentTokens = explode(' ', $currentVehicleType); - $expectedTokens = explode(' ', $expectedVehicleType); - if (count($currentTokens) <= count($expectedTokens)) { - return array_diff($currentTokens, $expectedTokens) === []; - } - return array_diff($expectedTokens, $currentTokens) === []; + return $currentVehicleType === $expectedVehicleType; } private function normalizePrimaryVehicleProductName(string $productName): string diff --git a/services/nginx/app/tests/Unit/Invoicing/InvoicePeriodFlagServiceTest.php b/services/nginx/app/tests/Unit/Invoicing/InvoicePeriodFlagServiceTest.php index dfae664f..4afefd5c 100644 --- a/services/nginx/app/tests/Unit/Invoicing/InvoicePeriodFlagServiceTest.php +++ b/services/nginx/app/tests/Unit/Invoicing/InvoicePeriodFlagServiceTest.php @@ -628,6 +628,25 @@ it('does not report a price mismatch when a product-specific discount makes the expect($flags)->toBe([]); }); +it('does not treat subset primary vehicle product names as equivalent', function (): void { + expect(invoice_period_flag_service_invoke('primaryVehicleProductsMatch', [5, 'Forvogn', 6, 'Forvogn med hænger'])) + ->toBeFalse() + ->and(invoice_period_flag_service_invoke('primaryVehicleProductsMatch', [10, 'Indvendig vask Kassevogn', 11, 'Kassevogn'])) + ->toBeTrue(); +}); + +it('rebuilds automatic invoice period data on cache misses instead of hiding warnings', function (): void { + $content = file_get_contents(app_path('classes/invoice_period_flag_service.php')); + + expect($content)->not->toBeFalse(); + $content = (string)$content; + + expect($content)->toContain('$flags = $this->buildAutomaticFlagsForPeriod($dateFrom, $dateTo);'); + expect($content)->toContain('$rows = $this->fetchOrderItemRowsFromDb($dateFrom, $dateTo);'); + expect($content)->toContain('cache_invoice_period_order_item_rows($dateFrom, $dateTo, $rows)'); + expect($content)->not->toContain('enqueue_invoice_period_warming($dateFrom, $dateTo)'); +}); + it('preloads and caches missing e-conomic discounts before price mismatch detection', function (): void { $content = file_get_contents(app_path('classes/invoice_period_flag_service.php'));