## 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>