Inventory
Read Conventions first.
stock_movements
The stock ledger. Balance = SUM(quantity) for an item+warehouse; negatives are issues.
id,business_id,item_idNOT NULL,warehouse_idNOT NULLquantitynumeric(14,3) NOT NULL - signedunit_costnumeric(12,2)reference_typeNOT NULL;reference_iduuid NOT NULL - polymorphic link to the source document (no FK)bin_location_id→ bin_locations NULL;created_at- Indexes:
(item_id, warehouse_id),(business_id, item_id)
Batch & serial tracking
- stock_movement_batch_allocations - which batch/serial a movement drew from.
id,stock_movement_idCASCADE;item_batch_idNULL,item_serial_idNULL - CHECK: exactly one of the two is set;quantity,created_at - item_batches -
id,business_id,item_id,warehouse_id,batch_number- UNIQUE (business_id, item_id, warehouse_id, batch_number);expiry_dateNULL;unit_cost;quantity_received,quantity_remainingNOT NULL;reserved_quantityDEFAULT 0;received_date,bin_location_id;reference_type/reference_idpolymorphic origin;created_at - item_serials -
id,business_id,item_id,warehouse_id,serial_number- UNIQUE (business_id, item_id, serial_number);unit_cost;statusDEFAULT ‘in_stock’ CHECK IN (‘in_stock’,‘consumed’);received_date,bin_location_id,reference_type,reference_id,created_at - batch_reservations -
id,business_id;item_batch_idCASCADE NULL /item_serial_idCASCADE NULL - CHECK: exactly one;quantity;reference_type,reference_id,reference_line_idall NOT NULL;created_by,created_at; partial UNIQUE index on(item_serial_id) WHERE item_serial_id IS NOT NULL- a serial can be reserved once
Non-PO goods movements
- goods_receipts - plain (non-PO) goods receipt.
id,business_id,document_noUNIQUE (business_id, document_no);branch_idNOT NULL,warehouse_idNOT NULL,receipt_date,reference,notes;statusCHECK IN (‘draft’,‘posted’,‘cancelled’); posted/cancelled stamps,created_by,created_at - goods_receipt_lines -
id,goods_receipt_idCASCADE,item_idNOT NULL,gl_account_idNOT NULL;quantity,unit_cost,line_total;batch_number,expiry_date,serial_numbers,admission_date;uom_id,cost_center_id,created_at - goods_issues - same shape as goods_receipts plus
reason;issue_dateinstead ofreceipt_date; UNIQUE (business_id, document_no) - goods_issue_lines -
id,goods_issue_idCASCADE,item_id,gl_account_id,quantity,unit_cost,line_total,uom_id,cost_center_id,created_at
Transfers
- inventory_transfer_requests - internal or external (inter-branch) transfer request.
id,business_id,document_noUNIQUE (business_id, document_no);from_branch_idNOT NULL,to_branch_idNOT NULL;from_warehouse_idNOT NULL,to_warehouse_idnullable;is_externalboolean DEFAULT false;request_date,journal_remarks,remarks;statusCHECK IN (‘open’,‘closed’,‘cancelled’); closed/cancelled stamps,created_by,created_at - inventory_transfer_request_lines -
id,inventory_transfer_request_idCASCADE,item_id,quantity,uom_id;delivered_quantityDEFAULT 0;row_statusCHECK IN (‘open’,‘closed’);created_at - inventory_transfers - carries a full logistics status chain.
id,business_id,document_noUNIQUE (business_id, document_no);from_branch_id/to_branch_idNOT NULL,from_warehouse_idNOT NULL,to_warehouse_idnullable;is_external;source_request_idNULL;transfer_date,reference,notes,vehicle_number;statusDEFAULT ‘draft’ - CHECK IN (‘draft’,‘picked’,‘loaded’,‘in_transit’,‘posted’,‘closed’,‘cancelled’); stage stamps:picked_at/picked_by,loaded_at/loaded_by,in_transit_at/in_transit_by,posted_at/posted_by,closed_at/closed_by,cancelled_at/cancelled_by;created_by,created_at - inventory_transfer_lines -
id,inventory_transfer_idCASCADE,item_id,quantity,uom_id;received_quantityNULL;batch_allocationsjsonb NOT NULL DEFAULT ‘[]’;source_request_line_id→ inventory_transfer_request_lines RESTRICT;created_at
Opening balances
- inventory_opening_balances -
id,business_id,document_noUNIQUE (business_id, document_no);branch_idNOT NULL,warehouse_idNOT NULL,balance_date,reference,notes;statusCHECK IN (‘draft’,‘posted’,‘cancelled’); posting/cancel stamps;created_by,created_at - inventory_opening_balance_lines -
id,inventory_opening_balance_idCASCADE,item_id,gl_account_id;quantity,unit_cost,line_total;batch_number,expiry_date,serial_numbers;cost_center_id,created_at
Counting (two-document split)
The Counting document freezes stock when confirmed; a separate Inventory Posting document is what actually adjusts stock.
- inventory_counts -
id,business_id,document_noUNIQUE (business_id, document_no);branch_idNOT NULL,warehouse_idNOT NULL,count_date,reference,notes;statusDEFAULT ‘draft’ - CHECK IN (‘draft’,‘confirmed’,‘posted’,‘cancelled’); posting/cancel stamps;created_by,created_at - inventory_count_lines -
id,inventory_count_idCASCADE,item_id,gl_account_id;counted_quantityNOT NULL;is_frozenboolean DEFAULT true (blocks other stock-moving documents touching this item+warehouse);frozen_quantityDEFAULT 0 - the system quantity captured at line-creation time; the variance baseline, not recomputed at post time;unit_cost,cost_center_id,created_at - inventory_postings - copied from a confirmed count; posting it creates the stock/GL adjustment and releases the freeze.
id,business_id,document_noUNIQUE (business_id, document_no);inventory_count_idNOT NULL;branch_idNOT NULL,warehouse_idNOT NULL,posting_date,reference,notes;statusCHECK IN (‘draft’,‘posted’,‘cancelled’); posting/cancel stamps;created_by,created_at - inventory_posting_lines -
id,inventory_posting_idCASCADE,inventory_count_line_idNOT NULL;item_id,gl_account_id;counted_quantity,system_quantity,varianceNOT NULL;unit_cost,cost_center_id,created_at