Inventory Service

inventory-service is a Quarkus service that owns all stock data: quantities on hand, soft and hard reservations, location-level tracking, purchase orders, and inter-location transfers. Storage is PostgreSQL with store_id as a discriminator column for multi-tenant isolation.

Multiple Locations#

A store can have any number of locations. The locations table records each one with location_id, store_id, name, and type (warehouse, retail outlet, fulfilment centre, virtual). Stock is tracked per variant per location.

Stock Model#

Inventory state is held across two complementary structures.

stock_levels#

stock_levels is the current materialized view of stock for each (variant_id, location_id, store_id) combination. Columns:

ColumnTypeNotes
on_handINTPhysical units present
soft_reservedINTHeld by active carts (TTL-bound)
hard_reservedINTCommitted by in-progress checkouts
incomingINTExpected from confirmed purchase orders

Available to sell is a derived value: on_hand - soft_reserved - hard_reserved. This is the figure returned to callers and used for add-to-cart checks.

stock_movements#

stock_movements is an append-only ledger. Every stock change — regardless of source — writes an immutable row before stock_levels is updated. This provides a full audit trail and the ability to recompute stock_levels from scratch.

Supported movement types:

TypeTrigger
SOFT_RESERVATIONCart add-to-cart
SOFT_RESERVATION_EXPIREDTTL expiry of a soft reservation
HARD_RESERVATIONCheckout commits a reservation
SOFT_RELEASEDSoft reservation manually released
HARD_RELEASEDHard reservation released on checkout failure or cancellation
ORDER_CONSUMEDorder.placed event consumed — stock deducted
ADJUSTMENT_INManual positive stock adjustment
ADJUSTMENT_OUTManual negative stock adjustment
TRANSFER_INStock received at a location from a transfer
TRANSFER_OUTStock sent from a location to a transfer
PO_RECEIVEDPurchase order marked RECEIVED
ERP_SYNCAuthoritative sync from external ERP

Reservation Lifecycle#

Reservations move through a defined lifecycle. Soft and hard reservations are held at the store’s aggregate pool level, not pinned to a specific location — location assignment is a fulfilment concern handled downstream by the order or fulfilment service.

Add to Cart#

cart-service calls PlaceSoftReservation. On success: soft_reserved++, a SOFT_RESERVATION movement is written, and a reservation_id is returned with an attached TTL. If available_to_sell is insufficient and the variant’s backorder policy is DENY, the service returns INSUFFICIENT_STOCK.

Checkout#

checkout-service calls UpgradeToHardReservation. On success: soft_reserved--, hard_reserved++, and a HARD_RESERVATION movement is written.

Order Placed#

inventory-service consumes the order.placed Kafka event. On receipt: hard_reserved--, on_hand--, and an ORDER_CONSUMED movement is written.

Soft Reservation TTL Expiry#

inventory-service manages TTL autonomously. When a soft reservation expires: soft_reserved--, a SOFT_RESERVATION_EXPIRED movement is written, and inventory.reservation_expired is published to Kafka for cart-service to act on.

Checkout Failure or Order Cancellation#

checkout-service or order-service calls ReleaseReservations. The service reverses the appropriate counts and writes SOFT_RELEASED or HARD_RELEASED movements.

Backorder Policies#

variant_inventory_settings stores per-variant configuration for how the service should behave when available_to_sell reaches zero.

PolicyBehaviour
DENYDefault. Add-to-cart is rejected when available_to_sell = 0.
ALLOWUnits can be added beyond on_hand. No expected date is provided to the customer.
PREORDERLike ALLOW but an expected availability date is surfaced to the customer.

The table also stores low_stock_threshold INT, which triggers the inventory.low_stock event when on_hand drops below it.

Purchase Orders#

purchase_orders and po_line_items track incoming stock from external suppliers.

Purchase order statuses: DRAFTCONFIRMEDRECEIVED. When a purchase order is marked RECEIVED, inventory-service increments on_hand for each line item and writes a PO_RECEIVED movement.

incoming on stock_levels reflects the sum of quantities on all CONFIRMED purchase orders for that variant at that location.

Inter-Location Transfers#

stock_transfers and transfer_line_items model movement of existing stock between a store’s locations.

On processing a transfer: the source location’s on_hand is decremented and a TRANSFER_OUT movement is written; the destination location’s on_hand is incremented and a TRANSFER_IN movement is written.

gRPC Interface#

These methods are exposed to internal callers only.

MethodCalled ByPurpose
PlaceSoftReservation(variantId, storeId, quantity, ttlSeconds)cart-serviceReserve stock for a cart; returns reservationId or INSUFFICIENT_STOCK
UpgradeToHardReservation(reservationId, orderId)checkout-serviceCommit a soft reservation to a hard reservation
ReleaseReservations(reservationIds[])cart-service, checkout-service, order-serviceRelease soft or hard reservations on failure or cancellation
GetStockLevels(variantIds[], storeId)catalog-serviceReturn current stock levels for display at render time

Kafka Consumed#

TopicAction
order.placedConsume hard reservations → write ORDER_CONSUMED movements, decrement on_hand
order.cancelledRelease hard reservations → write HARD_RELEASED movements

Kafka Events Published#

TopicTrigger
inventory.stock_updatedAny stock level change
inventory.low_stockon_hand drops below low_stock_threshold
inventory.out_of_stockavailable_to_sell reaches 0
inventory.back_in_stockavailable_to_sell returns above 0 after being 0
inventory.reservation_expiredSoft reservation TTL expiry; consumed by cart-service to clean up the cart

Vault Configuration#

Key pathPurpose
ss3/kv/inventory-service/db.urlPostgreSQL JDBC URL
ss3/kv/inventory-service/db.usernameDatabase username
ss3/kv/inventory-service/db.passwordDatabase password
ss3/kv/shared/Shared Kafka bootstrap and OTel config