EP-005C — Night Audit RTM
EPIC: EP-005C Night Audit (sub-epic of EP-005 Front Desk Operations) Reconciled: 2026-05-24 Wave 1 of 21 — per-EPIC RTM fan-out. Follows the pilot template in
EP-002-rtm.mdand the rules in./PLAYBOOK.md. Source of truth for "% passing":hmp-docs/docs/platform-registry/2026-05-16-13-flow-demo-readiness.md— Flow 9 (Night Audit) 12/12 PASS, exemplary across the 13-flow suite.
Status legend
| Status | Meaning |
|---|---|
Built | Code exists + tests exist + tests pass |
Built-Untested | Code exists, no automated test coverage (or test exists but is a stub) |
Partial | Code exists for some acceptance criteria, gaps for others |
Stub | Code is a placeholder / TODO / returns mock data |
Not Started | No code |
Summary
| Metric | Count |
|---|---|
| Total stories | 9 |
| Catalog claims "Built" | 4 (US-005-07, US-005-22, US-005C-01, US-005C-07 — but see D-001 numbering drift) |
| Verified-Built | 6 (US-005-07, US-005-22, US-005C-01, US-005C-02, US-005C-03, US-005C-07) |
| Built-Untested | 0 |
| Partial | 2 (US-005C-05 cashier-shift workflow incomplete; US-005C-04 resume shipped but limited) |
| Stub | 0 |
| Not Started | 1 (US-005C-06 — chain orchestrator shipped but EP-018 chain admin UI missing) |
| Drift findings | 8 (D-001..D-008; 4 P0 / 3 P1 / 1 P2) |
| [SME-PROPOSED] refinements | 16 |
The EP-005C epic body is substantially stale relative to the shipped code. Multiple stories the EPIC marks "Not Started" or "Partial" have shipped (no-show processing, rollback expansion, scheduled audit via K8s CronJob, chain orchestrator, resume, PDF + CSV summary). Flow-9 was 12/12 PASS on 2026-05-16 — the strongest signal on the 13-flow suite — and the catalog/EPIC artifacts do not reflect that maturity.
US-005-07 — Night Audit Automation
Catalog status: Built (P0, 13 points) Verified status: Built
Note: EPIC body calls this
US-005-07while the per-EPIC review folder atreviews/EP-005/usesUS-005-004for the same surface (seeUS-005-004-review.md, dated 2026-03-07, verdict PARTIAL). The 2026-03-07 review is ~10 weeks stale — most NOT_IMPL items have shipped via AP-T09-001/002/003/005. P0 drift candidate (D-001).
Code locations:
hmp-frontdesk-service/src/night-audit/night-audit.service.ts— Core engine (1,652 lines).startNightAudit,_runNightAudit,processNightAudit,failAuditLog,acquireAuditLock/releaseAuditLock,getPropertyLocalMidnight(timezone),onModuleInit(GSA-133 stale-RUNNING recovery)hmp-frontdesk-service/src/night-audit/night-audit.controller.ts:252-273—POST /night-audit/:propertyId/start(with?dryRun=true);@Roles('SUPER_ADMIN','ADMIN','MANAGER','ACCOUNTS');@Audit({tableName:'night_audit_logs'})hmp-frontdesk-service/src/night-audit/night-audit.module.ts— Module registration; AP-T09-003 importsStorageModulefrom@hmp/infrafor S3 snapshot DIhmp-frontdesk-service/src/night-audit/night-audit-outbox.service.ts— W2.D.5 transactional outbox fornight.audit.completedevent (durable on commit, 30s relay cron drains PENDING)hmp-frontdesk-service/src/night-audit/night-audit-pdf.service.ts— pdfkit generator + RFC-4180 CSVhmp-frontdesk-service/src/night-audit/pos-business-day.client.ts— POS business-day cutover clienthmp-frontdesk-service/src/folios/folios.service.ts::postCharge— Charge posting path used inside audit transaction (W2.A.1 GST split persisted; AP-T07-001/004)hmp-web-dashboard/src/app/(dashboard)/night-audit/keynova.tsx— Stepper UI: pre-checks → audit progress → audit result. Reads selected property viauseAppStore(PMS-13 fix)hmp-web-dashboard/src/components/modules/night-audit/{pre-checks-step,audit-progress,audit-result-step}.tsx— Wizard step componentshmp-infrastructure/kubernetes/jobs/night-audit-cronjob.yaml— AP-T09-005 K8s CronJob (schedule: "30 20 * * *", 20:30 UTC = ~02:00 IST,concurrencyPolicy: Forbid,activeDeadlineSeconds: 7200,backoffLimit: 2); readsINTERNAL_SERVICE_TOKENfrom canonicalinternal-service-tokenSecret (B2)hmp-frontdesk-service/src/night-audit/night-audit-scheduler.service.ts— In-process NestJS@Cron(EVERY_HOUR)fallback (useNIGHT_AUDIT_CRON_DISABLED=trueto disable when K8s CronJob is active — AP-T09-005)
Unit + integration tests:
hmp-frontdesk-service/src/night-audit/night-audit.service.spec.ts(1,166 lines) — Core engine specs covering full pipeline, AP-T09-001 rollback expansion, GST splits, no-show processing, KPI calculation, concurrency lock, COMPLETED_WITH_ERRORS path, GSA-133 stale-RUNNING recovery, GSA-014/015 tenantId resolution + persistencehmp-frontdesk-service/src/night-audit/night-audit.controller.spec.ts(266 lines) — Controller specs incl. AP-T09-003 PDF/CSV branches + role gateshmp-frontdesk-service/src/night-audit/night-audit-gst.spec.ts— GST slab pinning (≤1000 = 12%, ≤7500 = 18%, >7500 = 28%) viacalculateGSTForRoom()from@hmp/constantshmp-frontdesk-service/src/night-audit/night-audit-outbox.service.spec.ts— W2.D.5 outbox enqueue + relay + DEAD_LETTERhmp-frontdesk-service/src/night-audit/outbox-replay-endpoint.spec.ts— DLQ replay controller specshmp-frontdesk-service/src/night-audit/storage-di.spec.ts— AP-T09-003 source-level guards that StorageService DI is real, not a casthmp-frontdesk-service/src/night-audit/__tests__/gsa-014-015-tenant-scope.spec.ts— Tenant-id resolution + persistence on every audit loghmp-frontdesk-service/src/night-audit/__tests__/gsa-025-026-authz.spec.ts— Role-guard coverage on every endpoint
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/flow-9-night-audit.spec.ts:: 7 scenarios (S1 pre-audit report, S2 start ?dryRun, S3 charge-posting via dryRun, S4 business-date advance, S5 completion, S6 duplicate-start guard, S7 role gate) — 12/12 PASS (2026-05-16-13-flow-demo-readiness.md, 2026-05-16). Side-effects gated byFLOW9_FULL_AUDIT=trueenv var; afterAll() attempts rollback to keep test property reusable.hmp-infrastructure/testing/flow-tests/src/flows/night-audit.flow.spec.ts— Legacy flow spec (predates flow-9 numbering)
Real-backend smokes:
hmp-web-dashboard/e2e/real-backend/smoke-night-audit.real.spec.ts— Night audit page loads + KPIs render against seed
E2E (Playwright):
hmp-web-dashboard/e2e/persona-frontdesk.spec.ts— frontdesk persona flow touches night-audit pagehmp-web-dashboard/e2e/persona-manager.spec.ts— manager persona drives manual trigger
Use cases referencing this story:
- none enumerated — drift candidate (no
uc-night-audit-*.yamlfound)
SME notes:
- Flow-9 12/12 PASS is exemplary on the 13-flow suite (flows 6/7/8 are 6/13, 0/10, 0/15 respectively) — the catalog/EPIC body should celebrate this, not understate it as
In Progress. - The 2026-03-07 review file (
US-005-004-review.md) lists 6 NOT_IMPL items (SAC 996311, business date advance, Kafkanight.audit.completed, no-show cancellation charges, IGST, rollback). Of those: business-date advance,night.audit.completedevent (W2.D.5 outbox), no-show first-night charge (audit step 2 perCLAUDE.md), IGST (W2.A.1 + AP-T07-004), and rollback (AP-T09-001 expanded to ROOM+OVERSTAY_NIGHT+NO_SHOW) have all SHIPPED. Only SAC code 996311 annotation on the room-charge line item remains unverified — drift candidate. - The K8s CronJob (AP-T09-005) is the canonical production trigger; the in-process
@Cron(EVERY_HOUR)scheduler is fallback. The EPIC body'sUS-005C-07 Scheduled Auditstory does not narrate this dual-path or document theNIGHT_AUDIT_CRON_DISABLED=trueopt-out — D-005. - Indian-hospitality reality: 02:00 IST is the standard hotel night-audit window (front-desk traffic minimum, restaurants closed). The CronJob runs at 20:30 UTC = 02:00 IST exactly. Property-level override via
Property.settings.nightAuditHouris honoured by the in-process scheduler but NOT by the K8s CronJob (the chain endpoint iterates all properties at one fixed UTC tick).
Drift findings:
D-001 — [P0] [Story-numbering-drift] Three numbering systems coexist for the same surface: EPIC body uses US-005-07; catalog (01-epic-catalog.md) uses US-005-004 ("Night Audit Automation") in the EP-005 row; review folder uses US-005-004-review.md. A new reviewer following the EPIC body link would not find the review file; a new reviewer reading the review file would not find the EPIC sub-story. This is a recurrence of the same EP-002 numbering drift documented as D-007 in the pilot RTM.
- [SME-PROPOSED] Adopt the catalog numbering
US-005-004as canonical for the parent surface, withUS-005C-01..07as the explicit sub-epic story IDs; rewrite the EPIC body'sUS-005-07heading toUS-005-004 / Night Audit Automationand add an alias note. Cascades to D-008.
D-002 — [P0] [Stale-review-vs-shipped-code] reviews/EP-005/US-005-004-review.md (2026-03-07) marks 6 ACs as NOT_IMPL: SAC 996311 annotation, business-date advance, Kafka night.audit.completed, no-show cancellation charges, IGST, rollback. Of these, 5 have shipped (business-date advance per _runNightAudit, Kafka event via W2.D.5 outbox, no-show first-night charge per audit step 2, IGST per AP-T07-004 deriveGstSplit, rollback per AP-T09-001 expanded scope). Only SAC 996311 annotation remains unverified. A reviewer reading this file alone would re-open closed work.
- [SME-PROPOSED] Re-run the US-005-004 / US-005-07 review with the 2026-05-24 codebase and refresh the verdict from PARTIAL → BUILT; explicitly flag SAC code 996311 annotation as the single remaining gap.
US-005-22 — Night Audit Report & Logs
Catalog status: Built (P0, 5 points) Verified status: Built
Note: EPIC body uses
US-005-22. Catalog row for EP-005 listsUS-005-022 Currency Exchange at Front Deskat story ID 22 (a different surface). The Night Audit Report surface lives partly inUS-005-004review and partly in EP-012'sUS-012-004 Night Audit Report & Day-End Summary(also marked Partial). Numbering drift D-001 covers this.
Code locations:
hmp-frontdesk-service/src/night-audit/night-audit.controller.ts:120-214—GET /night-audit/record/:id(record for export),GET /night-audit/record/:id/pdf(AP-T09-003 PDF),GET /night-audit/record/:id/summary?format=pdf|csv(AP-T09-003 CSV — W2.D.7 completion)hmp-frontdesk-service/src/night-audit/night-audit-pdf.service.ts(248 lines) —generatePdf(record)andgenerateCsv(record)(RFC-4180Metric,Valuetable with KPIs + metadata + oneDiscrepancy Nrow per error)hmp-frontdesk-service/src/night-audit/night-audit.controller.ts:285-298—GET /night-audit/:propertyId/history(past audit runs with KPIs)hmp-frontdesk-service/src/night-audit/night-audit.controller.ts:48-106— W2.D.5 outbox ops endpoints (GET /night-audit/outbox,POST /night-audit/outbox/:id/replay)hmp-web-dashboard/src/components/modules/night-audit/audit-result-step.tsx— Day-End Report UI (revenue summary, room stats, exception log)
Unit + integration tests:
hmp-frontdesk-service/src/night-audit/night-audit-pdf.service.spec.ts(164 lines) — Header, KPI rows, metadata rows, discrepancy rows + count, RFC-4180 escaping (commas/quotes/CR/LF), Decimal-object fieldshmp-frontdesk-service/src/night-audit/night-audit.controller.spec.ts— Default→PDF,?format=csv, uppercaseCSV, unsupported→400, role gates
Flow tests:
flow-9-night-audit.spec.ts:: S1 pre-audit report counts; history retrieval covered implicitly via S5 completion check
Real-backend smokes:
smoke-night-audit.real.spec.ts— KPIs render against seed data
Use cases referencing this story:
- none
SME notes:
- AP-T09-003 originally shipped PDF only in 2026-04-29; the matching CSV generator was the missing half and landed 2026-05-23 (W2.D.7). Both endpoints reuse
getNightAuditByIdfor the property-ownership check. - Indian-hospitality reality: Day-End Report (called "DER" or "Manager's Flash") is the daily artefact the GM receives at 06:00–07:00 IST. The PDF format here is fine for archival; the CSV is what Finance/Accounts actually imports into Tally or Zoho Books. Both formats are non-negotiable for an Indian hotel — the W2.D.7 CSV completion was correct prioritisation.
- W2.D.5 transactional outbox + manual
replayendpoint means a Kafka outage during audit completion no longer loses thenight.audit.completedevent (P0 for analytics MV ingestion). The earlier fire-and-forget pattern silently dropped events on transient broker failures. - Day-End Report does not include the per-source revenue breakdown (Direct vs OTA vs Walk-in). FU-55 (folio.posted dual-emit) carries
sourceassegmenton the canonical event but the night-audit-summary CSV does not re-aggregate it. Finance must cross-reference with analytics-servicerevenue_by_sourceMV — friction.
Drift findings:
D-003 — [P1] [Story-incomplete + India-hospitality-critical] Day-End Report / Night Audit Summary CSV does not include per-source revenue breakdown (Direct / OTA-by-channel / Walk-in / Corporate). This is the single most-used split on a Manager's Flash — finance and revenue management both need it. Today the data lives on analytics-service MVs (revenue_by_source per FU-55) but the night-audit-summary doesn't cross-reference.
- [SME-PROPOSED] Add AC: "Night audit summary CSV/PDF SHALL include a per-source revenue breakdown table (DIRECT, OTA_BOOKING_COM, OTA_AGODA, OTA_MAKEMYTRIP, OTA_GOIBIBO, WALKIN, CORPORATE) sourced from
Reservation.sourceon charges posted that audit day. Numeric rows: revenue, room nights, ADR per source." - [SME-PROPOSED] Add AC: "PDF/CSV SHALL include a per-room-type breakdown (room-type code, rooms occupied, revenue, ADR, RevPAR) for the audit date. Finance uses this for room-type yield analysis."
US-005C-01 — Pre-Audit Validation Report
Catalog status: Built (P0, 5 points) Verified status: Built
Code locations:
hmp-frontdesk-service/src/night-audit/night-audit.controller.ts:300-318—GET /night-audit/:propertyId/pre-audit-reportandGET /night-audit/:propertyId/validatehmp-frontdesk-service/src/night-audit/night-audit.service.ts—getPreAuditReport(propertyId)returns the 4 standard counters (totalOccupiedRooms,pendingCheckouts,unsettledFolios,totalChargesAmount) +canProceedflag;validatePreAudit(propertyId)returnswarnings[]hmp-frontdesk-service/src/night-audit/validate-pre-audit-pos.spec.ts(170 lines) — Pre-audit POS validation (open KOTs, unsettled POS tabs)hmp-frontdesk-service/src/night-audit/pos-business-day.client.ts— POS business-day cutover client used by validationhmp-web-dashboard/src/components/modules/night-audit/pre-checks-step.tsx— Pre-Audit Checklist UI
Unit + integration tests:
validate-pre-audit-pos.spec.ts— POS validation coveragenight-audit.service.spec.ts—getPreAuditReportandvalidatePreAuditcovered as part of the 1,166-line suite
Flow tests:
flow-9-night-audit.spec.ts:: S1 — Pre-audit report — PASS (12/12 PASS)
Real-backend smokes:
smoke-night-audit.real.spec.ts
Use cases referencing this story:
- none
SME notes:
- Flow-9 S1 explicitly tests the 4 standard counters object — passes.
- EPIC body says pre-check returns "1 cashier shift open — please close before proceeding" but the actual implementation returns a structured
warnings[]array, not a free-text message. UI does the friendly-string rendering. No bug, but the EPIC Gherkin is misleading. - Pre-audit validation does not block on open POS KOTs today — it warns. An open KOT at 02:00 IST means the kitchen forgot to close an order; if the audit posts the room charge before the F&B charge is consolidated, the guest's folio will show partial F&B. Real-world: hotels frequently allow audit to proceed on open KOTs because the cashier will reconcile in the morning. SME-debated.
- EPIC body's "Pre-Audit Checklist UI (Partial: shows exceptions; no guided resolution flow)" status is now stale — the Keynova page renders a structured stepper (pre-checks → audit progress → result) with action buttons per exception.
Drift findings: (none new — story scope matches code)
- [SME-PROPOSED] Add AC: "Pre-audit validation SHALL surface 6 named warning categories: (1) open cashier shifts, (2) unsettled folios > INR 100K, (3) pending check-outs past departureDate, (4) open POS KOTs, (5) unassigned rooms with active reservation, (6) night-audit-blocked rooms (engineering OOO). Each warning carries a
severity: 'block' | 'warn'flag; onlyblockrows setcanProceed=false."
US-005C-02 — No-Show Processing (Automated)
Catalog status: Not Started (P0, 8 points) Verified status: Built
Code locations:
hmp-frontdesk-service/src/night-audit/night-audit.service.ts::processNightAuditstep 2 — Find CONFIRMED reservations witharrivalDate < todayAND nocheckedInAt, mark as NO_SHOW, post first-night-rate charge + GST viatransactionCode='NO_SHOW'to the open folio- Per
CLAUDE.md(src/night-audit/CLAUDE.mdtable): "No-show processing: Find CONFIRMED reservations witharrivalDate < today, mark as NO_SHOW, post first-night charge + GST to folio" hmp-frontdesk-service/src/night-audit/night-audit.service.spec.ts— No-show processing coverage including "AP-T09-001: rollback voids ROOM, OVERSTAY_NIGHT, and NO_SHOW transactions in the same call"
Unit + integration tests:
night-audit.service.spec.ts— No-show flow specs (mark NO_SHOW, post charge, KPI counternoShowsProcessed)- AP-T09-001 rollback test pins the NO_SHOW transactionCode handling
Flow tests:
flow-9-night-audit.spec.ts— no-show count surfaces in audit history record after full-audit run (S5)
Real-backend smokes:
- none specific
Use cases referencing this story:
- none
SME notes:
- EPIC body marks this Not Started — the code has shipped. P0 catalog drift (D-004). Likely the EPIC body predates the no-show step being absorbed into
processNightAudit. - Charge amount is first-night rate (per
CLAUDE.mdGotcha 3: "Only the first night's rate is charged as a no-show fee, not the full reservation amount"). The EPIC body's Gherkin says "100% of first night" — matches. - The EPIC body Gherkin for "Flexible policy → 0 charge" is NOT implemented — current code charges the first night unconditionally; it does not consult
CancellationPolicy.noShowChargefor a policy-driven percentage or for the 0% case. - DPDPA + consumer-protection angle: under the Consumer Protection (E-Commerce) Rules 2020, a no-show charge must be disclosed at booking. Today the booking-engine + reservation-service ack-email do show the policy, but the night-audit step does not record which policy version was in force at booking time — recoverable from
Reservation.cancellationPolicySnapshotif it exists. - Notification email per EPIC body —
reservation.no_showevent is published, EP-014 notification-service handles the email. Not enumerated in the night-audit code path; cross-service.
Drift findings:
D-004 — [P0] [Catalog-vs-code] EPIC body §6 marks US-005C-02 No-Show Processing as Not Started but the code has shipped + is tested + is verified by flow-9 (S5 full-audit path increments noShowsProcessed). New engineer assigned to "build" this story would re-author closed work. Same drift family as D-002 (stale review file).
- [SME-PROPOSED] Promote
US-005C-02status in EPIC body and catalog fromNot Started→Builtwith caveat: "Charge amount is hard-coded to first-night rate; policy-driven percentage (e.g. Flexible = 0%) is NOT implemented." OpenUS-005C-02-followupfor the policy-driven branch.
D-005 — [P1] [Story-incomplete + India-hospitality-critical] No-show charge does not consult CancellationPolicy.noShowCharge. EPIC Gherkin explicitly demands the Flexible-policy 0%-charge branch — code charges the first-night rate unconditionally. A "0% no-show" tariff cannot be honoured today.
- [SME-PROPOSED] Add AC: "No-show charge amount SHALL be computed as
reservation.cancellationPolicy.noShowCharge×firstNightRatewhennoShowChargeis a percentage 0..100, OR equalnoShowChargeFlatwhen the policy uses a flat-amount mode. A nullnoShowChargedefaults to 100% (first-night-rate)." - [SME-PROPOSED] Add AC: "The no-show charge SHALL inherit the same GST split (CGST+SGST vs IGST) as a regular room charge, derived from
customerStatevsproperty.stateper AP-T07-004."
US-005C-03 — Night Audit Rollback
Catalog status: Partial (P1, 8 points) Verified status: Built
Code locations:
hmp-frontdesk-service/src/night-audit/night-audit.controller.ts:339-357—POST /night-audit/:id/rollback;@Roles('SUPER_ADMIN','ADMIN','MANAGER');@Audit({tableName:'night_audit_logs'})hmp-frontdesk-service/src/night-audit/night-audit.service.ts::rollbackNightAudit— Voids ROOM + OVERSTAY_NIGHT + NO_SHOW transactions in one pass (AP-T09-001), recomputes folio totals, revertsProperty.currentBusinessDate, setsNightAuditLog.status='ROLLED_BACK'- Per
src/night-audit/CLAUDE.mdGotcha 6 (AP-T09-001, 2026-04-28): "Filter is{ in: ['ROOM','OVERSTAY_NIGHT','NO_SHOW'] }, NOT'ROOM'alone. Older limited-to-ROOM behaviour left overstay and no-show charges orphaned on folios."
Unit + integration tests:
night-audit.service.spec.ts:: "AP-T09-001: rollback voids ROOM, OVERSTAY_NIGHT, and NO_SHOW transactions in the same call" — pinning test
Flow tests:
flow-9-night-audit.spec.ts— afterAll() attempts rollback to keep test property reusable across runs (uses POST/night-audit/:id/rollback)
Real-backend smokes:
- none specific
Use cases referencing this story:
- none
SME notes:
- EPIC body marks status
Partial("State restoration incomplete"). The AP-T09-001 expansion (2026-04-28) closed this for the 3 transaction codes the audit posts. The "incomplete" residual gap is cashier-shift state restoration and the 2-hour rollback window Gherkin from EPIC §7 — neither is implemented in code. - Indian-hospitality: rollback within the 2-hour window is the right design — Day-N+1 has already advanced for downstream systems (POS, channel-service rate push, OTA sync) and rolling back hours later would create cascading consistency problems. Today the code has no 2-hour window check; an admin can rollback an audit from yesterday with no friction. SME-debated: depending on the operator's discipline, this is either flexibility or a footgun.
- Rollback does NOT republish a
night.audit.rolled_backevent today — downstream analytics MVs will show stale numbers until a re-run. EP-018 chain admin would miss the signal. - Rollback does NOT reverse
Property.currentBusinessDateon chain audits — if 5 properties rolled out and property 3 needs rollback, the chaincurrentBusinessDatestate is now inconsistent.
Drift findings:
D-006 — [P1] [Story-incomplete + missing-guard] EPIC body specifies a 2-hour rollback window ("Rollback window expired — contact Finance Manager") — not implemented. An audit from 3 days ago can be rolled back today with no operator gate.
- [SME-PROPOSED] Add AC: "Rollback SHALL be rejected with
code=NIGHT_AUDIT_ROLLBACK_WINDOW_EXPIRED(HTTP 409) whenDate.now() - completedAt > 2 hours. SUPER_ADMIN MAY override the window with explicit?force=truequery param + reason; override emits an additionalnight.audit.rollback.forcedaudit-log entry." - [SME-PROPOSED] Add AC: "Successful rollback SHALL publish
night.audit.rolled_backevent with{auditLogId, propertyId, tenantId, auditDate, rolledBackBy, rolledBackAt, reason}via the same W2.D.5 outbox pattern asnight.audit.completed. Consumers: analytics-service MV refresher, EP-018 chain admin dashboard."
US-005C-04 — Night Audit Resume
Catalog status: Partial (P1, 5 points)
Verified status: Partial — resumeAudit shipped but treats resume as re-run (not step-aware)
Code locations:
hmp-frontdesk-service/src/night-audit/night-audit.controller.ts:320-337—POST /night-audit/:id/resumehmp-frontdesk-service/src/night-audit/night-audit.service.ts::resumeAudit— PerCLAUDE.md: "marks the failed audit asROLLED_BACK, then re-runs the full audit. Since the original failed audit was inside a transaction, all its partial work was rolled back automatically — no double-posting risk."
Unit + integration tests:
night-audit.service.spec.ts— Resume coverage; status-FAILED gate (Only FAILED audits can be resumed)
Flow tests:
- none specific to resume
Real-backend smokes:
- none
Use cases referencing this story:
- none
SME notes:
- EPIC body specifies "step-aware resume after pause" with persistence of step-completion state to Redis (per §10 Technical Notes). The actual implementation is full re-run under the safety of Prisma transactional rollback — simpler, correct for the in-transaction steps, but does NOT match the spec.
- For an audit that fails at step 5 (advance business date) — a post-transaction step — the safe rerun does work because the rerun is idempotent up to the date-advance step (the dedup guard catches the already-COMPLETED row). For an audit that fails at step 7 (Kafka outbox enqueue) the W2.D.5 outbox makes this a non-issue (the enqueue is part of the same transaction as the log update; if the transaction rolled back, no PENDING row exists; if it committed, the relay drains it).
- A genuine step-aware resume (per spec) would only be needed if the in-transaction steps grew beyond the 60s transaction timeout (Gotcha 2 — "For properties with 500+ rooms, this may need increasing"). For Property #1 (62 rooms) the simple-rerun design is fine.
Drift findings:
D-007 — [P2] [Story-vs-impl-design-mismatch] EPIC body specifies step-aware resume via Redis-persisted step completion state; implementation is full re-run under transaction safety. The design simplification is defensible (the in-tx rollback makes step-tracking unnecessary at current scale) but the EPIC body has not been updated to reflect the choice.
- [SME-PROPOSED] Update EPIC body §10 Technical Notes to drop the Redis step-tracker design and document the actual full-rerun-with-transaction-rollback approach. Add an explicit AC: "
resumeAuditSHALL be permitted only when the source audit's status isFAILED;COMPLETED_WITH_ERRORSandROLLED_BACKSHALL be rejected with appropriate error codes."
US-005C-05 — Cashier Shift Close
Catalog status: Partial (P1, 5 points)
Verified status: Partial — Cashier-shift module shipped in hmp-frontdesk-service; integration with night-audit blocking-validation incomplete
Code locations:
hmp-frontdesk-service/src/cash-drawer/(CashDrawerModule, perCLAUDE.md) — Cash drawer management, shift open/close, variance trackinghmp-frontdesk-service/src/night-audit/night-audit.service.ts::validatePreAudit— Reads cash-drawer state to surface "open shift" warning (per Flow-9 S1 +validate-pre-audit-pos.spec.tsanalog)
Unit + integration tests:
hmp-frontdesk-service/src/cash-drawer/*— Cash drawer + shift close specs (separate module)validate-pre-audit-pos.spec.ts— POS analog test pattern (open KOT detection); the cashier-shift analog is implied but not enumerated
Flow tests:
flow-9-night-audit.spec.tsdoes NOT exercise the EPIC body'sScenario: Audit blocked by open cashier shiftGherkin path
Real-backend smokes:
- none specific
Use cases referencing this story:
- none
SME notes:
- EPIC body says "Summary exists; closing workflow incomplete" — accurate at story level. The cashier-shift close itself is implemented (
CashDrawerModule); the night-audit integration that blocks when a shift is open is the missing half. - Indian-hospitality reality: front-desk traditionally runs 3 shifts (Morning 06–14, Evening 14–22, Night 22–06). The night-audit fires at 02:00 IST which is mid-Night-Shift — the Night shift WILL be open and should NOT block the audit. The block applies to Morning + Evening shifts that should have closed by 22:00. EPIC Gherkin doesn't make this distinction.
- Cash variance ≥ INR 500 typically goes to manager-approval (
PENDING_APPROVALstatus perCashDrawerShiftmodel). EPIC body's cashier-shift Gherkin does not narrate the variance-approval interaction with audit-blocking.
Drift findings:
D-008 — [P1] [Story-incomplete + India-hospitality-critical] Cashier-shift open detection does NOT distinguish Morning/Evening shifts (should block audit) from Night shift (mid-audit, should NOT block). A naive "any open shift blocks" check would prevent the 02:00 IST audit from ever running.
- [SME-PROPOSED] Add AC: "Pre-audit validation SHALL flag open Morning and Evening cashier shifts as
severity=block. Open Night-shift entries withshiftType='NIGHT'(oropenedAtafter 22:00 local ANDshiftTypenull) SHALL be flaggedseverity=warn(informational) — the active night-auditor shift is expected to be open during the audit window." - [SME-PROPOSED] Add AC: "
PENDING_APPROVALcashier variances above INR 500 SHALL be surfaced asseverity=warnin the pre-audit report with manager-approval pending link. Audit MAY proceed; the unapproved variance is captured on the next-day Day-End Report as an exception."
US-005C-06 — Chain Audit Orchestration
Catalog status: Not Started (P2, 13 points) Verified status: Built (backend) + Not Started (chain-admin UI per EP-018 dependency)
Code locations:
hmp-frontdesk-service/src/night-audit/chain-audit-orchestrator.service.ts(362 lines) —startChainAuditAsync(tenantId, userId)+getChainAuditStatus(trackingId); sequential property-by-property audit; aggregates results; publishes chain audit completion eventhmp-frontdesk-service/src/night-audit/night-audit.controller.ts:216-248—POST /night-audit/chain(returns 202 + trackingId) +GET /night-audit/chain/status/:trackingId;@Roles('SUPER_ADMIN','ADMIN')hmp-infrastructure/kubernetes/jobs/night-audit-cronjob.yaml— The K8s CronJob callsPOST /api/night-audit/chain, exercising this surface in production every 02:00 IST- Per
CLAUDE.md: "Chain audit runs properties sequentially (not in parallel) to avoid DB lock contention"
Unit + integration tests:
hmp-frontdesk-service/src/night-audit/chain-audit-orchestrator.service.spec.ts(254 lines) — Sequential audit, aggregation, blocked-property handling, chain summary
Flow tests:
- none specific to chain audit (flow-9 is per-property only)
Real-backend smokes:
- none specific
Use cases referencing this story:
- none
SME notes:
- The chain orchestrator backend has shipped + has 254 lines of tests. EPIC body's "Not Started" status is wrong (D-002 family).
- The missing half is the EP-018 chain-admin dashboard — there is no UI to drive
POST /night-audit/chainfrom a tenant-level admin console nor to render the per-property status panel fromGET /chain/status/:trackingId. The CronJob fires it server-to-server today. - Indian-hospitality chains: most Tier-2 Indian chains run 3–10 properties. Sequential audit (1 property at a time) for 10 properties × 15 min avg = 2.5 hr total, which is fine inside the 02:00–05:00 IST window. Parallel would shave to 15 min but the DB-lock contention argument is sound for the shared Postgres model.
- The chain orchestrator does NOT re-issue
night.audit.completedaggregated per-tenant — each property emits its own. Analytics MV ingestion is per-property — fine. EP-018 chain dashboard will need anight.audit.chain.completedevent for the aggregate.
Drift findings: (carries forward D-002; backend half done, UI half blocked on EP-018)
- [SME-PROPOSED] Promote US-005C-06 status from
Not Started→Partial(backend shipped, EP-018 chain-admin UI not started). OpenUS-005C-06-followupfor the UI half. - [SME-PROPOSED] Add AC: "Chain audit SHALL publish a single
night.audit.chain.completedevent after the last property finishes, with{tenantId, trackingId, totalProperties, succeeded, failed, blocked, totalRevenue, completedAt}. EP-018 chain dashboard consumes this for the cross-property roll-up."
US-005C-07 — Scheduled Audit (Auto-Trigger)
Catalog status: Partial (P1, 5 points) Verified status: Built
Code locations:
hmp-infrastructure/kubernetes/jobs/night-audit-cronjob.yaml(AP-T09-005, 2026-04-29) — Production trigger: K8s CronJob,schedule: "30 20 * * *"(20:30 UTC = 02:00 IST),concurrencyPolicy: Forbid,activeDeadlineSeconds: 7200,backoffLimit: 2. UsesINTERNAL_SERVICE_TOKENfrom the canonicalinternal-service-tokenSecret (B2)hmp-frontdesk-service/src/night-audit/night-audit-scheduler.service.ts(56 lines) — In-process NestJS@Cron(EVERY_HOUR)fallback; readsProperty.settings.nightAuditHour(default 2) per-property; usesDistributedLockService.withLock(cron:night-audit:${propertyId}, 30min, ...)to prevent overlap- Env:
NIGHT_AUDIT_CRON_DISABLED=truedisables the in-process scheduler when the K8s CronJob is active (perhmp-infrastructure/CLAUDE.mdAP-T09-005 gotcha)
Unit + integration tests:
night-audit-scheduler.service.spec.ts(60 lines) — Cron tick, per-property hour matching, lock acquisition
Flow tests: none (cron schedule not flow-testable)
Real-backend smokes: none
Use cases referencing this story:
- none
SME notes:
- The dual scheduler (K8s CronJob + in-process Nest cron) is deliberate: K8s CronJob for AWS EKS production; in-process cron for docker-compose dev/QA where K8s isn't available. The
NIGHT_AUDIT_CRON_DISABLED=trueenv opt-out is the safety toggle. - Property-level
Property.settings.nightAuditHourper-property override is ONLY honoured by the in-process scheduler. The K8s CronJob fires once at 02:00 IST and iterates ALL properties via/api/night-audit/chain— a property that wanted to audit at 03:00 IST will be ignored in production. Real-world: this is a documented limitation; in practice all India-region properties want 02:00 IST. - The CronJob's
activeDeadlineSeconds: 7200(2 hours) is a fail-safe. A real chain audit takes ~15 min per property × 10 properties = 2.5 hr — JUST exceeds the deadline. SME risk: large chains may need this bumped.
Drift findings: (none new; story scope matches code)
- [SME-PROPOSED] Add AC: "When
NIGHT_AUDIT_CRON_DISABLED=true(production K8s mode), the property-levelProperty.settings.nightAuditHouroverride SHALL be ignored. Documented limitation: all properties in a tenant audit at the K8s CronJob's UTC tick. Per-property hour-override is dev/QA only." - [SME-PROPOSED] Add AC: "K8s CronJob
activeDeadlineSecondsSHALL be calibrated totenantPropertyCount × 20 minutes + 30 min buffer. Default 7200s (2 hr) is sized for 8-property tenants; larger tenants MUST override per-overlay inkubernetes/overlays/<env>/."