• The framework's seventh PBC, and the first one whose ENTIRE purpose
    is to react to events published by other PBCs. It validates the
    *consumer* side of the cross-PBC event seam that was wired up in
    commit 67406e87 (event-driven cross-PBC integration). With pbc-finance
    in place, the bus now has both producers and consumers in real PBC
    business logic — not just the wildcard EventAuditLogSubscriber that
    ships with platform-events.
    
    What landed (new module pbc/pbc-finance/, ~480 lines including tests)
      - JournalEntry entity (finance__journal_entry):
          id, code (= originating event UUID), type (AR|AP),
          partner_code, order_code, amount, currency_code, posted_at, ext.
          Unique index on `code` is the durability anchor for idempotent
          event delivery; the service ALSO existsByCode-checks before
          insert to make duplicate-event handling a clean no-op rather
          than a constraint-violation exception.
      - JournalEntryJpaRepository with existsByCode + findByOrderCode +
        findByType (the read-side filters used by the controller).
      - JournalEntryService.recordSalesConfirmed / recordPurchaseConfirmed
        take a SalesOrderConfirmedEvent / PurchaseOrderConfirmedEvent and
        write the corresponding AR/AP row. @Transactional with
        Propagation.REQUIRED so the listener joins the publisher's TX
        when the bus delivers synchronously (today) and creates a fresh
        one if a future async bus delivers from a worker thread. The
        KDoc explains why REQUIRED is the correct default and why
        REQUIRES_NEW would be wrong here.
      - OrderEventSubscribers @Component with @PostConstruct that calls
        EventBus.subscribe(SalesOrderConfirmedEvent::class.java, ...)
        and EventBus.subscribe(PurchaseOrderConfirmedEvent::class.java, ...)
        once at boot. Uses the public typed-class subscribe overload —
        NOT the platform-internal subscribeToAll wildcard helper. This
        is the API surface plug-ins will also use.
      - JournalEntryController: read-only REST under
        /api/v1/finance/journal-entries with @RequirePermission
        "finance.journal.read". Filter params: ?orderCode= and ?type=.
        Deliberately no POST endpoint — entries are derived state.
      - finance.yml metadata declaring 1 entity, 1 permission, 1 menu.
      - Liquibase changelog at distribution/.../pbc-finance/001-finance-init.xml
        + master.xml include + distribution/build.gradle.kts dep.
      - settings.gradle.kts: registers :pbc:pbc-finance.
      - 9 new unit tests (6 for JournalEntryService, 3 for
        OrderEventSubscribers) — including idempotency, dedup-by-event-id
        contract, listener-forwarding correctness via slot-captured
        EventListener invocation. Total tests: 192 → 201, 16 → 17 modules.
    
    Why this is the right shape
      - pbc-finance has zero source dependency on pbc-orders-sales,
        pbc-orders-purchase, pbc-partners, or pbc-catalog. The Gradle
        build refuses any cross-PBC dependency at configuration time —
        pbc-finance only declares api/api-v1, platform-persistence, and
        platform-security. The events and partner/item references it
        consumes all live in api.v1.event.orders / are stored as opaque
        string codes.
      - Subscribers go through EventBus.subscribe(eventType, listener),
        the public typed-class overload from api.v1.event.EventBus.
        Plug-ins use exactly this API; this PBC proves the API works
        end-to-end from a real consumer.
      - The consumer is idempotent on the producer's event id, so
        at-least-once delivery (outbox replay, future Kafka retry)
        cannot create duplicate journal entries. This makes the
        consumer correct under both the current synchronous bus and
        any future async / out-of-process bus.
      - Read-only REST API: derived state should not be writable from
        the outside. Adjustments and reversals will land later as their
        own command verbs when the real P5.9 finance build needs them,
        not as a generic create endpoint.
    
    End-to-end smoke verified against real Postgres
      - Booted on a fresh DB; the OrderEventSubscribers @PostConstruct
        log line confirms the subscription registered before any HTTP
        traffic.
      - Seeded an item, supplier, customer, location (existing PBCs).
      - Created PO PO-FIN-1 (5000 × 0.04 = 200 USD) → confirmed →
        GET /api/v1/finance/journal-entries returns ONE row:
          type=AP partner=SUP-PAPER order=PO-FIN-1 amount=200.0000 USD
      - Created SO SO-FIN-1 (50 × 0.10 = 5 USD) → confirmed →
        GET /api/v1/finance/journal-entries now returns TWO rows:
          type=AR partner=CUST-ACME order=SO-FIN-1 amount=5.0000 USD
          (plus the AP row from above)
      - GET /api/v1/finance/journal-entries?orderCode=PO-FIN-1 →
        only the AP row.
      - GET /api/v1/finance/journal-entries?type=AR → only the AR row.
      - platform__event_outbox shows 2 rows (one per confirm) both
        DISPATCHED, finance__journal_entry shows 2 rows.
      - The journal-entry code column equals the originating event
        UUID, proving the dedup contract is wired.
    
    What this is NOT (yet)
      - Not a real general ledger. No debit/credit legs, no chart of
        accounts, no period close, no double-entry invariant. P5.9
        promotes this minimal seed into a real finance PBC.
      - No reaction to ship/receive/cancel events yet — only confirm.
        Real revenue recognition (which happens at ship time for most
        accounting standards) lands with the P5.9 build.
      - No outbound api.v1.ext facade. pbc-finance does not (yet)
        expose itself to other PBCs; it is a pure consumer. When
        pbc-production needs to know "did this order's invoice clear",
        that facade gets added.
    zichun authored
     
    Browse Code »

  • BLOCKER: wire Hibernate multi-tenancy
    - application.yaml: set hibernate.tenant_identifier_resolver and
      hibernate.multiTenancy=DISCRIMINATOR so HibernateTenantResolver is
      actually installed into the SessionFactory
    - AuditedJpaEntity.tenantId: add @org.hibernate.annotations.TenantId so
      every PBC entity inherits the discriminator
    - AuditedJpaEntityListener.onCreate: throw if a caller pre-set tenantId
      to a different value than the current TenantContext, instead of
      silently overwriting (defense against cross-tenant write bugs)
    
    IMPORTANT: dependency hygiene
    - pbc-identity no longer depends on platform-bootstrap (wrong direction;
      bootstrap assembles PBCs at the top of the stack)
    - root build.gradle.kts: tighten the architectural-rule enforcement to
      also reject :pbc:* -> platform-bootstrap; switch plug-in detection
      from a fragile pathname heuristic to an explicit
      extra["vibeerp.module-kind"] = "plugin" marker; reference plug-in
      declares the marker
    
    IMPORTANT: api.v1 surface additions (all non-breaking)
    - Repository: documented closed exception set; new
      PersistenceExceptions.kt declares OptimisticLockConflictException,
      UniqueConstraintViolationException, EntityValidationException, and
      EntityNotFoundException so plug-ins never see Hibernate types
    - TaskContext: now exposes tenantId(), principal(), locale(),
      correlationId() so workflow handlers (which run outside an HTTP
      request) can pass tenant-aware calls back into api.v1
    - EventBus: subscribe() now returns a Subscription with close() so
      long-lived subscribers can deregister explicitly; added a
      subscribe(topic: String, ...) overload for cross-classloader event
      routing where Class<E> equality is unreliable
    - IdentityApi.findUserById: tightened from Id<*> to PrincipalId so the
      type system rejects "wrong-id-kind" mistakes at the cross-PBC boundary
    
    NITs:
    - HealthController.kt -> MetaController.kt (file name now matches the
      class name); added TODO(v0.2) for reading implementationVersion from
      the Spring Boot BuildProperties bean
    vibe_erp authored
     
    Browse Code »
  • vibe_erp authored
     
    Browse Code »