Supplier Credit Application — supplier credits now actually do something¶
Date: 2026-06-27 Status: shipped Audit findings: #36–#39 — supplier credits could be created but the whole application/cancellation flow was a shell.
Problem / motivation¶
A supplier credit note (the supplier crediting us, reducing what we owe) could be
created, but did nothing: no ledger posting, supplier_credit_status stuck at
UNAPPLIED, due_amount hardcoded to 0, the supplier_invoice_detail_id line
link never read, and cancel was a no-op. The sales-credit flow, by contrast, is
fully wired — this brings the supplier side to parity.
What shipped¶
Creating a supplier credit linked to a supplier invoice now applies it:
- GL posting via the new
SupplierCreditLedgerService.record_supplier_credit: DR Accounts Payable (we owe less) and CR the line GL account (expense/inventory — unwinding the original purchase). This mirrors the supplier-invoice posting in reverse and balances by construction. - Invoice settlement: the linked supplier invoice's
canceled_amountrises and itsdue_amountfalls; each credited line'scanceled_quantity/canceled_amountis rolled forward via the previously-deadsupplier_invoice_detail_idlink (audit #39). - Status + balance:
supplier_credit_status→APPLIED, and the credit's owndue_amountis computed (full when unapplied,0once applied) instead of a hardcoded0(audit #38).
cancel_supplier_credit now genuinely reverses everything: it reverses the GL
entry, restores the invoice's outstanding balance and line cancellations, and
sets the status to CANCELED (audit #36/#37).
Bug fixed along the way¶
SupplierCreditDetailInput.to_orm_model passed note=self.note raw, leaking
strawberry.UNSET into the DB insert (every other detail input uses
optional_field). Fixed — supplier credits with no line note can now be created.
Files¶
- New supplier_credit_ledger_service.py.
- supplier_credit_repository.py —
addapplies,cancel_supplier_creditreverses,_apply_to_credited_lines. - supplier_invoice_balance_repository.py —
apply_supplier_credit. - supplier_credit_balance_repository.py —
due_amountcomputed. supplier_credit_detail_input.py—noteUNSET fix.- tests/graphql/credits/test_supplier_credit_application.py.
Data model / GraphQL surface¶
No schema change — all fields (supplier_credit_status, due_amount,
supplier_invoice_detail_id) already existed; this wires the behaviour. The
createSupplierCredit / cancelSupplierCredit mutations keep their signatures.
Frontend impact¶
- Creating a supplier credit against a supplier invoice now reduces that
invoice's outstanding balance and posts to the GL; the credit shows
APPLIED. - Canceling a supplier credit now actually reverses it (was a silent no-op).
Future additions¶
- Tax routing: line totals (incl. tax) are credited back to the line GL account rather than a separate input-tax account, to avoid re-resolving per-line tax accounts. If precise input-tax reversal is needed, resolve the input-tax account per line as the supplier-invoice posting does.
- Standalone (unapplied) credits: a credit with no
supplier_invoice_idposts to the GL but staysUNAPPLIEDwithdue_amount = total; applying it to an invoice later is not yet wired.