Adds the second core PBC, validating that the pbc-identity template is
actually clonable and that the Gradle dependency rule fires correctly
for a real second PBC.
What landed:
* New `pbc/pbc-catalog/` Gradle subproject. Same shape as pbc-identity:
api-v1 + platform-persistence + platform-security only (no
platform-bootstrap, no other pbc). The architecture rule in the root
build.gradle.kts now has two real PBCs to enforce against.
* `Uom` entity (catalog__uom) — code, name, dimension, ext jsonb.
Code is the natural key (stable, human-readable). UomService rejects
duplicate codes and refuses to update the code itself (would invalidate
every Item FK referencing it). UomController at /api/v1/catalog/uoms
exposes list, get-by-id, get-by-code, create, update.
* `Item` entity (catalog__item) — code, name, description, item_type
(GOOD/SERVICE/DIGITAL enum), base_uom_code FK, active flag, ext jsonb.
ItemService validates the referenced UoM exists at the application
layer (better error message than the DB FK alone), refuses to update
code or baseUomCode (data-migration operations, not edits), supports
soft delete via deactivate. ItemController at /api/v1/catalog/items
with full CRUD.
* `org.vibeerp.api.v1.ext.catalog.CatalogApi` — second cross-PBC facade
in api.v1 (after IdentityApi). Exposes findItemByCode(code) and
findUomByCode(code) returning safe ItemRef/UomRef DTOs. Inactive items
are filtered to null at the boundary so callers cannot accidentally
reference deactivated catalog rows.
* `CatalogApiAdapter` in pbc-catalog — concrete @Component
implementing CatalogApi. Maps internal entities to api.v1 DTOs without
leaking storage types.
* Liquibase changeset (catalog-init-001..003) creates both tables with
unique indexes on code, GIN indexes on ext, and seeds 15 canonical
units of measure: kg/g/t (mass), m/cm/mm/km (length), m2 (area),
l/ml (volume), ea/sheet/pack (count), h/min (time). Tagged
created_by='__seed__' so a future metadata uninstall sweep can
identify them.
Tests: 11 new unit tests (UomServiceTest x5, ItemServiceTest x6),
total now 49 unit tests across the framework, all green.
End-to-end smoke test against fresh Postgres via docker-compose
(14/14 passing):
GET /api/v1/catalog/items (no auth) → 401
POST /api/v1/auth/login → access token
GET /api/v1/catalog/uoms (Bearer) → 15 seeded UoMs
GET /api/v1/catalog/uoms/by-code/kg → 200
POST custom UoM 'roll' → 201
POST duplicate UoM 'kg' → 400 + clear message
GET items → []
POST item with unknown UoM → 400 + clear message
POST item with valid UoM → 201
catalog__item.created_by → admin user UUID
(NOT __system__)
GET /by-code/INK-CMYK-CYAN → 200
PATCH item name + description → 200
DELETE item → 204
GET item → active=false
The principal-context bridge from P4.1 keeps working without any
additional wiring in pbc-catalog: every PBC inherits the audit
behavior for free by extending AuditedJpaEntity. That is exactly the
"PBCs follow a recipe, the framework provides the cross-cutting
machinery" promise from the architecture spec.
Architectural rule enforcement still active: confirmed by reading the
build.gradle.kts and observing that pbc-catalog declares no
:platform:platform-bootstrap and no :pbc:pbc-identity dependency. The
build refuses to load on either violation.