From f0b3fc467537e43cc421e9e50a3d4b2b7423a46c Mon Sep 17 00:00:00 2001 From: Jeppe B <2jepp9350@gmail.com> Date: Sat, 15 Aug 2026 20:52:25 +0200 Subject: [PATCH] fix(invoicing-period): propagate flagged wash start date to Selvvask view (#304) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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= ``` `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 Co-authored-by: openhands --- .../components/InvoicingPeriodFlagList.vue | 14 +++++ .../InvoicingBillingPeriodViewSelfWash.vue | 20 +++++++- tests/unit/invoicing-period-flag-list.spec.js | 51 ++++++++++++++++++- tests/unit/superuser-invoices-view.spec.js | 7 ++- 4 files changed, 87 insertions(+), 5 deletions(-) diff --git a/src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/components/InvoicingPeriodFlagList.vue b/src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/components/InvoicingPeriodFlagList.vue index 322138d4..3269a38a 100644 --- a/src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/components/InvoicingPeriodFlagList.vue +++ b/src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/components/InvoicingPeriodFlagList.vue @@ -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); }; diff --git a/src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/views/InvoicingBillingPeriodViewSelfWash.vue b/src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/views/InvoicingBillingPeriodViewSelfWash.vue index 57243587..28bb260c 100644 --- a/src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/views/InvoicingBillingPeriodViewSelfWash.vue +++ b/src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/views/InvoicingBillingPeriodViewSelfWash.vue @@ -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")); @@ -18,8 +34,8 @@ const selfWashTitle = computed(() => i18n.global.t("nav.self_wash"));
{ 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 ); }); diff --git a/tests/unit/superuser-invoices-view.spec.js b/tests/unit/superuser-invoices-view.spec.js index 47fac268..29cd5258 100644 --- a/tests/unit/superuser-invoices-view.spec.js +++ b/tests/unit/superuser-invoices-view.spec.js @@ -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");