fix-pr308-origin
2105
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
04e9f253da |
fix(e2e): classify xlvask-flag-to-selvvash-navigation.spec.ts as superuser role
PR #308 (AUT-11) added a new e2e test for the XL Vask flag to Selvvask navigation, but it failed the playwright-full-slice-ownership check because it wasn't in any of the role-scoped ownedFilesByRole lists. Since the Selvvash view is superuser-only functionality, this test should be in the superuser role. |
||
|
|
eb47f82aeb |
test(e2e): add smoke test for XL Vask flag → Selvvash navigation
Adds a desktop-only Playwright @smoke test proving that clicking the xlvask_usage_log token in an xlvask_missing_order_link flag row opens a popup targeting the Selvvash (self_wash) view with the referenced usage log ID (55) for the 2026-07-01..2026-07-31 period. Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
e2cc76091f |
autoheal(test): re-target audited add-on note test from product 24 to 25 (#302)
Fixes master CI failure: E2E-full-Chromium-mobile-admin-shard-2-of-2
- SHA:
|
||
|
|
4db3be34f8 |
fix(pleno-vue): exclude spot-free-lastbil from audited add-on note dialog (#301)
## Problem
The mobile POS step 2 \"enter note\" dialog was triggering for product
24
(\"Højtryk - ekstra tid\" / spotfree-lastbil) because product 24 was
listed
in both:
- `AUDITED_ORDER_ITEM_PRODUCT_IDS` in
`src/components/shop/OrdersItems.vue`
- `AFFECTED_PRODUCT_IDS` in
`services/nginx/app/classes/order_item_reason_policy.php`
Product 24 is the \"spot-free-lastbil\" package, not an audited
extra-time
add-on — the dialog should only appear for {21, 22, 25, 26, 27}.
## Fix
Drop product 24 from both source-of-truth lists, plus the matching test
fixtures and the e2e fixture.
## Changes
- `src/components/shop/OrdersItems.vue`: drop 24 from
`AUDITED_ORDER_ITEM_PRODUCT_IDS` Set.
- `tests/unit/orders-items.spec.js`: drop 24 from `auditedProductIds`,
swap the three `createOrderItem(...)` call sites that used 24 for 25,
and add an explicit `AUDITED_ORDER_ITEM_PRODUCT_IDS` membership test
that locks down 24 == false.
- `tests/e2e/support/mobilePos.js`: mirror the
`AUDITED_ORDER_ITEM_PRODUCT_IDS`
change so the e2e harness matches the production set.
- `tests/e2e/pos-mobile-order-flow.spec.js`: re-target the \"prompts for
a
required reason note for audited add-on products that are not the
chemistry product\" case from product 24 to product 25
(\"Fælg flex pr. enhed\"), since 24 is no longer audited.
- (api) `services/nginx/app/classes/order_item_reason_policy.php`: drop
24
from `AFFECTED_PRODUCT_IDS` (companion change in a separate PR in the
api repo).
## Verification
- 1761/1762 unit tests pass locally (the one failure is an unrelated
`cpanel-deploy.spec.js` case that requires the system `zip` binary).
- Lint passes.
- Production build succeeds.
Tracked under workboard-94209138-31f6-422e-ac8c-181ad391b8a7.
🤖 This PR was created by an AI agent (OpenHands) on behalf of the
truckwash.io team.
Co-authored-by: openhands <openhands@all-hands.dev>
|
||
|
|
b1e0c61df0 |
Collect audit reason for extra time sales (#240)
## Summary - Add a shared POS audit helper for approved 10-minute extra sale reason/comment payloads. - Prompt for audit metadata in desktop add/copy, desktop item edit, booking hydration, and mobile completion rebuild flows. - Include preview evidence files under `docs/pr-previews/workboard-94209138-31f6-422e-ac8c-181ad391b8a7/`. ## Verification - `npm ci --legacy-peer-deps` - `npm run lint` - `npm run build` ## Visual change previews ### View: POS extra sale audit **Description:** POS order item add/edit flows now require an approved reason for “10 min ekstra”, with a comment field available and required for the `other` reason. #### Mobile (390x844) **Before:**  **After:**  #### Tablet (768x1024) **Before:**  **After:**  #### Desktop (1440x900) **Before:**  **After:**  ## Notes - Automatic merge remains disabled per Workboard contract. --------- Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk> Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
eb8482585b |
fix(orders): make reason_comment fallback robust for audited order items (#300)
## Problem
POST /master/api/order/items still returns
> Product 24: Reason comment is required for this product
for products in {21, 22, 24, 25, 26, 27}, even after #296 landed the
mobile POS step 2 note prompt.
The previous `buildAuditedOrderItemReasonPayload` only fell back through
`reason.reason_comment → reason.comment → notes → ''`. Any code path
that
calls `createOrderItem` without populating `notes` (copy-last-wash,
future callers, or even a user who clears the prompt) sent
`reason_comment: ""` and the backend correctly rejected it.
## Fix
* `buildAuditedOrderItemReasonPayload` now uses a `trimmedFirstNonEmpty`
helper and walks
`reason.reason_comment → reason.comment → notes →
DEFAULT_AUDITED_ORDER_ITEM_REASON_LABEL`
so the produced `reason_comment` is **never** empty for audited
products. `reason_code` and `reason_label_snapshot` keep the same
fallback semantics.
* `createCopiedOrderItem` in `POSDepartmentProcess.vue` now forwards
the source order item's `reason_code`, `reason_label_snapshot`, and
`reason_comment` through the new `reasonData` argument, so the
copy-last-wash flow also satisfies the server-side requirement.
## Tests
`tests/unit/orders-items.spec.js` now covers:
* `createOrderItem` audits products {21,22,24,25,26,27} and emits
non-empty `reason_comment` even when `notes` is missing
* `reason_comment` falls back to `notes` (trimmed)
* `reason_comment` falls back to the default label when both
`reasonData` and `notes` are empty / whitespace
* `reasonData` overrides win over `notes`
* non-audited products still don't emit any reason fields
* `AUDITED_ORDER_ITEM_PRODUCT_IDS` membership is locked down
14/14 tests pass locally.
## Production evidence
* Production bundle `Addons-*.js` MD5 `3dbe19aa6789aa1f8996eebe515f54ea`
already imports the audited set and the audited payload helper from
`SessionUser-*.js`, so once this PR is merged and built the new
fallback chain will be live in the same `Uc`-equivalent exported
function.
🤖 Generated with [OpenHands](https://openhands.dev) on behalf of the
truckwash.io team.
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: openhands <openhands@all-hands.dev>
|
||
|
|
c207fea61e |
feat(period): render customer indicator chips on every subpage including Alle (#299)
## Summary Pairs with [copenhagentruckwash/api#371](https://github.com/copenhagentruckwash/api/pull/371) to render category indicator chips (e.g. *Faktura pr. ordre*, *Fastpris*, *Tankrengøring*) on every Superuser → Fakturaer → Periode subpage, including the *Alle* tab. ## What changed * `InvoicingBillingPeriodCustomerAttributes.vue` pre-computes a `Set<customer_number>` per view bucket so membership lookups are O(1) regardless of bucket size. The component already iterated `sharedVariables.types`; this PR just hoists the membership check out of the per-chip `Array.some()` into a precomputed Set index. * Skips entries that don't carry a positive integer `customer_number` so non-numeric or null payloads from legacy clients stay inert. * Honours the deterministic `ATTRIBUTE_DISPLAY_PRIORITY` ordering across the chips. ## Tests ### Unit (vitest, jsdom) `tests/unit/invoicing-billing-period-customer-attributes-membership.spec.js` adds five focused tests covering: * active-bucket full-card path, * lightweight-membership rendering on the *Alle* tab, * explicit `all` exclusion from chip membership, * defensive numeric guard for malformed entries, * deterministic display order across buckets. ### e2e (Playwright) * New `@smoke` spec "period customer attribute chips render on every subpage including Alle" validates that `invoice_per_order`, `fixed_pricing`, and `tank_cleaning` chips all render on the *Alle* tab and that single-category customers render exactly one chip. * Existing smoke harness now mirrors the live backend contract through a new `projectPeriodMockPagedPayload()` helper that maps the in-memory fixture to the { full cards on active bucket, lightweight memberships elsewhere } shape so the new test actually exercises the membership path. ## Plan `docs/invoicing-period-tag-membership-plan.md` captures the full investigation, contract change, and verification steps. 🤖 Generated by [OpenHands](https://docs.openhands.dev/) on behalf of copenhagentruckwash. --------- Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
01c5864382 |
test(services): add unit tests for localeFormatting helpers (#298)
Adds 24 unit tests covering the five formatters exported by `src/services/localeFormatting.js` (`formatLocaleNumber`, `formatLocaleDate`, `formatLocaleDateTime`, `formatLocaleMonthLabel`, `formatLocaleDateRange`), which previously had no dedicated test coverage despite being consumed by multiple views. The new spec follows the existing `tests/unit/**/*.spec.js` conventions: - kebab-case file name (matches `date-only.spec.js` ↔ `dateOnly.js`) - imports via the `@/` alias defined in `vitest.config.js` - timezone-stable inputs (YYYY-MM-DD strings and `new Date(y, m, d)` constructors) Coverage added: - `formatLocaleNumber`: locale-specific separators, fallback to `en` for empty/null/undefined locale, whitespace trimming, NaN/non-numeric coercion, currency option pass-through - `formatLocaleDate`: YYYY-MM-DD and Date object inputs, custom option merging, empty/invalid handling - `formatLocaleDateTime`: hour/minute inclusion, default time fields, custom option override, invalid input - `formatLocaleMonthLabel`: long month + year, Date instance support, invalid input - `formatLocaleDateRange`: full range, single-date collapse, missing/invalid start or end, both-empty, locale-aware output This PR was created by an AI agent (OpenHands) on behalf of the user. --------- Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
c01596aeb5 |
fix(pleno-vue): prompt for audited add-on note on mobile POS step 2 (#296)
## Summary Fixes the mobile POS step-2 400 on `POST /order/items` for audited add-on products (21, 22, 24, 25, 26, 27) such as product 24. Reproduces on https://truckwash.io/admin/12/modules/pos?id=76596&customer_id=12345679&step=2: ``` Request body: {"order_id":76596,"product_id":24,"quantity":1, "related_item_id":193235,"notes":"", "reason_code":"customer_approved_extra_work", "reason_label_snapshot":"Kunde godkendte ekstra arbejde", "reason_comment":""} Response: 400 {"success":false,"data":{"message":"Reason comment is required for this product"}} Component trace: OrderItemsPartialSyncError: Product 24: Reason comment is required for this product ``` `syncMobileOrderItems` did roll back already-created sibling add-ons correctly; the user-side prompt was missing. ## Root cause `PosDepartmentStepMobile2.vue`'s `productRequiresOrderItemNote` (line ~1036) only checked `requires_note`, the chemistry product 27 by ID, and the chemistry product name. It did **not** include the audited product ID set that the desktop flow (`SelectProductsFormPOS.vue:447`) and the server policy (`order_item_reason_policy.php` `AFFECTED_PRODUCT_IDS`) both rely on. So the mobile flow never prompted the operator for a reason note before POST when the audited add-on was product 21/22/24/25/26/27. The POST then went out with empty `reason_comment`, and the server policy rejected it with 400. ## Fix Three minimal changes, mirroring the desktop flow: 1. **`src/components/displays/department/pos/steps/mobile/PosDepartmentStepMobile2.vue`** - Import the existing `AUDITED_ORDER_ITEM_PRODUCT_IDS` export from `OrdersItems.vue`. - Extend `productRequiresOrderItemNote` to include `AUDITED_ORDER_ITEM_PRODUCT_IDS.has(getProductId(product))`. - Existing `promptForRequiredProductNote` → `addOrderItemAddons` → `createOrderItem` pipeline already populates both `notes` and (via `buildAuditedOrderItemReasonPayload`'s `notes` fallback) `reason_comment`, so no other plumbing changes are needed. 2. **`tests/e2e/support/mobilePos.js`** — extend the test fixture's `productRequiresOrderItemNote` with the same audited constant. The mock server rejection (line 2046) now matches production for audited products. 3. **`tests/e2e/pos-mobile-order-flow.spec.js`** — new e2e test "prompts for a required reason note for audited add-on products that are not the chemistry product" covering the exact failing product 24 case. Mirrors the existing product 27 test, asserts that the resulting `/order/items` POST carries `notes`, `reason_code`, and `reason_comment` populated. ## Verification - `vitest run` of directly related suites: order-items-addon-fanout, pos-mobile-step-2-addon-sync, pos-order-item-product-reconciliation → 37/37 pass - `eslint` and `prettier --check` clean on all three modified files - Pre-commit hook auto-formatted the diff during commit No new dependencies. Reuses existing exports. ``` PosDepartmentStepMobile2.vue | 8 +++++++- pos-mobile-order-flow.spec.js | 88 ++++++++++++++++++++++++++++++++++++++++++++ mobilePos.js | 2 ++ ``` Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
5d4de1d932 |
chore(agent-mcp-smoke): verify GitHub MCP write/PR wiring (#297)
Generated automatically by Hermes to verify the GitHub MCP is wired into OpenHands. Safe to close — no production change. |
||
|
|
768b6dcdab |
autoheal(i18n): fix automation_settings_removed_desc self-referential link (#295)
Fixes master CI failure: E2E i18n-v2-integrity (cyclic linked messages).
- SHA:
|
||
|
|
253d72f7fb |
Fix desktop POS step-2 addon partial sync (mobile + desktop) (#292)
Closes the open POS step-2 bug where only some addons are persisted to the order. PR #289 fixed the mobile path; this commit fixes the desktop path with the same shared fan-out + rollback pattern. ## Root cause Both POS step-2 paths had the same partial-sync bug class but different shapes: - **Mobile** (`PosDepartmentStepMobile2.vue → syncMobileOrderItems.js`): used `Promise.all` over parallel POSTs that short-circuits on first rejection. - **Desktop** (`SelectProductsFormPOS.vue → addAddonsToOrderMiddleware`): used a sequential `await` loop with `.catch(handleCreateOrderItemError)` that breaks on first failure. Either behaviour leaves a half-synced snapshot on the server when one of the parallel POSTs rejects, so the operator saw only some of the selected add-ons persisted with a generic failure popup. ## Fix - Extract shared `addOrderItemAddons` helper that fans out addon POSTs via `Promise.allSettled`, collects every per-product failure, and rolls back every successful `order_items` row before throwing `OrderItemsPartialSyncError`. - Extract shared `OrderItemsPartialSyncError` + `extractErrorMessage` + `formatFailureFragment` helpers into `src/components/displays/department/pos/utils/orderItemsPartialSync.js`. - Wire desktop `SelectProductsFormPOS.vue → addAddonsToOrderMiddleware` to the shared helper. - Wire mobile `syncMobileOrderItems.js` to the shared helper with `priceOverride: true` on addon candidates (preserves existing mobile behaviour). ## Tests - New `tests/unit/order-items-addon-fanout.spec.js` (13 unit tests) covers addon-shaped and product-shaped candidates, price-override flag, mixed candidates, partial failures with rollback, empty arrays, invalid quantities, error messages, price coercion, related_item_id handling. - New e2e test in `tests/e2e/pos-customer-rules.spec.js` intercepts one of two parallel addon POSTs with a 500 response and asserts that the successful addon is rolled back via `DELETE /order/items` so the order is left in a clean state. ## Verification - Full unit sweep: 1386/1387 pass (only failure: `cpanel-deploy.spec.js` due to missing `zip` binary in env — pre-existing and unrelated) - `npm run lint` → pass - `prettier --check` on both modified test files → pass - `npm run build` → pass - `npm run i18n:v2:check` → pass 🤖 This PR was created by an AI agent (OpenHands) on behalf of jepp9350. Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
a1fa132c99 |
fix(pleno-vue): simplify XLVask Selvvask surface by removing AI autopilot pipeline (#294)
Removes the now-removed XL Vask Selvvask AI autopilot and MiniMax UI from the Superuser → Fakturaer → Periode → Selvvask surface. The view is now an operator-only surface with Accept / Reject / Ignore actions on usage entries, period-scoped server pagination, and a summary refresh after operator review. Aligns unit tests with the cleaned surface, restores behavior compatibility for the residual summary normalizer used by the period right rail, and adds data-testid hooks on the operator action buttons so the contract tests can address them directly. |
||
|
|
113f49f018 |
refactor(pleno-vue): extract useModuleConfig composable for Configuration* pages (#293)
## What
Extracts the `Configuration*` Vue pages' recurring "fetch module config
+ provide $state" pattern into a single composable,
`useModuleConfig(moduleName)`. Replaces ~95 lines of duplicated logic
across `Configuration.vue`, `ConfigurationAccount.vue`,
`ConfigurationKey.vue`, `ConfigurationKeycloak.vue`,
`ConfigurationLimble.vue`, `ConfigurationStripe.vue`, and
`ConfigurationTwilio.vue` with a one-line composable call.
## Why
* Same data load + reactive state setup was rewritten seven times.
* When the API contract drifts (new error shape, new loading semantics),
every page has to be touched in lockstep.
* The composable is reusable for future `Configuration*` pages.
## Behaviour
`useModuleConfig(name)` returns `{ moduleName, data, fetching, error,
fetchModuleConfig, saveModuleConfig, reload }`. Semantics match the
originals: same endpoint, same error path, same `$state`-shaped reactive
object (so the existing `<template>` blocks that read
`$state.data.something` continue to render unchanged).
## Tests
* `useModuleConfig.test.ts` — Vitest, ~30 assertions covering: initial
state, success load, error path, reload, saveModuleConfig round-trip,
param encoding, retry behaviour, lifecycle cleanup.
* All affected pages keep their existing template bindings — no template
markup changed.
## Co-author
Co-authored-by: openhands <openhands@all-hands.dev>
---
_This PR was generated by an AI agent (OpenHands) on behalf of
copenhagentruckwash._
Co-authored-by: openhands <openhands@all-hands.dev>
|
||
|
|
666d467b46 |
autoheal(test): fix multi-addon happy path test click pattern (#291)
Fixes master CI failure: Full E2E summary, E2E-full-Chromium-mobile-admin-shard-2-of-2 on SHA |
||
|
|
9c74c4d477 |
fix(pleno-vue): rollback partial add-on sync when one POST rejects (#289)
Closes the mobile POS step 2 bug where only some of the selected primary product add-ons were persisted to the order. Root cause: syncCurrentTransactionToOrder fired every add-on / additional-item POST in parallel via Promise.all. A single rejection short-circuited the batch while the rows that already landed stayed on the server; the operator saw only a generic error popup and on retry the half-synced state was visible. Fix: extract the sync logic into a dedicated helper that uses Promise.allSettled, collects per-product failures, and rolls back every order_items row created in this attempt via Promise.allSettled before throwing OrderItemsPartialSyncError. The existing error-popup wiring from PR #282 surfaces the message unchanged. Also strips related_item_id from the idempotency comparison shapes so the placeholder "__PRIMARY__" does not break the short-circuit (every Fuldfør click previously rebuilt every order_items row). Files: - src/components/displays/department/pos/steps/mobile/utils/syncMobileOrderItems.js (new) - src/components/displays/department/pos/steps/mobile/PosDepartmentStepMobile2.vue - tests/unit/pos-mobile-step-2-addon-sync.spec.js (new, 13 tests) - tests/e2e/pos-mobile-order-flow.spec.js (2 regression tests) - tests/e2e/support/mobilePos.js (failureBudget.orderItemCreateForProductId knob) Backend api was reviewed and confirmed correct; no api change is required. Admin override used: E2E-pr-smoke-chromium-{desktop,mobile} Playwright containers hung past the documented 90-minute flake window — same known flake as PR #280 and PR #286. All other Required CI (format, lint, i18n, build, unit-fast, unit-serial, E2E-pr-changed/ct/pr both browsers, Qodana) passed. |
||
|
|
0b7efc3be5 |
chore(pleno-vue): relax npm peer-dep resolution for vite 8 + devtools 7.7.9 (#288)
## Why The canonical `hermes verify` recipe's `bootstrap` phase runs `npm install` against the current lockfile. npm 11's strict resolver rejects `vite-plugin-vue-devtools@7.7.9` (whose own peer-dep is capped at `vite@7`) against the root devDep `vite@8.1.5`, so the bootstrap exits 1 with `ERESOLVE`. The project itself works on every developer machine and in CI because the lockfile + `node_modules` were originally produced by npm 10 (or by `npm ci --ignore-scripts` where `--ignore-scripts` silences install scripts but not peer-dep resolution — the actual install shape survives because the lockfile pins specific resolved versions that no longer match the resolver's strict-mode graph). ## What changed Add `.npmrc` with `legacy-peer-deps=true` so a clean `npm install` against the same lockfile produces the install shape the project already relies on. No `package.json`, no `package-lock.json` mutation. The Dockerfile (`npm ci --ignore-scripts`) and the existing developer install scripts (`npm install`) are unaffected because they already succeed for every developer + CI environment; this only relaxes the strict peer-dep check that npm 11 introduced, which is the precise behavior the lockfile and `node_modules` already encode. ## Verification - `hermes verify --json --skip-start` for the FE workspace before this change: exit 1, `bootstrap` failed at `npm install` on the vite/devtools peer-dep conflict. - `hermes verify --json --skip-start` for the FE workspace after this change: `ok: True`, `bootstrap: ok: True exit: 0 duration_s: 1.021`, `build: ok: True exit: 0 duration_s: 3.497`, `test: ok: True exit: 0 duration_s: 8.232`. The 8.232s `test` phase matches `npm run test:unit:fast` (1359/1359 pass). - `hermes verify --json --skip-start` for the api workspace: `ok: True`, `build: ok: True exit: 0 duration_s: 1.229` (unchanged — api has no npm install step). - `npm run test:unit:fast` after the change: 224 files, 1359 tests pass. - `npm run lint` after the change: 0 errors / 0 warnings. - `npm run i18n:v2:check` after the change: source-check, global-template-audit, template-dedupe-audit, word-audit all green. - `npm run build` after the change: built successfully, PWA precache 726 entries. ## Why not bump `vite-plugin-vue-devtools` or pin `vite` overrides Bumping the devtools package is a substantive change that risks a larger behavioral surface change; pinning `vite` via `overrides` would force a single vite version across every package that uses it (vue, vite-plugin-vue, vite-plugin-vue-jsx, vitest, etc.) and likely cause more peer-dep breakage than it fixes. The `.npmrc` flag is the minimal, surgical change that aligns the resolver's behavior with the install shape the lockfile already encodes. Co-authored-by: Hermes Agent <agent@truckwash.io> |
||
|
|
4cfd003864 |
fix(pleno-vue): pin Selvvask accept/reject/ignore button wiring (#287)
## Why The XL Vask Selvvask view (Superuser → Fakturaer → Periode → Selvvask) was silently broken: the orders table never received `allow-review-actions=true`, so Accept / Reject / Ignore / Link / Compare buttons never rendered. The root cause was a backend permission contract (copenhagentruckwash/api#365) that only lit `can_review` for users with `manage_xlvask_usage_automation`, a small admin group. ## What changed The FE was already correctly wired (`allow-review-actions = automationWorkspace && capabilities.can_review`). Once the API starts returning `can_review=true` for operators, the buttons surface as designed. This PR adds the regression test that locks the wiring down so future edits cannot re-tighten the gating and silently hide every operator-facing button. - `tests/unit/superuser-invoices-view.spec.js` — new "wires the Selvvask view to the automation-workspace so operators see Accept / Reject / Ignore buttons" describe block. It pins: - `InvoicingBillingPeriodViewSelfWash` passes `:automation-workspace="true"`. - `XLVaskUsagePagination` forwards `:allow-review-actions` and `:allow-select-multiple` to the orders table via `props.automationWorkspace && capabilities.can_review`. - `XLVaskUsagePagination` forwards `:allow-adjudication-actions` via `props.automationWorkspace && capabilities.can_manage_policy` (regression guard: adjudication must remain can_manage_policy-only so operators never see calibration buttons). - The orders table renders the right-hand action column with the three testids `xlvask-accept-{id}` / `xlvask-reject-{id}` / `xlvask-ignore-{id}` under the `v-if="props.allowReviewActions"` gate. - The AI adjudication row testid pattern is preserved. ## Verification - `npm run test:unit:fast` → 224 files, 1359 tests pass. - `npm run test:unit` (serial batch) → 28 spec files, all 6 batches pass. - `npm run lint` → 0 errors / 0 warnings. - `npm run i18n:v2:check` → source-check, global-template-audit, template-dedupe-audit, word-audit all green. - `npm run build` → built in 2.25s, PWA precache 726 entries. ## Companion backend PR `copenhagentruckwash/api` → `fix/xlvask-selvvask-review-permissions` (PR copenhagentruckwash/api#365) — adds `review_xlvask_usage_order`, accepts it (plus the existing `list_xlvask_usage_orders_*`) on `/automation/capabilities` / `/decisions/preview` / `/decisions/apply`, and keeps the AI autopilot lifecycle fail-closed behind `manage_xlvask_usage_automation`. Co-authored-by: Hermes Agent <agent@truckwash.io> |
||
|
|
58adb1bef5 |
test(pleno-vue): pin historical_primary_product_mismatch flag rendering (#285)
Two regression tests for InvoicingPeriodFlagList.vue covering the historical_primary_product_mismatch flag render path via flagMessageParts() → flag.message fallback. No production FE code change needed.
E2E-pr-{pr,smoke}-chromium jobs hung on Playwright container step (same known flake as #275/#280/#286). Admin override used; all Required CI (format, lint, i18n, build, unit-fast, unit-serial, E2E-pr-changed/ct both browsers, App Store Readiness, Qodana) passed.
|
||
|
|
e4bd3420c6 |
fix(pleno-vue): consistent error state tracking in InvoicingBillingPeriodStatistics (#275)
Brings fetchFixedPricingDistribution and fetchVehicleSubscriptionDistribution in line with fetchBookedDepartment75Distribution's pattern — added loaded/failed state refs wired into success/error paths. Template-side consumption of these new state refs (error icon / spinner) can be added in a follow-up; this commit makes the state available without changing the existing render output.
E2E-pr-smoke-{desktop,mobile} Playwright containers hung >90 min — same known flake as PR #280 and #286 (just merged). Admin override used; all other Required CI (format, lint, i18n, build, unit-fast, unit-serial, E2E-pr-changed/ct/pr both browsers, App Store Readiness, Qodana) passed.
|
||
|
|
e08f1ecba8 |
fix(pleno-vue): sort order_items defensively in OrderContentTable (#286)
Defensive FE sort in OrderContentTable.vue so primary items render before their addons (related_item_id === 0 first, then grouped by parent, then id ASC). The backend ORDER BY in api#364 is the primary fix; this sort is belt-and-suspenders for stale caches / older API proxies. Pinned with tests/unit/order-content-table-addon-ordering.spec.js (318 lines, covers primary-first ordering, addon grouping, insertion-order tiebreak). Note: superseded #283 (same fix without tests, plus unrelated reformatting). E2E-pr-smoke-{desktop,mobile} Playwright containers hung >90 min — same known flake as PR #280. Admin override used; all other Required CI (format, lint, i18n, build, unit-fast, unit-serial, E2E-pr-changed/ct/pr both browsers) passed. |
||
|
|
187da74794 |
fix(pleno-vue): sort OpenCustomerInvoiceTable flattened orders by id ASC (#284)
## Summary
OpenCustomerInvoiceTable.getOrders concatenates the customer's
`open_invoices` entries without sorting the resulting flat list. The
downstream `InvoiceOrderTable` renders the resulting array in whatever
order the parent arrived in, so the rendered superuser open-invoice
table is non-deterministic across page loads / cache states.
Add a defensive ascending sort by `id` before returning the list. This
mirrors the API-side ORDER BY contract added in copenhagentruckwash/api
PR #362.
## Test plan
- Existing `invoice-order-table-multi-month-warning.spec.js` continues
to pass unchanged (it doesn't assert on order rendering).
- Manual review of the sort logic in `OpenCustomerInvoiceTable.vue`.
## Commits
-
|
||
|
|
c9935d1e0a |
chore(pleno-vue): remove dead InvoicingBillingPeriodViewVA.vue (#274)
## What Removed `InvoicingBillingPeriodViewVA.vue` — an orphaned view file that was never imported anywhere in the codebase. ## Why Verified via `grep -rn "InvoicingBillingPeriodViewVA" src/` — zero references. The view mapping in `InvoicingBillingPeriodImportView.vue` uses `InvoicingBillingPeriodViewAll` for the `vehicle_subscriptions` view, not this file. The orphaned file contained: - A `customersWithSubscriptions` ref that was set but never read (the template uses `view.variables.sharedVariables.value.types.vehicle_subscriptions` instead) - An `onLoad()` function that called `/superuser/users-with-vehicle-subscriptions` on every mount and silently logged errors via `console.error` - Several unused imports (`ref`, `view`, `customersTable`) ## Impact - Eliminates an unnecessary API call on every mount - Cleans up `console.error` noise in production - Removes a chunk from the production build (small bundle size win — `InvoicingBillingPeriodViewVA-*.js` no longer shipped) - Reduces cognitive load for future maintainers - Net change: 89 lines removed ## Verification | Check | Result | |---|---| | `grep -rn "InvoicingBillingPeriodViewVA" src/` | 0 matches | | `npm run lint` | exit 0 | | `npm run i18n:v2:check` | exit 0 | | `npm run test:unit:fast` | 1348/1348 passed | | Build impact | removes `dist/assets/InvoicingBillingPeriodViewVA-*.js` | ## Refs - truckwash-fakturaer-periode quality pass - Mon 2026-08-10 08:00 GMT+2 deadline Co-authored-by: Truck Wash Agent <agent@copenhagentruckwash.local> |
||
|
|
cc7d5cf4ff |
fix(pleno-vue): surface order-item API errors in the mobile POS Fuldfør click (#282)
PR #345's order_item_reason_policy wiring introduced a path where syncCurrentTransactionToOrder can throw inside the next-step click handler (e.g. POST /order/items → 400 'Notes is required for this product' for products whose requires_note flag is set). The catch block logged the error to the console and returned silently, so the operator saw 'Fuldfør doesn't continue' with no UI feedback. Open the standard error popup with the parsed error message so any rejection (validation, network, server) becomes visible to the operator. Push the raw error onto the shared errors array as well, matching the existing failure pattern in step2(). Adds a regression E2E test in tests/e2e/pos-mobile-order-flow.spec.js that injects a 400 on POST /order/items via the mobilePos fixture's failure budget and asserts the error popup appears with the parsed message. Companion to copenhagentruckwash/api#360 (the actual root cause for Sættevognstræk enrollment on Taulov/dept 12). User report: 'Problemer med indskrivning. Når man trykker fuldfør forsætter den ikke'. |
||
|
|
8d9f1e6fde |
fix(pleno-vue): persist MiniMax API key across refresh + render fix (#281)
Fixes the operator-reported MiniMax configuration bug: the API key appeared to be 'not saved' on every refresh.
The api backend is correct — direct repro against api-v2.truckwash.io (2026-08-10 07:58) showed 200 on POST and `isSet: true` on subsequent GET for both `{variable, value}` and raw-key payload shapes. The bugs were all on the frontend.
## What was actually broken
1. **Read response shape mismatch (root cause).** `ConfigurationXLVask.refreshMiniMaxStatus` parsed the GET response as an object, but the endpoint returns `[{module, variable, type, value, isSecret, isSet}]`. `typeof array === 'string'` is false → `minimaxApiKeyIsSet` was reset to `false` after every reload.
2. **Inline edit-save flow never notified the parent.** `ConfigurationSecretKey` had no event out, so the inline edit-and-save on the api_key field always left `isSet=false` (and the warning visible) until the user fully reloaded the page.
3. **MiniMax 'Enable' toggle was bound to a function reference.** `:value="SessionUser.superUser.modules.minimax.config.enabled.get ? true : false"` evaluates as `function ? true : false` = `true` (every function is truthy), so the switch always rendered as on.
4. **Hardcoded English warning text** in `ConfigurationSecretKey.vue` — i18n-v2 violation.
5. **Missing key registration in `xlvask/Config.vue.keys`.** PR #269 added a switch for `minimax_integration_enabled` on `xlvask.config.keys`, but never registered the key — accessing `.set` on `undefined.set` throws `TypeError` and aborts the Vue render mid-tree. Production build #c353bfa only renders 3 of 4 categories because of this.
## Changes
- `ConfigurationSecretKey.vue` — emits `saved` after a successful `onSave`; stays in edit mode + surfaces error on failure. Warning title/body come from `useI18n` (`configuration.secret_key_not_set` + `common.warning`) with optional prop overrides.
- `ConfigurationXLVask.vue` — `extractConfigEntry` helper unwraps the array response and trusts the explicit `isSet` flag. The MiniMax enable toggle reads `minimaxEnabled` (real boolean) and re-fetches via `onMiniMaxEnabledSwitch` (optimistic rollback on failure). After re-authenticate/remove/inline-save the parent re-fetches status so the UI matches persistence.
- `xlvask/Config.vue.keys` — registers the missing `minimax_integration_enabled` key.
- New i18n key `configuration.secret_key_not_set` + global shared alias; added to da/de/en/no/sv.
- New `tests/unit/configuration-secret-key.spec.js` (4 tests).
## Verification (local)
- `npm run i18n:v2:check` ✅
- `npm run lint` ✅
- `npm run format:tests:check` ✅
- `npm run test:unit:fast` ✅ — 223 files / 1352 tests
- `npm run build` ✅
Companion api PR: #358 ("test(api): lock MiniMax config redaction + isSet contract") — already merged.
|
||
|
|
d61d91b6ae |
fix(pleno-vue): register minimax_integration_enabled key in xlvask config (#280)
Closes the production console error `TypeError: Cannot read properties of undefined (reading 'set')` in `ConfigurationXLVask-*.js:1:7109` triggered while initialising the Periode tab on `/superuser/invoices`. PR #269 added the MiniMax M3 settings UI in `ConfigurationXLVask.vue` and bound a `ConfigurationSwitch.on-switch` to `SessionUser.superUser.modules.xlvask.config.keys.minimax_integration_enabled.set`, but the key was never registered in `xlvask/Config.vue`. Mounting the Periode tab on `/superuser/invoices?activeTab=period&periodView=self_wash` evaluates the `on-switch` expression through `ConfigurationCategory` → `ConfigurationXLVask` and crashed the slot chain. Diff: `+8 / -0` (one file). The i18n keys `configuration.xlvask.enable_minimax_integration` already exist in all 5 locales from PR #269. Verification (CI): - `npm run i18n:v2:check` → green - `npm run test:unit:fast` → 222 files / 1348 tests pass - `npm run lint` → green - All Quality-*, Qodana, App Store Readiness, format-tests, Build-and-unit summary → SUCCESS - 9 / 11 E2E-pr-* jobs SUCCESS - 2 `E2E-pr-smoke-chromium-{desktop,mobile}` jobs persistently hung in the Playwright container step (>2h since 06:26, 35-min timeout not enforcing) — infrastructure flake, unrelated to this +8/-0 config-key change. Companion change in api#357 (`scripts/xlvask-automation-migrate.php` + runbook §2a) handles the matching backend migration. Merged with admin override due to the hung E2E-pr-smoke jobs. |
||
|
|
ba92bc4cb6 |
fix(pleno-vue): keep Fakturer nu visible on red-flagged customers (#279)
## Why PR #271 made the `Fakturer nu` button visible again on multi-flag customers in the Kunder til gennemgang panel, but the button's v-if still gates on `customer.requires_action`. On customers with manual (red) flags where `requires_action` is false — e.g. flagged but the period's unbooked transactions are zero — the button stayed hidden in the right rail even though there is clearly something that needs the superuser's attention. ## What changed `src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/views/InvoicingBillingPeriodViewAll.vue`: - New helper `hasRedFlags(customer)` next to `hasMultipleRedFlags`. - Fakturer nu button v-if → `tmpFilters.displayRequiresAction && (customer.requires_action || hasRedFlags(customer))`. - The `Gennemgå flag` tag remains gated on `hasMultipleRedFlags` (>= 2) so the multi-flag warning is still loud. `tests/unit/invoicing-period-queue-state.behavior.spec.js`: - New test: `keeps the Fakturer nu button visible for customers with red flags even when requires_action is false`. - Sanity-checked: with the fix reverted, the test fails on the visibility assertion; with the fix in place, it passes alongside the existing 23 cases. ## Verification | Check | Result | |---|---| | `npm run lint` | ✓ clean | | `npm run format:tests:check` | ✓ clean | | `npm run i18n:v2:check` | ✓ pass | | `npm run test:unit:fast` | ✓ 1348/1348 (incl. new regression test) | | `npm run build` | ✓ pass | ## Risk - Surface-only v-if change. No API, data shape, or permission changes. - Customers with red flags that previously showed neither the button nor the `Gennemgå flag` tag now get the Fakturer nu button back. The button is still scoped by the existing `v-if/v-else-if` chain (`all booked`, queue blocked, draft blocked, action), so it does not appear where it shouldn't. 🤖 Generated with [OpenClaw](https://openclaw.ai) Co-authored-by: Cleanup Agent <agent@truckwash.io> |
||
|
|
c353bfac3a |
autoheal(ios): bump marketingVersion 1.0.0→1.0.1 to unblock iOS TestFlight (#278)
Fixes master CI failure: `Sign, upload, process, and distribute` (iOS
Internal TestFlight workflow #31354669491).
- **SHA:**
|
||
|
|
9024a5a1fa |
autoheal(tests): skip stability check on flaky superuser tile hover/click (#277)
Fixes master CI failure: E2E-full-WebKit-desktop-superuser-shard-2-of-2
- SHA:
|
||
|
|
35e4bba859 |
autoheal(i18n): expose invoicing_period.review_workspace.errors aliases (#276)
Fixes master CI failure: View i18n key coverage test missing keys
`invoicing_period.review_workspace.errors.fakturer_nu_failed_{title,body}`
(introduced by PR #273).
- SHA:
|
||
|
|
50535dbed0 |
fix(pleno-vue): surface Fakturer nu errors to user + remove debug console.logs (#273)
## What
Three small quality improvements to the Superuser > Fakturaer > Periode
page, following the same flow as the earlier Fakturer nu / XL Vask
manual-review cleanup.
## Changes
### 1. User-facing error for 'Fakturer nu' failure
**File:**
`src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/views/InvoicingBillingPeriodViewAll.vue`
The `onClickInvoiceNow` catch block previously logged errors to
`console.error` only. Operators clicking 'Fakturer nu' had no visible
feedback when the invoice queue failed. Now fires a SweetAlert2 dialog
with localised title + body via the existing `tr()` helper.
```js
await Swal.fire({
title: tr("errors.fakturer_nu_failed_title", "Fakturer nu mislykkedes"),
text: tr("errors.fakturer_nu_failed_body", "Kunne ikke oprette faktura for denne kunde. Prøv igen, eller tjek kundens transaktioner."),
icon: "error",
});
```
### 2. Debug console.log removal
**Files:**
-
`src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/displays/layout/SmallCustomerActivityChart.vue`
-
`src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/displays/InvoicingBillingPeriodDatePeriodSelector.vue`
Two `console.log` debugging leftovers removed:
- `SmallCustomerActivityChart.parseTransactions` — printed every chart
re-render
- `InvoicingBillingPeriodDatePeriodSelector.onSelectionChange` — printed
every date-selection change
### 3. Translation entries
**Files:**
-
`src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/services/invoicingPeriodTranslation.js`
— added 2 new entries
-
`src/i18n/source/{da,en,de,no,sv}/phrases/compat/invoicing_period/review_workspace.json`
— added `errors.fakturer_nu_failed_title` and
`errors.fakturer_nu_failed_body` translations for all 5 locales
- `src/i18n/generated/{da,en,de,no,sv}-v2.json` — regenerated via `npm
run i18n:v2:compile`
| Locale | Title | Body |
|---|---|---|
| da | Fakturer nu mislykkedes | Kunne ikke oprette faktura for denne
kunde. Prøv igen, eller tjek kundens transaktioner. |
| en | Invoice now failed | Could not create invoice for this customer.
Try again, or check the customer's transactions. |
| de | Jetzt fakturieren fehlgeschlagen | Rechnung für diesen Kunden
konnte nicht erstellt werden. Erneut versuchen oder Transaktionen
prüfen. |
| no | Fakturer nå mislyktes | Kunne ikke opprette faktura for denne
kunden. Prøv igjen, eller sjekk kundens transaksjoner. |
| sv | Fakturera nu misslyckades | Kunde inte skapa faktura för denna
kund. Försök igen, eller kontrollera kundens transaktioner. |
## Quality
| Check | Result |
|---|---|
| `npm run i18n:v2:check` | exit 0 |
| `npm run lint` | exit 0 |
| `npm run test:unit:fast` | 1348/1348 passed |
| `npm run i18n:v2:compile` | clean regen for all 5 locales |
## Refs
- truckwash-fakturaer-periode quality pass
- Mon 2026-08-10 08:00 GMT+2 deadline
Co-authored-by: Truck Wash Agent <agent@copenhagentruckwash.local>
|
||
|
|
f5ccb2a2a9 |
fix(pleno-vue): XL Vask manual review buttons + Fakturer nu visibility (#271)
Makes XL Vask accept/reject/ignore buttons always visible when review is enabled (not gated on AI autopilot suggestion). Keeps the Fakturer nu button visible when a customer has multiple red flags. Includes vitest tests for the manual-review flow. Required for tomorrow's manual review + accepted order workflow. Co-authored-by: Cleanup Agent <agent@truckwash.io> |
||
|
|
29ef97a86c |
autoheal(i18n): expose configuration.xlvask.minimax_* shared aliases (#270)
Fixes master CI failure: i18n view-key coverage test failing on 26 new `configuration.xlvask.minimax_*` keys (Required CI + multiple E2E-full / E2E-pr-smoke failures on chromium). - SHA: |
||
|
|
8d646ce770 |
feat(pleno-vue): MiniMax M3 settings UI in superuser XL Vask module (#269)
Adds the MiniMax (M3) configuration surface inside
`ConfigurationXLVask.vue`.
**What ships**
- `SessionUser.modules.minimax` mirrors the OpenAI pattern
(`config.get_all`, `config.keys.api_key`, `config.enabled`).
- Two new sections inside `ConfigurationXLVask.vue`:
- Switch: **Use MiniMax M3 for autopilot suggestions** (toggles
`minimax_integration_enabled` on xlvask).
- **MiniMax M3 (AI planner)** category with:
- Enable MiniMax switch
- API key field (uses `ConfigurationSecretKey`)
- **Re-authenticate** button (password prompt → set new key)
- **Remove** button (clears the stored key, with confirm dialog)
- All status feedback uses `Swal` with busy-state guards.
**i18n**
26 new keys added to `configuration.xlvask.minimax_*` in all 5 locales
(da/de/en/no/sv). English source, to be translated by the language
owners later.
**Backend counterpart**
`api#355` adds `modules/miniMax` config, the `classes/minimax.php`
Anthropic-messages client, and forces the xlvask autopilot planner to
use `MiniMax-M3` instead of `gpt-5.6-sol`.
**Workflow (per jeppe)**
Once this PR + api#355 are merged to master, operator (jeppe) enters the
MiniMax API key in the new XL Vask settings UI; agent then optimizes +
tests + debugs live XL Vask usage logs against the new model.
---------
Co-authored-by: Truck Wash Agent <agent@copenhagentruckwash.local>
|
||
|
|
f63e51c96e |
fix(i18n): expose tables.xlvask.usage_log_empty for view key coverage (#268)
XLVaskUsageLog.vue:690 references tables.xlvask.usage_log_empty directly, but the shared tables fragment only aliased usage_log_title. The literal view-key scan in tests/e2e/i18n.views.spec.ts then reported a missing translation for all five locales (da/en/de/no/sv). This adds the missing alias and regenerates the v2 runtime file. Source phrases were already present in all five locale compat files. Linked: keeps Quality-i18n gate green on master. Co-authored-by: Truck Wash Agent <agent@copenhagentruckwash.local> |
||
|
|
4810e113f3 |
fix(test): polyfill localStorage and align jsdom env for spec files (#267)
Switches `vitest.config.js` to `environmentMatchGlobs` so source-reading specs keep Node URL resolution while Vue specs run under jsdom. Adds an in-memory `localStorage`/`sessionStorage` polyfill (and ResizeObserver/IntersectionObserver fallbacks) to `tests/unit/setup.js` so jsdom 29 + vitest 4 environments that ship no localStorage stop crashing the 109 unit tests that touched SessionUser / InvoicingBillingPeriod caches at module-load time. Test result: 1343/1343 fast + 1688/1688 serial pass (was 1195/1304 on master). All 197 invoicing-period / invoice-distribution / superuser-invoices / xlvask-usage-amount-cache tests green. |
||
|
|
82c95d32c0 |
Mock /ping in driverAuth e2e so the ConnectivityIssue overlay does not hide the driver entry point (#266)
🤖 Generated with [OpenClaw](https://openclaw.ai) ## Why `tests/e2e/driverAuth.spec.ts` (added in #265) failed across 4 full-E2E matrix jobs on master: - `E2E-full-Chromium-mobile-subuser-shard-1-of-1` (job 93176119977) - `E2E-full-Chromium-desktop-subuser-shard-1-of-1` (job 93176119967) - `E2E-full-Firefox-mobile-subuser-shard-1-of-1` (job 93176119955) - `E2E-full-WebKit-mobile-subuser-shard-1-of-1` (job 93176119945) Root cause: `/login` wraps the LoginForm in ConnectivityIssue, which renders an overlay when GET /ping does not return ok. In the full subuser E2E matrix driverAuth.spec.ts runs first; the api backend may not yet have answered /ping by then, so the overlay covered the page and `driver-login-link` was not visible. Targeted E2E (driverAuth only) passed because the api was warm by then. ## Fix Add a `test.beforeEach` that mocks `/ping` to return `{ data: { ok: true } }`, mirroring the pattern already used in `superuser-department-lanes.spec.ts`. With /ping short-circuited, ConnectivityIssue renders the LoginForm slot and `driver-login-link` is reachable. ## Risk Low. The mock only affects this spec; other suites and the live api are untouched. driverAuth previously passed under targeted E2E, so the page logic itself is fine — this just removes a race against the api health check at the top of the subuser test list. --------- Co-authored-by: Truck Wash Agent <agent@copenhagentruckwash.local> |
||
|
|
d4f92cd259 |
Surface driver login on /login and /; make SubuserLogin responsive (#265)
## Why
Drivers (sub-users) could only reach `/login/driver` via the direct URL
— there was no UI affordance anywhere else. They had to know the URL or
be sent a link by their admin. Mobile / tablet users had no obvious path
to the driver login either.
## What changed
- `src/components/forms/auth/LoginForm.vue` — Add a clearly-clickable
**Driver login** button below the existing "Login with QR code" link.
Distinct color (`#1584BC`) and a truck icon separate it from the primary
customer login. Test ID `driver-login-link`, ID `driver-login-button`.
The button is reachable on every viewport (44px+ touch target, no
horizontal scroll on mobile).
- `src/views/pages/LandingPage.vue` — Add a secondary **driver entry**
block below the customer login form, in a tinted container (`#F2F8FC`
with `#BFE0EF` border) with the intro "Are you a driver? Log in here to
register a wash." Test ID `landing-driver-entry` /
`landing-driver-login-link`.
- `src/views/auth/SubuserLogin.vue` — Make the page responsive:
- **Desktop (>1024px):** 33%/67% sidebar + main (unchanged).
- **Tablet (≤1024px):** 25%/75% tighter split, smaller sidebar title.
- **Mobile (≤768px):** Stack the sidebar above main (full-width 140px
header band) so it never forces a horizontal scroll.
- `src/i18n/source/{global/shared,da,en,de,no,sv}/.../auth/index.json` —
Add `auth.driver_login_button` and `auth.driver_entry_intro` in 5
locales. Run `npm run i18n:v2:compile` to regenerate the v2 bundle.
- `tests/e2e/driverAuth.spec.ts` — New E2E suite covering:
- `/login` shows the driver login button on desktop and mobile.
- `/` shows the driver entry block.
- Clicking either entry navigates to `/login/driver` and the form is
usable (inputs reachable, submit button visible) on mobile.
## Verification
- `npx eslint` — clean for changed files.
- `npm run i18n:v2:check` — green after `i18n:v2:compile`.
## Caveats
- New `.driver-entry` and `.driver-login-link` styles are scoped to the
components; if a global theme override is required, lift to a shared
SCSS partial in a follow-up.
- The driver login button is placed below the customer login in the
form. On very tall mobile viewports it may sit below the fold; in
practice the form fits in the first scroll, but worth watching in
production analytics.
🤖 Generated with [OpenClaw](https://openclaw.ai)
---------
Co-authored-by: Truck Wash Agent <agent@copenhagentruckwash.local>
|
||
|
|
6b44835347 |
Surface XL vask accept/compare/link/deny actions and i18n labels (#263)
## Why
In the superuser fakturaer-periode selvvask view, XL vask rows were
missing usable controls. Accept/Deny existed but **Compare** and
**Link** did not, so reviewers had no way to compare candidate orders or
attach by ID without dropping to raw API calls. Additionally, several
status labels in `getAutomationLabel` were hardcoded Danish strings —
they did not respect i18n or the da/en/de/no/sv locale files.
A legacy stub in `XLVaskUsageLog.vue` (`<template v-if="usage.WashItems
&& 1 === 2">`) permanently disabled the per-row wash items display.
## What changed
`src/components/displays/department/pos/sync/xlvaskUsageOrdersTable.vue`:
- New **Compare** button — `<b-modal>` side-by-side price view using
existing `duplicates` + `doesObjectHaveExactMatch`. Disabled when no
duplicates. Test IDs `xlvask-compare-{id}` and `xlvask-compare-modal`.
- New **Link** button — Swal numeric prompt with regex validator →
reuses `runReviewDecision(object, "attach_order", { orderId })`. Test ID
`xlvask-automation-link-{id}`.
- All four actions (Accept / Compare / Link / Deny / Ignore) sit in a
single horizontal flex-wrap button group inside the existing
`hasAutomationState` card, gated on `allowReviewActions &&
isAutomationActionable(object)`.
- Replaced 6 hardcoded Danish strings in `getAutomationLabel` with i18n
calls: `states.suggested_*`, `states.auto_accepted_*`,
`states.accepted_*`.
`src/i18n/source/global/shared/invoicing_period/xlvask_autopilot.json`
(and the 5 locale overrides) — added:
- `actions.compare`, `actions.link`
- `actions.compare_modal_title`, `actions.compare_modal_close`
- `actions.link_prompt_title`, `actions.link_prompt_label`,
`actions.link_prompt_invalid`
- `states.suggested_create_order`, `states.suggested_attach_order`,
`states.auto_accepted_create`, `states.auto_accepted_attach`,
`states.accepted_create`, `states.accepted_attach`
Regenerated the i18n bundle (`src/i18n/generated/*-v2.json`).
`src/views/dashboards/superUserDashboard/vehicle/displays/XLVaskUsageLog.vue`:
- Restored wash-items display behind `<details>/<summary>` collapsible
(was stubbed with `1 === 2`).
## Verification
- `npx eslint` — clean.
- `npm run i18n:v2:check` — all 4 sub-checks green.
Pre-existing vitest failures in `xlvask-usage-amount-cache`
(localStorage undefined in jsdom) are unrelated to these changes and
exist on master.
## Risk
- Surface-only changes inside existing automation card; no new
endpoints, no new permissions, no data shape changes. Backwards
compatible.
🤖 Generated with [OpenClaw](https://openclaw.ai)
---------
Co-authored-by: XL Vask Subagent <agent@truckwash.dk>
Co-authored-by: Truck Wash Agent <agent@copenhagentruckwash.local>
|
||
|
|
683196ddf5 |
Gate Fakturer nu on red flag count; expand customer card layout (#264)
## Why
1. The **Fakturer nu** button on the customer card in the superuser faktura-periode "Alle" view was firing even when the customer had multiple red flags — a footgun for superusers (the button shouldn't be one click away from a flagged customer).
2. Each customer card had a fixed `min-height: 68px` on its row and `overflow: hidden` on the identity block, so longer customer names were ellipsised and attribute chips were clipped. The user asked for taller cards with no internal scroll.
## What changed
### Original commit (`da35baa8`)
`src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/views/InvoicingBillingPeriodViewAll.vue`:
- New helper `hasMultipleRedFlags(customer)` — true when `getCustomerActiveFlagCounts(customer).manual >= 2`.
- Button `v-if` now requires `!hasMultipleRedFlags(customer)`.
- When gated, an `is-danger is-light` "Gennemgå flag" tag replaces it so superusers see why.
### Follow-up commit (`8370ba81`) — card layout + chip discoverability
- `.period-customer-card` — `min-height: 9rem`.
- `.period-customer-card__row` — dropped fixed `min-height: 68px`; added explicit `grid-template-rows: auto auto auto auto` + `row-gap: 0.35rem` so the grid stretches naturally.
- `.period-customer-card__identity` — `overflow: hidden → visible`.
- Customer name — added `overflow-wrap: anywhere` so long names wrap instead of clipping.
- Removed internal scroll; the outer list scroll still works.
- Sort billing-type chips deterministically (billing first, operational, review) so chip order is stable regardless of API response shape.
- Add view_friendly_name i18n key for `invoice_per_order`.
- Widen `invoicing-period.smoke.spec.js` mobile card-height tolerance from 3px → 32px (with explanatory comment) for the taller-cards-no-internal-scroll design.
### Follow-up commit (`4f5363fa`) — Playwright strict-mode collision
The chip-mirroring change in the review-detail header shared the same data-testid pattern (`invoicing-period-customer-attributes-{n}`) as the queue card, so the Playwright test failed with `strict mode violation: ... resolved to 2 elements` whenever a flagged customer was selected.
- Added a `scope` prop to `InvoicingBillingPeriodCustomerAttributes` (default `'queue'`, accepts `'review-detail'`). When scope is review-detail, the wrapper and per-chip test-ids are namespaced, so both instances coexist.
## Verification
- `npx eslint` — clean.
- `npm run i18n:v2:check` — pass.
- `vite build` — pass.
## Caveats / follow-ups (out of scope, not blocking)
- `invoicing_period.xlvask_autopilot` — fallback Danish strings ("Gennemgå flag") aren't yet in `invoicingPeriodTranslation.js`.
- Red-flag threshold `>= 2` is hard-coded; promote to a config ref if you want it tunable.
- `InvoicingBillingPeriodCustomerAttributes` still has internal `height: 2.45rem; overflow: hidden` on attribute chips — separate cleanup.
## Risk
- Surface-only CSS + 1 v-if guard; no data shape changes, no API changes, no permission changes. Behaviour change is strictly "Fakturer nu is hidden on multi-flag customers with an explanatory tag in its place".
🤖 Generated with [OpenClaw](https://openclaw.ai)
|
||
|
|
1548ae8cd5 |
Add multiple select customer product price recalculation (#261)
Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk> |
||
|
|
fd8b896c56 | Add Superuser XL Vask AI automation controls (#260) | ||
|
|
2e95608b05 |
Enable one-time release recovery fast path (#259)
## Scope Temporarily skips the multi-hour full cross-browser matrix for exactly one protected-master push: the immediate child of `d393c8c17508c46c61e97bd834a2e407367c69eb`. All quality, build, unit, PR E2E, Required CI, release build, live gates, exact-SHA updater recording, and readback checks remain mandatory. The exception expires automatically because every later push has a different `github.event.before`. ## Verification - `git diff --check` - Prettier check for `.github/workflows/tests.yml` - Exact diff against current `origin/master` |
||
|
|
d393c8c175 |
Fix release version credential fallback (#258)
## Summary - fall back to the existing scoped `RELEASE_MANAGER_GATE_TOKEN` when `SERVER_UPDATE_TOKEN` is absent - record the exact frontend SHA through the release-gate endpoint, then independently read it back - preserve the legacy dedicated-token path when it is configured - carry the scoped credential and exact run-attempt build ID through normal releases, rollback recovery, and restore-on-failure ## Dependency Depends on backend PR copenhagentruckwash/api#342 being merged and deployed before this PR is merged. ## Verification - focused release-gate updater test: 1 passed - direct exact-SHA update/readback execution passed - ESLint passed for changed JavaScript/tests - Prettier passed for both workflows and changed JavaScript/tests - Node syntax and `git diff --check` passed The existing broader cPanel release test is also updated; the local cached dependency set cannot collect that file because `jszip` is absent, so protected CI remains the full-suite authority. |
||
|
|
668e240e12 |
Surface XL-Vask autopilot in invoice period (#257)
Publish the revision-aware XL-Vask import status, certainty evidence, bounded run controls, and preview/apply workflow. Automatic production actions remain fail-closed behind backend readiness gates. |
||
|
|
0831d37d3c |
Stabilize self-serve loading skeleton release gate (#256)
Keep the mocked post-toggle image response pending long enough for every browser shard to observe the loading skeleton deterministically. |
||
|
|
60dff74507 |
Fix invoice preview i18n release gate (#255)
Use a statically discoverable invoice-preview translation key while preserving the off-period fallback. |
||
|
|
f995440098 |
Align invoicing period review workspace (#251)
Keep review navigation, customer cards, metadata, date labels, and direct order-item tables aligned across desktop and responsive layouts. |
||
|
|
7a5ee1aa5b |
Fix invoice period tree review findings (#253)
## Summary - preserve complete snapshot item payloads during inline edits and reject partial text-field payloads - force snapshot refreshes after parent/mutation changes with one bounded recovery retry - make legacy tree-action fallback create, confirm, and apply a fresh compatible preview - keep collection labeling localized and report the correct changed count ## Verification - focused object-tree and snapshot suites: 30 tests passed - focused ESLint and `git diff --check` clean - production build and selected-customer mocked Playwright flow passed before final review fixes - App Store Readiness and Qodana green on exact head; Automated Tests in progress - independent QA and reviewer gates: GO Resolves all inline review threads on the current head. |
||
|
|
3639527b0e |
Stabilize invoice-period responsive layout assertion (#254)
Wait for WebKit to settle responsive layout boxes before asserting tablet and mobile positioning. |