๐ The Complete Data Flow Diagram
Part 1's Module Interconnection Master Diagram and Flagship Document Flows showed which documents feed into which modules. This part goes one layer deeper: what actually happens, step by step, inside the framework, from the moment a user clicks a button to the moment the resulting ledger impact shows up in a report.
This exact shape โ UI โ client script โ server โ permission check โ controller โ business logic โ database โ side-effect records โ report โ is the same for every module and every DocType in ERPNext. Only two steps ever change: the Business Logic step (what a Sales Invoice's controller does is different from what a Leave Application's controller does) and the resulting side-effect records (GL Entry and Stock Ledger Entry for transaction DocTypes, but a Communication Log entry for a support Issue, or nothing at all for a pure master record like Customer). Once this one diagram is internalized, tracing any new document type through the system is a matter of asking "what does step 8 do here?" rather than learning a new architecture.
๐ Document Lifecycle Internals
Every transaction DocType in ERPNext carries a docstatus field with exactly three possible values โ this is verified core Frappe behavior, stable across recent versions: 0 = Draft, 1 = Submitted, 2 = Cancelled. What's less obvious from the UI is what actually fires at each transition:
| Action | docstatus | What happens internally |
|---|---|---|
| Save | stays 0 | validate() runs (field-level checks, defaults, computed totals), then before_save, then the record is written to its table with docstatus = 0. No GL Entry or Stock Ledger Entry exists yet for a transaction document at this point. |
| Submit | 0 โ 1 | validate() runs again, then before_submit, then docstatus flips to 1, then on_submit runs. For transaction DocTypes (Sales Invoice, Delivery Note, Stock Entry, Payment Entry, โฆ), on_submit is typically where the actual GL Entry and/or Stock Ledger Entry rows get created โ see GL Entry generation and Stock Ledger Entry generation below. |
| Cancel | 1 โ 2 | on_cancel runs, docstatus flips to 2, and โ this is the important part โ the framework auto-cancels/reverses any GL Entries and Stock Ledger Entries that were linked to this document, rather than leaving them as orphaned postings against a now-cancelled voucher. The linkage that makes this possible is the same voucher_type / voucher_no pairing described in GL Entry generation below. |
| Amend | new doc at 0 | Amending a cancelled document does not edit it in place. It creates a fresh new document, copied from the cancelled one's field values, linked back to it via the amended_from field. The original cancelled record is preserved untouched as history rather than rewritten. |
๐ฐ How a Sales Invoice Becomes a GL Entry
Take a concrete example: a Sales Invoice for a customer with a subtotal of 50,000, VAT of 5,000, and a grand total of 55,000. Conceptually โ not claiming to reproduce ERPNext's actual source code line by line โ the posting logic on submit does the following: it walks the invoice's line items and tax rows and creates one GL Entry row per side of the transaction that needs to balance.
| Account | Debit | Credit |
|---|---|---|
| Accounts Receivable / Debtors (configured on the Customer or Company) | 55,000 | โ |
| Sales / Income account (configured per invoice line item) | โ | 50,000 |
| Tax Payable account (configured per tax line) | โ | 5,000 |
Two categories of claim in that table are worth separating clearly:
- Verified structural behavior: every
GL Entryrow carries anaccount, adebitamount, acreditamount, avoucher_type(e.g. "Sales Invoice"), avoucher_no(the specific invoice's name), and anagainst_voucherfield used later for reconciliation โ this shape is stable across versions and is what makes the ledger queryable and cancellable at all. - Configuration-dependent: which specific account names get used (the Receivable account, the Income account, the Tax account) depends entirely on the Customer, Item Group, Company, and Tax Template setup on the specific site โ verify the actual accounts on any real installation rather than assuming the names above.
The full theory behind why debits and credits, journal entries, and the double-entry rule work this way at all is covered independently of ERPNext in Double-Entry Bookkeeping and Journal Entries โ this section is that theory's concrete ERPNext implementation.
๐ฆ How a Delivery Note Becomes a Stock Ledger Entry
The stock side follows the same pattern with a different table. Take a Delivery Note moving 10 units of an item out of Warehouse A. On submit, a Stock Ledger Entry row is created carrying: item_code, warehouse, posting_date/posting_time, actual_qty (-10 โ negative because this is an outward movement), qty_after_transaction, valuation_rate, and stock_value_difference. Immediately after, the Bin record for that Item + Warehouse combination is updated to reflect the new on-hand quantity.
That last step is the distinction worth being precise about: Stock Ledger Entry is the append-only movement history (every stock-affecting transaction ever posted for that item/warehouse), while Bin is a derived, current-snapshot cache of quantity and valuation kept in sync as ledger entries post. Part 4 covers this relationship in schema terms in the Item ER diagram's Bin vs Stock Ledger Entry distinction.
GL Entry is also typically created alongside the sale โ a debit to Cost of Goods Sold and a credit to the Inventory/Stock asset account โ connecting this Delivery Note's stock movement to the Sales Invoice's revenue posting from the previous section. Whether this fires, and which accounts it hits, depends entirely on that site's accounting settings; do not assume it is universally on.
๐ธ๏ธ Internal Connections, Fully Enumerated
Part 3's module tour listed which modules connect to which. Here's the same pairing with a third column added: exactly what record gets created when the connection actually fires.
| Module pair | Trigger | What actually gets created |
|---|---|---|
| Selling โ Stock | Delivery Note submit | Stock Ledger Entry (outward) + Bin update |
| Selling โ Accounting | Sales Invoice submit | GL Entry (Receivable / Revenue / Tax) |
| Buying โ Stock | Purchase Receipt submit | Stock Ledger Entry (inward) + Bin update |
| Buying โ Accounting | Purchase Invoice submit | GL Entry (Payable / Expense-or-Inventory / Tax) |
| Manufacturing โ Stock | Work Order + Job Card โ Stock Entry | Stock Ledger Entry (material transfer out + finished goods receipt in) |
| Manufacturing โ Accounting | Manufacture Stock Entry submit | GL Entry (WIP / Inventory) โ where perpetual inventory accounting is enabled |
| HR โ Payroll | Attendance + Salary Structure | Salary Slip |
| Payroll โ Accounting | Payroll Entry submit | GL Entry (Salary Payable / Expense) |
| Projects โ Accounting | Timesheet โ billed via Sales Invoice | GL Entry (Receivable / Revenue) |
The pattern from Part 1 holds under this closer look too: almost every row's "what gets created" column ends in either GL Entry or Stock Ledger Entry โ those two tables really are the settlement layer every module ultimately writes to.
๐ External Connections in Depth
Part 1's Internal vs External Connections section sketched the external side in one line. Here's the fuller picture of how ERPNext talks to systems outside its own database:
ERPNext
REST API
- /api/resource/<DocType>
Webhooks
- HTTP POST on doc event
OAuth
- provider or consumer
E-commerce
Payment Gateways
SMS / Email Providers
Other ERPs
| Mechanism | What it does |
|---|---|
| REST API | The framework-generated /api/resource/<DocType> surface covered in Part 1; full deployment, auth, and troubleshooting depth is in Part 8. |
| Webhooks | Fire an HTTP POST to an external URL when a chosen DocType event happens โ e.g. "on Sales Invoice submit, notify our Next.js app" โ configured per DocType and event without writing a custom app. |
| OAuth | Lets ERPNext act as either an OAuth provider (issuing tokens so a third-party app can act on a user's behalf) or an OAuth consumer (logging into ERPNext via an external identity provider). |
| Integration Request | A logging DocType some integrations use to record outbound/inbound API call attempts โ payload, response, status โ purely for debugging failed integrations after the fact. |
| Typical external systems | Payment gateways, e-commerce platforms, SMS/email providers, and other ERPs/systems are the most common consumers of the mechanisms above. |
๐งฎ Reconciliation, ERPNext-Style
This series has stayed mostly framework-specific; the accounting series covers the theory framework-agnostically. Here's where they connect directly: ERPNext's Payment Reconciliation Tool, and the against_voucher field on GL Entry plus the Payment Entry Reference child table, are the concrete implementation of the abstract Payment Allocation and Reconciliation concepts described there.
A short worked example, mirroring the "one payment settles many invoices" scenario from that article's scenario playbook: a single Payment Entry of 100,000 comes in from a customer. Rather than being tied to one invoice, it's allocated across two open Sales Invoices โ 40,000 against the first, 60,000 against the second โ via two rows in the Payment Entry's Payment Entry Reference child table, each row pointing at its invoice through against_voucher. Both invoices move from "Unpaid" to "Paid" without a single GL Entry needing to guess which invoice a given rupee/dollar/taka belongs to โ the reference rows carry that mapping explicitly.
๐ง Common Integration Mistakes
| Mistake | Why it breaks things |
|---|---|
| Manually creating GL Entry or Stock Ledger Entry records via script | Bypasses the validation, permission checks, and voucher linkage that submitting the proper transaction document provides โ and breaks the audit trail, since the "reversal" record on cancel won't know these rows exist |
| Disabling perpetual inventory accounting mid-year | Creates a valuation gap between transactions posted before and after the switch, since stock value was being tracked in the GL for one period and not the other |
| Reading Bin for "current stock" in a hot path without understanding its role | Bin is a derived cache updated from Stock Ledger Entry, not the source of truth itself โ code that needs a guaranteed-accurate, point-in-time quantity should reason from Stock Ledger Entry directly |
| Manually reversing GL/Stock entries after cancelling a document | Cancel already auto-reverses linked GL Entries and Stock Ledger Entries โ reversing them again creates duplicate offsetting postings |
| Not setting up default accounts per Item Group/Warehouse/Company | Postings silently fall back to whatever default account is configured at a higher level, which is rarely the account anyone actually wanted for that transaction |
๐บ๏ธ What's Next
This part assumed the module tour from Part 3 and the schema/table background from Part 4 as prerequisites โ if either felt unfamiliar while reading this, they're the right place to go back to first. From here, Part 6 covers how to safely extend the posting logic described above with your own Custom Fields, Client/Server Scripts, and hooks, without ever touching ERPNext core.