Skip to content

AR/AP read side: outstanding/aging queries, due dates, and scheduled dunning #56

Description

@flarexium

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions