Skip to content

E-invoice validation module, GST return gap analysis, and the government registration routes - #479

Merged
sridharkalaibala merged 5 commits into
developfrom
claude3_stream
Sep 4, 2026
Merged

sridharkalaibala merged 5 commits into
developfrom
claude3_stream

Conversation

@sridharkalaibala

Copy link
Copy Markdown
Contributor

What does this PR do?

Two things, both on the India GST track opened by #456.

Code: the e-invoice validation module, PR 2 of the delivery plan in docs/INDIA_EINVOICING_DESIGN.md. Pure functions, no database, no network, not wired to any route yet. Addresses #43.

Documentation: the GST return gap analysis (#45), and a factual write-up of how software actually gets registered with the GST system, which answers a question that was blocking planning.

The code

File What it is
api/src/services/einvoice/gstin.js GSTIN structure, the mod-36 check digit, state code and PAN extraction, same-state comparison
api/src/services/einvoice/uqc.js GST unit quantity codes and the mapping from the free-text units Posnic stores
api/src/json/uqc_codes.json 45 codes plus an alias table
api/src/services/einvoice/applicability.js The India-only gate. One function every surface will call
api/src/services/einvoice/findings.js The EI-xxx catalogue: severity, title, and which screen fixes it
api/src/services/einvoice/validate.js The checks themselves, per sale and per period
api/tests/fixtures/einvoice/index.js Synthetic fixtures
4 test suites 121 tests

Three decisions worth reviewing rather than skimming:

A walk-in sale is not_applicable, not an error. Most bills in a retail shop are to consumers and none of them are e-invoices. If those came back as failures the readiness page would show hundreds of red rows on first run and the real problems would be invisible inside them. But a customer marked as a registered business with the GSTIN field left empty is still checked, because that is exactly the invoice a shop needs to hear about.

An unmappable unit produces no code, not OTH. OTH (OTHERS) is a real UQC and for some goods it is right, but choosing it is a decision about what a shop declares to a tax authority. sheet, plate and cup are all seeded by our own installer and none has a code, so they surface as finding EI-105 with the unit named. Same rule as country-tax.js: an honest gap beats a confident guess. The one judgement call is qty to NOS, because qty is our default unit and leaving it unmapped would block essentially every line — that is documented in the file.

EI-012 is a warning, not a block. The design proposed blocking when the shop's GSTIN and its typed state disagree. Implementing it showed that is wrong: the export derives the state code from the GSTIN, so it is internally consistent either way, and EI-108 already blocks the case that actually matters. Flagged here because it is a deliberate deviation from the merged design.

The India-only rule

Asked for directly: this feature must only appear for Indian shops. It is one function, applicability.js, rather than an if repeated in a route, a controller and a template where the fourth copy is the one somebody forgets.

The test that carries the weight is AND THE FLAG CANNOT OVERRIDE THAT: with module_einvoice_enable forced on and the shop set to Kenya, the feature is still off. Availability is checked before the switch, always. That is the state a shop reaches by enabling the feature and later changing country, or by a settings document copied between branches.

Country is resolved through the existing tax-profile registry, so tax_profile_override: 'IN' counts as Indian and there are not two competing answers to "which country's tax is this shop under".

A bug this found in itself

The robustness test caught a real crash: a null entry in a sale's items array threw a TypeError, which would have taken the readiness page down for any shop whose history contains one damaged row. Fixed, and the damaged line is now reported (EI-113) rather than skipped — exporting an invoice with a line quietly missing would file a total that does not match the bill the shop printed.

A correction to the merged readiness document

An automated sweep of the repo reported that HSN codes are never persisted on sale lines, because sale.model.js does not declare the field. I checked before repeating it, and the conclusion is wrong: the line schema is strict: false and sale.service.js:592 does write hsncode from the item master at sale time. New sales carry an HSN; older ones may not. docs/INDIA_GST_RETURNS_GAPS.md states the corrected version, and docs/INDIA_EINVOICING_READINESS.md was already right.

The two documents

docs/INDIA_GST_RETURNS_GAPS.md (#45). Every claim in it was verified against the code by hand, not taken from a summary. The headline: there is exactly one filing-capable artefact (the GSTR-1 B2B JSON) and one correct tax position (the per-head netting in taxPayable). Around them:

  • The GSTR-1 screen aggregation groups by a ten-field tuple with no accumulators, so it is a DISTINCT — two lines on one invoice at the same rate and same total collapse into one and the second is lost.
  • Credit notes are filed in the invoice's month, because the returns aggregation reuses the sales date filter. A credit note raised in October against an August invoice appears in August.
  • The same shared filter carries customer_gst_type: {$in: ['regular','composite']}, so credit notes to unregistered buyers (CDNUR) are silently dropped.
  • Four of the five GSTR "JSON" exports are DOM scrapes emitted as data:text/csv with a .json extension. Keys are English column headings. No filing tool can read them.
  • Nothing anywhere records exempt, nil-rated, non-GST, zero-rated or reverse-charge status, so GSTR-1 tables 4B, 6 and 8 cannot be derived from stored data at all.
  • No cess field exists anywhere; csamt is hardcoded to 0.

It ends with twelve items in dependency order. Items 4 and 5 are the best value: both small, and both fix wrong numbers rather than missing ones.

docs/INDIA_GOVERNMENT_INTEGRATION.md. What each government programme is, what it demands, and when to apply. Three findings that change planning:

  • The NIC e-invoice sandbox is free, self-service and instant. ERP is one of the four registration categories. Nothing needs to be finished first.
  • Production credentials need a test report of roughly 390 logged operations run through Posnic itself, not through NIC's own tester, plus up to four static Indian IPs. Then support.einv.api@gov.in, four to five days.
  • Automatic return filing is GSP-only, and that window closed in July 2024. The financial bar was low (50 lakh average turnover, MSMEs relaxed); the real barriers are MPLS links to two data centres and an annual ISO 27001 audit. Partnering with one of the 62 existing GSPs is the sensible version.

It also records, with the source, that no government list of recommended billing software exists — the government's own free-software page carries an explicit non-endorsement clause. What is achievable is appearing as a registered ERP in the dropdown inside the government's portal, which is a real and defensible claim.

Acceptance criteria covered

From #43:

  • Rejected cases for invalid GSTIN format, missing buyer GSTIN, missing HSN, missing place of supply and mismatched tax split
  • All names, addresses, GSTIN-like values and invoice numbers synthetic and marked as test data
  • Deterministic, no internet, no credentials, no external APIs
  • Test names explain the operator problem each fixture represents
  • The two passing fixtures are present and asserted at the findings level; asserting their exported amounts belongs to GST e-invoice: add deterministic export fixture #44, which needs contract.js

From #45:

  • Lists current GST/tax report fields and source files
  • Identifies gaps for sales, purchases, returns, cancellations, discounts, exempt/nil/non-GST, reverse charge, the CGST/SGST/IGST split, and cess
  • Separates current behaviour from proposed behaviour
  • Lists the smallest next PRs

How was it tested?

cd api && npm test -- tests/unit/services/einvoice-
  • 121 tests pass, 4 suites
  • Prettier clean on every new file
  • All 132 relative links and heading anchors in the changed docs resolve from the directory of the file they appear in
  • Markdown tables have consistent column counts
  • git diff --check clean
  • The synthetic GSTINs were checked to be genuinely valid, so the checksum tests fail for the right reason
  • Linted clean for no-undef, no-unused-vars, no-dupe-keys, no-unreachable, no-redeclare
  • Partly not run locally: the repo's own ESLint config and the full API suite. node_modules is not installed in this checkout, and api/eslint.config.js needs @eslint/js, so the lint above used an equivalent minimal flat config rather than the project's. Both gate in CI.

Checklist

  • One focused change per PR — one module plus the two documents that scope the next ones
  • Commits are signed off (git commit -s, DCO)
  • Works fully offline. Nothing here opens a socket; that is the point of the module
  • Matches surrounding code style
  • No real customer, tax, payment, credential, token or production data

What is deliberately not here

  • The feature toggle is not registered yet. module_einvoice_enable needs settings-groups.js, setting.model.js, settings_write.html, six places in settings.js, 14 language files and the backfill script. Those files have had five or more commits in the last two days and that is a conflict-prone change that deserves its own PR. Until it lands there is no feature to show, so the India-only rule is enforced by applicability.js from the moment there is.
  • The three arithmetic checks (line tax against rate times taxable value, header discount allocation, invoice total against the sum of lines) need the restated amounts from contract.js. findings.DEFERRED names them so it is a stated gap. They depend on open questions 3 and 4 in the design.
  • No route, controller or UI. PR 5.

Open questions still blocking

Of the eight in the design, three now have answers and five are still yours. Question 7, the bulk tool envelope, is resolved: the tool consumes the same schema objects in an array. Question 6, where live submission lives, is now much better informed by the government integration document. The rest stand.

🤖 Generated with Claude Code

sridharkalaibala and others added 5 commits September 4, 2026 10:52
Three pieces of the India GST work opened by #456.

CODE (PR 2 of the design's delivery plan, issue #43)

api/src/services/einvoice/ - pure functions, no database, no network,
not yet wired to a route:

- gstin.js: the mod-36 check digit, so a transposed character is caught
  at the till instead of by the portal after the customer has left. The
  existing regex accepts 33AAAAA0000A1Z0 as readily as ...1Z9.
- uqc.js + uqc_codes.json: GST unit codes, and the mapping from the free
  text Posnic stores. A unit nobody can map produces NOTHING rather than
  OTH - choosing OTHERS is a decision about what a shop declares, and it
  is the shop's to make.
- applicability.js: the India-only gate, in one place. Availability is
  checked before the feature switch, so module_einvoice_enable left on
  by a shop that changed country cannot revive an Indian-only feature.
- findings.js: the EI-xxx catalogue, with the screen that fixes each.
- validate.js: the checks. A walk-in sale is not_applicable, not an
  error; a customer MARKED registered with no GSTIN still is one.

121 tests over four suites. The robustness test found a real crash: a
null entry in items threw, which would have taken the readiness page
down for any shop with one damaged row. It is now reported (EI-113)
rather than skipped, because exporting an invoice with a line quietly
missing would file a total that does not match the printed bill.

EI-012 is a warning, not the block the design proposed. The export takes
the state code from the GSTIN, so it is consistent either way, and
EI-108 blocks the case that matters.

DOCS

docs/INDIA_GST_RETURNS_GAPS.md (issue #45): what the GST reports compute
today, verified by hand against the code. One filing-capable artefact
and one correct tax position; around them a GSTR-1 aggregation that
groups without accumulators and loses duplicate lines, credit notes
filed in the invoice's month rather than the return's, unregistered
credit notes dropped by an inherited filter, four DOM-scraping exports
no filing tool can read, and no exemption, reverse-charge or cess field
anywhere. Twelve items in dependency order.

docs/INDIA_GOVERNMENT_INTEGRATION.md: how software is actually
registered with the GST system. The e-invoice sandbox is free and
self-service today; production credentials need ~390 logged operations
run through our own application; return-filing APIs are GSP-only and
that window is closed. Records, with the source, that no government list
of recommended billing software exists.

Corrects a claim that HSN is never persisted on a sale line: the model
does not declare it, but the line schema is strict:false and
sale.service.js does write it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Sridhar Bala <sridharkalaibala@gmail.com>
A second research pass replaced the weakest section of the document
with primary sources.

What changed:

- Caution money on the Government e-Marketplace is ABOLISHED, not
  "contested" - the current general terms exempt all sellers regardless
  of turnover. The old five/ten/twenty-five thousand rupee slabs are
  still quoted widely and are wrong. Transaction charges start only
  after twenty lakh of merchandise value in a year.
- Class-I local supplier status is added, and it is the best-value item
  on the page: free, self-declared, and a real gate rather than a badge
  since non-local suppliers cannot bid in domestic tenders at all. DPIIT
  says in writing that no "Make in India certificate" exists, so anyone
  selling one is selling nothing.
- Udyam registration added as the step that makes the small-enterprise
  exemptions apply.
- Startup India: the age limit is the hard gate, and section 80-IAC is a
  separate and much harder application than recognition itself.
- A "not achievable, or not real" section: MeitY empanelment is for
  cloud providers with their own data centres; BIS does not certify
  software at all; ONDC fits a seller app rather than a seller's
  infrastructure and carries an annual audit; STQC certifies a website,
  not a product, and nothing in GST requires it; and ICAI's listings
  carry an explicit disclaimer that it does not endorse.
- No state publishes an approved-billing-software list either.

Four more unconfirmed items recorded rather than smoothed over.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Sridhar Bala <sridharkalaibala@gmail.com>
.husky/pre-commit was committed 100644 with a #!/usr/bin/env sh shebang
(b8ad720). tests/executable-scripts.test.js exists to catch exactly
that and has been failing the Desktop tests job on develop since.

This is not cosmetic. A hook without the executable bit is a hook git
declines to run, so the lint-staged formatting it was added to enforce
would silently never have happened.

One line, and it is the fix the test's own message asks for:
  git update-index --chmod=+x .husky/pre-commit

Included here rather than in its own pull request because develop is red
and every branch cut from it inherits the failure.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Sridhar Bala <sridharkalaibala@gmail.com>
@sridharkalaibala
sridharkalaibala merged commit 381b494 into develop Sep 4, 2026
7 checks passed
@github-actions github-actions Bot added the ready for QA Merged to develop and live on develop.posnic.io - anyone can test it label Sep 4, 2026
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Merged to develop. Anyone can test this - you do not need write access.

Try it at https://develop.posnic.io, or run it yourself:

git fetch origin develop && git checkout develop
npm install && npm --prefix api install
npm run dev   # then http://localhost:3000

When you have tested it, say what you did and what happened, and set
QA passed or QA failed. If you cannot set labels, just comment -
a maintainer will.

Reporting that something is broken is as useful as fixing it. It is
better found here than by a shopkeeper.

sridharkalaibala added a commit that referenced this pull request Sep 4, 2026
E-invoice validation module, GST return gap analysis, and the government registration routes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready for QA Merged to develop and live on develop.posnic.io - anyone can test it

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant