supplier_items — overview
Procurement pricing catalogue: links each inventory item to the suppliers that source it, carrying the supplier's own SKU and quoted unit price. Enables the "multiple vendors per item" model and feeds purchase_orders (#227-3) and inventory_reorder (#227-4).
What it is
Admin-authenticated CRUD under /api/v1/supplier_items/ (JWT + RBAC). A clinic links its suppliers to inventory items, one pricing row per (supplier, item) pair.
Routes:
GET /api/v1/supplier_items— list withsupplier_id/inventory_item_idfilteringGET /api/v1/supplier_items/{id}— get onePOST /api/v1/supplier_items— create a link (201)PATCH /api/v1/supplier_items/{id}— update SKU / priceDELETE /api/v1/supplier_items/{id}— soft-delete the link (204, setsis_active=false)
Data model
supplier_items — link table between suppliers.id and inventory_items.id. Fields: supplier_sku, price (Numeric(12,2)), is_active (soft delete). A UNIQUE (supplier_id, inventory_item_id) constraint enforces one pricing row per pair; a duplicate pair surfaces as a 409. Denormalizes clinic_id for rapid multi-tenant filtering.
Migration: sui_0001_initial on its own Alembic branch (supplier_items), depending on suppliers@supp_0001 + inventory@inv_0002.
Service layer
SupplierItemService encapsulates the link lifecycle:
create_link: Validates both ends exist in-clinic (thesuppliersrow and theinventory_itemsrow), revives a soft-deleted row for the same pair instead of 409ing, and returns(link, supplier_name, item_name)so routers/tools build denormalized responses without extra queries.list_links: Paginated join query, optionalsupplier_id/inventory_item_idfilters, active-only.get_link: Retrieves one active link, clinic-scoped; a cross-clinic or inactive id 404s.update_link: Updates SKU / price, forwarding only supplied fields (exclude_unset, M4).deactivate_link: Soft-delete — setsis_active=false(L7), keeping the row for historical purchase-order references.
Duplicate pairs surface as a 409 from the UNIQUE constraint (not a select-then-insert race). The soft delete keeps historical references intact.
Agent tools
Four tools exposed: list_supplier_items, get_supplier_item, create_supplier_item, update_supplier_item. Each wraps the corresponding service method, filters by ctx.clinic_id, returns native values (coerced at the registry), and is marked exposes_free_text=True because supplier/item names are user-entered prose kept off the cloud LLM path under redaction.
Tenancy
Every query filters by clinic_id; a cross-clinic supplier or item id 404s rather than 403s, matching repo convention. Both ends of a link are validated in-clinic at creation (L1).
Constraints
Own Alembic branch (supplier_items) depending on suppliers@supp_0001 + inventory@inv_0002 — the module imports the Supplier, Contact and InventoryItem models. manifest.depends = ["contacts", "inventory", "suppliers"].
See ./permissions.md and ./events.md for full detail.