Follow-up to the lightweight AR/AP write-side (#50 counterparty master data, #52 counterparty_id + SourceDoc on entries, #53 settle, #54 seed + manual-test). The write side is done; there is no read side yet — "who owes us how much / how old" is only answerable with raw SQL today.
Phase 1 — object-level read side (no schema change)
Everything needed is already in journal_lines (+ journal_entries, journal_relations, counterparties):
- Outstanding per counterparty =
SUM(debit - credit) over the AR/AP account (1141 / 2141) grouped by counterparty_id. Handles partial payments automatically (it's a balance).
- Aging by invoice date = the same, bucketed on
journal_entries.entry_date.
- Settled? for an invoice =
RelationsTo(invoiceID) has a settles relation.
Deliver as: a repo query method (e.g. OutstandingByCounterparty), an agent tool (e.g. ar_status / outstanding) so the bookkeeper can answer "台積電還欠多少" in chat, and optionally a ledger report ar CLI.
Phase 2 — due dates → overdue aging
We only have entry_date (invoice date), not a due date, so we can age by invoice age but not by overdue-past-due-date. Add a due date (or payment terms, e.g. net-30) — most naturally on SourceDoc at the entry level, optionally defaulting from the counterparty's terms. Then aging can compute days overdue.
Phase 3 — scheduled AR/AP sweep (built-in, ledger-as-service)
Aging/dunning is rule-driven, not LLM-driven — the same mold as ClosePeriod (today triggered by an external scheduler running ledger close). The intent here is that cron becomes a built-in feature once ledger runs as a long-running service (internal scheduler), rather than relying on external crontab. First version should be read-only / alerting (compute aging, list overdue, emit a report/notification) — no automatic postings. Auto-posting late fees / write-offs, if ever wanted, is a separate, carefully-gated step.
Known limits (out of scope here — Phase B real subledger)
- Per-invoice precise remaining with split payments: the
settles relation carries no amount, so when one payment clears several invoices, the per-invoice split isn't derivable. Object-level totals are always correct; per-invoice precise open amounts are not.
- These need a real AR/AP subledger / open-item model (the heavyweight path we deliberately deferred).
References
- Design memory: lightweight AR/AP (Source 1:1, AR/AP derived not stored).
- 401 angle (separate): VAT filing is per-invoice and counterparty belongs on the document — would argue for counterparty/統編 on
SourceDoc, not scattered on revenue lines. Tracked separately if pursued.
Follow-up to the lightweight AR/AP write-side (#50 counterparty master data, #52 counterparty_id + SourceDoc on entries, #53 settle, #54 seed + manual-test). The write side is done; there is no read side yet — "who owes us how much / how old" is only answerable with raw SQL today.
Phase 1 — object-level read side (no schema change)
Everything needed is already in
journal_lines(+journal_entries,journal_relations,counterparties):SUM(debit - credit)over the AR/AP account (1141 / 2141) grouped bycounterparty_id. Handles partial payments automatically (it's a balance).journal_entries.entry_date.RelationsTo(invoiceID)has asettlesrelation.Deliver as: a repo query method (e.g.
OutstandingByCounterparty), an agent tool (e.g.ar_status/outstanding) so the bookkeeper can answer "台積電還欠多少" in chat, and optionally aledger report arCLI.Phase 2 — due dates → overdue aging
We only have
entry_date(invoice date), not a due date, so we can age by invoice age but not by overdue-past-due-date. Add a due date (or payment terms, e.g. net-30) — most naturally onSourceDocat the entry level, optionally defaulting from the counterparty's terms. Then aging can compute days overdue.Phase 3 — scheduled AR/AP sweep (built-in, ledger-as-service)
Aging/dunning is rule-driven, not LLM-driven — the same mold as
ClosePeriod(today triggered by an external scheduler runningledger close). The intent here is that cron becomes a built-in feature onceledgerruns as a long-running service (internal scheduler), rather than relying on external crontab. First version should be read-only / alerting (compute aging, list overdue, emit a report/notification) — no automatic postings. Auto-posting late fees / write-offs, if ever wanted, is a separate, carefully-gated step.Known limits (out of scope here — Phase B real subledger)
settlesrelation carries no amount, so when one payment clears several invoices, the per-invoice split isn't derivable. Object-level totals are always correct; per-invoice precise open amounts are not.References
SourceDoc, not scattered on revenue lines. Tracked separately if pursued.