feat(accounting): counterparty master data + find_counterparties (PR 1/3) - #50
Merged
Conversation
…_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
force-pushed
the
feat/counterparty-master-data
branch
from
June 15, 2026 03:19
f904a96 to
e70fae1
Compare
This was referenced Jun 15, 2026
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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).FormatCounterpartyID→CP-0001(mirrorsFormatEntryID).CounterpartyMatchlexical tiers: exact id → name → tax id → alias → substring.CounterpartyAddedevent onaccounting.counterparty.added.LedgerRepositorygainsCounterparty/Counterparties/PutCounterparty.Event-sourced, but not seed data
Counterpartyis projected fromCounterpartyAddedbyApplyCounterparty(wired incompose.go, implemented in both adapters — postgres migration0003+ sqlc). But it is operational data, not setup, soScenario/ledger seeddeliberately do not carry it (unlike accounts/branches/periods). The operator-facing create path is a later phase.Agent
find_counterpartiesresolves a name, alias, or tax id to aCP-id. It ranks lexically in the agent overrepo.Counterparties()rather than going through a hybrid/pgvector index likefind_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
counterparty_idline dimension + invoice/receiptSourceDocon entries +post_journalschema/validator.settleintent writing the existingRelationSettles; "is this invoice paid?" queries.Tests
TestFormatCounterpartyID,TestCounterpartyMatch(tier ladder).TestFindCounterpartiesHandler— alias resolution, kind filter, inactive-disabled.TestApplyCounterparty_Projects— CounterpartyAdded projects through the bus.go build ./...,go vet ./..., andgo test ./...all pass.🤖 Generated with Claude Code