Skip to content

feat(accounting): counterparty master data + find_counterparties (PR 1/3) - #50

Merged
flarexium merged 1 commit into
mainfrom
feat/counterparty-master-data
Jun 15, 2026
Merged

feat(accounting): counterparty master data + find_counterparties (PR 1/3)#50
flarexium merged 1 commit into
mainfrom
feat/counterparty-master-data

Conversation

@flarexium

@flarexium flarexium commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

First of three PRs adding invoice/receipt + AR/AP support. This one lays the foundation: customer/supplier master data, with nothing yet wired into journal postings.

What

Domain

  • Counterparty{ID, Name, Kind, TaxID, Active, Aliases, Description} + CounterpartyKind (customer / supplier / both).
  • FormatCounterpartyIDCP-0001 (mirrors FormatEntryID).
  • CounterpartyMatch lexical tiers: exact id → name → tax id → alias → substring.
  • CounterpartyAdded event on accounting.counterparty.added.
  • LedgerRepository gains Counterparty / Counterparties / PutCounterparty.

Event-sourced, but not seed data

Counterparty is projected from CounterpartyAdded by ApplyCounterparty (wired in compose.go, implemented in both adapters — postgres migration 0003 + sqlc). But it is operational data, not setup, so Scenario / ledger seed deliberately do not carry it (unlike accounts/branches/periods). The operator-facing create path is a later phase.

Agent

find_counterparties resolves a name, alias, or tax id to a CP-id. It ranks lexically in the agent over repo.Counterparties() rather than going through a hybrid/pgvector index like find_accounts: counterparties are directly-named, low-cardinality entities, so alias coverage beats semantic ranking. Active matches first; inactive flagged disabled and not referenceable.

Scope boundary

  • PR 2: counterparty_id line dimension + invoice/receipt SourceDoc on entries + post_journal schema/validator.
  • PR 3: settle intent writing the existing RelationSettles; "is this invoice paid?" queries.
  • Operator create path for counterparties (CLI or agent intent) — TBD.

Tests

  • TestFormatCounterpartyID, TestCounterpartyMatch (tier ladder).
  • TestFindCounterpartiesHandler — alias resolution, kind filter, inactive-disabled.
  • TestApplyCounterparty_Projects — CounterpartyAdded projects through the bus.

go build ./..., go vet ./..., and go test ./... all pass.

🤖 Generated with Claude Code

…_counterparties

Add Counterparty as event-sourced master data plus a lexical lookup tool for
the agent. PR 1 of the invoice/AR-AP work; posting-time reference
(counterparty_id line dimension, invoice/receipt SourceDoc) is a later phase.

Domain:
- Counterparty{ID, Name, Kind, TaxID, Active, Aliases, Description} with
  CounterpartyKind (customer/supplier/both); FormatCounterpartyID (CP-0001);
  CounterpartyMatch lexical tiers (exact id/name/tax-id/alias, then substring).
- CounterpartyAdded event on accounting.counterparty.added.
- LedgerRepository gains Counterparty/Counterparties/PutCounterparty.

Projection / wiring:
- ApplyCounterparty handler projects CounterpartyAdded; compose.go subscribes
  the subject; both memory and postgres adapters implement the new methods
  (postgres migration 0003 + sqlc queries).
- Counterparties are operational data, not setup: Scenario / ledger seed do
  not carry them. The operator create path lands in a later phase.

Agent:
- find_counterparties resolves a name, alias, or tax id to a CP-id, ranking in
  the agent over repo.Counterparties() rather than a hybrid index --
  counterparties are directly-named, low-cardinality entities. Active matches
  listed first; inactive flagged disabled.
@flarexium
flarexium force-pushed the feat/counterparty-master-data branch from f904a96 to e70fae1 Compare June 15, 2026 03:19

@jarvis-flarex jarvis-flarex left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@flarexium
flarexium merged commit a903a7b into main Jun 15, 2026
1 check passed
@flarexium
flarexium deleted the feat/counterparty-master-data branch June 15, 2026 04:20
flarexium added a commit that referenced this pull request Jun 15, 2026
`ledger seed` on NATS failed with "unknown event type accounting.CounterpartyAdded"
— PR #50 added the event but never taught the NATS codec about it, and the gap
was invisible because every test (and the bundled-seed smoke test) uses the
in-proc bus, which doesn't encode.

Add the subject to supportedSubjects and the encode/decode switches. Split
decodeMsg into a testable decodeBySubject, and add a codec test that round-trips
every supported subject so the next new event type fails in CI, not at runtime.
flarexium added a commit that referenced this pull request Jun 15, 2026
* feat(seed): seed counterparties + AR/AP manual-test scenarios

Counterparties are now seedable reference data like accounts and branches —
restore Scenario.Counterparties and the SeedScenario CounterpartyAdded loop
that PR 1 had dropped. Without a create-counterparty path yet, seeding is the
only way to get a customer/supplier into the ledger, so the AR/AP features
(counterparty_id, SourceDoc, settle) had no manual test path.

- taiwan_ledger.yaml gains five counterparties (台積電/鴻海/中華電信/台電/全聯)
  plus one disabled, with aliases for find_counterparties.
- docs/tui-manual-test.md adds section 4 (AR/AP): T-13 credit sale with
  counterparty + invoice source, T-14 settle the receipt, T-15 credit purchase
  with a supplier, T-16 disabled-counterparty reject; sections renumbered and
  acceptance checkpoints extended.
- AGENTS.md: counterparties seed like accounts/branches (runtime create path
  can come later).

* fix(nats): register CounterpartyAdded in the event codec

`ledger seed` on NATS failed with "unknown event type accounting.CounterpartyAdded"
— PR #50 added the event but never taught the NATS codec about it, and the gap
was invisible because every test (and the bundled-seed smoke test) uses the
in-proc bus, which doesn't encode.

Add the subject to supportedSubjects and the encode/decode switches. Split
decodeMsg into a testable decodeBySubject, and add a codec test that round-trips
every supported subject so the next new event type fails in CI, not at runtime.

* feat(seed): report counterparty count in the seed summary

* fix(agent): counterparty_id is optional — don't reject an unregistered customer

The PR2 behavior rule read as mandatory, so the agent rejected a credit sale to
a generic/unregistered customer (e.g. "台中客戶") instead of posting AR with an
empty counterparty_id. Soften the rule: attach a counterparty when find_counterparties
returns an active match, otherwise post with counterparty_id empty; only reject when
the user insists on a party shown as disabled. Manual-test doc notes the behavior.

* fix(agent): drop test-specific example from the counterparty prompt rule

The prompt should state the general rule (unregistered/generic/one-off party),
not name a manual-test scenario's wording.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants