Skip to main content

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.md and 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​

StatusMeaning
BuiltCode exists + tests exist + tests pass
Built-UntestedCode exists, no automated test coverage (or test exists but is a stub)
PartialCode exists for some acceptance criteria, gaps for others
StubCode is a placeholder / TODO / returns mock data
Not StartedNo code

Summary​

MetricCount
Total stories9
Catalog claims "Built"4 (US-005-07, US-005-22, US-005C-01, US-005C-07 — but see D-001 numbering drift)
Verified-Built6 (US-005-07, US-005-22, US-005C-01, US-005C-02, US-005C-03, US-005C-07)
Built-Untested0
Partial2 (US-005C-05 cashier-shift workflow incomplete; US-005C-04 resume shipped but limited)
Stub0
Not Started1 (US-005C-06 — chain orchestrator shipped but EP-018 chain admin UI missing)
Drift findings8 (D-001..D-008; 4 P0 / 3 P1 / 1 P2)
[SME-PROPOSED] refinements16

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-07 while the per-EPIC review folder at reviews/EP-005/ uses US-005-004 for the same surface (see US-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 imports StorageModule from @hmp/infra for S3 snapshot DI
  • hmp-frontdesk-service/src/night-audit/night-audit-outbox.service.ts — W2.D.5 transactional outbox for night.audit.completed event (durable on commit, 30s relay cron drains PENDING)
  • hmp-frontdesk-service/src/night-audit/night-audit-pdf.service.ts — pdfkit generator + RFC-4180 CSV
  • hmp-frontdesk-service/src/night-audit/pos-business-day.client.ts — POS business-day cutover client
  • hmp-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 via useAppStore (PMS-13 fix)
  • hmp-web-dashboard/src/components/modules/night-audit/{pre-checks-step,audit-progress,audit-result-step}.tsx — Wizard step components
  • hmp-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); reads INTERNAL_SERVICE_TOKEN from canonical internal-service-token Secret (B2)
  • hmp-frontdesk-service/src/night-audit/night-audit-scheduler.service.ts — In-process NestJS @Cron(EVERY_HOUR) fallback (use NIGHT_AUDIT_CRON_DISABLED=true to 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 + persistence
  • hmp-frontdesk-service/src/night-audit/night-audit.controller.spec.ts (266 lines) — Controller specs incl. AP-T09-003 PDF/CSV branches + role gates
  • hmp-frontdesk-service/src/night-audit/night-audit-gst.spec.ts — GST slab pinning (≤1000 = 12%, ≤7500 = 18%, >7500 = 28%) via calculateGSTForRoom() from @hmp/constants
  • hmp-frontdesk-service/src/night-audit/night-audit-outbox.service.spec.ts — W2.D.5 outbox enqueue + relay + DEAD_LETTER
  • hmp-frontdesk-service/src/night-audit/outbox-replay-endpoint.spec.ts — DLQ replay controller specs
  • hmp-frontdesk-service/src/night-audit/storage-di.spec.ts — AP-T09-003 source-level guards that StorageService DI is real, not a cast
  • hmp-frontdesk-service/src/night-audit/__tests__/gsa-014-015-tenant-scope.spec.ts — Tenant-id resolution + persistence on every audit log
  • hmp-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 by FLOW9_FULL_AUDIT=true env 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 page
  • hmp-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-*.yaml found)

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, Kafka night.audit.completed, no-show cancellation charges, IGST, rollback). Of those: business-date advance, night.audit.completed event (W2.D.5 outbox), no-show first-night charge (audit step 2 per CLAUDE.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's US-005C-07 Scheduled Audit story does not narrate this dual-path or document the NIGHT_AUDIT_CRON_DISABLED=true opt-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.nightAuditHour is 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-004 as canonical for the parent surface, with US-005C-01..07 as the explicit sub-epic story IDs; rewrite the EPIC body's US-005-07 heading to US-005-004 / Night Audit Automation and 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 lists US-005-022 Currency Exchange at Front Desk at story ID 22 (a different surface). The Night Audit Report surface lives partly in US-005-004 review and partly in EP-012's US-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) and generateCsv(record) (RFC-4180 Metric,Value table with KPIs + metadata + one Discrepancy N row 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 fields
  • hmp-frontdesk-service/src/night-audit/night-audit.controller.spec.ts — Default→PDF, ?format=csv, uppercase CSV, 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 getNightAuditById for 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 replay endpoint means a Kafka outage during audit completion no longer loses the night.audit.completed event (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 source as segment on the canonical event but the night-audit-summary CSV does not re-aggregate it. Finance must cross-reference with analytics-service revenue_by_source MV — 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.source on 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-report and GET /night-audit/:propertyId/validate
  • hmp-frontdesk-service/src/night-audit/night-audit.service.ts — getPreAuditReport(propertyId) returns the 4 standard counters (totalOccupiedRooms, pendingCheckouts, unsettledFolios, totalChargesAmount) + canProceed flag; validatePreAudit(propertyId) returns warnings[]
  • 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 validation
  • hmp-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 coverage
  • night-audit.service.spec.ts — getPreAuditReport and validatePreAudit covered 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; only block rows set canProceed=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::processNightAudit step 2 — Find CONFIRMED reservations with arrivalDate < today AND no checkedInAt, mark as NO_SHOW, post first-night-rate charge + GST via transactionCode='NO_SHOW' to the open folio
  • Per CLAUDE.md (src/night-audit/CLAUDE.md table): "No-show processing: Find CONFIRMED reservations with arrivalDate < 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 counter noShowsProcessed)
  • 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.md Gotcha 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.noShowCharge for 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.cancellationPolicySnapshot if it exists.
  • Notification email per EPIC body — reservation.no_show event 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-02 status in EPIC body and catalog from Not Started → Built with caveat: "Charge amount is hard-coded to first-night rate; policy-driven percentage (e.g. Flexible = 0%) is NOT implemented." Open US-005C-02-followup for 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 × firstNightRate when noShowCharge is a percentage 0..100, OR equal noShowChargeFlat when the policy uses a flat-amount mode. A null noShowCharge defaults 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 customerState vs property.state per 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, reverts Property.currentBusinessDate, sets NightAuditLog.status='ROLLED_BACK'
  • Per src/night-audit/CLAUDE.md Gotcha 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_back event 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.currentBusinessDate on chain audits — if 5 properties rolled out and property 3 needs rollback, the chain currentBusinessDate state 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) when Date.now() - completedAt > 2 hours. SUPER_ADMIN MAY override the window with explicit ?force=true query param + reason; override emits an additional night.audit.rollback.forced audit-log entry."
  • [SME-PROPOSED] Add AC: "Successful rollback SHALL publish night.audit.rolled_back event with {auditLogId, propertyId, tenantId, auditDate, rolledBackBy, rolledBackAt, reason} via the same W2.D.5 outbox pattern as night.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/resume
  • hmp-frontdesk-service/src/night-audit/night-audit.service.ts::resumeAudit — Per CLAUDE.md: "marks the failed audit as ROLLED_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: "resumeAudit SHALL be permitted only when the source audit's status is FAILED; COMPLETED_WITH_ERRORS and ROLLED_BACK SHALL 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, per CLAUDE.md) — Cash drawer management, shift open/close, variance tracking
  • hmp-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.ts analog)

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.ts does NOT exercise the EPIC body's Scenario: Audit blocked by open cashier shift Gherkin 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_APPROVAL status per CashDrawerShift model). 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 with shiftType='NIGHT' (or openedAt after 22:00 local AND shiftType null) SHALL be flagged severity=warn (informational) — the active night-auditor shift is expected to be open during the audit window."
  • [SME-PROPOSED] Add AC: "PENDING_APPROVAL cashier variances above INR 500 SHALL be surfaced as severity=warn in 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 event
  • hmp-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 calls POST /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/chain from a tenant-level admin console nor to render the per-property status panel from GET /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.completed aggregated per-tenant — each property emits its own. Analytics MV ingestion is per-property — fine. EP-018 chain dashboard will need a night.audit.chain.completed event 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). Open US-005C-06-followup for the UI half.
  • [SME-PROPOSED] Add AC: "Chain audit SHALL publish a single night.audit.chain.completed event 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. Uses INTERNAL_SERVICE_TOKEN from the canonical internal-service-token Secret (B2)
  • hmp-frontdesk-service/src/night-audit/night-audit-scheduler.service.ts (56 lines) — In-process NestJS @Cron(EVERY_HOUR) fallback; reads Property.settings.nightAuditHour (default 2) per-property; uses DistributedLockService.withLock(cron:night-audit:${propertyId}, 30min, ...) to prevent overlap
  • Env: NIGHT_AUDIT_CRON_DISABLED=true disables the in-process scheduler when the K8s CronJob is active (per hmp-infrastructure/CLAUDE.md AP-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=true env opt-out is the safety toggle.
  • Property-level Property.settings.nightAuditHour per-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-level Property.settings.nightAuditHour override 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 activeDeadlineSeconds SHALL be calibrated to tenantPropertyCount × 20 minutes + 30 min buffer. Default 7200s (2 hr) is sized for 8-property tenants; larger tenants MUST override per-overlay in kubernetes/overlays/<env>/."