J-06 — Stay, Modify, Check-Out
One-liner: During the stay, charges accumulate on the folio; on departure, payment is settled, the room is released, and lock credentials are revoked.
1. Purpose
While the guest stays, ad-hoc charges (room service, mini-bar, late-checkout fees, extras) accumulate on the folio. On departure, the folio is settled (cash / card / mixed), the room status is released to housekeeping, and lock credentials are revoked. Outcome: reservation Checked-out, folio Closed, room Vacant Dirty (HK queue updated), key credentials revoked, receipt issued.
2. Persona Context
- Persona: Receptionist (primary actor); guest (subject); housekeeping (downstream).
- Surface: Electron Desktop.
- Primary BFF:
bff-backoffice-service.
- Backing services:
folio-service, payments-service, lock-and-key-service, housekeeping-service, notification-service, audit-service.
- Preconditions: Reservation in
Checked-in state; folio open.
- Trigger: Receptionist clicks "Check-out" on a reservation row OR adds a charge during the stay.
3. Entry Points
| # | Entry | Notes |
|---|
| 1 | Click "Check-out" on reservation row | Departure flow |
| 2 | Click "Add charge" on reservation row | Mid-stay charge addition |
| 3 | Click "Modify" on reservation row | Date change, room move |
| 4 | "Late checkout request" notification | See J-11 |
4. Screen-by-Screen Flow
4.1 FolioDetailScreen (mid-stay)
- Layout: Folio header (guest, room, dates, balance), running ledger of charges, "Add charge" CTA, "Modify reservation" CTA, "Check out" CTA.
- Components:
FolioHeader, LedgerTable, AddChargeButton, Button (variants).
- Offline: Reads from SQLite; charges added locally; outbox flushes on reconnect.
- AI: None on ledger. Phase 2: AI categorisation suggestion for ambiguous charges.
- Errors: Charge entered with mismatched currency -> validation error; lock unlock anomaly -> sidebar alert.
- Loading: Sub-200 ms (local cache).
- A11y: Ledger is a
<table> with <th>s; balance announced on charge add.
- RTL: Mirror.
- Perf: Mount <= 200 ms.
- Telemetry:
frontend.folio.viewed { reservationId }; frontend.folio.charge_added { type, amount }.
- Layout: Charge type (food, mini-bar, services), description, amount, tax behaviour, attach photo of bill (optional), notes.
- Components:
Form, ChargeTypeSelect, MoneyInput, Button ("Add").
- Offline: Same as online; queued on add.
- AI: Phase 2 — categorisation suggestion.
- Errors: Negative amount disallowed; over-cap warning per tenant policy.
- Loading: Spinner on submit.
- A11y: Field-by-field labels; modal traps focus; Esc to close.
- RTL: Mirror.
- Perf: Submit <= 200 ms (local) / <= 500 ms (online RTT).
- Telemetry:
frontend.folio.charge_added.
4.3 ModifyReservationModal
- Layout: Date pickers, room move selector, rate-impact preview, policy disclosure (cancellation / modification fee).
- Components:
DateRangePicker, RoomMoveSelector, RateImpactPreview, Button ("Apply").
- Offline: Modifications queued; conflict warning if overlap risk.
- AI: None.
- Errors: Conflict with another reservation -> blocking modal with options (override, suggest alternative).
- Loading: Rate impact recalculated <= 500 ms.
- A11y: Date picker keyboard-navigable; impact preview announced.
- RTL: Mirror.
- Perf: Apply <= 1 s p95.
- Telemetry:
frontend.folio.modified { newRoomId?, newDates? }.
4.4 CheckoutWizardStep1ReviewBalance
- Layout: Balance summary, taxes breakdown, voucher refund (if applicable), past payments list.
- Components:
BalanceSummaryCard, TaxBreakdown, Button ("Continue to settlement").
- Offline: Reads from SQLite; settlement queued.
- AI: None.
- Errors: Suspect missing charge -> banner with link to add charge first.
- Loading: Sub-200 ms.
- A11y: Balance announced via
aria-live.
- RTL: Mirror.
- Perf: Mount <= 200 ms.
- Telemetry:
frontend.checkout.balance_reviewed { reservationId }.
4.5 CheckoutWizardStep2Settlement
- Layout: Payment-method selector (Cash / Card / Mixed); split-payment editor; payment provider element if card; cash drawer integration (P2 — log to drawer).
- Components:
PaymentMethodPicker, SplitPaymentEditor, PaymentProviderElement, CashDrawerLogger (P2), Button ("Settle").
- Offline: Cash settlement local; card settlement queued (with policy disclosure: "Card will charge when online; if it fails, we'll notify").
- AI: None.
- Errors: Card decline -> alternate-method offer; partial payment short -> warning + option to write-off (with audit reason).
- Loading: "Settle" spinner; success animation suppressed for accessibility.
- A11y: Split-payment editor announces remaining balance.
- RTL: Mirror.
- Perf: Settle <= 2 s p95 (online card) / <= 200 ms (cash local).
- Telemetry:
frontend.checkout.settled { method, amount }.
4.6 CheckoutWizardStep3KeyRevokeAndRoomRelease
- Layout: Lock credential revoke status (online vendor revoke vs offline-cert revoke); room status set to "Vacant Dirty"; HK assignment trigger automatic; "Done" CTA.
- Components:
KeyRevokePanel, RoomStatusUpdater, Button ("Complete").
- Offline: Revoke via offline-cert; vendor reconciliation queued.
- AI: None on revoke. Phase 2 — anomaly indicator if unusual unlock pattern detected during stay.
- Errors: Vendor revoke 5xx -> queue for retry; status pill "Revoke pending".
- Loading: Sub-200 ms.
- A11y: Revoke status announced; "Complete" announces "Check-out complete".
- RTL: Mirror.
- Perf: Wizard total <= 60 s p95.
- Telemetry:
frontend.checkout.completed { reservationId }.
5. State Machine
6. Data Requirements
6.1 Server state (online)
| Operation | Endpoint | Idempotency | Notes |
|---|
getFolio | GET /api/v1/folios/:id | n/a | Cached in SQLite |
addCharge | POST /api/v1/folios/:id/charges | X-Idempotency-Key | |
modifyReservation | PATCH /api/v1/reservations/:id | X-Idempotency-Key | |
getCheckoutBalance | GET /api/v1/folios/:id/checkout-quote | n/a | |
settleFolio | POST /api/v1/folios/:id/settle | X-Idempotency-Key | Multi-method splits |
revokeKey | POST /api/v1/lock/key/revoke | X-Idempotency-Key | |
releaseRoom | POST /api/v1/front-desk/checkout/:id/release-room | X-Idempotency-Key | |
completeCheckout | POST /api/v1/front-desk/checkout/:id/complete | X-Idempotency-Key | |
6.2 Local persistence
- SQLite tables:
folios, folio_charges, payments_local, key_revokes_local, room_status_local, outbox.
- Outbox flush ordered: charges -> settlement -> revoke -> release -> complete.
6.3 Idempotency
- Per-action ULID; reused on retry.
7. AI Behavior
n/a in P1. P2: charge categorisation suggestion (HITL); unlock-pattern anomaly indicator.
8. Offline Behavior
- Mid-stay charges added locally; flush on reconnect.
- Modifications queued with conflict-warning UX.
- Cash settlement local; card settlement queued; receipt printed locally.
- Lock revoke via offline-cert; vendor reconciliation queued.
- Room status "Vacant Dirty" set locally; housekeeping queue updated locally; reconciliation on reconnect.
9. Error States
| Error | Trigger | UX shown | Recovery | Telemetry |
|---|
FOLIO_NEGATIVE_AMOUNT | Negative charge entered | Inline error | User edits | frontend.folio.invalid_charge |
FOLIO_OVER_CAP_WARNING | Charge > tenant cap | Confirm modal | User confirms or cancels | frontend.folio.over_cap |
RESERVATION_CONFLICT_ON_MODIFY | New dates / room overlap | Blocking modal with options | User picks alt or overrides with reason | frontend.folio.modify_conflict |
PAYMENT_DECLINED (card) | Card decline at settlement | Inline error; offer alternate / split | User resolves | frontend.checkout.payment_declined |
LOCK_VENDOR_REVOKE_FAILED | Vendor 5xx | Pill "Revoke pending"; offline-cert revoke local | Auto retry | frontend.checkout.revoke_failed |
OUTBOX_FLUSH_PARTIAL | Some events fail to sync | Sync Center shows failed items | User triggers manual retry | frontend.sync.partial_failure |
10. E2E Test Gates
- Composite gate
G-DESK-2: stay -> charges added -> checkout -> settlement (cash/card) -> key revoked -> room released -> done. Online + offline variants.
- Card decline at settlement -> alternate path -> success.
- Modify reservation conflict -> override -> audit log entry.
| Metric | Target |
|---|
| Folio mount | <= 200 ms (local) |
| Charge add | <= 200 ms local / <= 500 ms online |
| Settle (cash) | <= 200 ms local |
| Settle (card) | <= 2 s p95 |
| Wizard total | <= 60 s p95 |
12. Accessibility Requirements
- All keyboard-completable.
- Balance announcements via
aria-live.
- Focus management on wizard step changes.
- Modals trap focus; Esc closes.
- High contrast / reduced motion respected.
13. Telemetry
Frontend events
frontend.folio.viewed { reservationId }
frontend.folio.charge_added { type, amount }
frontend.folio.modified
frontend.checkout.balance_reviewed
frontend.checkout.settled { method, amount }
frontend.checkout.completed { reservationId }
Domain events emitted
melmastoon.folio.charge.added.v1
melmastoon.folio.modified.v1
melmastoon.folio.settled.v1
melmastoon.lock_and_key.credential.revoked.v1
melmastoon.front_desk.checkout.completed.v1
melmastoon.housekeeping.task.created.v1
melmastoon.audit.recorded.v1
14. Success Criteria
- Folio total balance always reconciled to ledger sum (zero drift).
- Receipts produced (printable + emailable) <= 5 s after settlement.
- Housekeeping queue updated within 10 s of room release.
- Key credentials revoked end-to-end; if vendor 5xx, offline-cert revoke local + reconciliation queued.
- All checkout actions appear in audit log within 5 s.
- Wizard total <= 60 s p95 online.
References