Inventory (Vendor)
Executive summary
Section titled “Executive summary”Inventory spans warehouses, stocks, lots, adjustments, and internal transfers under /dashboard/inventory/*. Module inventory applies; some URLs redirect (e.g. warehouse “new”).
Goals and non-goals
Section titled “Goals and non-goals”| Goals | Non-goals |
|---|---|
| Track stock truth for the selected store | Replace barcode warehouse automation |
| Document route quirks (redirects) | Teach FIFO/LIFO accounting standards |
Current platform behavior
Section titled “Current platform behavior”- Route matrix in table below; warehouses/new may redirect per comment in doc.
- Use StoreSelector context consistently before adjustments and internal transfers.
Sequence: happy path
Section titled “Sequence: happy path”- Open Inventory subtree → warehouses / stocks as needed.
- Create adjustment with reason per SOP, or create internal transfer from the warehouse action when moving stock between warehouses.
- Verify stock levels on stocks detail.
Security and operations
Section titled “Security and operations”- Adjustments are fraud-sensitive—dual control offline if policy requires.
Test scenarios (UAT / QA)
Section titled “Test scenarios (UAT / QA)”| ID | Scenario | Expected |
|---|---|---|
| I1 | Employee with inventory | Inventory routes open |
| I2 | Adjustment saved | Stock reflects after refresh |
| I3 | Internal transfer saved | Source warehouse stock decreases and destination warehouse stock increases |
| I4 | Without module | Blocked |
Routes (app-router.tsx)
Section titled “Routes (app-router.tsx)”| Route | Notes |
|---|---|
/dashboard/inventory/warehouses | Warehouse list |
/dashboard/inventory/warehouses/new | Redirects to /dashboard/inventory/warehouses (no standalone create URL) |
/dashboard/inventory/warehouses/:id | Warehouse detail; tabs include overview, stocks, lots, adjustments, and internal transfers |
/dashboard/inventory/stocks, /dashboard/inventory/stocks/:id | Stock list and detail |
/dashboard/inventory/lots, /dashboard/inventory/lots/new, /dashboard/inventory/lots/:id/edit, /dashboard/inventory/lots/:id | Purchase lots (draft edit at …/edit) |
/dashboard/inventory/lots/:id?receive=1 | Opens Receive stock modal on purchase detail (legacy /goods-receipts/receive?lot_id= redirects here) |
/dashboard/inventory/adjustments, adjustments/new, adjustments/:id | Stock adjustments |
/dashboard/inventory/internal-transfers/new | Internal transfer form; warehouse actions can preselect source via warehouseId query |
Access (RBAC)
Section titled “Access (RBAC)”Prefix /dashboard/inventory maps to module inventory in vendor-module-access.ts. Create/edit flows (e.g. new lot, new adjustment) require write permission where the rule applies. Owners always pass.
APIs (code-backed)
Section titled “APIs (code-backed)”Services live under apps/vendor-web/src/services/:
| Area | Service | Endpoints (representative) |
|---|---|---|
| Summary KPIs | vendor-inventory.api.ts | GET /vendor/inventory/summary — counts warehouses, stock records, lots, adjustments, low-stock |
| Warehouses | vendor-warehouses.api.ts | GET /vendor/warehouses/list, GET /vendor/warehouses/:id, POST /vendor/warehouses, PATCH /vendor/warehouses/:id, DELETE /vendor/warehouses/:id |
| Stocks | vendor-stocks.api.ts | GET /vendor/stocks/list, GET /vendor/stocks/:id, POST /vendor/stocks/import-excel (multipart .xlsx; preferred from vendor web), POST /vendor/stocks/import (JSON lines or raw_rows) — absolute target_quantity per line — by stock_id or by warehouse + item + optional variation |
| Lots | vendor-lots.api.ts | GET /vendor/lots/list, GET /vendor/lots/:id, POST /vendor/lots (pending), POST /vendor/lots/draft (save draft — warehouse/supplier optional), POST /vendor/lots/:id/finalize (single supplier submit), POST /vendor/lots/split-submit (batch by warehouse + supplier per group), PATCH /vendor/lots/:id/prices (pending only — supplier unit prices), PATCH /vendor/lots/:id/status |
| Goods receipts | vendor-goods-receipts.api.ts | GET /vendor/lots/:id/receivable-items, POST /vendor/goods-receipts |
| Adjustments | vendor-adjustments.api.ts | GET /vendor/adjustments/list, GET /vendor/adjustments/:id, POST /vendor/adjustments (lines with stock_id, item_id, qty, add/subtract type; optional reference_no for your own document number, unique among adjustments) |
| Internal transfers | vendor-internal-transfers.api.ts | GET /vendor/internal-transfers/list (optional warehouse_id returns transfers where that warehouse is source or destination); POST /vendor/internal-transfers (source/destination warehouse, transfer date, optional reference_no and note, lines with source stock_id, item_id, and quantity) |
Store scope: list/detail calls often accept store_id (and related filters). The global StoreSelector in the dashboard top bar drives useVendorStore (selectedStoreId); many inventory queries pass the current store when the API supports it.
Behavior notes
Section titled “Behavior notes”- Stocks Excel: export downloads
.xlsxwithstock_id, warehouse and product identifiers, SKU,current_quantity, andtarget_quantity(initially equal to current). Edittarget_quantityto the desired on-hand count, then import. The dashboard uploads the file toPOST /vendor/stocks/import-excel(multipart fieldfile, plusadjustment_dateYYYY-MM-DD, optionalnote, optionalstore_id); the server reads the first worksheet and applies the same rules as JSON import. Each row must includetarget_quantityand eitherstock_idor a natural key:warehouse_idorwarehouse_name,item_idorproduct_name, optionalvariation_label(matchesitem_variations.variant_key). The server resolves the singlestocksrow per warehouse + item (+ variation) and creates adjustments so each line moves from current to target (rows in the same warehouse are grouped into one adjustment per warehouse). - Adjustments are created with a warehouse, date, optional reference (
reference_no, unique when set; otherwise the server assigns a numeric reference), optional note, and line items. The vendor UI sendsstock_idon each line so the server resolves the exact stock row; legacy lines withoutstock_idresolve by warehouse + item (+ optional variation). On create, the API records who opened the vendor session:created_typeisvendor_ownerorvendor_employee(from the JWT), withcreator_sub/creator_emailand a name snapshot for list/detail and creator search — clients must not send these fields; the server sets them from the authenticated vendor user. - Internal transfers are created from a source warehouse to a different destination warehouse. The server validates both warehouses are active and accessible to the signed-in vendor, checks each source stock row has enough available quantity, writes
internal_tranfers/internal_tranfer_items, decreases the sourcestocks.available_qty, and increases or creates the destination stock row for the same item and variation. Warehouse detail shows a dedicated Internal transfers tab listing transfers where the warehouse is either source or destination. The adjustment and transfer forms both auto-add a product when the search input exactly matches an item or variation barcode. - Warehouse status can be toggled inline from the warehouse list. The UI PATCHes
status(1active /0inactive) throughPATCH /vendor/warehouses/:id. - Adjustments list is returned newest first (by
created_at, thenid). - Lots track batch-level inventory; see the lot form/detail pages for fields exposed in the UI.
- PO ↔ goods receipt (partial receive):
lots/lot_itemsremain the purchase order (unit_quantity= ordered,received_qtycumulative).goods_receipts/goods_receipt_itemspost one or more receives;lots.po_receive_statusis0open,1partial,2completed. From a pending lot detail, Receive stock opens a modal (fixed header/footer, scrollable lines). If a PO is partial and vendor will not deliver the remaining quantity, Mark delivered now requires an explicit short-delivery close confirmation; this closes PO without auto-receiving the remaining lines. Migrations:1783681000000-GoodsReceiptsAndPoReceiveTracking, backfill1783691000000-BackfillLotReceivedQtyFromDelivered. - Purchase unit price entry: in Lots / Purchase order form,
unit_pricestarts empty (0) and is manual. The form does not auto-fill final supplier price from catalog defaults/history; use estimate/final invoice values when vendor confirms. On a pending lot detail page, Enter supplier prices opens a modal to set every line’sunit_priceviaPATCH /vendor/lots/:id/prices(recalculatestotalAmount). - Purchase payment expense: marking a lot delivered (
PATCH /vendor/lots/:id/status) or completing receive via goods receipt (PO auto-closes when fully received) creates the firstpurchase_paymentexpense when payable amount > 0 and none exists yet. Payable amount uses received qty × unit price when any line hasreceived_qty> 0; otherwise lottotalAmount. - Low stock → auto draft purchase (background): when
items.auto_add_purchase_on_low_stock = 1(product form Grocery / retail), a backend cron every 30 minutes (Nest@Cron, default0 */30 * * * *) re-syncs aggregateditems.stockper active store, finds SKUs in the low-stock band, and appends missing lines (qty 1, purchase unit = Receiving unit when a conversion row hasis_default_import_unit, otherwise the product base unit; unit price from last purchase / catalog cost scaled to that unit) to the store’s draft lot whosenoteisauto_low_stock_purchase. The lot form Add low-stock items uses the same default unit rule. Vendors still finalize that draft from Inventory → Lots. Disable withCRON_LOW_STOCK_AUTO_PURCHASE_ENABLED=falsewhenDB_ENABLED=true. - Lot delivered → purchase cost history: when a lot status moves to delivered (
PATCH /vendor/lots/:id/status), the backend updates stock and appendsitem_cost_price_eventsper line (supplier, unit cost, lot reference). Vendors see the timeline on the product detail tab Purchase cost history. See Price & cost tracking in the catalog guide. - Lot delivered → price approval batch: on the same delivered transition, the backend may create one price approval batch (
source=purchase, linked bysource_lot_id) with pending proposals per line where an active pricing rule suggests catalog retail/wholesale prices from the receipt unit cost. Review batches under Products → Price approval; open a batch to approve lines, then Apply so approved catalog retail prices update the product and appear in Selling price history (same audit trail as manual catalog edits).
Stock model (source of truth)
Section titled “Stock model (source of truth)”Warehouse rows in stocks are authoritative; items.stock is a derived cache (default-warehouse aggregate today, not a second place to “fix” quantity). Conversion-aware aggregation and column removal are planned in later phases — see Inventory stock model (ADR).
Related
Section titled “Related”- Inventory stock model (ADR) — SSOT, virtual vs cache, conversion units, roadmap.
- Catalog and products — items, SKUs, and price/cost history.
- Vendor panel overview — module
inventory.