fix(api): include wash_id in xlvask_missing_order_link flag text (AUT-49/TRU-49) (#375)

## Summary

The XL Vask missing-order-link flag previously rendered the literal
placeholder text `'XL Vask wash'` as the interactive link text.
`wash_id` was already present in the per-flag message context but was
not being threaded into the link text, so users couldn't tell which wash
the warning referred to.

This wires `wash_id` through both `messageParts()` (used to render the
clickable link) and `automaticMessage()` (the plain-text fallback) for
the `xlvask_missing_order_link` flag.

## Changes

`services/nginx/app/classes/invoice_period_flag_service.php`
- `messageParts()` `xlvask_missing_order_link` branch: split into three
parts so the `xlvask_usage_log` button text comes from
`$params['wash_id']`, with the literal "XL Vask wash " as a leading text
part and " is neither ignored nor linked to an order in the selected
period." as a trailing text part.
- `automaticMessage()` `xlvask_missing_order_link` branch: now
interpolates `wash_id` into the message string ("XL Vask wash {wash_id}
is neither ignored nor linked to an order in the selected period."),
with a sensible fallback to the original wording when `wash_id` is
missing.


`services/nginx/app/tests/Unit/Invoicing/InvoicePeriodFlagServiceTest.php`
- Updated the `message_parts` expectation in `it builds interactive
message parts for order and wash certificate warnings` to assert the new
three-part structure with the actual `wash-55` id.

## Verification

- `vendor/bin/pest tests/Unit/Invoicing/InvoicePeriodFlagServiceTest.php
--compact` → `Tests: 34 passed (183 assertions)`, exit 0
- `vendor/bin/pest tests/Unit/Invoicing/ --compact` → `Tests: 245 passed
(1 warning, 1 skipped)`, exit 0
- `vendor/bin/phpstan analyse classes/invoice_period_flag_service.php` →
`[OK] No errors`

## Issue

AUT-49 / TRU-49 — XL Vask missing-order-link flag text should reference
the actual wash (not 'XL Vask wash').

## Out of scope

- The Vue side at `copenhagentruckwash/pleno-vue` still has a hardcoded
fallback `"XL Vask wash"` for the i18n token
`invoice_period.flags.tokens.xlvask_usage_log`. That path is only used
when `flag.message_parts` is absent, which no longer happens for this
flag now that the backend populates it correctly. A follow-up on the Vue
side could remove that fallback or repurpose it as a tooltip label.
- No `Writerside2` topic covers `message_parts`, and `openapi.yaml` does
not formally document the field, so no spec update was required for this
content-only fix.

---

_This pull request was created by an AI agent (OpenHands) on behalf of
Jeppe B._

Co-authored-by: Jeppe <jeppe@copenhagentruckwash.io>
Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
Jeppe B
2026-08-15 22:18:18 +02:00
committed by GitHub
co-authored by Jeppe openhands
parent f4ba70623e
commit 5cde8103f9
2 changed files with 8 additions and 3 deletions
@@ -1495,6 +1495,7 @@ class invoice_period_flag_service
{
$product = (string)($params['product'] ?? 'Item');
$expectedProduct = (string)($params['expected_product'] ?? 'expected product');
$washId = (string)($params['wash_id'] ?? '');
return match ($definitionKey) {
'price_mismatch' => "{$product} product price differs from expected.",
'customer_rule_restrict_addon_services' => "{$product} violates restricted addon services.",
@@ -1514,7 +1515,9 @@ class invoice_period_flag_service
'duplicate_vehicle_subscription_charge_same_month' => "Duplicate vehicle subscription charges exist in the same month.",
'vehicle_subscription_type_mismatch' => "{$product} does not match the vehicle subscription type {$expectedProduct}.",
'historical_primary_product_mismatch' => "{$product} differs from the registration number's usual product {$expectedProduct}.",
'xlvask_missing_order_link' => "XL Vask wash is neither ignored nor linked to an order in the selected period.",
'xlvask_missing_order_link' => $washId === ''
? 'XL Vask wash is neither ignored nor linked to an order in the selected period.'
: "XL Vask wash {$washId} is neither ignored nor linked to an order in the selected period.",
default => "Automatically detected invoice-period issue.",
};
}
@@ -1541,7 +1544,8 @@ class invoice_period_flag_service
['type' => 'text', 'text' => ' is attached without a wash certificate item.'],
],
'xlvask_missing_order_link' => [
['type' => 'xlvask_usage_log', 'text' => 'XL Vask wash'],
['type' => 'text', 'text' => 'XL Vask wash '],
['type' => 'xlvask_usage_log', 'text' => (string)($params['wash_id'] ?? '')],
['type' => 'text', 'text' => ' is neither ignored nor linked to an order in the selected period.'],
],
default => [],
@@ -167,7 +167,8 @@ it('builds interactive message parts for order and wash certificate warnings', f
['type' => 'text', 'text' => ' is present without a wash certificate.'],
]);
expect($xlVaskFlag['message_parts'])->toBe([
['type' => 'xlvask_usage_log', 'text' => 'XL Vask wash'],
['type' => 'text', 'text' => 'XL Vask wash '],
['type' => 'xlvask_usage_log', 'text' => 'wash-55'],
['type' => 'text', 'text' => ' is neither ignored nor linked to an order in the selected period.'],
]);
});