Skip to content

Accounts receivable (Phase 2): design

Status: decided under the user’s “complete phase 2” goal (decisions D1–D20 below); the compliance-sensitive choices are listed at the end for the owner to confirm. Roadmap: Phase 2, “AR: customer role, sales invoices, credit notes, receipts, aging, statements”. Architecture: §18.2 item 3 (party roles), §19 Phase 2 table (AR row), §5.5 (semantics), §10 (ledger), §11 / §11A.6 (MyInvois is the sales side’s obligation). Template: the merged AP design (2026-09-26-accounts-payable-design.md, D1–D12, plan Rulings P1–P48). AR mirrors it wherever the mirror is honest; every place it does not is a decision below. Migrations: 00310–00329 (this item uses 00310–00314). E2E lane 1: pg 56412, minio 59110, api 3110, vite 5192.

In: the customer master, sales invoices and customer credit notes shaped for MyInvois and frozen once issued, output SST recorded per line with the tax code snapshotted on the line, customer receipts (including money received on account), append-only allocation of receipts and credit notes to invoices, customer balances, AR aging, customer statements of account, a printable invoice / credit note, and the web screens for all of it.

Out, each its own item or later:

  • MyInvois submission, validation, status polling, retry, cancellation, QR code — the MyInvois item (reads this spec’s §“Hand-off to MyInvois”).
  • SST-02 return — the SST-02 item (reads §“How SST-02 reads AR”).
  • Bad-debt write-off and SST bad-debt relief (D17).
  • Debit notes, refund notes and customer refunds (D6, D9) — a debit is a new invoice in v1; a refund of a credit balance waits for its own item. — superseded: see debit notes.
  • Quotation → SO → DO → invoice (Phase 4), item master / price lists (Phase 4 inventory), recurring invoices, dunning / reminders (no mailer yet), emailing invoices or statements.
  • Credit limits and credit control.
  • Linking one party as both supplier and customer (D1).

AP D1 already made party role-based and gave it the shared fields (tin, registration_no, country_code, structured address, email, phone). AR adds:

  • party.registration_type ∈ brn | nric | passport | army (nullable; a type needs a number). MyInvois names the buyer’s identification by type; registration_no alone does not say whether it is an SSM number or an NRIC. Vocabulary values are strings.
  • party.sst_registration_no (nullable; NULL means not SST-registered — the MyInvois item maps NULL to the “NA” the payload wants).
  • party_customer (org_id, party_id PK, code, payment_terms_days, currency, default_revenue_account_id, default_tax_code_id, is_active, revision, …). code unique per org. No bank details: AR never pays a customer in v1.

A new customer always creates a new party row, as AP does. Attaching the customer role to an existing supplier’s party would make edits on one screen change the other’s record; that is a deliberate feature, and it belongs with the import/migration tooling item that will also merge duplicate parties. Out of v1.

D2. The control account already exists: ar, “Trade receivables” 1540

Section titled “D2. The control account already exists: ar, “Trade receivables” 1540”

The chart has carried control kind ar since Phase 1 (account_control_kind_known, IsSubledgerKind), and the MY pack seeds 1540 Trade receivables with it; nothing posts to it yet. So AR needs no new control kind, no chart change and no provisioning step for existing organizations — unlike AP, which had to add ap_trade because ap was taken by expense claims. Every journal line on an ar account carries the customer’s party_id; a drift test asserts Σ open items per customer (base) = the ar control balance per party.

D3. One platform package and one module; invoice and credit note share a table

Section titled “D3. One platform package and one module; invoice and credit note share a table”

internal/platform/receivables (+ receivablestest) owns every AR rule; internal/modules/receivables is the HTTP shell — AP Ruling P1’s shape. sales_invoice.kind ∈ {sales_invoice, customer_credit_note} equals the workflow kind and the journal source_kind (AP P2); lines share sales_invoice_line.

D4. The sales document is shaped for MyInvois and frozen once issued (the Ruling)

Section titled “D4. The sales document is shaped for MyInvois and frozen once issued (the Ruling)”

Header (all on sales_invoice):

  • customer, currency, fx_rate (required when ≠ base, exactly 1 at base), payment terms (defaulted from the customer), an optional customer_reference (the buyer’s PO number), totals net_total, tax_total, gross_amount (= net + tax, a named CHECK) and gross_base.
  • Set at issue, never before: reference (the e-Invoice number, D5), issued_at (the issuance date-time MyInvois asks for), invoice_date, due_on, and two snapshots:
    • seller — seller_name, seller_tin, seller_registration_no, seller_sst_no, seller_msic, seller_address, copied from the company profile;
    • buyer — buyer_name, buyer_tin, buyer_registration_type, buyer_registration_no, buyer_sst_no, buyer_email, buyer_phone, buyer_address_line1/2/city/postcode/state, buyer_country_code, copied from the customer’s party. A frozen document must say what it said on the day, whatever later happens to the customer record or the company profile. A named CHECK ties them: issued_at, reference, invoice_date, due_on, buyer_name, seller_name are all NULL or all set.
  • A credit note also carries original_invoice_id (required) and reason (required) — D6.

Lines (sales_invoice_line): description, classification_code (three digits, required; the web defaults to 022 “Others”), quantity (> 0, NUMERIC(19,10) / money.Rate, the expense-claim precedent), optional unit_code (UN/ECE Rec 20, e.g. C62, H87), unit_price, discount_amount, net_amount = round-to-cents(quantity × unit price) − discount, account_id (an income account), the tax code and its snapshot (D7), tax_amount, optional tax_exemption_reason, net_base, tax_base, analytic shares (project revenue).

Money on a sales document is to the sen: net_amount, tax_amount, discount_amount have a named cents CHECK. Two kernel helpers round once, half away from zero: Amount.MulRateCents (price × quantity) and Amount.PercentCents (net × rate %). Tax is computed, never entered — unlike AP D8, where the supplier rounds its own way; on our own invoice we are the one rounding.

Frozen: the draft guard triggers (AP 00302’s shape) refuse any change to the header’s content columns and to any line once the instance’s semantic is not draft. The issue snapshot is written in the issuing transaction before workflow.Transition. An invoice is still a draft then; a credit note is issued from pending_approval (D6), so the header guard has one narrow exception: a pending document may take its issue stamp once (issued_at NULL → set) and only with its 14 content columns unchanged (kind, customer, original invoice, reason, customer reference, currency, rate, terms, the four totals, instance, preparer). Lines stay frozen from submission. Corrected only by credit note; a draft can be withdrawn.

D5. Issuing: no approval step, number and date at issue, the issued state is final

Section titled “D5. Issuing: no approval step, number and date at issue, the issued state is final”

Seeded sales_invoice graph:

draft ──issue (reporter)──▶ issued (accrual, control ar, terminal)
└──withdraw (reporter, reason)──▶ cancelled ("Withdrawn", terminal)
  • No approval before issue. A sales invoice records revenue the customer will see and dispute if wrong; the control that matters is on the correction (credit notes, D6) and on receipts’ voids (D8). An administrator who wants a review step adds one in the workflow editor: the kind is an accrual-only kind (the validator’s rule 3 already permits a pending step before the accrual), and every draft save reports the base gross with workflow.SetAmount, so amount bands work unchanged.
  • The number is allocated at issue, INV-<FY>-NNNN from numbering (scope sales_invoice, period = fiscal year, AP P12), so the issued series is gapless — a withdrawn draft never consumed a number. Drafts are identified by id and shown as “Draft”.
  • Invoice date = issue date = posting date (clock.TodayUTC), because the engine posts on the transition’s business date. This removes, for AR, the cut-off question AP left open (“posting date is the approval date, not the document date”). No back-dating in v1.
  • due_on = invoice date + the document’s payment terms.
  • issued is terminal. The Ruling allows correction only by credit note, so v1 has no void edge, and the validator requires a non-terminal state to have an exit. MyInvois cancellation (the issuer’s short window after validation) and “rejected by LHDN” handling need a way out of issued; that is a v2 of this seeded graph owned by the MyInvois item (§“Hand-off to MyInvois”).

D6. Customer credit note: tied to its invoice, approved by a second person

Section titled “D6. Customer credit note: tied to its invoice, approved by a second person”

Seeded customer_credit_note graph (AP’s document graph minus the void):

draft ──submit (reporter)──▶ pending_approval ──issue (target, OWNER seat)──▶ issued (accrual, terminal)
│ └──reject (advance, reason)──▶ rejected ──revise──▶ draft
└──withdraw (reporter, reason)──▶ cancelled
  • Approval is required. A credit note reduces revenue, output tax and a customer’s debt: it is how a diverted receipt is hidden. The preparer cannot issue it (the engine’s self_approval refusal, AP P9); the seeded grant names the OWNER seat; administrators re-grant.
  • It must name an issued invoice of the same customer (original_invoice_id — MyInvois requires the original e-Invoice reference) and a reason (record text). It takes the original’s currency and rate (not editable), so offsetting it never leaves an unposted FX difference (AP P23).
  • Cap: Σ gross of the invoice’s issued credit notes (this one included) ≤ the invoice’s gross — checked at submit and again at issue under the original invoice’s row lock.
  • At issue it allocates itself to the original invoice, up to that invoice’s outstanding amount (posts nothing: both sides are the customer’s, on ar). A remainder (the invoice was already paid) stays as the customer’s credit, allocatable to other invoices of the same customer, currency and rate (D10). Refunding it is out of v1.
  • No debit notes in v1: an extra charge is a new invoice. No refund notes (MyInvois type “refund”). — superseded: see debit notes.

D7. Output SST: per line, tax code snapshotted, posted at issue

Section titled “D7. Output SST: per line, tax code snapshotted, posted at issue”
  • Every sales line requires a tax code of usage sales or both (AP amendment 1’s tax_code.usage; the MY pack’s ST10 ST5 SVT6 SVT8 EXEMPT OS are all sales). A seller that is not SST-registered picks OS (out of scope). MyInvois’s tax type is mandatory on every line, so a line without a code would be unsubmittable.
  • The code must be active on the issue date (checked on every save against today, and again at issue).
  • Snapshot on the line, written on every draft save and frozen with the line: tax_code_id, tax_code (its code), tax_pack_kind (sales_tax | service_tax | exempt | out_of_scope, the pack’s own classification), tax_rate. Only rate is immutable on tax_code (tax_code_immutable_guard); code, pack kind and account can be edited later, and the return must report what the invoice said. This is what the roadmap’s SST-02 note asked for, done on the sales side.
  • tax_amount = PercentCents(net, tax_rate). A code with a positive rate must name an account (2140 SST payable in the pack): output tax is never absorbed (422 tax_code_account otherwise).
  • Posting at issue (D11): Cr income per line (net), Cr the tax code’s account per line (tax), each line carrying tax_code_id; Dr ar (gross) with party_id. A credit note posts the mirror.
  • tax_exemption_reason is stored when given and not interpreted: whether it is required for an exempt line is a MyInvois rule, and pack_kind has meaning only inside countrypack (Ruling R27).

Kind customer_receipt, cash_only:

draft ──record (reporter)──▶ received (payout, non-terminal) ──void (target, OWNER seat, reason)──▶ voided
└──withdraw (reporter, reason)──▶ cancelled
  • Fields: customer, deposit account, receipt date (≤ today), bank reference, currency, rate, amount, and planned allocations to the customer’s issued invoices (AP P20: planned in customer_receipt_line while a draft, effective ar_allocation rows when it enters received).
  • No approval to record. Money arrived; the bank statement is the evidence, and the bank reconciliation item will match it. The control is on the reversal: voiding a receipt (a bounced cheque, a wrong customer) needs the OWNER seat, a reason, and reverses its allocations.
  • Where the money lands: an active postable account that is either control kind bank or the default account of an active cash journal (so 1520 Bank and 1510 Cash in hand both work). The entry posts on that account’s journal (AP P25’s journalFor). GET /receivables/deposit-accounts lists them, because a Finance user may not hold accounting.ledger.read-level knowledge of journals.
  • Payout channels are not reused. A payout channel describes how the organization pays a person (payee fields sealed per member); a receipt needs only the account the money reached. No “payment method” field in v1 (it is optional in MyInvois).
  • Number RCT-<FY>-NNNN, allocated when it enters received (the same rule as D5).
  • The engine posts on the recording date; receipt_date is the document’s date. This is AP’s open “posting date” question, and stays open (receipts can be recorded from yesterday’s statement).

D9. Money received on account (advances, overpayments)

Section titled “D9. Money received on account (advances, overpayments)”

A receipt need not be fully allocated when it is in the base currency. The remainder posts as a credit on ar for the customer (the customer’s credit balance) and is allocated to invoices later; that allocation posts nothing (both sides on ar, same party, rate 1). A foreign-currency receipt must be fully allocated when recorded (AP D4’s rule): allocating a foreign remainder later to an invoice at a different rate would leave a realised FX difference nothing posts.

A customer credit balance is shown on ar rather than a separate “customer deposits” liability: it is what AutoCount and most SME ledgers do, the balance sheet still shows it (a credit balance within receivables), and it keeps allocation posting-free. Refunding a credit balance is out of v1.

D10. Allocation is append-only; balances, aging and statements are derived

Section titled “D10. Allocation is append-only; balances, aging and statements are derived”

ar_allocation mirrors ap_allocation after AP Ruling P21: invoice_id, exactly one of receipt_id / credit_note_id, generated source_kind, amount, base_amount, currency, effective_on, reverses_id, created_by/at. No UPDATE/DELETE grant; a reversal is a mirroring row, once. A guard trigger locks the invoice (and the source) and refuses over-allocation, cross-customer, cross-currency, cross-rate (credit notes) and an unposted target.

  • Receipt allocations made at recording are mirrored in the receipt’s journal entry (Cr ar per invoice at the invoice’s frozen base); for a foreign receipt they are undone only by voiding the receipt (AP P22). For a base-currency receipt any allocation may be reversed on its own (it moves an amount between the invoice and the customer’s credit; rate 1, nothing to post).
  • Credit-note allocations are reversible on their own.
  • Invoice outstanding = gross − Σ ar_allocation.amount for it; a credit note’s or receipt’s remainder likewise.

receivables.Rules(): AccrualRule{sales_invoice}, AccrualRule{customer_credit_note}, ReceiptRule{} (payout), registered from internal/modules/receivables/boot.go. The accrual rule posts on the sales journal in the document currency with the frozen bases (AP P13: no FX line on an accrual). The receipt rule posts Dr deposit account (amount at the receipt’s rate), Cr ar per allocation at the invoice’s frozen base, Cr ar the remainder (base currency only); ledger.Post adds the fx_difference line on fx_gain_loss when the bases differ — that line is the realised FX, as in AP D9. Reversal (a voided receipt) is the engine’s mirror. No rule compares a state name.

AP D9 and P13 unchanged: currency + rate per document; bases frozen per line; realised FX on the receipt. A credit note inherits its invoice’s currency and rate (D6). No unrealised revaluation in v1.

A report in reports, like AP aging (AP P4, P31): GET /reports/ar-aging?as_of under reports.read, and CSV/XLSX/PDF through POST /reports/exports with report = 'ar_aging' under reports.export. Open items as of a date: issued invoices (positive), credit-note remainders and on-account receipt remainders (negative, bucketed from their own date), a document counting when its posting entry is dated on or before the date and no reversal of it is, allocations when effective_on ≤ the date. Days past due and the buckets are AP’s own helpers (payables.BucketFor, payables.BucketAmounts) — reused, not copied; a neutral home for them is warranted only if a third aging appears. GET /receivables/customers/{id}/balance is the same for one customer, by currency.

A statement is built from the ledger, not from the documents: every normal journal line on an ar account for the customer’s party in one currency, between two dates, with the opening balance before the first date and a running balance — so the statement cannot disagree with the books. Its closing balance equals the customer’s aging total in that currency as of the end date (a test holds it). The statement also carries the aging buckets at its end date, in that currency. Dates are entry dates (when it hit the books), which for a receipt may differ from its receipt date (D8).

  • JSON: GET /receivables/customers/{id}/statement?from&to&currency under receivables.invoice.read.
  • File: report = 'customer_statement' with customer_id, from, to, currency through the existing export pipeline (CSV/XLSX/PDF), under reports.export.
  • English column titles (record language, §11A.3), as every existing export.

GET /receivables/invoices/{id}/pdf renders an issued document synchronously with the existing render PDF writer: the seller and buyer snapshots, number, dates, currency and rate as the page’s header fields; the lines as the table; net, tax per tax code, and total rows. It is a commercial copy, not MyInvois’s visual representation — that needs the validated UUID and QR code, which the MyInvois item adds to this same endpoint. A draft has no PDF (409).

receivables.customer.read, receivables.customer.manage, receivables.invoice.read, receivables.invoice.create, receivables.invoice.transition, receivables.receipt.read, receivables.receipt.create, receivables.receipt.transition. Seeded: Owner and Finance get all eight (new organizations through provisioning.RegisterRolePermissions; existing ones through one migration, the 00305 pattern). There is no key that must stay Owner-only: the second-person controls (credit note issue, receipt void) are workflow grants on the OWNER seat, which administrators configure. AR aging and statement files reuse reports.read / reports.export.

ar is a sub-ledger kind, so no manual journal can clear an invoice, and a credit note is the wrong tool for a bad debt (it tells the customer and MyInvois the sale was reduced, and reduces output tax). Until the write-off item lands, an uncollectible invoice stays open in aging; the year-end impairment an auditor requires is a manual journal to an allowance account (not a control account), which already works. SST bad-debt relief is part of the write-off item.

No supplier-number duplicate check applies (we number our own documents). A customer’s code is unique per org. A buyer TIN is checked with taxrecord’s shape when present; MyInvois’s general TINs for consolidated B2C and foreign buyers (EI00000000010, EI00000000020) fit that shape. Buyer TIN and identification are not required to issue in v1 (below-threshold organizations are exempt from e-Invoicing and must still invoice); the MyInvois item adds the organization’s “e-Invoice in scope” setting that makes them required at issue (owner to confirm).

Issued documents are never edited or deleted (D4, D5). party_customer, sales_invoice, sales_invoice_line, customer_receipt, customer_receipt_line are amendable (no DELETE grant); ar_allocation is appendOnly (no UPDATE, DELETE). Seven years, like every record (§11A.2).

D20. Every write is audited; issuing announces itself

Section titled “D20. Every write is audited; issuing announces itself”

Every mutation writes audit_log last (AP P33). Entering the issued state publishes receivables.EventDocumentIssued (DocumentIssued{OrgID, DocumentID, Kind, Reference}) on the transaction’s event buffer, dispatched after commit — the documents package’s EventUploaded pattern. No listener exists in v1.

What the MyInvois item can rely on, and what it owns.

The data contract. An issued document is a sales_invoice row with issued_at IS NOT NULL. Its e-Invoice type follows from kind (invoice / credit note); its number is reference; its issuance date-time is issued_at; currency and exchange rate are currency / fx_rate; seller and buyer parties are the snapshot columns (D4), never the live customer or company rows; a credit note’s original e-Invoice is original_invoice_id → that row’s reference. Lines give classification, description, quantity, measurement (unit_code), unit price, discount, subtotal (net_amount), tax type (from tax_pack_kind via a mapping the MY pack owns), tax rate, tax amount, exemption reason. All of it is frozen by trigger. receivables.GetDocument(ctx, tx, id) reads a document with its lines.

The hook. Two, by design: EventDocumentIssued after commit for promptness, and the durable condition “issued and no submission row” for correctness (an event is lost if the process dies between commit and dispatch). The MyInvois item’s worker should sweep the condition; the event only wakes it.

What it owns, and should not put on sales_invoice:

  • its own table (e.g. einvoice_submission (org_id, document_id, uuid, long_id, status, validated_at, …)), so the freeze trigger never needs an exception;
  • the organization’s “e-Invoice in scope” setting and the stricter issue-time checks it implies (buyer TIN / identification / address / contact required, tax_exemption_reason for exempt lines, state and country codes);
  • the LHDN code tables (classification, unit, state, country, tax type) and validation against them — v1 checks only their shape;
  • v2 of the sales_invoice and customer_credit_note seeded graphs: issued becomes non-terminal with an owner-granted, reason-required, reversing exit for cancellation within LHDN’s window and for documents LHDN rejected as invalid — and a provisioning step that moves existing organizations onto it;
  • the QR code and validated UUID on the PDF endpoint (D15);
  • the seller fields the company profile does not yet carry (business activity description; tourism tax number), which the seller snapshot will then include.

Unsure, for that item to settle: how long after the issuance date-time LHDN accepts a submission (we believe 72 hours); whether issue and submission must therefore be one action; whether advance payments (D9) must themselves be e-Invoiced on receipt.

  • Sales tax (goods): Σ net_base, tax_base of sales_invoice_line grouped by tax_pack_kind and tax_rate, for documents whose accrual journal entry is dated in the taxable period (the same “posted as of” join the aging uses); credit-note lines subtract. tax_code / tax_code_id identify the code without ambiguity — no reliance on account = tax_code.account_id.
  • Service tax: we believe service tax becomes due when payment is received (or 12 months after the service if unpaid) — not verified. If so, SST-02 needs the paid proportion of each invoice in the period: ar_allocation rows (effective_on in the period, receipts and credit notes told apart by source_kind) against the invoice’s gross give it; the invoice’s service-tax lines give the tax.
  • Exempt and out-of-scope sales are reported from the same lines by tax_pack_kind.
  • Nothing in SST-02 needs to read journal lines to tell tax from revenue on the sales side.

API (all in api/openapi.yaml first): customers list/get/create/update, balance, statement; deposit accounts; sales documents list (kind, customer, state, currency, open), get, create, update draft, transition, allocations, PDF; allocations create (credit note or receipt remainder → invoice) and reverse; receipts list/get/create/update draft/transition; /reports/ar-aging; exports ar_aging, customer_statement. Money and quantity are strings on the wire.

Web: three items appended to the sidebar’s Accounting group (Customers, Sales invoices, Receipts), following AP P35 and the nav’s append-only rule; credit notes are a tab of Sales invoices; AR aging is a tab of Reports; a statement (with its export buttons) is a section of the customer drawer. en/ms/zh.

  • RLS for every new table (the existing drift test, once they exist); retention lists.
  • Freeze: an issued document’s header and lines refuse UPDATE by name (presence: a draft accepts it).
  • Issue: number allocated only at issue and gapless across a withdrawn draft; snapshots written; the document date and entry date are the business date.
  • Posting: invoice, credit note, receipt (base, foreign with FX, part on account) balanced on the right accounts, party_id on every ar line, tax_code_id on every tax line.
  • Tax snapshot survives an edit of the tax code’s code / pack kind.
  • Credit note: needs a same-customer issued invoice; cap refused over; self-issue refused (presence: Owner issues); auto-allocation; remainder allocatable only at the same rate.
  • Receipts: foreign remainder refused (presence: base-currency remainder accepted); void reverses allocations and the entry; reversal rules by currency.
  • Allocation guard: over-allocation, cross-customer, double reversal refused.
  • Sub-ledger = control per customer after a mixed sequence (drift test, watched failing once).
  • Statement closing = aging total for the customer and currency.
  • Aging boundaries (0, 1, 30, 31, 90, 91 days) and a past “as of”.
  • The export CHECK accepts every report the export package knows (drift test — a later migration that rewrites report_export_report_known without ar_aging fails the build).
  • e2e: customer → invoice issued → aging shows it → receipt → aging shows zero → statement lists both.
  1. No approval to issue a sales invoice (the preparer issues); credit notes need a second person. Alternative: a review step on invoices — one more click per invoice; an administrator can add it. Confirmed (2026-09-29): issuing overstates rather than drains assets; the control that matters is on reductions (credit note, void), and those stay second-person.
  2. Invoice number allocated at issue (gapless issued series). Alternative: at draft creation, as AP does — gaps from withdrawn drafts, which a tax auditor asks about. Confirmed (2026-09-29): a gapless issued series is what auditors and MyInvois expect.
  3. Invoice date = issue date = posting date; no back-dating. Alternative: a document date earlier than issue — posts into a period that may be closing and may conflict with MyInvois’s timing rule. Confirmed (2026-09-29): matches MyInvois’s issue-time rule and keeps the invoice out of closing periods.
  4. Receipts post without approval; voiding needs the OWNER seat. Alternative: approve every receipt — slower cash application for a risk the bank reconciliation also catches. Confirmed (2026-09-29): cash in carries low risk; the void is the control point and stays second-person.
  5. Money on account allowed in MYR only, held as a credit on Trade receivables (not a “customer deposits” liability). Alternative: a separate liability account — cleaner presentation, but every later allocation must post. Unsure whether advance payments need their own e-Invoice. Confirmed (2026-09-29): year-end reclassification of credit balances is a presentation adjustment. Needs outside authority: whether an advance payment needs its own e-Invoice — a tax agent, against the LHDN e-Invoice Specific Guideline (tracked as EINV-10).
  6. Output SST recognised at issue for both sales and service tax; the SST-02 item decides service tax’s due-on-payment timing from allocations. Unsure of the service-tax time-of-supply rule. Confirmed (2026-09-29): the statutory figure comes from the return (SST D5); 2140’s timing difference is reconciled by the drift test.
  7. Credit note needs its original invoice and a reason, may not exceed it, and inherits its rate; no debit or refund notes in v1. Unsure whether Malaysian rules require a specific credit-note wording. Confirmed (2026-09-29): additional charges go on a new invoice; the prescribed-particulars check is folded into SST-3’s tax-agent review.
  8. Buyer TIN / identification not required to issue until the MyInvois item adds “e-Invoice in scope”. Risk: an in-scope organization issues without them and must credit and reissue. Confirmed (2026-09-29): superseded — the MyInvois lane closed the risk: when in scope, the guard requires TIN, ID, phone and address before issue.
  9. No bad-debt write-off or SST bad-debt relief in v1; uncollectible invoices stay in aging; year-end impairment by manual journal to an allowance account. Confirmed (2026-09-29): the allowance method is MFRS-compliant; relief goes through SST-02 manual adjustment (SST-5).
  10. Classification code defaults to 022 (“Others”) and is shape-checked only — the LHDN code list has not been verified here. Confirmed (2026-09-29): superseded — already checked against the SDK’s code list at issue when in scope, not only shape-checked. Re-verify the list at go-live (DEPLOY_NOTES).
  11. The invoice PDF is a commercial copy, not MyInvois’s visual representation (no UUID/QR yet). Confirmed (2026-09-29): superseded — done by MyInvois D15; the PDF prints UUID, validation link and QR once valid.
  12. AR aging under reports.read, as AP aging (AP’s own open decision). Ruled: O-9 — every Phase 2 report’s export and on-screen route needs the owning module’s read key.
  13. Statements are ledger-based and dated by entry date, in English. Alternative: document dates, or a Malay statement layout. Confirmed (2026-09-29): for AR, entry date equals invoice date (item 3), so the two bases coincide; English is the record language.