Skip to content
ERPNext Data Model
Esc
navigateopen⌘Jpreview
On this page

GSTR-1 Outward Return Preparation & Filing Pipeline

A design reference for turning transactional outward-supply data into a statutory return, and driving that return through a government filing sequence

1. Requirements

1.1 Functional requirements

  • Aggregate a legal entity’s outward-supply transactions (sales invoices and their tax lines) for a chosen return period into the statutory GSTR-1 section taxonomy: business-to-business supplies (including SEZ and deemed-export variants), large business-to-consumer invoices, other business-to-consumer supplies, exports, nil-rated/exempt/non-GST supplies, credit/debit notes to registered and unregistered parties, advances received and adjusted, an HSN-wise summary, a document-issued summary, and e-commerce operator supplies under two distinct liability sections.
  • Support both a monthly and a quarterly filing preference per registration (the quarterly scheme groups three calendar months into one filing period), sourced from and kept synchronized with the government portal, and invalidate any cached aggregation the moment that preference itself changes.
  • Cache the computed aggregation so that repeat views do not re-scan the ledger, and invalidate that cache only on an explicit trigger: an operator-requested recompute, a filing-preference change, or a later transaction landing inside a period whose return has not yet reached a state where changes are restricted.
  • Retrieve the government portal’s own version of the same return period — either the return the registration has already filed, or a live snapshot of what the portal currently holds before filing — and reconcile it line by line against the locally computed version, surfacing category-level and field-level differences rather than a single pass/fail flag.
  • Drive the statutory filing sequence against the returns-filing government API: reset a return that is mid-preparation on the portal, upload/save section-wise data, request a pre-file summary for confirmation, and finally submit the return using a second-factor verification code tied to a specific signatory.
  • Track each of those filing actions as a discrete, asynchronous unit of work carrying its own portal-assigned reference, because the portal does not always resolve a request within the same call.
  • Handle a “processing is queued, retry after N minutes” response from the portal for large per-section downloads by parking the request for later resumption rather than blocking the caller or discarding the request.
  • Provide two supplementary, read-only views — a sales register and an HSN-wise summary of outward supplies — that reuse the identical section/sub-section classification engine, for ad hoc reference outside the return-preparation workflow.
  • On successful filing, offer to generate an accounting journal entry that nets out reverse-charge output liability already reported through the return, using output-tax accounts resolved through the shared tax engine’s account configuration (see the tax computation engine, documented separately).
  • Guard already-filed periods: once a registration’s return history shows outward filings completed up to a given date, optionally block further edits or submissions to sales-side source documents dated within that window unless the acting user holds an explicit override role.

1.2 Non-functional requirements

  • Idempotent regeneration: recomputing the local aggregation must not duplicate or corrupt a cached snapshot — a recompute replaces the whole cached section outright, never silently merges partial results, unless a caller explicitly asks for a non-destructive update (as the error-report path does).
  • Diagnosability: every category/sub-category comparison between the local and portal versions must retain both sides of the comparison plus a named list of differing fields, not just a boolean mismatch flag.
  • Bounded concurrency per return: only one filing action may be in flight against a given registration/period at a time; a second attempt must either wait for the first to resolve or be explicitly forced past it.
  • Non-repudiable submission: the actual filing call requires a fresh, single-use verification code bound to a specific signatory’s identifier — a stale or reused code must fail rather than silently resubmitting the return.
  • Backward compatibility: the section taxonomy has evolved over time (an HSN-bifurcation split was introduced at a known cutoff date); a period filed before that cutoff must still summarize correctly under the taxonomy that existed when it was filed.

1.3 Constraints

  • All outbound calls to the government portal are issued through the shared returns-filing transport and credential/session layer described elsewhere (see the GST API credential and session management reference) — this subsystem never opens a connection to the portal directly.
  • The portal is the sole source of truth for filed data. Once a period is filed, this subsystem can display whatever amendment figures the portal itself reports for that period, but it cannot originate a revision of a filed period on its own.
  • Classifying a transaction into a section is rule-based and mutually exclusive by construction: a transaction is tested against a fixed, ordered list of category predicates and stops at the first match. There is no “unclassified, needs human triage” bucket — categories either match or contribute nothing.

2. High-Level Design

2.1 Component diagram

2.2 Data flow — generating the books aggregation

This path is a short branching procedure rather than a pure graph, so it is written as annotated steps:

  1. Operator requests generation for a registration/period. If a generation is already running, or a portal download for this period is queued, the request short-circuits with a busy message instead of starting a second run.
  2. Filing-preference check. If the return’s filing preference was never set, or the portal now reports a different preference than what is cached, the aggregation is forced to recompute and the cached preference is refreshed from the portal.
  3. Cache check. If a current, complete set of cached snapshots already exists for this period, it is returned immediately and nothing below runs.
  4. API-availability branch. If the returns-filing API is not enabled for this registration, or an operator setting says not to compare against the portal’s still-unfiled data, only the local books aggregation is computed, summarized, and returned — no portal call is made at all.
  5. Portal-aware path. Otherwise:
    1. the registration’s filing status for the period is checked (Filed vs. not) against the portal;
    2. the matching payload is downloaded section by section — a filed period pulls the authoritative filed summary, an unfiled period pulls a live snapshot — and any section the portal defers is parked as a queued fetch rather than treated as a failure;
    3. the books aggregation is computed fresh from ledger data for the period;
    4. books and portal data are reconciled row by row;
    5. all three views (books, portal, reconciled) are summarized into per-category/sub-category totals and persisted;
    6. a completion event is published so any open view can refresh.

2.3 External interaction — the filing round trip

Two details this diagram compresses, expanded in §3.4: the “reference token” branch is also how a queued portal-side download (a different, earlier step in the pipeline) gets resumed — the same reference/poll shape recurs there — and the pre-file confirmation step treats a “return already ready to file” response as a recoverable state, not a failure.


3. Deep Dive

3.1 Data model

Return Log — one record per (registration, return period, return type). This is the durable center of the pipeline, and the same record type is shared across every return family this application prepares (outward returns and the summary return alike), distinguished only by its return-type field. It carries:

Field Purpose
generation status In Progress / Generated / Failed for the local aggregation step (a further, code-only value marks a portal download as queued)
filing status free-form: Not Filed / Uploaded / Ready to File / Filed
filing preference Monthly or Quarterly, synced from the portal
is latest data whether the cached books snapshot is still current
is nil whether the period is a nil return
acknowledgement number / filing date set only once a filing call succeeds
five paired attachments compressed snapshots — books, the portal’s unfiled view, the portal’s filed view, the reconciled diff — plus a pre-computed summary for each

Filing Action Record — a child row on the Return Log, one per attempted filing action (reset, upload, proceed-to-file, file). It carries the action’s own portal-issued reference token, a creation timestamp, and a status that starts empty and is filled in only once the portal reports a terminal outcome. That emptiness is exactly what the double-submission guard inspects: an action with no status yet is “still pending,” and a second action of any kind cannot start while one is pending unless explicitly forced.

Portal Fetch Queue Entry — one row per (registration, return type, section, period) whose per-section download the portal deferred with a “queued” outcome. It carries the portal’s reference token and, once resolved, either a “no data” marker or nothing (on success, the token is cleared and the row becomes inert). The mere presence of any entry with a live token is what keeps a periodic sweep switched on; the sweep disables itself the moment no entry has a live token left, rather than running unconditionally forever.

The per-period working record — a thin front door scoped to one registration and period, holding the period, year, and filing preference an operator is looking at. It mostly delegates: every meaningful action (recompute, sync with the portal, mark as filed, generate) is forwarded straight to the Return Log for that period.

Section taxonomy — roughly a dozen top-level sections, most split into two to five sub-categories:

Section Sub-categories
B2B / SEZ / Deemed Export Regular, Reverse Charge, SEZ (with tax), SEZ (without tax), Deemed Export
B2C (Large)
B2C (Others)
Exports With payment of tax, Without payment of tax
Nil-Rated / Exempted / Non-GST
Credit/Debit Notes (Registered) mirrors the B2B split
Credit/Debit Notes (Unregistered)
Advances Received / Advances Adjusted
HSN Summary split by B2B vs. B2C once the HSN-bifurcation schema applies
Document Issued
E-commerce Supplies liable-to-collect (TCS) vs. liable-to-pay (reverse charge)

3.2 Algorithm

Classification. Every invoice line is tested against a fixed, ordered set of category predicates — nil-rated/exempt/non-GST first, then business-to-business, export, large business-to-consumer, small business-to-consumer, credit/debit note to a registered party, credit/debit note to an unregistered party, and e-commerce-operator supply as an overlay on top of whichever category already matched. The first predicate to return true wins; nothing falls through to a second pass. Once a category is chosen, that category’s own sub-category function runs a second, narrower decision (for example, business-to-business branches further into deemed export, SEZ with or without tax, reverse charge, or plain regular, based on the invoice’s GST category and reverse-charge flag).

Aggregation. Matching rows are summed into per-sub-category totals (taxable value plus the four tax components), then rolled up into per-category totals. A category with no contributing sub-categories in the period is dropped from the summary entirely — it never appears as an explicit zero row.

Reconciliation. For every section, books rows are matched against portal rows by a natural key (typically the document number). A matched pair produces a per-field delta and a match status of Matched or Mismatch, listing exactly which fields differ. A books row with no portal counterpart is labeled “Not Uploaded”; a portal row with no books counterpart synthesizes an empty books row labeled “Missing in Books” so the operator sees a symmetric comparison either way. Once a period is Filed and nothing about the cached books data has changed, this whole comparison is served from its own cache rather than recomputed.

3.3 Adapter / API contract

Each government JSON section (business-to-business, large consumer, exports, small consumer, nil-rated, the two credit/debit-note shapes, HSN summary, advances, document-issued, e-commerce, and the return summary itself) is served by its own small adapter class sharing one common base. Each adapter knows only its own section’s field-name mapping and value-formatting rules, and implements the same two operations in both directions:

adapter.convert_to_internal_data_format(government_json) -> internal rows
adapter.convert_to_gov_data_format(internal_rows) -> government_json

This symmetry is what lets the reconciliation step in §3.2 treat a books row and a portal row as directly comparable objects — both sides are normalized into the same internal shape by the same per-section adapter before any comparison happens, mirroring the adapter-registry pattern used for external payment providers elsewhere in this design set.

3.4 Error handling

  • Named non-error outcomes. The shared returns-filing transport already classifies “no documents found,” “not yet generated,” “requested before the cutoff date,” and “queued, retry later” as distinct outcomes rather than raw failures (see the credential/session/rate-limit reference for how that classification is implemented). This subsystem treats “no documents found” as an empty section (not a missing one) and treats “queued” by creating a Portal Fetch Queue Entry rather than failing the whole generation run.
  • In-progress guard. Before starting reset, upload, proceed-to-file, or file, the pipeline checks every Filing Action Record on the log for an empty status. If one exists, the new action is refused with a “request in progress” message — unless the caller explicitly forces past it, in which case the stale record’s local status is simply marked “Ignored.” Forcing does not itself ask the portal whether that abandoned action actually completed; it only stops the local guard from blocking a new one.
  • “Already ready to file.” The pre-file confirmation call can come back with a specific error meaning the return is already staged and ready on the portal’s side. That response is deliberately treated as a recoverable, expected outcome rather than a failure — the pipeline proceeds to fetch the authoritative pre-file summary instead of aborting.
  • Stale summary. If the filing call itself reports that the previously fetched pre-file summary is no longer current, the cached summary is cleared and the period’s filing status reverts to “Not Filed,” forcing the operator to re-run the confirmation step before attempting to file again.

4. Scale and Reliability

  • Filing window — code-enforced. A configurable guard, off by default, blocks submitting or cancelling sales-side source documents dated on or before the latest date a registration’s outward returns are known to be filed up to, unless the acting user holds a designated override role or is an administrator. This is a real, code-level block against silently rewriting a period that has already been reported to the government — but it protects only the source documents feeding the return, not the return’s own statutory due date. Nothing in this subsystem tracks or enforces the calendar due date by which a period must be filed; that is left entirely to the operator and to whatever the portal itself rejects if a filing window has closed.
  • Filing window — left to the operator. The period-selection control in the working record only refuses to offer a future month or quarter as a choice — a client-side convenience, not a server-side rule. Nothing stops generating, reconciling, or attempting to file an arbitrarily old period; the portal is the only party that would reject a call outside its own accepted window.
  • Asynchronous portal processing. Two independent async mechanisms exist side by side. A queued download (an oversized or slow section fetch) is parked as a Portal Fetch Queue Entry carrying the portal’s own suggested retry delay, and resumed by a periodic sweep that re-issues the same download request with the stored reference token; the sweep turns itself off once no queued entry remains, rather than polling forever on an empty queue. A queued filing action (reset, upload, proceed-to-file) is tracked instead as a Filing Action Record with an empty status, resolved by client-driven polling that re-checks status on a bounded retry schedule from the operator’s own session — there is no server-side scheduled job chasing filing-action completion, only the download queue has one.
  • Double-file prevention. The primary structural guard is the in-progress check described in §3.4: only one Filing Action Record may sit unresolved at a time per return. Beyond that, the actual filing call requires a freshly issued, single-use verification code tied to a specific signatory — a human-in-the-loop step that is the practical barrier against an automated or accidental resubmission, since no code path can manufacture that code on its own. No explicit “already Filed, refuse to file again” check was found at this layer; the operating UI stops offering the filing action once a period’s status reads Filed, and a genuine repeat attempt would be left to the portal’s own rejection.
  • Amendment/revision. Not modeled as an in-app workflow. Once a period is Filed, the only amendment-related behavior is display: a “net liability from amendments” figure the portal itself supplies is folded into the summary view. There is no action anywhere in this subsystem that opens a filed period for revision or files an amendment on the registration’s behalf.
  • Load pattern. Bursty around each filing deadline rather than steady — most of a period’s calls (multiple section downloads, a reconciliation pass, then four sequential filing actions) cluster into a short window once an operator commits to filing, rather than spreading evenly across the month.
  • Session and rate-limit coupling. Every call in this pipeline rides the same registration-scoped session and reactive rate-limit discovery described in the credential/session reference — this subsystem adds no rate governance of its own on top of that shared layer.
  • Monitoring. A rising count of Portal Fetch Queue Entries that never clear signals the sweep is not keeping pace with the portal’s own queued-download backlog; a Filing Action Record that stays empty far longer than its peers signals a filing action the operator abandoned mid-flow without forcing past it.

5. Trade-off Analysis

Decision Trade-off
Fixed, ordered category predicates with first-match-wins Guarantees every transaction lands in exactly one section with no ambiguity, but a predicate ordering mistake silently reclassifies transactions into the wrong section rather than raising a visible error — correctness depends entirely on the ordering being right.
Reconciliation cached and skipped once Filed Saves real work on a return that will not change again, but means a portal-side amendment made after the reconciliation was cached would not surface until something explicitly invalidates that cache.
Filing actions tracked as an append-only child list with empty-status-means-pending Simple to reason about and cheap to query, but “empty status” is a fragile pending marker — a partial failure that crashes before writing any status leaves a record indistinguishable from one still genuinely in flight, relying entirely on the force override to recover.
Queued downloads resolved by a self-disabling periodic sweep, but queued filing actions resolved only by client-side polling Keeps server load near zero when nothing is queued, but a filing action left pending because the operator closed their browser has no background process that will ever resolve it — only a future interactive check (or a forced override) moves it forward.
Backdated-transaction guard is opt-in and role-overridable Lets organizations that don’t need strict period-locking skip the friction entirely, but means the protection a reviewer might assume exists (“you can’t touch a filed period”) is not actually active unless someone deliberately turned it on.
No in-app amendment/revision workflow Keeps the filing state machine simple — every period only ever moves forward to Filed once — but pushes any correction of a filed error entirely outside this system, onto the portal’s own amendment mechanism, which is only ever displayed here, never operated.

6. What to Revisit as the System Grows

  • Give the filing-action guard a real liveness signal. Replace “empty status” with an explicit in-flight marker that also records when the action was issued, so a crashed or abandoned action can be distinguished from one genuinely still processing, without requiring an operator to force past it blindly.
  • Add a background sweep for stuck filing actions, mirroring the queued-download sweep, so a filing action left pending by a closed session or a lost connection eventually resolves itself instead of waiting indefinitely for the next interactive check.
  • Track the statutory filing due date explicitly. Today nothing in this subsystem knows or warns about a period’s actual filing deadline; surfacing that date (and flagging periods approaching or past it) would convert a portal-side rejection into an earlier, friendlier warning.
  • Make the backdated-transaction guard the default, or at least surface it more prominently during period-close, once organizations increasingly rely on it as their only defense against post-filing edits.
  • Invalidate the reconciliation cache on a portal-side amendment, rather than only on an explicit operator recompute, once amendments become common enough that a stale cached diff is a real risk rather than a theoretical one.

Was this page helpful?