PRD: Shift reports (X/Z)
| Module | Reports (CORE-11) | PRD ID | PRD-SHF-001 |
| Status | In-progress | Owner | Reports squad |
| Date | 2026-04-22 | Version | v0.1 |
| Packages | @nx/sale · @nx/core · @nx/finance | URD | SHF |
TL;DR
Lets a cashier read shift totals and reconcile the cash drawer without leaving the counter: an X report for a live, repeatable mid-shift snapshot and a Z report for the final, locked close-shift figures. Each report pairs cash reconciliation (opening float, cash sales, pay-in/out, expected vs. actual, discrepancy) with a sales summary, so every shift closes against a verifiable number instead of a guess.
1. Context & Problem
POS cashiers run shifts against an open PosSession. Today a session tracks cash movements but produces no report, so there is no way to read shift totals or reconcile the drawer at the counter: a cashier cannot see expected-vs-actual cash, cannot pull an interim mid-shift summary, and has no locked snapshot to hand off at close. Without that, shift handovers rely on manual counting and the owner has no auditable record of what each shift took in - a blocker for cash-honest operations at the HKD/SME scale KICKO targets.
This increment delivers the two reports a cashier expects against an open session - an interim X report and a closing Z report - backed by a persisted close-shift snapshot, on top of the existing POS session lifecycle and cash-movement tracking.
2. Goals & Non-Goals
Goals
- An X report (interim): a live in-shift snapshot generated from the current open session, repeatable any number of times.
- A Z report (closing): the final close-shift report, read once per session and backed by a persisted
PosSessionReportsnapshot. - Cash reconciliation: opening float, cash sales, pay-in / pay-out, expected vs. actual counted cash, and discrepancy, with a close-recount count on the session.
- A sales summary on the report: gross, discount, tax, net, and order count.
- Merchant + sale-channel scoping and session listing with pagination.
Non-Goals
- Daily / product / category sales reports - owned by the
SLSfeature (URD-SLS). - Profit & loss, inventory valuation, customer analytics, export, and scheduled reports -
ADV, Planned (URD-ADV). - Payment-method and category breakdowns on the shift report (Should-level URD-SHF-005..006).
- Multi-employee shifts and PIN step-up on report access.
3. Success Metrics
| Metric | Target / signal |
|---|---|
| Reconciliation coverage | 100% of closed sessions produce exactly one Z report |
| Cash accuracy | Discrepancy = counted cash − (opening float + cash sales − pay-outs); surfaced on every close |
| Interim usability | X report is repeatable mid-shift with no session mutation |
| Scoping correctness | Zero cross-merchant figures returned on any report |
4. Personas & Use Cases
| Persona | Goal in this feature |
|---|---|
| Cashier | Pull an X report mid-shift; close the shift to produce the Z report with a counted-cash reconciliation |
| Manager | Review any shift's Z report for their merchant |
| Owner | See shift reconciliation across their merchants |
Core scenarios: open a POS session → pull an X report mid-shift (repeatable) → at close, count actual cash → the system computes expected vs. actual and locks a Z report snapshot, one per session.
5. User Stories
- As a cashier, I want to pull an X report from my open session, so I can check interim cash and sales totals without closing the shift.
- As a cashier, I want the X report to be repeatable, so I can re-check totals as the shift progresses.
- As a cashier, I want to close my session with a counted-cash recount, so the Z report shows expected vs. actual and the discrepancy.
- As a manager, I want exactly one locked Z report per session, so closed-shift figures can't drift.
- As an owner, I want shift reports scoped to my merchant, so no other merchant's figures leak in.
6. Functional Requirements
| # | Requirement | URD ref |
|---|---|---|
| FR-1 | X report (interim): generate a live snapshot from the current open session via POST …/x-report; repeatable, no session mutation | URD-SHF-001 |
| FR-2 | Z report (closing): aggregated close-shift report read once per session via GET …/z-report, backed by the persisted PosSessionReport snapshot | URD-SHF-002 |
| FR-3 | Cash reconciliation: opening float, cash sales, pay-in / pay-out, expected vs. actual counted cash, discrepancy, plus a close-recount count on the session | URD-SHF-003 |
| FR-4 | Sales summary on the report: gross, discount, tax, net, order count | URD-SHF-004 |
| FR-5 | Reports scoped per merchant and sale channel; session listing with pagination and filters | URD-SHF-001..002 |
| FR-6 | Authorization subjects PosSession.getXReport / PosSession.getZReport (READ action) gate the two report routes | URD-SHF-001..002 |
Full requirement text and acceptance criteria live in the Reports URD. This PRD references them rather than restating them. Payment-method and category breakdowns (URD-SHF-005..006, Should) are out of this increment -
SHFremains In-progress.
7. Non-Functional Requirements
| Area | Requirement |
|---|---|
| Data integrity | The Z report is a one-per-session terminal snapshot; once locked at close it does not change |
| Read-only | Reports aggregate over cash movements and completed orders; the X report never mutates the session |
| Tenancy & authz | All operations scoped per merchant (x-merchant-id) and sale channel; report routes gated by the PosSession.getXReport / getZReport READ subjects |
| Precision | Cash and monetary math uses float(value, 4) |
| Performance / scale | Session listing is paginated; report aggregation runs over a single session's movements |
| i18n | User-facing labels/statuses are bilingual ({ en, vi }) |
8. UX & Flows
Key screens (in apps/client): the shift/session view, the X-report action, the close-shift recount, and the Z-report view. The reports are served by the @nx/sale shift controller.
9. Data & Domain
| Entity | Role |
|---|---|
PosSession | The open shift - lifecycle (open/close), cash-movement tracking, close-recount count |
PosSessionReport | The persisted close-shift (Z) snapshot - cash reconciliation + sales summary, one per session |
FinanceTransaction | Cash movements aggregated by the report; the session reference (pos_session_id) is intentionally not held here |
Conceptual only - full schema and invariants in the sale domain model.
10. Dependencies & Assumptions
Depends on
- POS session lifecycle & cash-movement tracking (
@nx/saleshift service) - the X/Z reports aggregate an open or just-closed session. - Completed orders (Orders) - the sales summary aggregates completed orders in the session.
- Cash / payment movements (
@nx/finance) - pay-in/out and cash sales feed the reconciliation. @nx/coresale schemas - host thePosSession/PosSessionReporttables.
Assumptions
- A cashier opens a session with an opening float before transacting.
- Each session closes exactly once, producing exactly one Z report.
11. Risks & Open Questions
| Risk / question | Mitigation / status |
|---|---|
| Cash reconciliation diverging from the finance ledger | Report aggregates cash movements directly; pos_session_id removed from FinanceTransaction so the report - not the ledger - owns the session view |
| Double Z report per session | Z report is read once per session against a persisted snapshot; terminal by design |
| Payment-method / category breakdowns expected by some merchants | Out of this increment (URD-SHF-005..006); SHF stays In-progress until delivered |
| Multi-employee shifts and PIN step-up on report access | Out of scope here; tracked separately |
12. Release Plan & Launch Criteria
| Aspect | Plan |
|---|---|
| Phase | P2 - see URD feature catalog |
| Rollout | All merchants; no feature flag |
| Migration | Adds PosSession / PosSessionReport tables; removes pos_session_id from FinanceTransaction |
| Launch criteria | X report repeatable on an open session; exactly one Z report per closed session; cash reconciliation and sales summary verified against counted cash |
| Monitoring | Z reports per closed session (expect 1:1), reconciliation discrepancy distribution, report request error rate |
13. FAQ
What is the difference between an X report and a Z report? The X report is an interim, repeatable mid-shift snapshot pulled while the session is open; the Z report is the final close-shift report, produced once per session and locked.
Does pulling an X report change anything? No - it is read-only and repeatable; it does not mutate the session or finance records.
Can a session have more than one Z report? No - exactly one Z report is produced per session at close, backed by a persisted snapshot.
Why isn't the session referenced on the finance transaction? The report aggregates cash movements directly, so the session reference (pos_session_id) is not held on FinanceTransaction; the report owns the session view.
Do shift reports include payment-method or category breakdowns? Not in this increment - those are Should-level (URD-SHF-005..006) and remain open.
References
- URD: Reports → Shift Reports - requirements (SHF area)
- Related: Sales Reports · Access & Scoping · Advanced Analytics
- Module: Reports - overview + traceability
- Developer: @nx/sale - shift service, POS session domain model