• 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.
    zichun authored
     
    Browse Code »