Sales Invoice — Chart of Accounts & Tax Routing¶
Accounts required at invoice creation¶
The Sales Invoice flow creates a core InvoiceModel inside django-ledger, so
the standard account requirements apply when creating a sales invoice:
Cash account (role:
ASSET_CA_CASH) — records cash receipts on paymentReceivable account (role:
ASSET_CA_RECEIVABLES) — records the amount owed by the customerDeferred revenue account (role:
LIABILITY_CL_DEFERRED_REVENUE) — holds revenue until the invoice is approved
Accounts required on sellable items¶
For every product or service added as a line item, the underlying ItemModel
must have the following accounts assigned before it can appear on an invoice:
Earnings account — where revenue is recognized (e.g., Sales Revenue)
COGS account (products only) — cost of goods sold
Inventory account (physical products only) — stock value
These are set on the item record, not on the invoice itself.
Choosing the right income account per line item¶
When you add a line item to an invoice you select an earnings account — this is the account that gets credited in the general ledger when the invoice is approved. The choice determines which bucket revenue lands in on your Income Statement (Profit & Loss).
Role (django-ledger) |
What it means |
Correct use |
|---|---|---|
|
Operational / trading revenue |
Product sales, service fees, shipping charges to customers, e-commerce sales |
|
Passive / investing income |
Rental income, royalties, licensing fees |
|
Interest earned |
Bank interest, loan interest received |
|
Asset disposal gain or loss |
Selling a vehicle, equipment, or investment at a profit |
|
Miscellaneous income |
One-off items that don’t fit the categories above |
Important
Picking the wrong role does not break the books (debits still equal credits), but your P&L will misrepresent the business — for example, regular service revenue appearing under Capital Gains.
Project sales account presets
The project now provisions dedicated sales accounts automatically for each entity default Chart of Accounts. This happens during migrations, when a default CoA is populated, and when the Sales Invoice flow is opened for an existing entity.
Code |
Account Name |
Used for |
|---|---|---|
4010 |
Sales Income |
Existing generic sales fallback |
4011 |
Product Sales |
Physical goods sold to customers |
4012 |
Service Revenue |
Labour, consulting, creative services |
4013 |
Freight Revenue |
Shipping charges billed to customers |
4014 |
E-commerce Sales |
Online channel revenue (Shopify, Amazon) |
The line-item Account dropdown defaults to Product Sales when available, then falls back to Sales Income.
Note
Freight Revenue (4013) is the correct account for the Shipping line
item. When a customer is charged for shipping, credit Freight Revenue.
When you pay the carrier, that is a separate transaction debiting a
Freight Out expense account (under COGS or EXPENSES).
Tax payable accounts (GST / PST / HST)¶
Project tax payable presets
The project provisions these current-liability accounts automatically under
the LIABILITY_CL_TAXES_PAYABLE role:
Code |
Account Name |
Role |
|---|---|---|
2080 |
Sales Tax Payable |
Taxes Payable / LC |
2081 |
GST Payable |
Taxes Payable / LC |
2082 |
PST/QST Payable |
Taxes Payable / LC |
2083 |
HST Payable |
Taxes Payable / LC |
2084 |
US Sales Tax Payable |
Taxes Payable / LC |
The provisioning step also creates or updates the entity’s
SalesTaxAccountMapping:
default_tax_payable_account-> 2080 Sales Tax Payablegst_account-> 2081 GST Payablepst_account-> 2082 PST/QST Payablehst_account-> 2083 HST Payableus_sales_tax_account-> 2084 US Sales Tax Payable
If a code is already used by a different account, the project picks the next available nearby code and still maps the tax code to the created account.
What the sales invoice stores
SalesInvoiceProfile.recalculate_snapshot() writes:
SalesInvoiceTaxComponentrows — aggregate GST/PST amounts for the whole invoiceSalesInvoiceLineTaxComponentrows — per-line breakdown for each itemSalesInvoiceProfile.tax_total_amount— total tax collectedauto-generated invoice item transactions for tax payable accounts
This data is used for reporting and display. The underlying InvoiceModel
total (amount_due) includes the tax amount so the customer is billed
correctly, and the tax portion uses the mapped Taxes Payable accounts instead
of ordinary sales income accounts.
How tax flows through the journal (double-entry)
When an invoice is approved and migrate_state() runs, django-ledger posts
the following journal entries automatically:
Dr Accounts Receivable $560.00 (full amount incl. tax)
Cr Sales Revenue $500.00 (pre-tax line items)
Cr GST Payable $25.00 (5% × $500 taxable)
Cr PST Payable $35.00 (7% × $500 taxable)
The mapping is implemented through auto-generated tax item transactions. Each tax item uses its mapped Taxes Payable account as the item earnings account, so django-ledger’s normal invoice migration credits the correct liability account.
Tax-exempt items¶
An item is treated as tax-exempt when its additional_info dict contains:
{"sales_tax_exempt": True}
Set this on the ItemModel record. The recalculate_snapshot method
reads this flag per line item and skips tax calculation for exempt items.
Exempt lines display an Exempt badge in the line items editor.
Discount allocation¶
When a discount is applied, it is pro-rated across taxable line items before tax is calculated. A $50 discount on two taxable lines of $300 and $200 is split $30/$20 respectively, reducing the taxable base for each line and therefore reducing the GST/PST amounts proportionally.
Summary flow¶
Customer Order
│
▼
SalesInvoiceProfile (created with InvoiceModel)
│ ship_to_* + tax_calculation_mode + tax_preset
│
▼
Add Line Items ──► ItemTransactionModel saved
│ │
│ post_save signal
│ │
▼ ▼
recalculate_snapshot() ──► SalesInvoiceLineSummary
SalesInvoiceLineTaxComponent (per-line GST/PST)
SalesInvoiceTaxComponent (aggregate)
SalesInvoiceProfile totals (subtotal/tax/total)
│
▼ (on approval)
migrate_state() ──► Revenue entries posted to sales accounts
Tax entries posted to mapped payable accounts