The minimal pbc-finance landed in commit bf090c2e only reacted to
*ConfirmedEvent. This change wires the rest of the order lifecycle
(ship/receive → SETTLED, cancel → REVERSED) so the journal entry
reflects what actually happened to the order, not just the moment
it was confirmed.
JournalEntryStatus (new enum + new column)
- POSTED — created from a confirm event (existing behaviour)
- SETTLED — promoted by SalesOrderShippedEvent /
PurchaseOrderReceivedEvent
- REVERSED — promoted by SalesOrderCancelledEvent /
PurchaseOrderCancelledEvent
- The status field is intentionally a separate axis from
JournalEntryType: type tells you "AR or AP", status tells you
"where in its lifecycle".
distribution/.../pbc-finance/002-finance-status.xml
- ALTER TABLE adds `status varchar(16) NOT NULL DEFAULT 'POSTED'`,
a CHECK constraint mirroring the enum values, and an index on
status for the new filter endpoint. The DEFAULT 'POSTED' covers
any existing rows on an upgraded environment without a backfill
step.
JournalEntryService — four new methods, all idempotent
- settleFromSalesShipped(event) → POSTED → SETTLED for AR
- settleFromPurchaseReceived(event) → POSTED → SETTLED for AP
- reverseFromSalesCancelled(event) → POSTED → REVERSED for AR
- reverseFromPurchaseCancelled(event) → POSTED → REVERSED for AP
Each runs through a private settleByOrderCode/reverseByOrderCode
helper that:
1. Looks up the row by order_code (new repo method
findFirstByOrderCode). If absent → no-op (e.g. cancel from
DRAFT means no *ConfirmedEvent was ever published, so no
journal entry exists; this is the most common cancel path).
2. If the row is already in the destination status → no-op
(idempotent under at-least-once delivery, e.g. outbox replay
or future Kafka retry).
3. Refuses to overwrite a contradictory terminal status — a
SETTLED row cannot be REVERSED, and vice versa. The producer's
state machine forbids cancel-from-shipped/received, so
reaching here implies an upstream contract violation; logged
at WARN and the row is left alone.
OrderEventSubscribers — six subscriptions per @PostConstruct
- All six order events from api.v1.event.orders.* are subscribed
via the typed-class EventBus.subscribe(eventType, listener)
overload, the same public API a plug-in would use. Boot log
line updated: "pbc-finance subscribed to 6 order events".
JournalEntryController — new ?status= filter
- GET /api/v1/finance/journal-entries?status=POSTED|SETTLED|REVERSED
surfaces the partition. Existing ?orderCode= and ?type= filters
unchanged. Read permission still finance.journal.read.
12 new unit tests (213 total, was 201)
- JournalEntryServiceTest: settle/reverse for AR + AP, idempotency
on duplicate destination status, refusal to overwrite a
contradictory terminal status, no-op on missing row, default
POSTED on new entries.
- OrderEventSubscribersTest: assert all SIX subscriptions registered,
one new test that captures all four lifecycle listeners and
verifies they forward to the correct service methods.
End-to-end smoke (real Postgres, fresh DB)
- Booted with the new DDL applied (status column + CHECK + index)
on an empty DB. The OrderEventSubscribers @PostConstruct line
confirms 6 subscriptions registered before the first HTTP call.
- Five lifecycle scenarios driven via REST:
PO-FULL: confirm + receive → AP SETTLED amount=50.00
SO-FULL: confirm + ship → AR SETTLED amount= 1.00
SO-REVERSE: confirm + cancel → AR REVERSED amount= 1.00
PO-REVERSE: confirm + cancel → AP REVERSED amount=50.00
SO-DRAFT-CANCEL: cancel only → NO ROW (no confirm event)
- finance__journal_entry returns exactly 4 rows (the 5th scenario
correctly produces nothing) and ?status filters all return the
expected partition (POSTED=0, SETTLED=2, REVERSED=2).
What's still NOT in pbc-finance
- Still no debit/credit legs, no chart of accounts, no period
close, no double-entry invariant. This is the v0.17 minimal
seed; the real P5.9 build promotes it into a real GL.
- No reaction to "settle then reverse" or "reverse then settle"
other than the WARN-and-leave-alone defensive path. A real GL
would write a separate compensating journal entry; the minimal
PBC just keeps the row immutable once it leaves POSTED.