Skip to content

Debit notes (AR and AP) — design

Status: approved in chat 2026-10-05, spec awaiting review. Not on docs/ROADMAP.md yet; add an item when the plan is written.

The AR design (2026-09-27-accounts-receivable-design.md, scope note and the “No debit notes in v1” line) and the MyInvois design (2026-09-27-myinvois-design.md §57, “AR and AP have none”) both say an extra charge is a new invoice. That stands as the workaround, not the rule: Malaysian ERPs (AutoCount, SQL Account) offer AR and AP debit notes, and customers migrating from them expect the document. This spec adds them. Amend both designs to point here.

  • customer_debit_note (AR): raises what a customer owes on a previously issued invoice (undercharge, freight, interest).
  • supplier_debit_note (AP): the supplier bills us more. It raises what we owe and behaves like a purchase invoice. Buyer-issued returns and claims stay with supplier_credit_note; this spec does not duplicate them.

New kind values in the existing sales_invoice and purchase_invoice tables, exactly as credit notes are. A table per note type was rejected: it duplicates numbering, posting, allocation, aging and PDF code. The sign of a line still comes from kind; lines stay positive.

  • Posts like an invoice, never mirrored. AR: Dr receivable gross, Cr income net, Cr tax. AP: Dr expense/asset/GRNI, Cr payable. accounting/ still reacts to the semantic tag.
  • Workflow: the invoice graph shape (preparer issues directly), registered as its own kind and added to workflow.AccrualOnlyKinds.
  • Optional original_invoice_id, and on the AR side an optional reason (purchase invoices have no reason column; the AP debit note carries none). When an original is given it must be an issued, uncancelled invoice of the same customer/supplier. No amount cap, no self-allocation, and the currency and rate are the document’s own, not inherited.
  • Payment terms and due date as an invoice (a credit note is due immediately).
  • A valid target for receipts, payments, credit-note offsets and payment batches. Never a source of an allocation.
  • Void refuses while allocations exist, as for invoices.
  • Reference prefixes DN (AR) and SDN (AP). There is no opening debit note: an old system’s open debit note is migrated as an opening invoice (same sign and ledger effect); settled ones go to the archive under their own kind.
  • No new permission keys: receivables.invoice.* and payables.invoice.* cover it.

Database (one migration, next free number)

Section titled “Database (one migration, next free number)”
  • Extend sales_invoice_kind_known, purchase_invoice_kind_known.
  • Original-link CHECKs: sales_invoice_credit_note_shape (as relaxed by 00640) and purchase_invoice_original_only_credit_note must allow an optional original on a debit note.
  • ar_allocation_target_is_invoice and its AP twin must accept the debit note as target.
  • tax_return_line_source_kind_known gains both kinds.
  • einvoice_submission_type_known and einvoice_submission_type_matches_source gain 03, 13.
  • archived_document_kind_known and trade-link pairs only if archive/trade lines need them (confirm in the plan; default: archive yes, trade link no).
  • The freeze triggers read semantics, not kinds, so they need no change. Every table keeps org_id, FORCE ROW LEVEL SECURITY, org_isolation; no new table is added.

AR (internal/platform/receivables): constants and SourceKinds; graph registration and step.go v2 kinds; Rules() entry plus label; resolveKind; checkIssuable (no cap); due-date logic in issue(); allocation target and receipt target checks; aging.go — the applied join and the sign CASE, which today treats every non-sales_invoice as a credit; delivery match (a debit note is not linked in v1, refused by name).

AP (internal/platform/payables): constants; documentGraph(KindDebitNote, …); Rules(); prefix and CreateInvoice guard; due-date logic; allocation target; checkPayment; aging.go join; matchedKind (not matched in v1); invoiceShares decision; assets and withholding-register lookups checked, default is exclude.

Cross-cutting: taxreturn/compute.go must not net a debit note as a credit even when original_invoice_id is set (the loader at ~L290-330 assumes any non-invoice row with an original is a credit note); payment-basis loader decision; imports/documents.go and archive.go; printing title keys (en, ms); webhooks pass-through; fixtures and cmd/worker/wiring_test.go.

AR debit note → type 03 with BillingReference to the original when present. AP self-billed debit note → type 13. Register the sales and purchase guards for the new kinds (an unregistered kind never queues an e-Invoice), extend the codec check and UBL builder, and the void-exit and publish loops in setting.go. Unverified against LHDN’s sandbox: record it on the roadmap as an open item beside the pre-mandate credit-note one, owned by the adviser.

Extend the enums in api/openapi.yaml: SalesDocumentKind, ApAgingRow.kind, ArAgingRow.kind, PurchaseInvoiceInput.kind, PurchaseInvoice.kind, EInvoiceSubmission.source_kind and .document_type, TaxReturnLine.source_kind, TradeChainInvoice.kind, ArchivedDocument.kind, and the three inline kind query parameters. No new endpoints. Then make generate; regenerate api/public-openapi.yaml (adding enum values is not breaking for the public baseline).

Third tab on both Invoices pages (routes.tsx search enum, InvoicesPage.tsx); dialogs follow the invoice branch with an optional original picker and reason; drawers; debit notes included in useOpenInvoices / receipt and payment pickers; en/ms/zh strings (receivables, payables, trade, imports, printing, einvoice) with the i18n parity test; page tests; e2e seed.

  • Posting: debit note posts unmirrored in AR and AP.
  • Aging: a debit note ages positive (fails today without the CASE fix — watch it fail).
  • Allocation: receipt/payment/credit-note offset against a debit note; a debit note is refused as a source; void refused while allocated.
  • SST return: debit note with an original is not netted as a credit.
  • DB drift: new CHECKs accept the kinds; allocation trigger accepts the target.
  • E-invoice: type 03/13 built and the guard queues it.
  • End to end: debit note issued → receipt → aging zero (web/e2e).
  • Presence-before-absence: each refusal test first shows the accepting case.

Refund notes (04/14), customer refunds, delivery/GRN matching for debit notes, and a buyer-issued AP debit note.

  • LHDN sandbox validation of types 03 and 13 (adviser).