Skip to content

Cross-Service Data Ownership & User Management

hospital-api (Codevertex Afya) is the target owner of all clinical-workflow entities (Patient, PatientVisit, TriageRecord, ExaminationRecord, LabOrder/Line, Prescription/Line, ControlledSubstanceLog, Ward/Bed/Admission, specialized-care programme records). This data currently lives in pos-api and will migrate out once hospital-api reaches feature parity — no migration has happened yet, so the ownership below is the target model, not the current database state. hospital-api references inventory-api (drug/lot/interaction master) and treasury-api (invoices/quotations/insurance claims, KRA eTIMS transmission opt-in per tenant/service) the same way pos-api already does.

Downstream services store only a minimal tenant reference (id, slug, name, status, use_case, sync_status, last_sync_at) — branding, contact info, and subscription data are fetched from auth-api's Redis-cached tenant projection rather than duplicated locally. Use case is scoped per-outlet, not per-tenant, so a single tenant can have outlets configured for different use cases (e.g. one retail outlet and one hospitality outlet under the same organisation). No data duplication anywhere: each service stores only what it owns, and reads everything else via REST, events, or gRPC references.

Overview

This document is the canonical definition of data ownership across Codevertex microservices. It ensures no data duplication: each service stores only the data it owns; any need for another service’s data is satisfied by reference IDs and access via REST, events (NATS), or gRPC — never by copying entities into another service’s database.


Expected Architecture: No Data Duplication

  • Single store per entity: Each entity (e.g. Product Master, orders, riders, payments) has exactly one owning service. That service is the only place that creates, updates, and stores that data.
  • Reference only elsewhere: Other services store only references (e.g. inventory_item_id, rider_id, payment_id) and optionally minimal snapshots for audit (e.g. amount at time of payment). They do not store a full copy of the entity.
  • Access via integration: To read or act on another service’s data, a service calls that service’s REST or gRPC APIs, or reacts to NATS events it publishes. Catalog/projection caches (e.g. POS Sales Catalog, ordering catalog cache) are synced from the owner (inventory-api), not authored locally. Core inventory master data (Items, Categories, Warehouses) is accessible via public GET endpoints to facilitate auto-discovery and synchronization.
  • Auth is always available: Tenant and user identity come from auth-api; all services use the same tenant UUID from auth. Other services may require a subscription check before use (subscriptions-api).

Core Principles

  1. Single Source of Truth: Each service owns and manages all data related to its domain; no other service duplicates that data.
  2. Reference Only: Other services store only reference IDs (and optional audit snapshots), never full copies of entities they do not own.
  3. Access via REST / events / gRPC: To use another service's data, call its API or consume its events; do not replicate tables.
  4. Tenant Service Availability: Check tenant subscription (subscriptions-api) before creating or referencing data in a dependent service (except auth-api, which is always available).
  5. SSO Authentication: All users authenticate via auth-service (SSO); service-specific data (e.g. rider profile, loyalty) is stored only in the owning service.
  6. Service Independence: Services can operate standalone or in combination based on tenant subscription.

Canonical Data Ownership Matrix

Domain Owner Service Data/Entities Integration Pattern
Identity auth-api Users, Tenants, Outlets, Roles SSO (JWT), user_id/outlet_id refs. Auto-provisions downstream (Inventory Warehouses, Ordering Outlets) via NATS events.
CRM / Customer Relationship marketflow-api Leads, Contacts, Deals, Pipelines, Accounts, Activities, Tasks, Campaigns, Funnels, NurtureSequences, ChatSessions, Meetings, CustomFields, AI Agents REST (GET), crm_contact_id refs, NATS events
Product Master inventory-api Items (SKUs), BOM, Recipes, Units, Categories (hierarchical), Variants, CustomFieldDefinition/Value, InventoryLot, VariantAttribute, Bundle/BundleComponent, Supplier, PurchaseOrder/Line, StockTransfer/Line, Warranty REST (GET), sku/product_id refs
Sales Catalog pos-api Catalogs, Modifier Groups, Local Prices, KDSStation/KDSTicket, Appointment, StaffMember, SerialNumberLog, CommissionRecord Sync from Inventory, NATS CatalogUpdated
Orders (Online) ordering-backend Carts, Online Orders, Catalog Projection, Booking/Appointment refs (loyalty is read/written via pos-api, not owned here) Projection of Global Catalog, NATS Events
Logistics logistics-api Riders, Tasks, Proof of Delivery, PricingRule, RiderShift REST, Webhooks, rider_id refs
Payments treasury-api Intents, Transactions, Refunds, Taxes, PaymentSplit, Settlement/SettlementLine, ReconciliationRun, InstallmentPlan/Installment REST, Webhooks, payment_intent_id refs
Subscription plans, tenant entitlements subscriptions-api All services: check plan before using inventory, POS, logistics, treasury, etc.
Notification templates, delivery status, channel preferences notifications-api Other services: trigger via events or API; store only notification_message_id etc. if needed
IoT devices, telemetry, alerts iot-service-api inventory-api (e.g. temperature/compliance), notifications; optional POS/inventory hardware integration
Loyalty & Referrals pos-api LoyaltyProgram, LoyaltyAccount, LoyaltyTransaction, Referral — keyed on crm_contact_id; ordering-backend is a client, not an owner REST, pos.loyalty.* events
AR / AP balances treasury-api CustomerBalance (credit sale, ageing, statements), VendorBalance (supplier opening/advance, ageing), supplier rebate credit notes, cost_center REST, customer_balance/vendor_balance refs
Procurement breakdown inventory-api StockBreakdown (bulk→retail uom-explode; cost carried parent→child) event inventory.stock.broken_down
Repair / job-card pos-api (services module) RepairJob (intake→diagnosis→parts→settle); parts from inventory, payment via treasury REST, pos.repair.* events
Financial documents treasury-api Invoices, Quotations, proforma, Sales Credit-Notes (eTIMS), AP vendor credit-notes — pos never duplicates these S2S create from pos context (return→/s2s/{t}/invoices/{id}/create-credit-note; cart→quotation)
Hospital clinical workflow (Codevertex Afya) hospital-api (target owner) Patient, PatientVisit, TriageRecord, ExaminationRecord, DiagnosisCatalog (tenant-custom), LabOrder/LabOrderLine, LabTest (tenant-custom), Prescription/PrescriptionLine, ControlledSubstanceLog, Ward/Bed/Admission, Appointment/OPD queue, Referral, specialized-care programme records (ANC/PNC/ART/TB/Immunization/Morgue) REST/events once implemented; references inventory_item_id/lot_id (inventory-api) and invoice_id/insurance_claim_id (treasury-api). Currently owned by pos-api pending migration.

Loyalty is a single source of truth in pos-api — both online (ordering) and in-store (pos) purchases earn into one balance, keyed on crm_contact_id, so ordering-backend calls pos's loyalty endpoints rather than owning its own. Financial documents (invoices, quotations, credit notes) live exclusively in treasury-api; pos-ui never builds a parallel entity for them, only creates them via S2S from a pos context (a return creates a credit note, a saved cart creates a quotation). The same "link, don't rebuild" rule applies across the frontends generally: pos-ui links out to treasury-ui/inventory-ui/marketflow-ui pages (an external redirect, with the target service enforcing its own RBAC) rather than recreating another service's UI — only the actual pos-to-service integration action lives in pos-ui itself.


Item Lifecycle & "Use Case" Flexibility

To support a wide range of business models (Hospitality, Retail, Warehouse), the system employs a tiered data model:

1. Master Product (inventory-api)

  • Authority: Owns the physical definition (Name, SKU, Base UoM, Recipe/BOM).
  • Flexibility: Stores the use_case (Retail vs Hospitality) at the Outlet level.

2. Sales Catalog (pos-api)

  • Authority: Owns the Menu/Catalog. Defines how Master Products are sold at a specific Outlet.
  • Data: Modifiers, POS-specific Categories, Button positions, Outlet Prices.
  • Integration: Publishes pos.menu.updated when the sales interface changes.

3. Fulfillment Projection (ordering-backend)

  • Authority: Owns the Online Storefront presentation.
  • Data: Read-only projection of the POS Menu, augmented with online-only flags (e.g., featured_on_web).
  • Integration: Zero-authority for master data; hydrates local cache via NATS events.

Generalization: cafe_id vs outlet_id

All services must use the generic outlet_id to refer to physical/logical locations. A "Cafe" is simply an outlet with a Hospitality use case. This allows the same services to manage "Warehouses" (Stock use case) or "Electronics Stores" (Retail use case).

Data Ownership by Service

Auth-Service (auth-api)

Owns (only store here; no duplication elsewhere): - User identity (email, password, phone, status, full_name, avatar) - Tenant definitions, UUIDs, and branding — single source of truth for tenant identity, logo, brand colors, contact info, use case, subscription cache - Tenant membership and roles - Sessions and MFA - OAuth client registry and consent

Other services reference (no copy of user/tenant data): - tenant_id (UUID) from JWT or auth-api events - auth_service_user_id / user_id (UUID) - Identity updates via events: auth.user.created, auth.user.updated, auth.user.deactivated, auth.tenant.*

Tenant data access pattern (March 2026): - Downstream services store only: id, slug, name, status, use_case, sync_status, last_sync_at - All branding (logo, colors, contact info) is fetched from auth-api GET /api/v1/tenants/by-slug/{slug} and cached in Redis with key tenant:{slug} and JWT-aligned TTL (6 hours default) via cache.GetTenantDetails() - Frontend services use TanStack Query with staleTime = JWT TTL to cache tenant branding - No service stores branding locally — no brand_colors, logo_url, contact_email, contact_phone, website, country, timezone, org_size, subscription_plan/status/expires_at/id, tier_limits, metadata in downstream tenant tables - Subscription enforcement reads from JWT claims (SubscriptionPlan, SubscriptionStatus, SubscriptionLimits), not from tenant DB - Branding editing: Only auth-ui (accounts.codevertexafrica.com/dashboard/settings?tab=branding). All other frontends redirect to auth-ui for branding management - Profile editing: Common fields (name, email, avatar) managed at auth-ui. Role-specific fields (rider KYC, customer preferences) managed by owning service


CRM Service (marketflow-api)

Owns (single source of truth for all customer relationship data): - Lead — potential customers/recruits captured via ads, funnels, chat, manual entry - Contact — known customers/partners with full profile, lifecycle stage, account link - Deal / Opportunity — sales pipeline records with value, stage, probability, close date - Pipeline / PipelineStage — tenant-configurable sales pipeline with ordered stages - Account — company/organization-level records linked to contacts - Activity — unified timeline of all interactions per entity (emails, calls, notes, meetings, SMS, WhatsApp) - Task — follow-up reminders and action items linked to leads/contacts/deals - Campaign — Meta/TikTok/Google/manual marketing campaigns - Funnel — multi-step landing pages for lead capture and qualification - NurtureSequence — automated multi-channel follow-up sequences (email/SMS/WhatsApp) - ChatSession — AI chatbot conversations (web widget, WhatsApp, funnel) - ScheduledMeeting — Cal.com meeting bookings linked to leads/contacts - CustomFieldDef / CustomFieldValue — per-tenant extensible fields for leads/contacts/deals - LeadScoringRule — configurable rule-based lead scoring conditions - CustomAgent / AgentRun — tenant-defined AI automation agents and their execution logs - ShortLink — URL shortener for tracking

Other services MUST NOT store lead profiles, contact email/phone/name, deal records, activity logs, pipeline data, or any CRM entity. They store only crm_contact_id (nullable UUID) as a reference FK.

Cross-service reference pattern: - pos-api LoyaltyAccount and Appointment: add nullable crm_contact_id UUID FK - ordering-backend Order: add nullable crm_contact_id UUID FK - treasury-api PaymentIntent: add nullable crm_contact_id UUID FK - ticketing-api Ticket: add nullable crm_contact_id and crm_lead_id UUID FKs - All FKs are nullable — existing records function without CRM; linkage is opt-in

Integration pattern (no duplication): - marketflow-worker subscribes to ordering.order.created, pos.sale.finalized, treasury.payment.succeeded, pos.appointment.completed — when crm_contact_id is set on the event payload, logs an Activity on the CRM contact timeline - Other services query contact details via GET /api/v1/contacts/{crm_contact_id} on marketflow-api using X-API-Key: INTERNAL_SERVICE_KEY when they need contact name/email for display - GET /api/v1/contacts/{id}/360 on marketflow-api aggregates cross-service data (orders from ordering-backend, payments from treasury-api, loyalty from pos-api) in one response

Events published by marketflow-api (via NATS outbox): - crm.lead.created, crm.lead.qualified, crm.lead.converted - crm.contact.created, crm.contact.updated, crm.contact.enriched - crm.deal.created, crm.deal.stage_moved, crm.deal.won, crm.deal.lost - crm.activity.logged - crm.task.created, crm.task.overdue - crm.agent.action_taken

Other services reference: crm_contact_id, crm_lead_id; contact/deal data via marketflow-api REST when needed.


Inventory-Service (inventory-api)

Owns (single source of truth for product master and stock): - Units of measure (UoM) — core shared, no tenant_id; one global unit list - Items (SKU master) — tenant-scoped; now includes barcode, barcode_type, compliance flags (age_verification, controlled_substance, perishable, serial_numbers, lots), weight_kg, dimensions_cm, duration_minutes - ItemVariants — attributes map, barcode, image_url, cost_price, weight_kg - Product categories (ItemCategory) — tenant-scoped; now hierarchical with parent_id, depth, path, slug, icon, sort_order - Recipes and BOM (recipe_ingredients) — tenant-scoped; Recipe now includes total_cost, cost_per_portion, target_margin_percent, suggested_price - Warehouses, inventory_balances (now with reorder_quantity, preferred_supplier_id, auto_reorder_enabled), reservations, consumptions - Stock adjustments, low-stock state - CustomFieldDefinition / CustomFieldValue — structured metadata per item/category (NEW) - InventoryLot — batch/lot tracking with expiry dates (NEW) - VariantAttribute — structured variant matrix definitions (NEW) - Bundle / BundleComponent — pre-packaged kits (NEW) - Supplier — vendor management (NEW) - PurchaseOrder / PurchaseOrderLine — procurement workflow (NEW) - StockTransfer / StockTransferLine — inter-warehouse transfers (NEW) - Warranty — serial number warranty tracking (NEW) - Hospitality SERVICE items — Items with use_case ∈ {HOSPITALITY_ROOM, HOSPITALITY_FACILITY, CONFERENCE, SALON_SERVICE, AMENITY} are the master for room-types, conference halls, facilities, and amenities, including their rates (via ItemPricing), meal_plan (RO/BB/HB/FB/AI), occupancy basis and capacity. pos-api references these by inventory_item_id and projects price via catalog sync. (NEW — June 2026) - Conference / event packages — modeled as Bundle (parent SERVICE Item) + BundleComponent rows (meal periods, AV, stationery, consumables, sessions). package_type ∈ {ROOM_RATE_PLAN, DDR, RDR, HALF_BOARD, FULL_BOARD, HALL_HIRE_ONLY, SERVICE_SESSIONS}; price_basis ∈ {per_delegate_per_day, per_person_sharing, flat, per_session}. This replaces any pricing/package authoring in pos-api (Room.rate_per_night, Facility.rate_per_session, ServicePackage.price). (NEW — June 2026) - ItemPricing outlet overrideoutlet_id (nullable) + tier_basis (nightly/per_session/per_delegate_per_day/peak/off_peak) for outlet-level and seasonal rate tiers. (NEW — June 2026)

Other services do not store items, units, recipes, lots, suppliers, purchase orders, or hospitality rates/packages; they reference by inventory_item_id, sku, recipe_id, lot_id, supplier_id and get data via REST (e.g. GET /items, GET /units, GET /recipes, GET /lots, GET /suppliers) or events. Ordering and POS may keep a read-only projection/cache of catalog synced from inventory.

Other services reference: inventory_item_id, inventory_sku, recipe_id, reservation_id, lot_id, supplier_id, purchase_order_id, transfer_id, warranty_id; catalog and units via inventory-api APIs or sync.

Reuse note: inventory-api's existing Asset/AssetMaintenance schemas (asset tag, category, location, warranty, maintenance schedule, depreciation fields) are the biomedical-equipment/hospital-asset register for hospital-api — surfaced there as "Biomedical Equipment" via asset_id reference, not a new asset module. Physical blood-bank units are modeled as a short-shelf-life InventoryLot-tracked item category, reusing the same batch/expiry mechanism drugs already use — no bespoke blood inventory system. See hospital-service/hospital-api/docs/integrations.md § 1.5-1.6.


Ordering-Service (ordering-backend)

Owns (order lifecycle and cafe context only): - Online orders (now with appointment_id, staff_preference_id, preferred_carrier), order_items (now with item_type, service_start_time, duration_minutes), carts, cart_items - Cafe/outlet context (cafes, outlets) as used by ordering - Promo codes, redemptions (loyalty accounts/transactions are now owned by pos-api; ordering reads/writes via pos loyalty API) - Cafe-specific user preferences/roles for ordering UX - CatalogOverride — now with requires_age_verification, item_type, variant_options

Catalog (catalog_items, catalog_categories): Not owned as master. Either (A) no local tables — catalog read from inventory-api (proxy or frontend calls inventory), or (B) read-only cache/projection synced from inventory-api (all catalog writes go to inventory-api). ordering-backend pulls public core master data from inventory-api. Ordering stores only item_id/sku/recipe_id references.

Does not store (reference only): Payment intents, payments, payment methods, refunds (treasury-api); notification events/templates/subscriptions (notifications-api); proof of delivery, logistics events (logistics-api). Ordering keeps only payment_intent_id on Order and uses treasury client for intent create/get; notifications and payments modules use stub/treasury-only repositories.

Other services reference: order_id, cafe_id; logistics and treasury use order refs for tasks and payments.


POS-Service (pos-api)

Owns (sales and shift context only): - POS orders, pos_order_lines, cash_drawers, tenders, price_books, price_book_items - catalog_items as projection/cache from inventory-api (not product master; sync or pull from inventory-api); CatalogItem now includes inventory_item_id, item_type, compliance flags, duration_minutes, cost_price, tags - OutletSetting — display_mode (list/card/image_grid), show_barcode_scanner, enable_kds, enable_appointments - ModifierGroup / Modifier — now with inventory_modifier_group_id / inventory_modifier_option_id for sync from inventory - POS connections, outlets, sessions - KDSStation / KDSTicket — Kitchen Display System routing and ticket lifecycle (NEW) - Appointment — salon/service scheduling (NEW) - StaffMember — staff with commission rates, service assignments (NEW) - SerialNumberLog — serial number tracking at POS (NEW) - CommissionRecord — commission tracking per staff member (NEW) - LoyaltyProgram / LoyaltyAccount / LoyaltyTransaction / Referral — in-store + cross-channel loyalty SoT, keyed on crm_contact_id; ordering-backend reads/writes via pos loyalty API and does not own a second balance - RepairJob (services module) — repair/job-card lifecycle (intake→diagnosis→parts→settle); parts referenced from inventory, settled via treasury - Hotel operationsRoom/RoomGuest (guest stay, check-in/out), RoomBooking (multi-room group header), RoomFolioItem (folio charges), RoomAmenity assignment, Facility/FacilityBooking, HousekeepingTask. These hold operational state only (status, occupancy, guest data); rates and room-type/facility/amenity masters live in inventory-api (referenced via inventory_item_id). (NEW — June 2026) - EventBooking (BEO) — conference/wedding/party bookings referencing an inventory Bundle (inventory_bundle_id) for the package master. (NEW — June 2026) - MealEntitlement — meal-card/voucher issuance & redemption per delegate × conference-day × meal-period (one-time issued→redeemed with validity window). The template of included meals lives on the inventory Bundle; redemption backflushes meal BOM to inventory. (NEW — June 2026) - Happy-hour promotionsPromotion/PromotionRule extended with promo_kind=happy_hour, daily window_start/window_end, days_of_week, outlet_id, auto_apply, and category/item scoping that references inventory category ids (synced via inventory.category.*). Happy hour is a sales-pricing operation on the projection, not a product master. (NEW — June 2026) - Guest ID documentRoomGuest.id_document_url stores only an object-storage key (PII; never the blob). The file lives in object storage with presigned, expiring access.

Does not own: Units, items, recipes, room-type/facility/amenity masters, conference/event package definitions, or any hospitality rate — obtained from inventory-api via REST or sync. Guest contact identity converges on marketflow-api CRM (crm_contact_id). Stock consumption reported to inventory-api via REST (POST /consumption) or event (pos.sale.finalized).

Other services reference: pos_order_id, pos_outlet_id, pos_connection_id, appointment_id, staff_member_id.


Hospital-Service (hospital-api)

Product: Codevertex Afya. Status: platform scaffolding (auth, tenant sync, health checks) is live; clinical domain schemas and business logic are not yet built, so none of the ownership below is implemented in a database today. This section documents the target ownership — pos-api's existing clinical/pharmacy tables (see below) are expected to migrate out to hospital-api over time, not stay there permanently.

Will own (once implemented): - Patient (MRN, demographics), PatientVisit/Encounter, Referral - TriageRecord, ExaminationRecord, DiagnosisCatalog (tenant-custom entries; the default catalogue is global reference data) - LabOrder/LabOrderLine, LabTest (tenant-custom entries; the default catalogue is global reference data) - Prescription/PrescriptionLine, ControlledSubstanceLog - Ward/Bed/Admission, discharge summaries - Specialized-care programme records: ANC, PNC, ART, TB, Immunization, Morgue

Does not own (references only, exactly like pos-api's existing pattern): drug/item master, lot/expiry, drug-interaction rules, controlled-substance schedule, KRA eTIMS item codes (inventory-api); invoices, quotations, insurance claims/coverage/remittance, payments, eTIMS transmission (treasury-api, eTIMS opt-in per tenant/service — not mandatory on every encounter); tenant/user identity (auth-api); service_tag: hospital subscription plans (subscriptions-api).

Migration note: pos-api currently owns all of the "will own" entities above (built for pharmacy dispensing at a retail till, then organically grew a full OPD/clinical workflow). Per feedback_erp_decisive_removal-style platform convention (decisive removal, no reference-ID shims), these move to hospital-api in full once it reaches feature parity — not yet executed. See hospital-service/hospital-api/docs/integrations.md § "Migration ADR" for the exact file/schema list. pos-api keeps its standalone "Codevertex Dawa" retail-pharmacy/chemist product (OTC till sale, no clinical workflow) — a tenant uses either pos-api Dawa or hospital-api, never both for the same outlet.


Treasury-Service (treasury-api)

Owns (single source of truth for money and tax): - Payment intents (now with allow_split), transactions, payment methods - Refunds, payouts, invoices - Taxes, payment gateway config, chart of accounts, ledger - PaymentSplit — split payments across multiple methods (NEW) - Settlement / SettlementLine — merchant settlement processing (NEW) - ReconciliationRun — gateway reconciliation (NEW) - InstallmentPlan / Installment — buy-now-pay-later support (NEW) - CustomerBalance — AR running balance, ageing, statements, credit-sale posting (keyed on crm_contact_id) - VendorBalance — AP running balance, supplier opening/advance balance, ageing, statements; supplier master stays in inventory-api - Supplier rebate / vendor credit notes + cost_center dimension on expense/journal lines - Quotations / QuotationLines — sales quotations and line items (from ERP finance) - Expenses / ExpenseCategories / ExpenseClaims — expense tracking and claims (from ERP finance) - TaxCodes / TaxPeriods / TaxFilings — tax configuration and compliance (from ERP finance) - eTIMSDevices / eTIMSInvoices — KRA eTIMS device registration and invoice transmission (from ERP finance) - Budgets / BudgetLines — budget management (from ERP finance) - ApprovalWorkflows / ApprovalSteps / ApprovalRecords — approval workflows for financial entities (from ERP finance) - VendorBills / VendorBillLines — vendor bill management (from ERP finance) - BankAccounts / BankStatements / BankStatementLines / ReconciliationRules — banking and reconciliation (from ERP finance) - Forecasts / ForecastDataPoints — cash flow forecasting (from ERP finance)

Other services store only payment references and minimal snapshots (e.g. amount at payment time); they do not duplicate treasury entities.

Other services reference: payment_intent_id, payment_id, payout_id, settlement_id, installment_plan_id, quotation_id, expense_id, budget_id, vendor_bill_id, bank_account_id; payment status via webhooks or events.

Quotation ↔ CRM Customer Integration Pattern (May 2026)

Quotations in treasury-api reference CRM customers from marketflow-api. The ownership rule:

  • CRM (marketflow-api) owns all customer/contact data: name, email, phone, address, company, lifecycle stage, custom fields.
  • treasury-api stores a nullable customer_id UUID FK (the CRM Contact UUID) on Quotation. It also caches customer_name and customer_email as snapshot strings directly on the quotation row for display/export without requiring a live CRM lookup.

Integration pattern for quotation creation: 1. UI calls GET /api/v1/marketflow/{tenant}/contacts?search=<query> (S2S with X-API-Key: INTERNAL_SERVICE_KEY) to search CRM contacts. 2. User selects a contact → UI populates customer_id (UUID), customer_name, customer_email in the form. 3. UI posts POST /api/v1/{tenant}/quotations with all three fields. 4. treasury-api stores them; customer_id is the authoritative FK.

Access pattern for display: treasury-api returns customer_name and customer_email from the quotation row (snapshot). For live CRM data (phone, address, 360 view), call GET /api/v1/marketflow/{tenant}/contacts/{customer_id}.

Services that must NOT store customer master data: treasury-api, pos-api, ordering-backend, logistics-api, inventory-api. These services store only crm_contact_id (nullable UUID FK) and optional snapshot fields (customer_name, customer_email) for audit/display.

Financial Documents ↔ Cross-Service Integration Patterns (May 2026)

All financial document types (Quotation, Invoice, Proforma Invoice, Credit Note, Sales Order, Delivery Challan, Payment Receipt) in treasury-api follow these data ownership rules for cross-service references:

Line Items ↔ Inventory-API (Product Catalog) - inventory-api owns all product master data: item name, SKU, description, unit of measure, cost price, tax rate, images, variants. - treasury-api stores a nullable item_id UUID FK (inventory-api inventory_item_id) and sku string on each line item. - rate (unit price), tax_rate, and description on the line item are snapshot values captured at the time the document is created — they do not change if the inventory item is later updated. - Integration pattern: treasury-ui calls GET /api/v1/{tenant}/inventory?search=<query> (inventory-api) to search products in the line items combobox. Selecting a product auto-fills rate and tax_rate from the current inventory master; these values are then submitted as part of the document and stored as snapshots. - treasury-api must NOT mirror or sync inventory items locally. Line item item_id is the authoritative reference; live product data is fetched from inventory-api on demand.

Delivery Challan Conversion ↔ Logistics-API - logistics-api owns all delivery task data: task lifecycle, proof of delivery, rider assignment, tracking, dispatch notes. - When a quotation is converted to a delivery challan, treasury-api calls POST /api/v1/{tenant}/tasks on logistics-api (S2S with X-API-Key: INTERNAL_SERVICE_KEY) with the quotation line items, shipping details, and source document reference. - treasury-api stores the returned logistics_task_id UUID on the quotation as a reference. - treasury-api does NOT store delivery task lifecycle, rider details, proof of delivery, or tracking status. These are fetched from logistics-api when needed. - NATS event treasury.quotation.delivery_challan_created is published with {quotation_id, logistics_task_id, tenant_id}.

Sales Order Conversion ↔ Ordering-Backend - ordering-backend owns all sales order/online order data including order lifecycle, fulfillment state, and customer-facing order status. - When a quotation is converted to a sales order, treasury-api calls POST /api/v1/{tenant}/orders on ordering-backend (S2S with X-API-Key: INTERNAL_SERVICE_KEY) with the quotation line items, customer reference, and source quotation ID. - treasury-api stores the returned order_id UUID on the quotation as a reference. - treasury-api does NOT store order lifecycle, fulfillment, or cart data. These are owned by ordering-backend. - NATS event treasury.quotation.converted_to_order is published with {quotation_id, order_id, tenant_id}.

Payment Receipts ↔ Banking Accounts - Bank accounts (for the "Deposited To" field in Payment Receipts) are owned by treasury-api itself (BankAccounts entity). No cross-service reference needed — treasury-api queries its own bank accounts. - GET /{tenant}/banking/accounts is the endpoint treasury-ui calls to populate the "Deposited To" dropdown in the RecordPaymentModal.

Financial Document Public Share Links - Public share pages (/q/{token}, /i/{token}) serve document data via a public_token UUID field stored on each document row. - Public endpoints (GET /api/v1/public/quotations/{token}, GET /api/v1/public/invoices/{token}) do not require authentication and are rendered as Next.js server components. - PDF/CSV/XLSX exports for authenticated users use blob download (TruLoad pattern): GET /{tenant}/quotations/{id}/pdf returns bytes; the client creates an object URL and triggers <a>.click(). Public pages use direct <a href> to treasury-api public endpoints.

Document Email/WhatsApp Delivery ↔ Notifications-API - treasury-api does NOT directly send email or WhatsApp messages. On SendQuotation / SendInvoice, treasury-api publishes NATS events: - treasury.quotation.sent{quotation_id, tenant_id, recipient_email, public_token} - treasury.invoice.sent{invoice_id, tenant_id, recipient_email, public_token} - notifications-api subscribes to these events and delivers the email/WhatsApp using the appropriate template (quotation_sent, invoice_sent). - For payment reminders: treasury-ui "Send Reminder" action triggers POST /{tenant}/invoices/{id}/send-reminder → treasury-api publishes treasury.invoice.reminder_sent → notifications-api delivers.

Summary of Cross-Service References on Financial Documents:

Field on Document Owner Service Reference Type
customer_id marketflow-api Nullable UUID FK + customer_name/email/phone snapshot
line item item_id inventory-api Nullable UUID FK + rate/tax_rate/description snapshot
line item sku inventory-api String snapshot (authoritative via item_id)
logistics_task_id (on quotation after DC conversion) logistics-api Nullable UUID FK
order_id (on quotation after SO conversion) ordering-backend Nullable UUID FK
bank_account_id (on payment receipt) treasury-api (self) Internal FK

Logistics-Service (logistics-api)

Owns: - Rider/fleet member profiles (KYC, documents, vehicle); FleetMember now includes specialization_tags, has_cold_storage, max_weight_capacity_kg - Delivery tasks (expanded task_type: food_delivery, retail_delivery, outlet_transfer, commercial_courier, drop_shipping; now with package_weight_kg, package_dimensions_cm, temperature_control, fragile/heavy flags, carrier_id), availability - Telemetry and location, proof of delivery - Rider earnings and payouts (logistics-side) - PricingRule — dynamic pricing: distance/weight/time/surge/flat rate rules (NEW) - RiderShift — shift management with zone assignment (NEW)

Other services reference: rider_id, logistics_task_id, pricing_rule_id, shift_id; rider/task data via logistics APIs (e.g. GET /fleet-members, POST /tasks).

Reuse note: ambulance/emergency dispatch for hospital-api reuses this service as-is — task_type is a free-form string field (no schema change needed to add ambulance_dispatch as a value), FleetMember is tagged ambulance via specialization_tags, and PricingRule's existing rule_type: "distance" + distance_tiers JSON matches Kenya's base-fee-plus-per-km ambulance pricing model. hospital-api stores only a reference logistics_task_id on its own AmbulanceBooking row — no new fleet/dispatch/pricing engine. See hospital-service/hospital-api/docs/integrations.md § 2A.


Subscriptions-Service (subscriptions-api)

Owns: - Subscription plans, plan features, tier limits - Tenant subscriptions, usage, billing state

Other services do not store plan or entitlement data; they check subscription/entitlement via subscriptions-api before using inventory, POS, logistics, treasury, etc.


Notifications-Service (notifications-api)

Owns: - Notification templates, delivery status - Channel preferences (per user, per tenant)

Other services reference: trigger sends via API or events; store only message/template IDs if needed for audit.


IoT-Service (iot-service-api)

Owns: - Devices, telemetry, rules, alerts

Consumers: inventory-api (e.g. temperature/compliance), notifications; optional POS/inventory hardware (terminals, scanners, scales, KDS) documented in architecture/integrations.


Entities That Must Not Exist in Non-Owner Services (No Duplication, No Legacy)

The following entities belong to a single owner. No other service may store them. Remove any such tables and logic from non-owner services; use references (IDs) and API/event calls only.

Entity / Table(s) Owner Must NOT exist in
Proof of delivery (signature, photo, OTP, recipient, rating) logistics-api ordering-backend, pos-api
Delivery task lifecycle events (task created, assigned, completed) logistics-api ordering-backend (no logistics_events table)
Notification templates, notification events, notification subscriptions notifications-api ordering-backend, pos-api, inventory-api
Payment intents, payments, payment methods, refunds, treasury webhook events treasury-api ordering-backend, pos-api (only refs on order: e.g. payment_intent_id UUID, payment_status)
PaymentSplit, Settlement, ReconciliationRun, InstallmentPlan/Installment treasury-api ordering-backend, pos-api, logistics-api
Product master (items, units, recipes, BOM, product categories) inventory-api ordering-backend, pos-api (only projection/cache synced from inventory; no authoring)
CustomFieldDefinition/Value, InventoryLot, VariantAttribute, Bundle/Component, Supplier, PurchaseOrder, StockTransfer, Warranty inventory-api ordering-backend, pos-api, logistics-api, treasury-api
KDSStation/Ticket, Appointment, StaffMember, SerialNumberLog, CommissionRecord pos-api ordering-backend, inventory-api, logistics-api (only refs e.g. appointment_id, staff_member_id)
Room/RoomGuest/RoomBooking/RoomFolioItem, Facility/FacilityBooking, EventBooking, MealEntitlement, HousekeepingTask (hotel operations) pos-api inventory-api, ordering-backend (only refs e.g. room_guest_id, event_booking_id)
Room-type/facility/amenity masters & rates, conference/event package definitions (room/facility pricing must NOT be authored in pos-api) inventory-api pos-api stores only inventory_item_id/inventory_bundle_id refs + synced price snapshot
PricingRule, RiderShift logistics-api ordering-backend, pos-api, treasury-api
Rider/fleet member profiles, KYC, vehicles, shifts logistics-api ordering-backend (only rider_id, logistics_task_id refs in order_assignments)
Tenant and user identity (full profile, sessions, MFA, OAuth) auth-api ordering-backend, pos-api (only tenant_id, user_id refs; minimal JIT cache allowed for FK only)
Quotations, quotation lines treasury-api erp (remove after migration), ordering-backend, pos-api
Expenses, expense categories, expense claims treasury-api erp (remove after migration), ordering-backend, pos-api
Tax codes, tax periods, tax filings, eTIMS devices, eTIMS invoices treasury-api erp (remove after migration), pos-api, inventory-api
Budgets, budget lines treasury-api erp (remove after migration), ordering-backend, pos-api
Approval workflow config for financial entities (approval workflows, steps, records) treasury-api erp (generic approvals module stays for non-financial workflows; financial approval config migrates to treasury)
Vendor bills, vendor bill lines treasury-api erp (remove after migration), inventory-api
Bank accounts, bank statements, bank statement lines, reconciliation rules treasury-api erp (remove after migration)
Forecasts, forecast data points treasury-api erp (remove after migration)
Leads, Contacts, Deals, Pipeline stages, Accounts, CRM Activities, CRM Tasks marketflow-api ordering-backend, pos-api, treasury-api, inventory-api, logistics-api (store only crm_contact_id nullable FK)
Patient, PatientVisit, TriageRecord, ExaminationRecord, LabOrder/Line, Prescription/Line, ControlledSubstanceLog, Ward/Bed/Admission (clinical workflow) hospital-api (target owner, not yet migrated) pos-api (remove once hospital-api reaches feature parity)

Ordering-backend cleanup (target state): - Remove (schemas + all associated logic): proof_of_delivery, logistics_events, notification_templates, notification_events, notification_subscriptions, payment_intents, payments, payment_methods, refunds, treasury_events. - Keep (refs only): order_assignments with logistics_task_id, rider_id (no PoD edge); orders with payment_intent_id (UUID), payment_status, and optional amount snapshot for display; trigger notifications via notifications-api API or events; get payment details from treasury-api when needed. - Catalog: catalog_items / catalog_categories only as read-only cache synced from inventory-api, or remove and proxy inventory-api for catalog (see plan).

Logistics-api (owner): Already has proof_of_delivery (task_id, fleet_member_id, signature_url, photo_url, etc.). ERD documents it. No ordering-backend copy.

Notifications-api (owner): Owns templates and delivery logs. Ordering and others send via API/events; no local template tables.

Treasury-api (owner): Owns payment intents, payments, refunds. Ordering and POS store only payment_intent_id and status on order; no local payment/refund tables.


How Data Is Accessed (No Duplication)

  • REST: Primary for reads and commands: catalog (GET items/units/recipes from inventory-api), reserve/consume (ordering → inventory-api), payments (ordering/pos → treasury-api), rider/task (ordering → logistics-api), JWT validation (all → auth-api).
  • NATS events: For async sync and lifecycle: order lifecycle (ordering.order.*), stock/reservation (inventory.stock.updated, inventory.reservation.confirmed), sale finalised (pos.sale.finalized → inventory backflush), auth sync (auth.user.*, auth.tenant.*). Consumers must be idempotent.
  • gRPC (optional): For low-latency calls where needed (e.g. stock check before add-to-cart); document in each service’s architecture.md if introduced.

Cross-Service Event Subscription Matrix

Publisher Event Subject Subscriber Action
Auth Service auth.user.created Ordering, Subscriptions, Notifications Create local user ref / trial / welcome
Auth Service auth.user.updated Ordering Sync profile
Auth Service auth.user.deactivated Ordering Deactivate local user
Auth Service auth.tenant.created Ordering, Subscriptions Init tenant / trial
Auth Service auth.tenant.updated Ordering Sync tenant
Ordering Service ordering.order.created Inventory (reserve), Notifications, Treasury Reserve stock, confirm, payment intent
Ordering Service ordering.order.ready Logistics Create delivery task
Ordering Service ordering.order.completed Inventory Consume reservation
Inventory Service inventory.stock.updated, inventory.stock.low Ordering (optional) Availability / out-of-stock flags
Inventory Service inventory.category.created/updated POS, Ordering Sync hierarchical categories
Inventory Service inventory.bundle.created/updated POS Project conference/event packages & rate plans into POS catalog
Inventory Service inventory.lot.expiring_soon Notifications Expiry alerts to tenant admins
Inventory Service inventory.purchase_order.received Notifications PO receipt confirmation
Inventory Service inventory.transfer.shipped Notifications Transfer dispatch notification
POS Service pos.sale.finalized Inventory Backflush / consumption
POS Service pos.kds.ticket.ready Notifications KDS ticket ready alert
POS Service pos.appointment.created/completed Notifications, Ordering Appointment lifecycle sync
POS Service hotel.booking.created Notifications Multi-room booking confirmation
POS Service conference.event.booked Notifications Conference/event (BEO) booked
POS Service conference.mealcard.issued Notifications Delegate meal cards generated
POS Service conference.mealcard.redeemed Inventory, Notifications Meal voucher redeemed → backflush meal BOM
POS Service pos.inventory.consumption.failed Notifications Backflush failure alert (retry queue)
Ordering Service ordering.booking.created POS, Notifications Service booking created
Treasury Service Payment webhooks (HTTP) Ordering Update order payment status
Treasury Service treasury.settlement.completed Notifications Settlement batch notification
Treasury Service treasury.installment.due Notifications Installment due reminder
Treasury Service treasury.invoice.created Notifications Invoice created notification
Treasury Service treasury.invoice.paid Notifications Invoice payment confirmation
Treasury Service treasury.invoice.overdue Notifications Overdue invoice alert
Treasury Service treasury.expense.submitted Notifications Expense claim submitted for review
Treasury Service treasury.expense.approved Notifications Expense claim approved notification
Treasury Service treasury.quotation.sent Notifications Quotation email/WhatsApp delivery (recipient_email, public_token)
Treasury Service treasury.quotation.accepted Notifications Quotation accepted notification
Treasury Service treasury.quotation.delivery_challan_created Logistics, Notifications Delivery challan created from quotation (logistics_task_id ref)
Treasury Service treasury.quotation.converted_to_order Ordering, Notifications Quotation converted to sales order (order_id ref)
Treasury Service treasury.invoice.sent Notifications Invoice email/WhatsApp delivery (recipient_email, public_token)
Treasury Service treasury.invoice.reminder_sent Notifications Payment reminder delivery
Treasury Service treasury.etims.transmitted Notifications eTIMS transmission confirmation
Treasury Service treasury.budget.approved Projects, ERP Budget approved — update project/ERP budget refs
Treasury Service treasury.budget.rejected Projects, ERP Budget rejected — notify requestor
Subscriptions Service subscription.billing.renewal Treasury Process subscription renewal payment
Subscriptions Service subscription.billing.overage Treasury Process overage charges
Subscriptions Service subscription.billing.proration Treasury Process proration adjustment
ERP Service erp.payroll.processed Treasury Create payroll payment journal entries
ERP Service erp.purchase_order.received Treasury Create vendor bill from received PO
Logistics Service Delivery webhooks (HTTP) Ordering Update order delivery status

Note: Catalog and units are read via REST from inventory-api; ordering and POS do not duplicate product master. Event subjects follow domain.entity.action (e.g. ordering.order.created). See each service’s integrations.md for full event catalog.


User Management Patterns

Pattern 1: Service-Specific User Data

Example: Rider User Management

  1. User Identity (auth-service):
  2. User created in auth-service with email, password, tenant membership
  3. Role: rider assigned in auth-service
  4. User authenticates via auth-service (SSO)

  5. Rider Profile (logistics-service):

  6. Rider-specific data stored in logistics-service:
    • KYC documents (national ID, license)
    • Vehicle information
    • Shift availability
    • Earnings and payouts
  7. Linked to auth-service user via auth_service_user_id

  8. Rider Creation Flow:

From ordering UI (ordering-frontend / cafe-website calling ordering-backend): the UI first checks GET /api/v1/tenants/{tenant_id}/services (subscriptions-api) for logistics in enabled_services; if it's missing, show an upgrade prompt. If enabled, either ordering-backend calls POST /v1/{tenant}/fleet-members on logistics-api server-side and stores only the returned rider_id, or the user is redirected to the logistics UI to self-onboard via SSO and ordering-backend picks up the rider_id on return.

Standalone logistics service: the user goes directly to the logistics UI, authenticates via SSO, and completes onboarding there. All rider data lives in logistics-api; ordering-backend is not involved.

Pattern 2: Tenant Service Availability Check

Before creating/referencing data in another service (except auth-api, which is always available):

// Pseudo-code example
func createRider(ctx context.Context, tenantID uuid.UUID, riderData RiderData) error {
    // 1. Check tenant has logistics service enabled (subscriptions-api)
    tenant, err := subscriptionService.GetTenantServices(ctx, tenantID)
    if err != nil {
        return err
    }
    if !contains(tenant.EnabledServices, "logistics") {
        return ErrServiceNotAvailable("Logistics service not enabled for this tenant")
    }

    // 2. Verify tenant exists in logistics-service
    exists, err := logisticsService.TenantExists(ctx, tenantID)
    if err != nil {
        return err
    }
    if !exists {
        return ErrTenantNotFound("Tenant not found in logistics-service")
    }

    // 3. Create rider in logistics-service (only owner stores rider data)
    riderID, err := logisticsService.CreateFleetMember(ctx, tenantID, riderData)
    if err != nil {
        return err
    }

    // 4. Store only reference ID locally (no duplication of rider profile)
    return orderingRepo.StoreRiderReference(ctx, tenantID, riderID)
}

Pattern 3: Service-to-Service Data Queries

Never duplicate data, always query the owning service:

// ❌ WRONG: Storing rider data locally
type OrderAssignment struct {
    RiderID      uuid.UUID
    RiderName    string  // ❌ Don't store
    RiderPhone   string  // ❌ Don't store
    VehicleType  string  // ❌ Don't store
}

// ✅ CORRECT: Store only reference, query when needed
type OrderAssignment struct {
    RiderID      uuid.UUID  // ✅ Only reference
}

// Query rider data from logistics-service when needed
func getRiderDetails(ctx context.Context, riderID uuid.UUID) (*Rider, error) {
    return logisticsService.GetFleetMember(ctx, tenantID, riderID)
}

Subscription Plan Integration

Service Availability Check

Subscription plans (subscriptions-api) determine which services a tenant can use — e.g. a starter plan might grant only ordering, while higher tiers add logistics, inventory, POS, treasury, notifications, and so on. The exact plan-to-service mapping changes as pricing evolves, so treat subscriptions-api as the source of truth rather than any list here.

Before creating/referencing data in another service: 1. Check tenant subscription plan: GET /api/v1/tenants/{tenant_id}/subscription 2. Verify service in plan features: plan.features.includes("logistics") 3. If not available: Show error or redirect to upgrade


Authentication & SSO

Single Sign-On (SSO)

  • All users authenticate via auth-service (https://sso.codevertexafrica.com/)
  • JWT access tokens contain: sub (user_id), tenant_id, tenant_slug, roles, and permissions (canonical codes from auth-service role–permission table). All services use these same permission codes for authorization.
  • All services validate tokens via JWKS from auth-service and read roles/permissions from the token (or from GET /me). If the token is valid but the service has no local user yet, the service JIT-provisions the user from token claims and continues (no 401 for "user not found").
  • Service-specific profile data (e.g. rider KYC, vehicle) is not in the token. It is collected via service-specific onboarding after SSO login, with identity (email, name) prefilled from the token.

Service-Specific Roles

  • Auth-Service: Global roles (superuser, admin, user)
  • Ordering-Service: Ordering/cafe-specific roles (customer, staff, admin)
  • Logistics-Service: Logistics-specific roles (rider, fleet_manager)
  • Combined: User can have multiple roles across services

Worked Example: Order Assignment with a Rider

Same shape as the rider-creation flow above, applied to assigning a rider to an order: ordering-backend calls GET /v1/{tenant}/fleet-members?status=available on logistics-api, stores only rider_id on order_assignments, and creates the delivery task via POST /v1/{tenant}/tasks. Logistics-api owns the task lifecycle; ordering-backend just consumes logistics.task.assigned / logistics.task.completed.


Best Practices

  1. No data duplication: Each entity lives in one service only; others hold references (IDs) and optionally minimal snapshots for audit.
  2. Check service availability: Before creating or referencing data in another service, verify tenant has that service enabled (subscriptions-api); auth-api is always available.
  3. Store only references: Never copy full entities across services; store *_id and call the owning service’s API or consume its events when you need data.
  4. Catalog from inventory: Product master (items, units, recipes, categories) is owned by inventory-api; ordering and POS use REST or synced projection/cache only.
  5. Use events for lifecycle sync: Use NATS for order lifecycle, stock updates, and auth sync; keep consumers idempotent.
  6. Support standalone mode: Each service should work when it is the only one enabled for the tenant.

Current State (Post-Cleanup Target)

  • Ordering-backend: No proof_of_delivery, logistics_events, notification_* tables, or payment/payment_intent/refund/treasury_events tables. Only refs on orders and order_assignments; catalog from inventory (cache or proxy).
  • Logistics-api: Single source of truth for proof_of_delivery, tasks, riders; erd.md reflects this.
  • Inventory-api: Single source of truth for items (with barcode/compliance/weight/dimensions), units, recipes (with costing), recipe_ingredients, warehouses, balances (with auto-reorder), reservations, consumptions, hierarchical categories, custom fields, lots, variant attributes, bundles, suppliers, purchase orders, stock transfers, warranties.
  • POS-api: Single source of truth for KDS stations/tickets, appointments, staff members, serial number logs, commission records; catalog items synced from inventory via NATS events (inventory.item.created/updated) with full compliance flags, item_type, inventory_item_id FK, barcode, duration_minutes. Sync handler in internal/modules/catalog/inventory_events.go. POS ModifierGroups reference inventory via inventory_modifier_group_id FK.
  • Logistics-api: Single source of truth for expanded task types (food_delivery, retail_delivery, outlet_transfer, commercial_courier, drop_shipping), pricing rules, rider shifts with zone assignment; fleet members with specialization and capacity.
  • Notifications-api: Owns templates and delivery; no template storage in ordering or POS.
  • Treasury-api: Owns all payment entities including split payments, settlements/lines, reconciliation runs, installment plans/installments; ordering/POS hold only payment_intent_id and status refs.

Migration Notes

  • No backward compatibility for wrong ownership. Remove legacy or duplicate entities from non-owner services; do not keep them for compatibility.
  • Legacy riderprofile and riderdocument in ordering-backend are removed. All rider data is in logistics-api; ordering-backend stores only rider_id and logistics_task_id refs.
  • Ordering-backend: Remove ProofOfDelivery, LogisticsEvent, NotificationTemplate, NotificationEvent, NotificationSubscription, Payment, PaymentIntent, PaymentMethod, Refund, TreasuryEvent schemas and all related handlers/repos; Order keeps payment_intent_id (UUID), payment_status; get PoD from logistics-api, payment details from treasury-api, send notifications via notifications-api.
  • Catalog/menu: Source of truth is inventory-api. Ordering-backend must not own menu_items/menu_categories as master — either remove and proxy inventory-api, or keep as read-only cache synced from inventory-api (see plan and service erd.md).

Shared Libraries (Uniformity)

All Go services should use these shared libraries from github.com/Bengo-Hub/:

Library Purpose Version Services
httpware HTTP middleware, tenant/user context, CORS v0.3.0 All 7 services
shared-events (events) Transactional outbox, NATS publishing v0.2.0 All 7 services
auth-client JWT validation, JWKS, permissions middleware v0.4.x All 7 services
cache Redis tenant cache (branding, config) v0.2.0 ordering, inventory, logistics, notifications, subscriptions
service-client gRPC/REST inter-service client v0.2.0 ordering, notifications, treasury, subscriptions
pagination Cursor/offset pagination helpers v0.1.0 notifications (adopt in others as needed)

Frontend shared package: @bengo-hub/shared-ui-lib v0.1.0 — SSOLoginModal, TreasuryPaymentModal, TrackingIframeModal. Used by ordering-frontend, pos-ui, inventory-ui, cafe-website. All must pin to #v0.1.0.


References

  • Microservice Architecture for POS, Inventory, Orders — research and use cases per service
  • Per-service docs: each backend’s docs/erd.md, docs/integrations.md, docs/architecture.md reference this document for cross-service ownership
  • Auth: auth-api docs/integrations.md; Ordering: ordering-backend docs/CROSS-SERVICE-DATA-OWNERSHIP.md (service-specific extension)