E-invoice validation module, GST return gap analysis, and the government registration routes - #479
Merged
Merged
Conversation
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>
Contributor
|
Merged to 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:3000When you have tested it, say what you did and what happened, and set Reporting that something is broken is as useful as fixing it. It is |
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
api/src/services/einvoice/gstin.jsapi/src/services/einvoice/uqc.jsapi/src/json/uqc_codes.jsonapi/src/services/einvoice/applicability.jsapi/src/services/einvoice/findings.jsapi/src/services/einvoice/validate.jsapi/tests/fixtures/einvoice/index.jsThree 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,plateandcupare all seeded by our own installer and none has a code, so they surface as finding EI-105 with the unit named. Same rule ascountry-tax.js: an honest gap beats a confident guess. The one judgement call isqtytoNOS, becauseqtyis 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 anifrepeated 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: withmodule_einvoice_enableforced 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
nullentry in a sale'sitemsarray threw aTypeError, 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.jsdoes not declare the field. I checked before repeating it, and the conclusion is wrong: the line schema isstrict: falseandsale.service.js:592does writehsncodefrom the item master at sale time. New sales carry an HSN; older ones may not.docs/INDIA_GST_RETURNS_GAPS.mdstates the corrected version, anddocs/INDIA_EINVOICING_READINESS.mdwas 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 intaxPayable). Around them:DISTINCT— two lines on one invoice at the same rate and same total collapse into one and the second is lost.customer_gst_type: {$in: ['regular','composite']}, so credit notes to unregistered buyers (CDNUR) are silently dropped.data:text/csvwith a.jsonextension. Keys are English column headings. No filing tool can read them.csamtis 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:support.einv.api@gov.in, four to five days.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:
contract.jsFrom #45:
How was it tested?
git diff --checkcleanno-undef,no-unused-vars,no-dupe-keys,no-unreachable,no-redeclarenode_modulesis not installed in this checkout, andapi/eslint.config.jsneeds@eslint/js, so the lint above used an equivalent minimal flat config rather than the project's. Both gate in CI.Checklist
git commit -s, DCO)What is deliberately not here
module_einvoice_enableneedssettings-groups.js,setting.model.js,settings_write.html, six places insettings.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 byapplicability.jsfrom the moment there is.contract.js.findings.DEFERREDnames them so it is a stated gap. They depend on open questions 3 and 4 in the design.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