J-11 — Late Checkout & Folio Dispute
One-liner: Guest disputes a charge at checkout; receptionist can request a manager approval, refund or credit-note.
1. Purpose
A guest at checkout disputes a charge (mini-bar item not consumed; cleaning fee considered unreasonable; late-checkout fee). The receptionist coordinates with a manager (over-the-shoulder approval or remote), removes / refunds / credit-notes the disputed line, and completes the checkout cleanly with a full audit trail. Outcome: dispute resolved, folio reconciles, audit log captures who-did-what, refund processed (cash on hand or card refund).
2. Persona Context
- Persona: Receptionist (primary) + Manager (approver).
- Surface: Electron Desktop.
- Primary BFF:
bff-backoffice-service. - Backing services:
folio-service,payments-service,audit-service,notification-service. - Preconditions: Manager logged in (locally or via remote approval flow); folio is open.
- Trigger: Receptionist clicks "Dispute charge" on a folio line.
3. Entry Points
| # | Entry | Notes |
|---|---|---|
| 1 | "Dispute" on folio line during checkout | Default |
| 2 | "Dispute" mid-stay | Pre-checkout |
| 3 | Manager-initiated review | P2 |
4. Screen-by-Screen Flow
4.1 DisputeChargeModal
- Layout: Charge details, dispute reason picker, free-text note, photo evidence (optional), proposed action (remove / refund / credit-note); approval level shown ("Manager required for amounts > X").
- Components:
DisputeReasonSelect,Form,PhotoUpload,ApprovalRequiredPill. - Offline: Editable; submission queued.
- AI: Phase 2 — dispute-pattern detector (HITL alert to manager).
- Errors: Validation per field.
- Loading: Sub-200 ms.
- A11y: Field labels; pill announces approval requirement.
- RTL: Mirror.
- Perf: <= 200 ms.
- Telemetry:
frontend.dispute.opened { lineId, reason }.
4.2 ManagerApprovalPanel
- Layout: Manager PIN / fingerprint pad (if configured) OR remote approval pending pill; reason + charge context displayed.
- Components:
ManagerPinPad,RemoteApprovalStatusPill. - Offline: PIN works locally; remote approval requires online.
- AI: None.
- Errors: Wrong PIN -> rate-limited retry; remote approval timeout -> banner.
- Loading: Spinner during submit.
- A11y: PIN pad announces input ("Manager PIN entered"); remote status announced live.
- RTL: Mirror.
- Perf: Local approval <= 500 ms; remote <= 30 s p95.
- Telemetry:
frontend.dispute.approval_submitted { method: pin|remote }.
4.3 ResolutionScreen
- Layout: Confirmation of action taken (line removed / refund issued / credit note created), updated folio total, "Continue checkout" CTA.
- Components:
ResolutionSummaryCard,Button("Continue"). - Offline: Refund via cash local; card refund queued.
- AI: None.
- Errors: Refund failed -> retry / alternate.
- Loading: Sub-second.
- A11y: Resolution announced via
aria-live. - RTL: Mirror.
- Perf: <= 1 s p95.
- Telemetry:
frontend.dispute.resolved { action }.
5. State Machine
6. Data Requirements
6.1 Server state
POST /api/v1/folios/:id/disputes(idempotent)POST /api/v1/folios/:id/disputes/:disputeId/approve(idempotent; carries manager auth)POST /api/v1/folios/:id/disputes/:disputeId/resolve(idempotent; with action: remove|refund|credit-note)
6.2 Local persistence
- SQLite
disputes_local,outbox,audit_local.
6.3 Idempotency
- All mutations carry
X-Idempotency-Key.
7. AI Behavior
n/a in P1; P2 — dispute-pattern detector (HITL alert to manager when patterns recur).
8. Offline Behavior
- Local PIN approval works.
- Remote approval requires online.
- Cash refunds local; card refunds queued.
- Credit notes local; reconciled to authority report on next online window.
9. Error States
| Error | Trigger | UX shown | Recovery | Telemetry |
|---|---|---|---|---|
MANAGER_PIN_INVALID | Wrong PIN | Inline error; rate-limited retries | Manager re-enters | frontend.dispute.pin_invalid |
REMOTE_APPROVAL_TIMEOUT | No response in 30 s | Banner; offer fallback (local PIN) | User switches method | frontend.dispute.remote_timeout |
REFUND_FAILED | Provider 5xx | Banner; retry / alternate | Retry or switch to credit note | error.surfaced { code } |
DISPUTE_OVER_CAP_FOR_ROLE | Receptionist tries to auto-approve over their cap | Modal: "Manager required" | Manager enters PIN | frontend.dispute.cap_exceeded |
10. E2E Test Gates
- Composite gate
G-DESK-OPS-3: dispute -> manager approval -> resolve (remove / refund / credit-note) -> checkout continues. - Remote approval timeout fallback.
- Refund failure -> credit note path.
11. Performance Requirements
| Metric | Target |
|---|---|
| Dispute open | <= 200 ms |
| Local approval | <= 500 ms |
| Remote approval | <= 30 s p95 |
| Resolve action | <= 1 s p95 |
12. Accessibility Requirements
- PIN pad accessible; alternative manager-auth method (smartcard) supported.
- Resolution announcement via
aria-live. - All keyboard-completable.
13. Telemetry
Frontend events
frontend.dispute.opened { lineId, reason }frontend.dispute.approval_submitted { method }frontend.dispute.resolved { action }
Domain events emitted
melmastoon.folio.dispute.opened.v1melmastoon.folio.dispute.approved.v1melmastoon.folio.dispute.resolved.v1(with action)melmastoon.payments.refund.issued.v1(if refund)melmastoon.audit.recorded.v1
14. Success Criteria
- Dispute resolved before checkout continues; folio reconciles.
- Manager approval recorded with method (PIN / remote / smartcard).
- Refund / credit note tied to original line; visible in folio history.
- Audit log entry for opened, approved, resolved.