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>
This commit is contained in:
Jeppe B
2026-08-12 00:12:04 +02:00
committed by GitHub
co-authored by Hermes Agent
parent 58adb1bef5
commit 4cfd003864
@@ -619,6 +619,46 @@ describe("Periode tab contract", () => {
expect(xlvaskUsageOrdersTableSource).toContain("const { loadList } = usePaginatedListInstance();");
});
it("wires the Selvvask view to the automation-workspace so operators see Accept / Reject / Ignore buttons", () => {
// The Selvvask view (Superuser → Fakturaer → Periode → Selvvask) must enable
// the automation workspace so the review actions surface in the orders table.
expect(periodViewSelfWashSource).toContain(':automation-workspace="true"');
// The pagination must forward `allow-review-actions` to the orders table using
// the per-request capability flag, so operators with can_review=true get the
// Accept / Reject / Ignore buttons. The AI-administrator-only flags
// (can_dry_run / can_execute / can_manage_policy) must remain on automationWorkspace
// directly so an operator without manage_xlvask_usage_automation cannot dry-run or
// execute the autopilot pipeline.
expect(xlvaskUsagePaginationSource).toContain(
':allow-review-actions="props.automationWorkspace && capabilities.can_review"'
);
expect(xlvaskUsagePaginationSource).toContain(
':allow-select-multiple="props.automationWorkspace && capabilities.can_review"'
);
expect(xlvaskUsagePaginationSource).toContain(
':allow-adjudication-actions="props.automationWorkspace && capabilities.can_manage_policy"'
);
// The orders table must render the operator-facing action buttons
// (Accept / Reject / Ignore / Audit) inside the right-hand action column,
// gated only on `allowReviewActions` (i.e. the API capability, not on
// manage_xlvask_usage_automation). This is the contract that lets the
// /superuser/invoicing/.../?periodView=self_wash view actually accept and
// edit XL Vask washes.
expect(xlvaskUsageOrdersTableSource).toContain(
'<div v-if="props.allowReviewActions" class="column is-2 xlvask-usage-actions-column">'
);
expect(xlvaskUsageOrdersTableSource).toContain(":data-testid=\"'xlvask-accept-' + object.id\"");
expect(xlvaskUsageOrdersTableSource).toContain(":data-testid=\"'xlvask-reject-' + object.id\"");
expect(xlvaskUsageOrdersTableSource).toContain(":data-testid=\"'xlvask-ignore-' + object.id\"");
// Regression guard: the AI-adjudication row (adjudication) must remain gated
// on allowAdjudicationActions (can_manage_policy) so a normal operator never
// sees the calibration label buttons.
expect(xlvaskUsageOrdersTableSource).toContain(':data-testid="`xlvask-adjudication-${outcome}-${object.id}`"');
});
it("keeps Selvvask usage entries wrapped inside the period content area", () => {
expect(xlvaskUsageOrdersTableSource).toContain("xlvask-usage-card-primary");
expect(xlvaskUsageOrdersTableSource).toContain("xlvask-usage-card-status");