Skip to main content

EP-012C — Tax & Compliance Reports RTM

EPIC: EP-012C Tax & Compliance Reports Parent EPIC: EP-012 Analytics, Reporting & Night Audit Reconciled: 2026-05-25 (Wave 1 of 21 parallel agents) Pattern: see ./PLAYBOOK.md and the pilot at ./EP-002-rtm.md. Source of truth for pass-rate: hmp-docs/docs/platform-registry/2026-05-16-13-flow-demo-readiness.md (flow-8 checkout + e-invoice was 0/15 — the worst on the platform; AP-T08-003/006/007 + W2.A.1 + AP-T07-004 + FU-55 landed since).

Catalog vs spec note: the catalog (01-epic-catalog.md lines 351-370) currently lists EP-012 as one flat EPIC with stories US-012-001..020. The EP-012C spec doc (epics/EP-012C-tax-compliance-reports.md) declares its own 9-story slate (US-012-04, US-012-05, US-012-14, US-012C-01..06). This RTM follows the EP-012C spec doc per task instructions and surfaces the dual numbering as drift D-001 (P0). The catalog story most engineers reach for first is US-012-005 — GST Reports (GSTR-1, GSTR-3B Preparation), which corresponds to spec US-012-04 + US-012-05.

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 stories (EP-012C spec)9
Catalog claims (EP-012-005 surrogate)Partial
Verified-Built0
Built-Untested0
Partial5 (US-012-04, US-012-05, US-012-14, US-012C-02, US-012C-06)
Stub1 (US-012C-01 CDNR — credit notes only exist as CreditNote model + generateCreditNote IRN path; no GSTR-1 Section 9 export)
Not Started3 (US-012C-03 Regional Tax, US-012C-04 Compliance Calendar, US-012C-05 STR Export)
Drift findings11 (D-001..D-011; 5 P0 / 4 P1 / 2 P2)
[SME-PROPOSED] refinements18

US-012-04 — GST GSTR-1 Report Generation​

Catalog status (proxy: US-012-005): Partial (P0, 13 points) Verified status: Partial — Two parallel implementations exist (frontdesk + analytics); both incomplete vs the NIC schema v3.4 requirement

Code locations:

  • hmp-frontdesk-service/src/compliance/gstr-reports.controller.ts — GET /compliance/gstr1, GET /compliance/gstr3b (manager+/accountant)
  • hmp-frontdesk-service/src/compliance/gstr-reports.service.ts — Gstr1ExportService.generateGstr1() (B2B / B2C / HSN summary from folio data)
  • hmp-analytics-service/src/gstr/gstr.controller.ts:43-100 — GET /reports/gstr3b?month=YYYY-MM&propertyId=UUID&forceRefresh=true (cache-first via MvGstr3bSnapshot, AP-T10-005)
  • hmp-analytics-service/src/gstr/gstr.service.ts — GstrService.computeGstr3b() aggregates from EventStore folio.posted events
  • hmp-analytics-service/src/reporting/reports/gst.definition.ts — gst-report v2 report (finance category; composite over MvGstr3bSnapshot + MvEInvoiceRegister; formats json/csv/pdf/xlsx)
  • hmp-analytics-service/prisma/schema.prisma — MvGstr3bSnapshot { tenantId, propertyId, month, taxableB2b, taxableB2c, igstAmount, cgstAmount, sgstAmount, hsnBreakdown JSON, computedAt }
  • hmp-web-dashboard/src/app/(dashboard)/settings/compliance/page.tsx — Compliance settings page

Unit + integration tests:

  • hmp-analytics-service/src/gstr/gstr.controller.spec.ts — controller specs
  • hmp-analytics-service/src/reporting/reports/gst.definition.spec.ts — gst-report v2 definition test
  • hmp-analytics-service/src/consumers/einvoice.consumer.spec.ts — EInvoiceConsumer upsert tests (feeds MvEInvoiceRegister)
  • hmp-frontdesk-service/src/folios/__tests__/gsa-067-igst-split.spec.ts — IGST vs CGST+SGST split assertion at folio post-time
  • hmp-frontdesk-service/src/folios/fu-55-folio-event-dual-emit.spec.ts — 8 specs covering canonical FOLIO_POSTED payload (segment / countryCode / roomTypeCode / ratePlanCode / roomNights / taxAmount)
  • hmp-frontdesk-service/src/folios/folios.service.spec.ts — postCharge GST split persistence (AP-T07-001, intra/inter/omitted-defaults)
  • hmp-frontdesk-service/src/folios/consumers/pos-folio-charge.consumer.spec.ts — 29 specs incl. 9 AP-T07-004 cases (explicit-split honour / derive intra-vs-inter / supplier-state fallback / outbox envelope)

Flow tests:

  • hmp-infrastructure/testing/flow-tests/src/flows/gst-tax-flags.flow.spec.ts — covers P0-5 (isTaxExempt), P0-6 (isReverseChargeApplicable), P0-7 (GET /compliance/gstr1 + gstr3b respond with rows + totals), P0-11 (TDS). PASS-rate TBD — part of the broader 13-flow baseline (2026-05-16-13-flow-demo-readiness.md).
  • hmp-infrastructure/testing/flow-tests/src/flows/nic-einvoice-live.flow.spec.ts — readiness smoke for NIC live-mode flip (asserts EInvoiceModule fails LOUDLY when EINVOICE_MOCK=false + missing creds; mock response envelope shape parity with NIC v1.04).

Real-backend smokes:

  • none specific for GST (compliance page covered indirectly via dashboard smokes)

E2E (Playwright): none specific

Use cases referencing this story:

  • task brief cites _data/use-cases/uc-gstr3b-prep.yaml — NOT YET AUTHORED (drift D-002 — proposed sidecar)

SME notes:

  • Two parallel GSTR-1 implementations: Gstr1ExportService in frontdesk computes from live Folio rows (take: 1000 cap, single-month) while analytics-service exposes gst-report v2 over MvGstr3bSnapshot + MvEInvoiceRegister. Gold-Standard charter ("one canonical impl not two") is violated. The catalog mentions only one surrogate row (US-012-005); spec splits into US-012-04 GSTR-1 + US-012-05 GSTR-3B; reality is two services × two stories = four code paths that can drift. Recommendation: retire gstr-reports.service.ts in frontdesk, route /compliance/gstr1 through the gateway to gst-report v2 (which already has the right MV backing + RBAC + export formats).
  • GSTN portal schema v3.4 validation is NOT in code anywhere. The acceptance criterion "JSON output passes schema validation against the GST government schema v3.4" has zero implementation. Risk: portal rejection on first upload, hotel files late, ₹50–200/day penalty. Per the spec's "Business Objective" this is THE objective.
  • B2C state code uses property's state, not place-of-supply (guest's state). Per US-012-005 review GAP-2, the frontdesk Gstr1ExportService derives B2C state code from property.state.substring(0, 2) — that's the supplier state, not the recipient state. Section 7 of GSTR-1 requires place-of-supply at the recipient state for unregistered consumers.
  • HSN/SAC code map is incomplete. Today hardcoded to 996311 (room) and 996331 (restaurant). Missing: 998114 (laundry), 999719 (spa), 9963 parent for accommodation, 9996 for restaurant parent — spec lines 42-43 enumerate 9963/996311-996329/9996/9997 — none of 9996/9997/998114/999719 are wired. Channel-service POS items have an HSN field but the GSTR-1 path doesn't read it (it reads transactionCode and hardcodes the SAC).
  • NIC IRP rate limits (1000 IRN/sec sustained, 2500 burst per GSTIN — published in NIC e-invoice IRP v1.04 docs): not enforced anywhere. A batch checkout night-audit run could swamp NIC and trigger throttling; payload retry-after handling is absent in nic-einvoice.adapter.ts (30-s timeout only, no Retry-After header parsing).

Drift findings:

D-001 — [P0] [Catalog-vs-spec-numbering] Catalog (01-epic-catalog.md:355) lists US-012-005 — GST Reports (GSTR-1, GSTR-3B Preparation) as a single Partial P0 row. The EP-012C spec doc declares US-012-04 + US-012-05 + US-012-14 + US-012C-01..06 as nine separate stories. Two referent docs disagreeing on story-IDs is a pilot-blocking drift (same shape as EP-002 D-007).

  • [SME-PROPOSED] Adopt the EP-012C spec doc numbering as canonical. Add an alias line to the catalog row: > Sub-epic EP-012C reshapes this into US-012-04 (GSTR-1) + US-012-05 (GSTR-3B) + US-012-14 (audit-log) + US-012C-01..06 (CDNR, rate tiers, regional, calendar, STR, IGST). See epics/EP-012C-tax-compliance-reports.md.

D-002 — [P0] [Story-has-no-UC + India-compliance-critical] Task brief cites _data/use-cases/uc-gstr3b-prep.yaml and uc-frontdesk-einvoice-irn.yaml + uc-frontdesk-einvoice-b2b.yaml. None of these YAMLs exist in docs/requirements/_data/use-cases/. The GSTR-3B and IRN flows are spec-text-only with no executable use-case definition.

  • [SME-PROPOSED] Author 3 new use-case YAMLs under use-case-proposals/:
    • uc-gstr3b-prep-SME-PROPOSED.yaml — given Finance Manager picks month, when she calls GET /api/reports/gstr3b?month=YYYY-MM&propertyId=UUID, then response includes B2B/B2C/IGST/CGST/SGST + HSN breakdown; cache-first.
    • uc-frontdesk-einvoice-irn-SME-PROPOSED.yaml — given folio close on a property with GSTIN + non-demo tenant, when frontdesk auto-fires e-invoice, then EInvoiceJob row is queued with documentType=INV; on COMPLETED, IRN + ackNumber + signedQRCode denormed onto Folio (AP-T08-006).
    • uc-frontdesk-einvoice-b2b-SME-PROPOSED.yaml — given folio with linked Company.gstin, when IRN generated, then supplyType='B2B' + buyerDetails.gstin = company.gstin + inter-state IGST split when company state ≠ property state (AP-T08-003).

D-003 — [P0] [Cross-service-drift + duplicate-impl] Two GSTR-1 implementations exist: hmp-frontdesk-service/src/compliance/gstr-reports.service.ts (frontdesk-owned, live folio scan, take: 1000 cap) AND hmp-analytics-service/src/reporting/reports/gst.definition.ts (analytics v2 report, MV-backed). Gold-Standard charter forbids parallel implementations. Risk: numbers don't reconcile between the two surfaces (already known via US-012-005 review GAP-2: B2C state code differs; GAP-9: tax split differs).

  • [SME-PROPOSED] Mark gstr-reports.service.ts deprecated; route the /compliance/gstr1 HTTP path through the gateway to gst-report v2 (analytics-service) which already has MV backing, RBAC, scheduling, multi-format export (json/csv/pdf/xlsx), audit, and 5-year retention. Frontdesk module deletion follows after one release cycle (mirrors the FU-55 dual-emit retirement plan).

D-004 — [P0] [Story-incomplete + India-compliance-critical] No GST portal schema v3.4 validation anywhere in code. The acceptance criterion "JSON output passes schema validation against the GST government schema v3.4" (US-012-04 AC-1) has zero implementation. Portal rejection on first upload is the platform's stated north-star failure mode.

  • [SME-PROPOSED] Add AC: "Both GSTR-1 and GSTR-3B JSON outputs SHALL be validated against the GST portal schema (currently v3.4) via ajv or zod schema at the boundary. Schema files (downloaded from https://www.gst.gov.in/download/returns) live in hmp-shared-packages/packages/common/src/gst-schemas/. A CI gate runs the validation on a corpus of 50 synthetic + 5 real-anonymized fixtures (last 6 months) per release."
  • [SME-PROPOSED] Add AC: "B2C state-code SHALL be derived from guest's billing address state (place-of-supply), not property state. When guest has no billing state, fall back to property state with a CTX-PLACE-OF-SUPPLY-FALLBACK warning logged for ops review."

D-005 — [P1] [Story-incomplete + integration-risk] NIC IRP rate-limit handling missing. nic-einvoice.adapter.ts has a 30-s timeout but no parsing of Retry-After headers, no exponential backoff on HTTP 429, no per-GSTIN concurrency cap. A night-audit run that posts 80 room charges + 80 e-invoice IRN requests in a 60-s window can saturate the 1000-IRN/sec sustained limit if the property runs multiple GSTINs through one bot.

  • [SME-PROPOSED] Add AC: "NicEInvoiceAdapter SHALL respect Retry-After response header and back off with exponential delay (10 s → 60 s → 300 s, capped at 600 s), and SHALL enforce a per-GSTIN concurrency cap of 50 in-flight requests via a Redis semaphore. Test: 60-request burst against a mocked-429 adapter completes in ≥3 batches without losing any request."

US-012-05 — GST GSTR-3B Summary Report​

Catalog status (proxy: US-012-005): Partial (P0, 8 points) Verified status: Partial — Output tax liability rows shipped (MvGstr3bSnapshot + gst-report v2); ITC (Input Tax Credit) section is always 0 with explanatory note; net-tax-payable computation lacks ITC offset

Code locations:

  • hmp-analytics-service/src/gstr/gstr.service.ts — computeGstr3b(): aggregates folio.posted events from EventStore for a calendar month, returns B2B/B2C split + CGST/SGST/IGST totals + HSN breakdown grouped by HSN/SAC code, caches in MvGstr3bSnapshot. Pass forceRefresh=true to bypass.
  • hmp-analytics-service/src/gstr/gstr.controller.ts:43-100 — GET /reports/gstr3b?month=YYYY-MM&propertyId=UUID (Roles: SUPER_ADMIN, ADMIN, MANAGER, ACCOUNTS)
  • hmp-frontdesk-service/src/compliance/gstr-reports.controller.ts — GET /compliance/gstr3b (legacy/duplicate per D-003)
  • hmp-analytics-service/src/consumers/folio.consumer.ts — populates MvRevenueBySource + MvDailySalesRegister from folio.posted (canonical event with FU-55 dual-emit fields: taxAmount, segment, countryCode, roomTypeCode, ratePlanCode, roomNights)
  • hmp-analytics-service/src/consumers/einvoice.consumer.ts — populates MvEInvoiceRegister from einvoice.completed (idempotent upsert by IRN)
  • hmp-analytics-service/src/reporting/reports/gst.definition.ts — gst-report v2 composite (merges GSTR-3B + IRN-level e-invoice rows)

Unit + integration tests:

  • hmp-analytics-service/src/gstr/gstr.controller.spec.ts — controller specs
  • hmp-analytics-service/src/reporting/reports/gst.definition.spec.ts — v2 report definition test
  • hmp-analytics-service/src/consumers/einvoice.consumer.spec.ts — register population
  • (No gstr.service.spec.ts — computeGstr3b() is untested directly; coverage only via controller spec mock paths)

Flow tests:

  • hmp-infrastructure/testing/flow-tests/src/flows/gst-tax-flags.flow.spec.ts — P0-7 portion asserts /compliance/gstr3b returns rows + totals (soft-skip if 404).

Real-backend smokes: none

Use cases referencing this story:

  • task brief cites _data/use-cases/uc-gstr3b-prep.yaml — proposed in D-002

SME notes:

  • ITC section is a known stub. Per US-012-005 review GAP-10 + AC-7: ITC always 0 with explanatory note. Real reason: HMP does not capture purchase invoices (vendor master + AP invoices) — that's hmp-asset-service vendor master scope, but no integration exists. GST law (Sec 16 CGST Act) requires net tax payable = output tax − eligible ITC; current GSTR-3B JSON understates ITC by 100%, overstates net payable. Tenant filing this verbatim WILL overpay.
  • No gstr.service.spec.ts file. The computeGstr3b() service that fills MvGstr3bSnapshot is the single source of GSTR-3B truth and has zero direct test coverage. Controller spec mocks the service — does not validate the SQL/aggregation arithmetic.
  • Folio take: 1000 cap (frontdesk path) silently truncates output for high-volume months. A 200-room hotel doing 5,000+ transactions/month would hit this without warning. The analytics path (EventStore stream) does not have this cap but ALSO does not paginate — relies on the consumer's order delivery; OOM risk on backfill of historical data.
  • GSTR-3B filing deadline: 20th of each month (CGST Rule 61); GSTR-1 by 11th. Late filing: ₹50/day + ₹50/day for nil returns up to ₹5,000 cap per return; interest 18% p.a. on unpaid tax. Spec narrates this but no "filing calendar" exists (US-012C-04 is Not Started).
  • IRN cancellation 24-hour window: NIC IRP allows IRN cancellation only within 24h of generation; after that the original IRN is permanent and corrections require a credit note. EInvoiceController has /einvoice/cancel but no time-window enforcement — a cancellation after 24h will silently fail at NIC, surfacing only via the FAILED job branch. Spec body doesn't mention this constraint.

Drift findings:

D-006 — [P0] [Story-incomplete + India-compliance-critical] GSTR-3B ITC section hardcoded to 0; no purchase-invoice model exists. Net tax payable in the JSON is gross output tax — files literally as-is will OVERPAY tax. Spec line 158 acknowledges this as "a known gap" but the JSON downloaded by Finance Manager has no marker that the ITC=0 row is a stub.

  • [SME-PROPOSED] Add AC: "GSTR-3B JSON output SHALL include a metadata.itcStubbed: true flag and a human-readable warning in the response envelope when ITC = 0. The dashboard download button SHALL render a confirmation modal: 'ITC not computed by HMP. You must add eligible ITC from your purchase records before filing to avoid overpaying GST. Proceed?'. Manual override required."
  • [SME-PROPOSED] Add UC uc-gstr3b-itc-manual-override-SME-PROPOSED.yaml: given a generated GSTR-3B with itcStubbed=true, when Finance Manager enters ITC totals per rate bucket, then a Gstr3bItcOverride row is persisted (tenantId, propertyId, month, itc5pct, itc12pct, itc18pct, itc28pct, attestation), and the cached MvGstr3bSnapshot is invalidated. Source-of-truth for audit trail.

D-007 — [P1] [Test-vs-code] gstr.service.ts::computeGstr3b() has no direct spec. The arithmetic that fills MvGstr3bSnapshot.cgstAmount/sgstAmount/igstAmount/hsnBreakdown is untested. A regression in the SQL aggregation would not be caught by the controller spec (which mocks the service).

  • [SME-PROPOSED] Add hmp-analytics-service/src/gstr/gstr.service.spec.ts covering: (a) empty month returns zero totals + empty hsnBreakdown, (b) single B2B folio with explicit CGST+SGST yields correct totals, (c) inter-state B2B folio with explicit IGST yields IGST-only totals, (d) HSN aggregation groups multiple folios with the same SAC code, (e) forceRefresh=true bypasses cache, (f) idempotency: two invocations against the same EventStore range produce byte-identical output.

US-012-14 — Audit Log with Integrity Chain​

Catalog status (proxy: US-012-014): Built (P0, 8 points) — note: catalog row says Built, spec aligns Verified status: Partial — MvAnomaly + ReportRun audit-trail exist; no SHA-256 hash-chain AuditLog entity is implemented anywhere. Spec's "tamper-evident hash chain" requirement is uncoded.

Code locations:

  • hmp-analytics-service/src/reporting/audit/ — ReportAuditService writes ReportRun rows on every v2 report execution (userId, filters, format, IP, UA, duration, rowCount, presigned export URL); 5-year retention per CERT-In + GST.
  • hmp-analytics-service/src/anomaly/ — MvAnomaly rows + dismiss audit (dismissedBy, dismissedAt, dismissReason) — anomaly trail, not financial event trail.
  • hmp-shared-packages/packages/security/src/audit/ — @hmp/security audit decorator @Audit() used across services; writes to per-service audit tables.
  • hmp-web-dashboard/src/app/(dashboard)/settings/compliance/page.tsx — compliance UI; no audit-log viewer with hash-chain status indicator.

Unit + integration tests:

  • v2 reporting framework tests (ReportAuditService test files implicit in reporting/) — coverage of audit-row writes
  • (No audit-log hash-chain spec exists — the entity doesn't exist)

Flow tests: none specific (ReportRun writes are exercised indirectly by any v2 report test)

Real-backend smokes: none

Use cases referencing this story: none

SME notes:

  • Spec misrepresents reality. EP-012C spec lines 73-74 + 87 + 175-198 + 251 + 259 + 265 describes AuditLog { id, entryHash, previousHash, eventType, payload, createdAt } with entryHash = SHA-256(previousHash + eventType + payload + createdAt) and GET /auditlog/verify returning the first tampered entry. None of this exists in code. MvAnomaly.entryHash doesn't exist; EventStore has no hash column; ReportRun has no chain link; no /auditlog controller in analytics-service or anywhere else.
  • Catalog says Built; reality says Not Started. This is a P0 story-vs-code drift identical to the EP-002 D-011 review-stale pattern but inverted: catalog optimistic, code absent.
  • What DOES exist: ReportRun is excellent for report-execution audit (who ran what when, presigned URL of the artifact) — but it's not the financial-event tamper-evident chain that DPDPA Art. 8(5) + RBI / IRDAI audit requirements + CERT-In 6-hour breach-notification context demand for financial trail integrity.
  • Hash-chain design today: would land naturally on EventStore (which already has the event-sourcing append-only shape), adding entryHash + previousHash columns + a chain-verify endpoint. Requires migration + backfill (compute hashes for existing rows in time-order).

Drift findings:

D-008 — [P0] [Catalog-says-Built + spec-says-Built + code-absent] Tamper-evident hash-chain audit log claimed in catalog US-012-014 "Built" and in EP-012C spec as a Built story with AuditLog { entryHash, previousHash } model + GET /auditlog/verify endpoint — none of this exists. Closest match is ReportRun (report-execution trail) and MvAnomaly (anomaly dismiss trail), neither of which is hash-chained. New reviewer trusting the spec/catalog would believe a CERT-In or RBI audit could be answered with this story — it cannot.

  • [SME-PROPOSED] Demote catalog status to Stub (or Not Started).
  • [SME-PROPOSED] Author plan to extend EventStore with entryHash String? + previousHash String? columns + a daily cron that backfills rows in createdAt order computing entryHash = SHA-256(previousHash || eventType || payload || createdAt). Add GET /auditlog/verify?fromDate=&toDate=&propertyId= returning the first tampered entry ID + chain-break range, RBAC COMPLIANCE_OFFICER | SUPER_ADMIN | ADMIN. Pin via spec at hmp-docs/docs/superpowers/specs/2026-NN-NN-eventstore-hash-chain-design.md.
  • [SME-PROPOSED] Until shipped, explicitly disclaim in the dashboard compliance page that audit-log integrity is via ReportRun (per-execution) not per-financial-event, and link to the planned EventStore hash-chain story.

US-012C-01 — CDNR (Credit/Debit Note Register)​

Catalog status: Not Started (P0, 5 points) Verified status: Stub — CreditNote model + IRN-generation path exist (AP-T08-007); GSTR-1 Section 9 export does NOT include them

Code locations:

  • hmp-frontdesk-service/prisma/schema.prisma — CreditNote { id, folioId, creditNoteNumber (unique), reason (RefundReason), cgstAmount, sgstAmount, igstAmount, creditNoteIrn String?, status (DRAFT | APPROVED | IRN_GENERATED) }
  • hmp-frontdesk-service/src/einvoice/einvoice.service.ts — generateCreditNote(folioId, originalIrn, propertyId) — queue-routed via EInvoiceJob documentType='CRN' (AP-T08-007); idempotent via findActiveCreditNoteJob; processor branches to write IRN onto CreditNote.creditNoteIrn (NOT Folio.irn).
  • hmp-frontdesk-service/src/einvoice/einvoice.controller.ts — POST /einvoice/credit-note (manager+/accountant)
  • hmp-frontdesk-service/src/einvoice/einvoice-job.processor.ts — documentType-aware completion (AP-T08-007)
  • (No GSTR-1 Section 9 CDNR export endpoint or report-definition exists)

Unit + integration tests:

  • hmp-frontdesk-service/src/einvoice/einvoice.service.spec.ts — generateCreditNote describe block (queue-routing + idempotency)
  • hmp-frontdesk-service/src/einvoice/einvoice-job.processor.spec.ts — CRN-vs-INV branch + retry/DLQ

Flow tests: none

Real-backend smokes: none

Use cases referencing this story: none

SME notes:

  • The Credit Note generation works (queue-routed, idempotent, dead-letter handled). The GSTR-1 reporting half does not. Spec AC-EP-012C-03: "CDNR section includes all credit notes linked to original invoices with correct IRN references." — there is no code that reads CreditNote.creditNoteIrn and emits a Section 9 block in the GSTR-1 JSON.
  • This is a 2026-04-29 implementation (AP-T08-007) sitting alone — the GSTR-1 export pipeline was never extended to consume it.

Drift findings:

D-009 — [P0] [Story-incomplete + India-compliance-critical] CreditNote IRN-generation path is shipped end-to-end (queue, retry, DLQ, idempotency) but the GSTR-1 export does not emit a Section 9 CDNR block referencing the original invoice. A cancellation processed through HMP creates a CreditNote with valid IRN but never reaches the GST portal — guest's tax liability stays inflated, hotel's outward-supply totals stay inflated.

  • [SME-PROPOSED] Author new story US-012C-01a "GSTR-1 Section 9 CDNR Export" with ACs: (a) gst-report v2 SHALL pull CreditNote rows for the period via a new CreditNoteServiceClient call to frontdesk; (b) emit Section 9 with fields (creditNoteNumber, creditNoteDate, originalInvoiceNumber, originalInvoiceDate, originalIrn, creditNoteIrn, reason, taxableValue, cgstAmount, sgstAmount, igstAmount); (c) Section 9 row count + net taxable reduction surfaced in GSTR-3B Table 3.1 outward-supply adjustments.

US-012C-02 — GST Rate Tier Configuration​

Catalog status: Partial (P0, 5 points) Verified status: Partial — Folio split (CGST/SGST/IGST columns) shipped via AP-T07-001 + AP-T07-004 + W2.A.1 migration 20260523120000; tariff-based rate-tier (0/12/18%) selection is NOT centralized; ad-hoc lookup at folio post-time

Code locations:

  • hmp-frontdesk-service/prisma/schema.prisma — FolioTransaction.cgstAmount/sgstAmount/igstAmount/customerState/placeOfSupply (W2.A.1 columns; migration 20260523120000_add_folio_transaction_gst_split finally added them; previously schema-only drift — see frontdesk CLAUDE.md Gotcha 29)
  • hmp-frontdesk-service/src/folios/folios.service.ts:582-597 — postCharge now passes split fields straight through (AP-T07-001)
  • hmp-frontdesk-service/src/folios/consumers/pos-folio-charge.consumer.ts:341-348 — deriveGstSplit(): explicit producer split honoured; rolled-up taxAmount+customerState derived intra-vs-inter via property GSTIN/state first 2 digits (AP-T07-004)
  • hmp-property-service/src/taxes/ — TaxConfiguration model (rate + inclusive/exclusive flag — AP-T02-005)
  • (No central GstRateTierResolver service that picks 0% / 5% / 12% / 18% from tariff band — each posting site applies its own rate)

Unit + integration tests:

  • hmp-frontdesk-service/src/folios/folios.service.spec.ts — 3 AP-T07-001 specs (intra-state, inter-state, omitted-defaults)
  • hmp-frontdesk-service/src/folios/consumers/pos-folio-charge.consumer.spec.ts — 29 specs incl. 9 AP-T07-004 cases
  • hmp-frontdesk-service/src/folios/__tests__/gsa-067-igst-split.spec.ts — IGST vs CGST+SGST switching

Flow tests:

  • hmp-infrastructure/testing/flow-tests/src/flows/gst-tax-flags.flow.spec.ts — P0-5/P0-6/P0-7 paths

Real-backend smokes: none

Use cases referencing this story: none

SME notes:

  • Indian GST rate tiers for hospitality (notification 11/2017-CT-Rate as amended, eff. 18-Jul-2022): room tariff <₹1,000/night → exempt (0%); ₹1,001–₹7,500 → 12%; >₹7,500 → 18%. Restaurant: 5% (without ITC) standalone or 18% if attached to a hotel >₹7,500/night room. Spec line 43 captures this. Code does NOT — no rate-tier resolver exists; each charge-posting site uses its own number (default 18% in einvoice.service.ts line 25: DEFAULT_GST_RATE = 18).
  • Risk: a room sold at ₹999 with the wrong default 18% rate creates a CGST+SGST overcollection — when the guest disputes, refund is the only fix and the GST liability already filed must be reversed via credit note (US-012C-01 D-009 issue).
  • Inclusive vs exclusive flag exists on TaxConfiguration (AP-T02-005) but is silent on the rate plan side — see EP-002 D-008 for the related drift.
  • Migration risk: 20260523120000_add_folio_transaction_gst_split is the column-add migration. Frontdesk CLAUDE.md Gotcha 29 calls out that pre-2026-05-23 the columns existed only in schema.prisma — a fresh migrate deploy would crash on first POS charge. CI environments that ran prisma db push papered over the drift — risk: a clean-slate prod deploy without this migration would corrupt every POS-driven GST split.

Drift findings:

D-010 — [P1] [Story-incomplete + India-compliance-critical] No central rate-tier resolver. DEFAULT_GST_RATE = 18 in einvoice + per-site rate selection in folios/POS means tariff-band logic is implicit, not enforced. A room sold at the ₹999 / ₹1,001 / ₹7,500 / ₹7,501 boundary can be mis-classified silently.

  • [SME-PROPOSED] Author @hmp/common/src/gst/rate-tier-resolver.ts exporting resolveGstRateTier(roomTariffPerNight: number, supplyType: 'ACCOMMODATION'|'RESTAURANT'|'LAUNDRY'|'SPA', restaurantAttachedToLuxuryHotel: boolean): {rate: 0|5|12|18, sacCode: string, basis: string}. Wire as the single source for rate selection in: folios.service.postCharge, pos-folio-charge.consumer, einvoice.service.generateIrnForFolio. Add 12 specs covering all boundary cases + amendment-effective-date variants.

US-012C-03 — Regional Tax Management (Luxury Tax / Tourism Levy)​

Catalog status: Not Started (P1, 5 points) Verified status: Not Started

Code locations: NONE FOUND

Unit + integration tests: NONE FOUND

Flow tests: NONE FOUND

Real-backend smokes: NONE FOUND

Use cases referencing this story: none

SME notes:

  • "Not Started" is honest — no RegionalTax model, no UI, no folio integration. Spec line 209-227 calls out Goa tourism levy (5%), Maharashtra luxury tax (tariff >₹7,500), Rajasthan tourism development fee as examples.
  • Regional taxes are India-specific levies in addition to GST — they appear as separate folio line items and on e-invoice but are NOT part of GSTR-1/3B (those are GST-only). A property in Goa today using HMP cannot legally invoice — the tourism levy column is missing.
  • Property #1 readiness: this story is launch-critical IF property is in any of the regional-tax states (Goa, Maharashtra, Rajasthan, J&K, several others). Spec marks it P1 — should be P0 for any non-Karnataka/Delhi/UP launch.

Drift findings: (none net new — status matches reality)

  • [SME-PROPOSED] Add AC: "Regional tax SHALL be configurable per property with (stateCode, taxName, taxCode, basis: 'PERCENT'|'FLAT', value, applicableTariffBand?, hsnCode?). Applied at folio charge-posting time as a separate FolioTransaction row with transactionType='TAX_REGIONAL'. Shown as a separate line on invoice + e-invoice. Bumped to P0 for any launch in Goa/Maharashtra/Rajasthan/J&K."
  • [SME-PROPOSED] Add UC uc-regional-tax-goa-tourism-levy-SME-PROPOSED.yaml: given property in Goa with 5% tourism levy configured, when room charge ₹8,000 posted, then folio shows ₹8,000 room + ₹1,440 GST 18% + ₹400 tourism levy + ₹9,840 total; tourism levy appears as separate line on e-invoice; NOT included in GSTR-1 GST totals.

US-012C-04 — Compliance Calendar & Due-Date Alerts​

Catalog status: Not Started (P1, 5 points) Verified status: Not Started

Code locations: NONE FOUND

Unit + integration tests: NONE FOUND

Flow tests: NONE FOUND

Real-backend smokes: NONE FOUND

Use cases referencing this story: none

SME notes:

  • No filing-deadline reminder system. Tenant relies on accountant or memory. GSTR-1 by 11th, GSTR-3B by 20th, TDS by 7th (most sections), TCS by 10th, Form 26AS reconciliation quarterly, e-invoice IRN generation 24h cancellation window, professional tax monthly, ESIC/EPF 15th.
  • Closest existing capability: analytics-service ReportSchedulerService (BullMQ cron) — could schedule a monthly "GSTR-1 reminder" notification to the Finance Manager via DistributionService (email/WhatsApp). No business code yet — just infra.

Drift findings: (none — story-vs-reality matches: Not Started)

  • [SME-PROPOSED] Add AC: "Compliance calendar SHALL surface upcoming due dates (next 30 days) for: GSTR-1, GSTR-3B, TDS (per section), TCS, e-invoice IRN cancellation window (per IRN), Form 26AS reconciliation, professional tax, ESIC, EPF. Alerts SHALL fire T-7d + T-3d + T-1d via notification-service email + dashboard banner."

US-012C-05 — STR Data Export​

Catalog status: Not Started (P3, 8 points) Verified status: Not Started

Code locations: NONE FOUND

Unit + integration tests: NONE FOUND

Flow tests: NONE FOUND

Real-backend smokes: NONE FOUND

Use cases referencing this story: none

SME notes:

  • STR (Smith Travel Research) Destination Report format: CSV with columns Property ID / Date / Rooms Available / Rooms Sold / Occupancy% / ADR / RevPAR. HMP has these metrics natively (MvDailyKpi + MvDailyOccupancy); a generator is one new str-export.definition.ts away.
  • P3 priority correct — STR is a paid subscription product opt-in, not regulatory. Indian Tier-2/3 hotels rarely participate; major chains (Marriott, IHG, Hyatt) require it via brand contract.

Drift findings: (none — Not Started matches)


US-012C-06 — IGST for Inter-State Bookings​

Catalog status: Partial (P1, 5 points) Verified status: Partial — IGST split correctly applied at folio post-time (AP-T07-004 derive logic); B2C inter-state path defaults to property state (US-012-005 review GAP-2)

Code locations:

  • hmp-frontdesk-service/src/folios/consumers/pos-folio-charge.consumer.ts:341-348 — deriveGstSplit(): property GSTIN/state first 2 digits compared to customer state → intra (CGST+SGST) vs inter (IGST). Honours explicit producer split.
  • hmp-frontdesk-service/src/folios/__tests__/gsa-067-igst-split.spec.ts — covers IGST vs CGST+SGST
  • hmp-frontdesk-service/src/einvoice/einvoice.service.ts:53-87 — generateIrnForFolio (B2B path AP-T08-003): buyerDetails.stateCode derived from company GSTIN first 2 digits, enabling inter-state IGST when different from property state
  • hmp-analytics-service/src/gstr/gstr.service.ts — aggregates IGST/CGST/SGST per folio event into MvGstr3bSnapshot

Unit + integration tests:

  • gsa-067-igst-split.spec.ts (frontdesk)
  • AP-T07-004 9-case suite in pos-folio-charge.consumer.spec.ts
  • B2B path 5 specs (AP-T08-003)

Flow tests:

  • gst-tax-flags.flow.spec.ts covers tax flags but not the inter-state path explicitly

Real-backend smokes: none

Use cases referencing this story:

  • task brief cites uc-frontdesk-einvoice-b2b.yaml — does not exist (D-002)

SME notes:

  • B2B path works — company GSTIN drives the stateCode; inter-state IGST applies cleanly.
  • B2C path is buggy — per US-012-005 review GAP-2, B2C state code in Gstr1ExportService uses property.state.substring(0, 2) (supplier state), NOT guest's billing state (place-of-supply). For a property in Maharashtra receiving a B2C guest from Karnataka, the supply is inter-state per Sec 7 IGST Act and IGST should apply — current code emits CGST+SGST against Maharashtra state code regardless. Risk: input-tax-credit mismatch on the guest side (if business traveller later expenses); state revenue allocation incorrect.
  • Guest billing-state field exists on Guest (cross-service read model in frontdesk) but the GSTR-1 export does not read it. Fix: same place D-003 retires Gstr1ExportService — the analytics-side gst-report v2 reads guest country/state from FU-55 FOLIO_POSTED payload (countryCode enriched).

Drift findings:

D-011 — [P1] [Story-incomplete + India-compliance-critical] B2C inter-state IGST path missing. Property in Maharashtra invoicing a Karnataka guest's walk-in stay emits CGST+SGST instead of IGST. Affects both per-transaction tax calc AND GSTR-1 Section 7 (B2C inter-state) classification.

  • [SME-PROPOSED] Add AC: "B2C charges SHALL resolve place-of-supply from the guest's billing address state (frontdesk Guest.billingState read-model field synced from guest-service). When billing state ≠ property state, IGST SHALL apply (full rate, no CGST/SGST). When billing state is null, log B2C-POS-FALLBACK-TO-PROPERTY and use property state with a 7-day ops review SLA to populate the billing state."
  • [SME-PROPOSED] Add 5 specs to gsa-067-igst-split.spec.ts: B2C intra-state → CGST+SGST; B2C inter-state with billing state → IGST; B2C null billing state → CGST+SGST + warning log; B2B unregistered (URP) inter-state → IGST per company-state-fallback; B2B null company stateCode → fail with IRN-BLOCKED-MISSING-COMPANY-STATE.