fix(invoicing-period): propagate flagged wash start date to Selvvask view (#304)

Fixes AUT-6 (TRU-10).

## Problem
When a user clicked an `xlvask_usage_log` token from a flag in the
InvoicingPeriod flag list, `openXlVaskUsageLog(flag)` navigated to:

```
/invoices?activeTab=period&periodView=self_wash&xlvaskUsageLogId=<id>
```

`InvoicingBillingPeriodViewSelfWash.vue` already read
`route.query.xlvaskUsageLogId` and passed it to `XLVaskUsagePagination`
as `highlight-usage-log-id`. However, the date range was hardcoded to
`dates.computed.formattedStartDate.value` /
`dates.computed.formattedEndDate.value` — the parent period view's
range. If the flag was emitted for a wash **outside** that range (e.g. a
flagged wash from a previous invoicing period), the highlighted wash was
filtered out of the Selvvask result list.

## Fix
- Forward the wash's start time as the new
`xlvaskUsageLogStartTime=YYYY-MM-DD` query parameter when navigating
from the flag list. The `extractDateOnly` helper in
`InvoicingPeriodFlagList.vue` accepts both `flag.context.start_time` and
`flag.start_time`, and tolerates the full ISO-8601 form (e.g.
`2026-04-28T08:15:00`).
- `InvoicingBillingPeriodViewSelfWash.vue` reads
`route.query.xlvaskUsageLogStartTime` via a `parseRouteDateOnly` helper
and uses the wash's date as both `initialDateFrom` and `initialDateTo`.
When the parameter is absent (e.g. direct navigation to Selvvask), the
view falls back to the parent period's range, preserving existing
behaviour.

## Tests
- 83/83 unit tests pass across the 6 affected specs:
- `tests/unit/invoicing-period-flag-list.spec.js` — updated the XL Vask
redirect assertion to include the new query param, plus two new tests
covering the `T`-separated ISO form and the missing-date fallback.
- `tests/unit/superuser-invoices-view.spec.js` — updated the
source-level contract assertion to match the new bindings and verify the
route-query usage.
- `tests/unit/invoicing-period-flag-badge.spec.js`,
`invoicing-period-flag-badge-buefy.spec.js`,
`invoicing-period-view-totals.spec.js`,
`order-content-table-flags.spec.js` — all unchanged, still green.
- ESLint clean on all four modified files.
- No new dependencies; uses existing `vue` (`useRoute`, `computed`),
`vue-router`, `vue-i18n`.

## Files changed (4 files, +87 / -5)
-
`src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/components/InvoicingPeriodFlagList.vue`
(+14)
-
`src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/views/InvoicingBillingPeriodViewSelfWash.vue`
(+18 / -2)
- `tests/unit/invoicing-period-flag-list.spec.js` (+50 / -1)
- `tests/unit/superuser-invoices-view.spec.js` (+5 / -2)

Logic-only frontend filter adjustment; no visual change (so no
before/after screenshots required) and no backend API endpoint touched.

_This PR was created by an AI agent (OpenHands) on behalf of the
OpenSymphony autonomous workflow._

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 20:52:25 +02:00
committed by GitHub
co-authored by Jeppe openhands
parent 1e7298245d
commit f0b3fc4675
4 changed files with 87 additions and 5 deletions
@@ -285,8 +285,18 @@ const openOrderItem = (flag: any) => {
SessionUser.functions.redirectTo.department(departmentId, `modules/pos/orders/${orderId}${suffix}`, true);
};
const extractDateOnly = (value: any) => {
const rawValue = String(value ?? "").trim();
if (rawValue === "") {
return "";
}
const match = /^(\d{4})-(\d{2})-(\d{2})/.exec(rawValue);
return match ? `${match[1]}-${match[2]}-${match[3]}` : "";
};
const openXlVaskUsageLog = (flag: any) => {
const usageLogId = Number(flag?.xlvask_usage_log_id || flag?.context?.xlvask_usage_log_id || flag?.target_id || 0);
const startTime = extractDateOnly(flag?.context?.start_time || flag?.start_time);
const query = new URLSearchParams({
activeTab: "period",
periodView: "self_wash",
@@ -296,6 +306,10 @@ const openXlVaskUsageLog = (flag: any) => {
query.set("xlvaskUsageLogId", String(usageLogId));
}
if (startTime !== "") {
query.set("xlvaskUsageLogStartTime", startTime);
}
SessionUser.functions.redirectTo.superUser(`/invoices?${query.toString()}`, true);
};
@@ -5,12 +5,28 @@ import { computed } from "vue";
import { useRoute } from "vue-router";
import i18n from "@/i18n";
const DATE_ONLY_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
const parseRouteDateOnly = (value: any) => {
const rawValue = Array.isArray(value) ? String(value[0] ?? "") : String(value ?? "");
const match = DATE_ONLY_PATTERN.exec(rawValue.trim());
if (!match) {
return "";
}
return `${match[1]}-${match[2]}-${match[3]}`;
};
const route = useRoute();
const highlightedUsageLogId = computed(() => {
const parsedId = Number.parseInt(String(route.query.xlvaskUsageLogId || ""), 10);
return Number.isInteger(parsedId) && parsedId > 0 ? parsedId : 0;
});
const flaggedWashStartDate = computed(() => parseRouteDateOnly(route.query.xlvaskUsageLogStartTime));
const initialDateFrom = computed(() => flaggedWashStartDate.value || dates.computed.formattedStartDate.value);
const initialDateTo = computed(() => flaggedWashStartDate.value || dates.computed.formattedEndDate.value);
const selfWashTitle = computed(() => i18n.global.t("nav.self_wash"));
</script>
@@ -18,8 +34,8 @@ const selfWashTitle = computed(() => i18n.global.t("nav.self_wash"));
<section data-testid="invoicing-period-self-wash-view">
<XLVaskUsagePagination
:title="selfWashTitle"
:initial-date-from="dates.computed.formattedStartDate.value"
:initial-date-to="dates.computed.formattedEndDate.value"
:initial-date-from="initialDateFrom"
:initial-date-to="initialDateTo"
:inherit-period-filters="true"
:load-all-at-once="false"
:highlight-usage-log-id="highlightedUsageLogId"
+50 -1
View File
@@ -390,7 +390,56 @@ describe("InvoicingPeriodFlagList", () => {
await token.trigger("click");
expect(SessionUser.functions.redirectTo.superUser).toHaveBeenCalledWith(
"/invoices?activeTab=period&periodView=self_wash&xlvaskUsageLogId=55",
"/invoices?activeTab=period&periodView=self_wash&xlvaskUsageLogId=55&xlvaskUsageLogStartTime=2026-05-11",
true
);
});
it("propagates the flagged wash start time when navigating to the Selvvask view", async () => {
const wrapper = mountList([
{
id: "auto-xlvask-root",
source: "automatic",
fingerprint: "xlvask-root",
definition_key: "xlvask_missing_order_link",
message_key: "invoice_period.flags.automatic.xlvask_missing_order_link",
target_type: "xlvask_usage_log",
target_id: 77,
xlvask_usage_log_id: 77,
context: {
xlvask_usage_log_id: 77,
start_time: "2026-04-28T08:15:00",
},
},
]);
await wrapper.get(".invoice-period-flag-token").trigger("click");
expect(SessionUser.functions.redirectTo.superUser).toHaveBeenLastCalledWith(
"/invoices?activeTab=period&periodView=self_wash&xlvaskUsageLogId=77&xlvaskUsageLogStartTime=2026-04-28",
true
);
});
it("navigates to Selvvask without a start-time query when the flag has no wash date", async () => {
const wrapper = mountList([
{
id: "auto-xlvask-no-date",
source: "automatic",
fingerprint: "xlvask-no-date",
definition_key: "xlvask_missing_order_link",
message_key: "invoice_period.flags.automatic.xlvask_missing_order_link",
target_type: "xlvask_usage_log",
target_id: 91,
xlvask_usage_log_id: 91,
context: {
xlvask_usage_log_id: 91,
},
},
]);
await wrapper.get(".invoice-period-flag-token").trigger("click");
expect(SessionUser.functions.redirectTo.superUser).toHaveBeenLastCalledWith(
"/invoices?activeTab=period&periodView=self_wash&xlvaskUsageLogId=91",
true
);
});
+5 -2
View File
@@ -577,8 +577,11 @@ describe("Periode tab contract", () => {
});
it("uses the selected period while keeping Selvvask results server-paginated", () => {
expect(periodViewSelfWashSource).toContain(':initial-date-from="dates.computed.formattedStartDate.value"');
expect(periodViewSelfWashSource).toContain(':initial-date-to="dates.computed.formattedEndDate.value"');
expect(periodViewSelfWashSource).toContain(':initial-date-from="initialDateFrom"');
expect(periodViewSelfWashSource).toContain(':initial-date-to="initialDateTo"');
expect(periodViewSelfWashSource).toContain("dates.computed.formattedStartDate.value");
expect(periodViewSelfWashSource).toContain("dates.computed.formattedEndDate.value");
expect(periodViewSelfWashSource).toContain("route.query.xlvaskUsageLogStartTime");
expect(periodViewSelfWashSource).toContain(':inherit-period-filters="true"');
expect(periodViewSelfWashSource).toContain(':load-all-at-once="false"');
expect(xlvaskUsagePaginationSource).toContain("inheritPeriodFilters");