Bỏ qua để đến nội dung

Inventory (Vendor)

Nội dung này hiện chưa có sẵn bằng ngôn ngữ của bạn.

Inventory spans warehouses, stocks, lots, adjustments, and internal transfers under /dashboard/inventory/*. Module inventory applies; some URLs redirect (e.g. warehouse “new”).


GoalsNon-goals
Track stock truth for the selected storeReplace barcode warehouse automation
Document route quirks (redirects)Teach FIFO/LIFO accounting standards

  • Route matrix in table below; warehouses/new may redirect per comment in doc.
  • Use StoreSelector context consistently before adjustments and internal transfers.

  1. Open Inventory subtree → warehouses / stocks as needed.
  2. Create adjustment with reason per SOP, or create internal transfer from the warehouse action when moving stock between warehouses.
  3. Verify stock levels on stocks detail.

  • Adjustments are fraud-sensitive—dual control offline if policy requires.

IDScenarioExpected
I1Employee with inventoryInventory routes open
I2Adjustment savedStock reflects after refresh
I3Internal transfer savedSource warehouse stock decreases and destination warehouse stock increases
I4Without moduleBlocked

RouteNotes
/dashboard/inventory/warehousesWarehouse list
/dashboard/inventory/warehouses/newRedirects to /dashboard/inventory/warehouses (no standalone create URL)
/dashboard/inventory/warehouses/:idWarehouse detail; tabs include overview, stocks, lots, adjustments, and internal transfers
/dashboard/inventory/stocks, /dashboard/inventory/stocks/:idStock list and detail
/dashboard/inventory/lots, /dashboard/inventory/lots/new, /dashboard/inventory/lots/:id/edit, /dashboard/inventory/lots/:idPurchase lots (draft edit at …/edit)
/dashboard/inventory/lots/:id?receive=1Opens Receive stock modal on purchase detail (legacy /goods-receipts/receive?lot_id= redirects here)
/dashboard/inventory/adjustments, adjustments/new, adjustments/:idStock adjustments
/dashboard/inventory/internal-transfers/newInternal transfer form; warehouse actions can preselect source via warehouseId query

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.

Services live under apps/vendor-web/src/services/:

AreaServiceEndpoints (representative)
Summary KPIsvendor-inventory.api.tsGET /vendor/inventory/summary — counts warehouses, stock records, lots, adjustments, low-stock
Warehousesvendor-warehouses.api.tsGET /vendor/warehouses/list, GET /vendor/warehouses/:id, POST /vendor/warehouses, PATCH /vendor/warehouses/:id, DELETE /vendor/warehouses/:id
Stocksvendor-stocks.api.tsGET /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
Lotsvendor-lots.api.tsGET /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 receiptsvendor-goods-receipts.api.tsGET /vendor/lots/:id/receivable-items, POST /vendor/goods-receipts
Adjustmentsvendor-adjustments.api.tsGET /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 transfersvendor-internal-transfers.api.tsGET /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.

  • Stocks Excel: export downloads .xlsx with stock_id, warehouse and product identifiers, SKU, current_quantity, and target_quantity (initially equal to current). Edit target_quantity to the desired on-hand count, then import. The dashboard uploads the file to POST /vendor/stocks/import-excel (multipart field file, plus adjustment_date YYYY-MM-DD, optional note, optional store_id); the server reads the first worksheet and applies the same rules as JSON import. Each row must include target_quantity and either stock_id or a natural key: warehouse_id or warehouse_name, item_id or product_name, optional variation_label (matches item_variations.variant_key). The server resolves the single stocks row 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 sends stock_id on each line so the server resolves the exact stock row; legacy lines without stock_id resolve by warehouse + item (+ optional variation). On create, the API records who opened the vendor session: created_type is vendor_owner or vendor_employee (from the JWT), with creator_sub / creator_email and 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 source stocks.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 (1 active / 0 inactive) through PATCH /vendor/warehouses/:id.
  • Adjustments list is returned newest first (by created_at, then id).
  • Lots track batch-level inventory; see the lot form/detail pages for fields exposed in the UI.
  • PO ↔ goods receipt (partial receive): lots / lot_items remain the purchase order (unit_quantity = ordered, received_qty cumulative). goods_receipts / goods_receipt_items post one or more receives; lots.po_receive_status is 0 open, 1 partial, 2 completed. 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, backfill 1783691000000-BackfillLotReceivedQtyFromDelivered.
  • Purchase unit price entry: in Lots / Purchase order form, unit_price starts 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’s unit_price via PATCH /vendor/lots/:id/prices (recalculates totalAmount).
  • 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 first purchase_payment expense when payable amount > 0 and none exists yet. Payable amount uses received qty × unit price when any line has received_qty > 0; otherwise lot totalAmount.
  • 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, default 0 */30 * * * *) re-syncs aggregated items.stock per active store, finds SKUs in the low-stock band, and appends missing lines (qty 1, purchase unit = Receiving unit when a conversion row has is_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 whose note is auto_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 with CRON_LOW_STOCK_AUTO_PURCHASE_ENABLED=false when DB_ENABLED=true.
  • Lot delivered → purchase cost history: when a lot status moves to delivered (PATCH /vendor/lots/:id/status), the backend updates stock and appends item_cost_price_events per 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 by source_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).

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).