About Experience Engineering Projects Infrastructure Blog Contact
ERP Accounting Architecture ๐Ÿ“– 28 min read

Invoice vs Payment: The Complete ERP Accounting Architecture

Published: Aug 27, 2026

๐Ÿ“Œ The Core Concept: Invoice = Obligation, Payment = Settlement

In ERP systems such as SAP, Odoo, Microsoft Dynamics, and any serious custom-built ERP, Invoice and Payment are related but they are never the same transaction. An invoice creates a financial obligation. A payment settles that obligation. Conflating the two is one of the most common design mistakes when building an ERP or POS from scratch.

A simple business transaction unfolds like this:

Customer buys goods โ†’ Sale happens โ†’ Invoice is created
โ†’ Customer owes money โ†’ Customer pays โ†’ Payment is recorded
โ†’ Invoice becomes paid

Sale/Order โ†’ Invoice โ†’ Receivable โ†’ Payment โ†’ Reconciliation โ†’ Invoice Paid

For a supplier, the mirror image applies: Purchase/Order โ†’ Vendor Bill โ†’ Payable โ†’ Payment โ†’ Reconciliation โ†’ Bill Paid.

ConceptInvoicePayment
MeaningMoney that is owedMoney that has actually moved
Creates obligation?YesNo
Customer sideAccounts ReceivableReceives money
Supplier sideAccounts PayablePays money
Can exist without the other?YesYes
Changes outstanding balance?Creates balanceReduces balance
Status valuesDraft, Posted, Partially Paid, Paid, CancelledPending, Posted, Reconciled, Cancelled
๐Ÿ’ก The one-line rule: An ERP should never think Invoice โ†’ Payment directly. It should think Business Transaction โ†’ Document โ†’ Accounting Document โ†’ Receivable/Payable โ†’ Payment โ†’ Reconciliation.

๐Ÿ—บ๏ธ How It Works: The Entity Relationship Map

Before any workflow or flowchart makes sense, the underlying relationships have to be right. The single biggest mistake is wiring Invoice and Payment together with a direct foreign key. In a real ERP, nothing connects to a payment directly โ€” everything passes through an allocation record, and every posted document mirrors itself into a journal entry. The map below shows the actual shape of the data for the customer/sales side (the supplier/purchase side is the exact mirror image, with Supplier, VendorBill and Accounts Payable in place of Customer, Invoice and Accounts Receivable).

Customer

  • id
  • name
  • credit_terms
  • credit_limit
โ†“ โ†“creates (1 : N)

Invoice

  • id, number, total
  • status, due_date

Payment

  • id, amount, method
  • status, reference
โ†“ โ†“N : M โ€” resolved through

PaymentAllocation

  • invoice_id (FK)
  • payment_id (FK)
  • allocated_amount
โ†“settles โ†’ derives

Reconciliation

  • ar_debit_je_id
  • ar_credit_je_id
โ†“posts to

General Ledger

  • account_id
  • debit / credit
๐Ÿ’ก Basic concept: An Invoice is a record of an obligation; a Payment is a record of money movement. Neither one "contains" the other โ€” they are two independent facts joined by a third record, PaymentAllocation, which is the only place the ERP is allowed to say "this much of that payment settles this much of that invoice."

๐Ÿง  Main concept: Every balance you display to a user โ€” outstanding amount, PAID/PARTIAL status, customer statement โ€” is derived at read time from the chain Invoice โ†’ PaymentAllocation โ†’ Reconciliation โ†’ GL. Nothing is hard-coded as a single mutable field that different modules race to update.

๐Ÿ›’ Sales Flow: Order โ†’ Delivery โ†’ Invoice โ†’ Payment

Sales is the commercial side of the transaction โ€” it answers what did we sell, to whom, and on what terms. Not every ERP needs every step; a simple POS can collapse straight to Sale โ†’ Invoice โ†’ Payment, while an enterprise flow keeps every stage distinct and auditable.

Quotationcommercial offer, no financial impact
โ†’
Sales Ordercommitment โ€” NOT yet a receivable
โ†’
Deliverystock โˆ’, COGS may post
โ†’
Invoiceobligation created โ€” AR +
โ†’
Paymentmoney received
โ†’
Allocationpayment โ†” invoice link
โ†’
ReconciledAR โˆ’ , Invoice = PAID

Worked example โ€” a customer buys เงณ10,000 of goods on credit:

Invoice INV-00001
Subtotal      เงณ9,000
VAT           เงณ1,000
---------------------
Total        เงณ10,000

Journal Entry:
  DR Accounts Receivable    10,000
      CR Sales Revenue                9,000
      CR VAT Payable                  1,000

Customer pays เงณ4,000 (partial):
  DR Cash                    4,000
      CR Accounts Receivable          4,000

Outstanding = 10,000 - 4,000 = 6,000  โ†’  status: PARTIALLY PAID

Customer pays remaining เงณ6,000:
  DR Cash                    6,000
      CR Accounts Receivable          6,000

Outstanding = 0  โ†’  status: PAID
โš ๏ธ Order โ‰  Invoice โ‰  Payment. An Order is a commitment ("I want to buy"). An Invoice is an obligation ("you owe me money"). A Payment is a settlement ("money has been received"). Treating a Sales Order as if it already creates a receivable is a common and costly modeling bug.

๐Ÿ“ฆ Purchase Flow: PO โ†’ Receipt โ†’ Vendor Bill โ†’ Payment

The purchase side mirrors sales exactly, but from the supplier's perspective:

Purchase Requestinternal need identified
โ†’
Purchase Ordercommitment โ€” NOT yet payable
โ†’
Goods Receiptstock +, GRNI accrues
โ†’
Vendor Billobligation created โ€” AP +
โ†’
Paymentmoney paid
โ†’
Allocationpayment โ†” bill link
โ†’
ReconciledAP โˆ’ , Bill = PAID

Because goods receipt and vendor billing can arrive at different times, mature ERPs also track Goods Received Not Invoiced (GRNI) as an accrued liability until the vendor bill lands.

Vendor Bill
Goods       เงณ100,000
VAT          เงณ15,000
---------------------
Total       เงณ115,000

  DR Inventory / Expense    100,000
  DR Input VAT               15,000
      CR Accounts Payable            115,000

Payment 1 = เงณ80,000
Payment 2 = เงณ35,000
------------------------
Outstanding = 0  โ†’  status: PAID

๐ŸŠ Activity Diagram: The Invoice-to-Cash Swimlane

A flow diagram shows what happens to the documents. An activity diagram shows who does each step and where responsibility hands off between actors โ€” the Customer, Sales, Accounting, and Finance. Read the numbered cards left-to-right, top-to-bottom; each handoff tag is where the process crosses into the next lane.

Customer
Sales
Accounting
Finance
1Places a Sales Orderโ†ท hands off to Sales
2Confirms order, arranges deliveryโ†ท hands off to Sales
3Creates & posts the Invoiceโ†ท hands off to Accounting
4Posts AR journal entry
DR Accounts Receivable / CR Revenueโœ“ AR created
5Makes a payment (cash / bank / gateway)โ†ท hands off to Finance
6Receives & posts the payment
DR Cash/Bank / CR AR (pre-allocation)โ†ท hands off to Accounting
7Allocates payment to invoice & reconciles ARโœ“ AR = 0
8Sees invoice status = PAID on their statement

Notice that Accounting touches the process twice โ€” once to record the obligation (step 4) and once to record the settlement (step 7) โ€” and that neither touch is triggered by the other directly. Each is triggered by its own upstream document (the Invoice, then the Payment), which is exactly why the two must stay independent tables joined by an allocation record, as shown in the relationship map above.

๐Ÿ’ณ Accounts Receivable & Accounts Payable

AR is money customers owe your company. AP is money your company owes suppliers. Neither is simply a flag on the invoice table โ€” each is effectively a subledger: a running balance per business partner, derived from every invoice, credit note, and payment posted against them.

Customer A statement
Opening Balance          เงณ0
+ Invoice 1          เงณ100,000
+ Invoice 2           เงณ50,000
- Payment             เงณ80,000
-----------------------------
Closing Balance        เงณ70,000

The AR subledger total must always reconcile with the AR control account in the General Ledger:

SUM(all customer balances)  =  GL "Accounts Receivable" balance
SUM(all supplier balances)  =  GL "Accounts Payable" balance

โš–๏ธ Double-Entry Accounting Fundamentals

Every financial event must have equal total debits and credits. This single invariant is what keeps an ERP's books internally consistent no matter how many modules write to them.

Assets = Liabilities + Equity + Revenue - Expenses
Account typeIncreaseDecrease
AssetDebitCredit
ExpenseDebitCredit
LiabilityCreditDebit
EquityCreditDebit
RevenueCreditDebit

"Debit = increase, credit = decrease" is a common misconception โ€” it depends entirely on the account type, as the table above shows. A few canonical entries:

# Sale on credit
DR Accounts Receivable
    CR Sales Revenue

# Cash received against an invoice
DR Cash
    CR Accounts Receivable

# Pay a supplier
DR Accounts Payable
    CR Bank
โš ๏ธ Payment never re-creates revenue. Revenue is recognized once, when the invoice posts. A payment only moves value from Accounts Receivable to Bank/Cash โ€” recording it as revenue again double-counts the sale.

๐Ÿ“’ Journal Entries & the General Ledger

Every financial event should ultimately become a journal entry โ€” the accounting truth behind the business document.

JE-2026-000001
Date: 27-Aug-2026          Reference: INV-2026-001

Account                       Debit       Credit
--------------------------------------------------
Accounts Receivable          55,000
Sales Revenue                             50,000
VAT Payable                                5,000
--------------------------------------------------
TOTAL                        55,000       55,000

A clean schema separates the journal entry header from its lines, because one entry always has two or more balanced lines:

journal_entry
--------------------------
id
date
reference
source_type      -- e.g. "invoice", "payment", "credit_note"
source_id
description
status
posted_at

journal_entry_line
--------------------------
id
journal_entry_id  (FK)
account_id         (FK -> chart of accounts)
debit
credit
partner_id
currency
exchange_rate

The General Ledger is simply the aggregation of every posted journal entry line, grouped by account:

Business Document (Invoice / Payment / Bill)
        โ†“
Accounting Event
        โ†“
Journal Entry (balanced: total debit = total credit)
        โ†“
General Ledger

๐Ÿ”— Payment Allocation: Many-to-Many by Design

This is the concept most custom ERPs get wrong first. Don't model invoice.payment_id as a one-to-one link โ€” in reality, one invoice can be settled by many payments, and one payment can settle many invoices.

Invoice INV-001                    Payment PAY-1001
Total = เงณ100,000                   Amount = เงณ100,000
                                           โ”‚
โ”œโ”€โ”€ Payment 1 = เงณ30,000                    โ”œโ”€โ”€ Invoice A โ†’ เงณ40,000
โ”œโ”€โ”€ Payment 2 = เงณ20,000                    โ”œโ”€โ”€ Invoice B โ†’ เงณ35,000
โ””โ”€โ”€ Payment 3 = เงณ50,000                    โ””โ”€โ”€ Invoice C โ†’ เงณ25,000

Total Paid = เงณ100,000               (Payment applied across 3 invoices)
Balance = เงณ0

The relationship Invoice โ†โ†’ Payment is therefore many-to-many, resolved through an allocation table:

payment
----------------
id
customer_id
payment_date
amount
payment_method
bank_account_id
reference
status

payment_allocation
------------------
id
payment_id     (FK)
invoice_id     (FK)
allocated_amount
allocation_date

If a payment isn't fully allocated โ€” say a customer pays เงณ100,000 against เงณ90,000 of open invoices โ€” the remaining เงณ10,000 becomes an on-account credit balance rather than being force-fit onto an invoice it doesn't belong to.

๐Ÿงฎ Reconciliation

Allocation and reconciliation are related but distinct. Allocation answers how much of this payment applies to this invoice? Reconciliation answers which AR journal entries offset each other?

Invoice Journal Entry:   AR  +100,000
Payment Journal Entry:   AR  -100,000
                              โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
Reconciled AR balance:          0     โ†’  Invoice = PAID

Partial reconciliation works the same way, just leaving a remainder:

AR Debit  (invoice)     100,000
AR Credit (payment)      40,000
-------------------------------
Remaining outstanding    60,000   โ†’  status: PARTIALLY PAID

๐Ÿ”„ Invoice & Payment Lifecycle

A professional ERP derives status from a controlled state machine rather than letting any code path set status freely.

Invoice lifecycle                    Payment lifecycle

DRAFT                                 Created
  โ†“                                     โ†“
CONFIRMED                             Pending
  โ†“                                     โ†“
POSTED  โ”€โ”€โ–บ JOURNAL ENTRY             Posted
  โ†“                                     โ†“
OUTSTANDING                           Allocated
  โ†“ (payment received)                  โ†“
PARTIALLY PAID                        Reconciled
  โ†“ (fully allocated)
PAID

The outstanding balance is always derived, never stored as a naive flag:

Outstanding Balance =
      Invoice Total
    - Credit Notes
    - Allocated Payments
    + Debit Adjustments

Outstanding = 0  โ†’  PAID

๐ŸŽ›๏ธ The Multi-Dimensional Status Model

A common mistake is cramming everything into one invoice.status field. In practice an invoice has four independent dimensions โ€” whether the document itself is finalized, how much of it is settled, whether it's overdue, and (separately) where the payment that's settling it stands. Mixing them together produces states like "overdue_partial_cancelled" that are impossible to query cleanly.

DimensionApplies toPossible valuesWhat it answers
Document statusInvoice / BillDRAFT, CONFIRMED, POSTED, CANCELLED, REVERSEDIs the document itself finalized and posted to the ledger?
Settlement statusInvoice / BillUNPAID, PARTIALLY_PAID, PAID, OVERPAID, WRITTEN_OFF, CREDITEDHow much of the obligation has actually been cleared?
Due statusInvoice / BillNOT_DUE, DUE_TODAY, OVERDUEHas the payment-term deadline passed?
Payment statusPaymentDRAFT, PENDING, POSTED, PARTIALLY_ALLOCATED, FULLY_ALLOCATED, REVERSED, CANCELLED, FAILEDWhere does the payment itself stand, independent of which invoice(s) it touches?
๐Ÿ’ก Why it matters: a real invoice can be POSTED + PARTIALLY_PAID + OVERDUE all at once โ€” three true facts, three separate columns (or derived properties), each independently queryable for reporting, dunning, and aging without regex-parsing a single mashed-together string.

๐Ÿ”€ Flowchart: The Settlement Decision Logic

This is the actual decision tree an ERP runs every time a payment is recorded against an invoice โ€” the logic behind the lifecycle and the status model above, made explicit.

Invoice Posted
AR + Invoice Total
โ†“
Payment received?
No โ†’ stays OUTSTANDING. If due_date has passed, also flag OVERDUE (an independent dimension โ€” see the status model).
โ†“ Yes
Record Payment
create PaymentAllocation against the invoice
โ†“
Allocated < Outstanding?
Yes โ†’ Settlement status = PARTIALLY_PAID. AR reduced by the allocated amount; remainder stays open for the next payment.
โ†“ No
Allocated > Outstanding?
Yes โ†’ Overpayment. Allocate only up to the outstanding balance; park the excess as a Customer Credit / on-account balance.
โ†“ No (exactly equal)
Outstanding = 0 โ†’ Settlement status = PAID
Reconciliation posted to the General Ledger

๐Ÿงพ Credit Notes, Refunds, Overpayments & Advances

Credit note

Never edit a posted invoice to reflect a return. Issue a credit note instead, preserving the audit trail:

Original Invoice       เงณ100,000
Credit Note              -เงณ20,000
----------------------------------
Net Receivable            เงณ80,000

Refund

Different from a credit note โ€” the ERP should preserve all three transactions rather than netting them away:

Invoice        เงณ100,000
Paid           เงณ100,000
Refund          -เงณ20,000
--------------------------
Net received     เงณ80,000

Overpayment

Invoice  = เงณ10,000
Payment  = เงณ15,000

Allocation to invoice = เงณ10,000  โ†’  invoice PAID
Unallocated           =  เงณ5,000  โ†’  customer on-account credit

Advance payment

Customers can pay before an invoice even exists โ€” the advance becomes a credit that later invoices draw down:

Advance payment          เงณ50,000  โ†’  Customer Credit
Later invoice             เงณ80,000
Applied from advance     -เงณ50,000
--------------------------------------
Outstanding remaining     เงณ30,000

๐Ÿงช Scenario Playbook: A เงณ1,000 Invoice Under 40+ Conditions

The formula from the lifecycle section โ€” Outstanding = Invoice Total โˆ’ Credits/Adjustments โˆ’ Allocated Payments + Debit Adjustments โ€” has to survive every real condition a customer, cashier, or bank can throw at it. Below is a single เงณ1,000 invoice run through the full range of scenarios an ERP must handle correctly. The same logic mirrors exactly onto the AP/supplier side.

1. Partial payment progression

The core case: any amount between เงณ0 and เงณ1,000 leaves the invoice PARTIALLY_PAID โ€” there is no special threshold at 25%, 50%, or 90%. Only Outstanding = 0 flips it to PAID.

Paid% of invoiceOutstandingSettlement status
เงณ00%เงณ1,000UNPAID
เงณ101%เงณ990PARTIALLY_PAID
เงณ10010%เงณ900PARTIALLY_PAID
เงณ25025%เงณ750PARTIALLY_PAID
เงณ50050%เงณ500PARTIALLY_PAID
เงณ90090%เงณ100PARTIALLY_PAID
เงณ99999.9%เงณ1PARTIALLY_PAID
เงณ1,000100%เงณ0PAID
เงณ1,200120%เงณ0PAID + เงณ200 customer credit

2. Many payments settling one invoice

There is no limit on how many payments can close one invoice โ€” the allocation table simply accumulates rows until the sum reaches the total.

BreakdownPaymentsSumResult
Two payments600 + 4001,000PAID
Three installments300 + 300 + 4001,000PAID
Four equal installments250 ร— 41,000PAID
Eight small receipts50+50+50+100+150+200+200+2001,000PAID

3. One payment settling many invoices

The mirror case, exercising the many-to-many allocation from the allocation section.

Open invoicesPaymentAllocationResult
A=1,000, B=2,000, C=3,000เงณ4,000Aโ†’1,000, Bโ†’2,000, Cโ†’1,000A, B PAID; C PARTIALLY_PAID (2,000 left)
A=1,000, B=1,000เงณ1,500Aโ†’1,000, Bโ†’500A PAID; B PARTIALLY_PAID (500 left)
A=1,000, B=2,000, C=3,000เงณ7,000Aโ†’1,000, Bโ†’2,000, Cโ†’3,000All PAID + เงณ1,000 customer credit

4. Overpayment, advances & credit balances

ScenarioNumbersResult
OverpaymentInvoice 1,000 / Payment 1,200PAID + เงณ200 credit balance
Large overpaymentInvoice 1,000 / Payment 2,000PAID + เงณ1,000 credit balance
Advance before invoiceAdvance 1,000, later Invoice 1,500Advance applied โ†’ Outstanding 500
Advance larger than invoiceAdvance 2,000, Invoice 1,000PAID + เงณ1,000 remains as credit
New payment spills into creditBalance 200 due, Payment 500200 โ†’ invoice (PAID), 300 โ†’ credit
Opening balanceMigrated AR opening = 1,000Posted as an AR opening entry, not a new invoice
Existing credit offsets new invoiceCredit 500, new Invoice 1,000Customer owes only เงณ500
Refund of credit balanceCredit 500, refund 300Remaining credit = เงณ200

5. Credit notes, discounts, write-offs & tax

ScenarioNumbersResult
Partial credit noteInvoice 1,000, Credit Note 100, Payment 500Outstanding = 400
Full credit noteInvoice 1,000, Credit Note 1,000Net invoice = 0 โ†’ SETTLED, no payment needed
Settlement discountInvoice 1,000, Discount 50, Payment 950Outstanding = 0 (DR Sales Discount 50)
Small write-offInvoice 1,000, Payment 995เงณ5 written off โ†’ PAID + WRITTEN_OFF
Large write-off (needs approval)Invoice 1,000, Payment 700เงณ300 written off on management approval
Payment + credit + write-off combinedPayment 850 + Credit 100 + Write-off 501,000 total โ†’ Outstanding = 0
VAT-inclusive postingTotal 1,000 = Net 909.09 + VAT 90.91DR AR 1,000 / CR Sales 909.09 / CR VAT 90.91

6. Reversals, cancellations & corrections

ScenarioCorrect handling
Draft invoice cancelledNo GL impact โ€” it was never posted, safe to delete/cancel outright
Posted, paid invoice needs cancellingNever delete โ€” issue a Credit Note, then a Refund if cash already moved
Payment reversed (bounced cheque / bank reject)Reversal JE: DR AR / CR Bank โ€” invoice returns to OUTSTANDING
Posted payment cancelled by mistakeReverse it (create an offsetting JE) โ€” never hard-delete a posted payment
Duplicate payment receivedSecond payment stays unallocated, then refunded or applied to a future invoice
Payment allocated to the wrong invoiceReverse the allocation, create a new one on the correct invoice โ€” both logged in the audit trail

7. Timing, currency & operational scenarios

ScenarioCorrect handling
Gateway payment pending/failedInvoice stays OUTSTANDING until the gateway confirms โ€” never mark PAID on "pending"
Gateway clearing & feeRoute through a Clearing Account, then split Bank + Fee on settlement (see Chart of Accounts)
Multi-currency invoiceStore invoice & payment at their own exchange rates; the difference posts as FX Gain/Loss
Cash shortage/overage at tillPost the difference to an approved Cash Over/Short account โ€” never silently adjust the customer payment
Paid early vs. paid lateTrack days_early / days_late separately for DSO, aging, and collection reports
Allocation orderingConfigurable rule: oldest-invoice-first (FIFO), due-date order, or manual user selection

8. Every scenario mirrors onto Accounts Payable

Nothing above is customer-specific โ€” replace Customerโ†’Supplier, Invoiceโ†’Vendor Bill, and Accounts Receivableโ†’Accounts Payable, and the exact same allocation, reconciliation, and status logic applies.

AR scenarioAP mirror
Invoice 1,000, Payment 0Bill 1,000, Payment 0 โ†’ AP = 1,000
Invoice 1,000, Payment 500Bill 1,000, Payment 500 โ†’ AP = 500
Invoice 1,000, Payment 1,000Bill 1,000, Payment 1,000 โ†’ AP = 0, PAID
Overpayment โ†’ customer creditOverpayment โ†’ supplier advance/credit
One payment across A/B/C invoicesOne payment across A/B/C vendor bills
โœ… The takeaway: none of these 40+ conditions need a special case in application code. They all fall out automatically once Outstanding is a derived value, allocations are many-to-many, and every adjustment (credit, write-off, discount, FX difference) is its own typed transaction rather than a hand-edit to paid_amount.

๐Ÿ” Three-Way Matching

On the purchase side, mature ERPs cross-check the Purchase Order, Goods Receipt, and Vendor Bill before releasing payment:

Purchase Order    100 units @ เงณ1,000
Goods Receipt      95 units received
Vendor Bill       100 units billed
                    โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
                    Mismatch = 5 units  โ†’  flagged for approval

The sales-side equivalent compares Ordered vs. Delivered vs. Invoiced quantities before an invoice is allowed to post.

๐Ÿ›ก๏ธ Audit Trail: Why You Never Delete Posted Documents

A posted invoice is referenced by AR, the General Ledger, tax reports, and customer statements. Deleting it silently breaks all of them. Enterprise systems only allow Draft โ†’ Edit, but Posted โ†’ Reverse / Cancel / Credit Note โ€” never Posted โ†’ Delete.

audit_log
--------------------
id
user_id
action          -- CREATE, UPDATE, POST, CANCEL
entity_type     -- "invoice", "payment", ...
entity_id
timestamp
old_values      (jsonb)
new_values      (jsonb)
reason

Corrections to posted financial data should be made by reversing the original entry and posting a new one โ€” never by rewriting history:

Original JE:  DR Expense 50,000 / CR Bank 50,000
Reversal JE:  DR Bank 50,000 / CR Expense 50,000
Corrected JE: (the accurate entry)
โš ๏ธ Fiscal period locking: Once a period (e.g. August 2026) is closed, no user should be able to post or edit entries dated within it without an explicit, authorized adjustment entry.

๐Ÿ—„๏ธ Recommended Database Schema (Django/PostgreSQL)

For a Django/PostgreSQL ERP, keep Sales, Purchase, and Accounting as separate layers connected through documents and journal entries โ€” not as tightly-coupled tables reaching into each other's internals.

Customer โ”€โ”€โ”ฌโ”€โ”€ Sales Order โ”€โ”€ Order Lines
           โ”‚
           โ””โ”€โ”€ Invoice โ”€โ”€ Invoice Lines
                  โ”‚
                  โ†“
          Accounts Receivable
                  โ”‚
                  โ†“
          Payment Allocation โ†โ”€โ”€ Payment
                  โ”‚
                  โ†“
            Journal Entry โ”€โ”€ Journal Entry Lines
                  โ”‚
                  โ†“
              GL Accounts

Supplier โ”€โ”€โ”ฌโ”€โ”€ Purchase Order โ”€โ”€ PO Lines
           โ”‚
           โ””โ”€โ”€ Vendor Bill โ”€โ”€ Bill Lines
                  โ”‚
                  โ†“
           Accounts Payable
                  โ”‚
                  โ†“
          Payment Allocation โ†โ”€โ”€ Supplier Payment
                  โ”‚
                  โ†“
            Journal Entry โ”€โ”€ Journal Entry Lines
                  โ”‚
                  โ†“
              GL Accounts

Core entities worth modeling explicitly:

Customer, Supplier
ChartOfAccount, Account

SalesOrder, SalesOrderLine
Delivery, DeliveryLine
Invoice, InvoiceLine

PurchaseOrder, PurchaseOrderLine
GoodsReceipt, GoodsReceiptLine
VendorBill, VendorBillLine

Payment, PaymentAllocation
CreditNote, CreditNoteLine, DebitNote

JournalEntry, JournalEntryLine
Reconciliation, ReconciliationLine

CashAccount, BankAccount
Tax, TaxRate, Currency, ExchangeRate
AuditLog, DocumentSequence
๐Ÿ’ก Document numbering: Never expose raw primary keys to users. Generate business references such as INV-2026-000123, PAY-2026-000045, PO-2026-000012 via a DocumentSequence table, and keep id internal.

๐Ÿ“Š Chart of Accounts & Control Accounts

Double-entry accounting needs a structured Chart of Accounts underneath it:

1000 Assets
โ”œโ”€โ”€ 1100 Cash
โ”œโ”€โ”€ 1200 Bank
โ”œโ”€โ”€ 1300 Accounts Receivable   (control account)
โ””โ”€โ”€ 1400 Inventory

2000 Liabilities
โ”œโ”€โ”€ 2100 Accounts Payable      (control account)
โ””โ”€โ”€ 2200 VAT Payable

3000 Equity
โ””โ”€โ”€ 3100 Owner Capital

4000 Revenue
โ”œโ”€โ”€ 4100 Product Sales
โ””โ”€โ”€ 4200 Service Revenue

5000 Expenses
โ”œโ”€โ”€ 5100 Salary
โ”œโ”€โ”€ 5200 Rent
โ””โ”€โ”€ 5300 Utilities

For payment methods like bKash, Nagad, or card gateways, model them as real financial accounts rather than free-text strings โ€” and route gateway payments through a clearing account so gateway fees reconcile cleanly:

Customer pays เงณ10,000 by card:
  DR Card Clearing Account    10,000
      CR Accounts Receivable          10,000

Bank settles, minus a เงณ200 gateway fee:
  DR Bank                      9,800
  DR Payment Gateway Fee         200
      CR Card Clearing Account         10,000

Card Clearing Account balance = 0

โœ… Best Practices Checklist

Five concepts to master first

  • Document lifecycle โ€” Order โ†’ Delivery โ†’ Invoice โ†’ Payment, each a distinct, independently-auditable step.
  • Double-entry accounting โ€” every financial event balances debits and credits.
  • AR / AP โ€” who owes whom, tracked as subledgers reconciled against GL control accounts.
  • Allocation & reconciliation โ€” a many-to-many link between payments and invoices, never a single foreign key.
  • Audit trail โ€” posted financial documents are reversed or credited, never silently edited or deleted.

Once these five are correct, Sales, Purchase, POS, Inventory, Tax, and Expense modules can all be built on top of the same financial foundation, with the accounting engine as the central authority rather than `invoice`, `payment`, `sales`, and `purchase` tables reaching directly into each other.

Final words

The most important architectural rule: don't make the invoice table responsible for everything. Business modules generate financial events; a dedicated accounting engine records and reconciles those events. That separation is what makes an ERP scale, audit cleanly, and survive years of feature growth without turning into a tangle of ad-hoc status flags.

โ†‘