The inventory module in src/cluster_erp/modules/inventory/ keeps the stock of the company. It holds warehouses and storage locations, one stock level row per product, model, version, place, batch, owning project, and custodian, and the goods movements that change those rows. It also manages stock reservations, usage decisions on received goods, project stock loans, batches, and serial numbers. Purchasing, production, nesting, and sales post their stock changes to this module through the EventBus.
At a glance¶
| Item | Value | Source |
|---|---|---|
| Package | src/cluster_erp/modules/inventory/ |
folder |
| Module class | Module |
src/cluster_erp/modules/inventory/module.py:16 |
name / version |
inventory / 0.1.0 |
src/cluster_erp/modules/inventory/module.py:17-23 |
| API prefix | /api/v1/inventory (ModuleBase.api_prefix) |
src/cluster_erp/core/module_base.py:37-38 |
| Sub-modules | 3: warehouses, stock, batches |
folder listing |
| Tables | 9: inventory_warehouses, inventory_storage_locations, inventory_stock_levels, inventory_goods_movements, inventory_usage_decisions, inventory_stock_reservations, inventory_project_stock_loans, inventory_batches, inventory_serial_numbers |
__tablename__ in the three models.py files |
| Entities in metadata | 14 keys in entities |
src/cluster_erp/modules/inventory/module.py:628-643 |
| Routes | 51 route decorators in the package | grep for @...router.get/post/put/patch/delete |
| Actions | 16 ActionMeta entries |
grep "ActionMeta(" in the package |
| Migrations | 17 files | files in migrations/versions/ whose text contains one of the nine table names |
| Web app presence | Catalogue app inventory; the custom widget warehouse_dashboard from a gated bundle |
src/cluster_erp/modules/app_store/catalog.json (entry inventory), gated_apps/inventory/app.js:170 |
| Desktop presence | src/cluster_erp/desktop/modules/inventory/ (warehouse_dashboard.py) |
folder |
What the module does¶
- Keeps warehouses with a code that is unique among live rows, and their storage locations (
src/cluster_erp/modules/inventory/warehouses/models.py:13-71). - Keeps stock per product, model, and version. The quantity is split into six categories: unrestricted, reserved, quality, blocked, blocked at goods receipt, and returns (
src/cluster_erp/modules/inventory/stock/models.py:25-123). - Posts goods movements of 13 types and writes one movement row per change (
src/cluster_erp/modules/inventory/stock/services.py:78-92). - Reserves unrestricted stock for a reference and later consumes or cancels the reservation (
src/cluster_erp/modules/inventory/stock/services.py:1901-2080). - Records a usage decision on a received quantity and posts the follow-up movement (
src/cluster_erp/modules/inventory/stock/services.py:1496-1666). - Moves stock between a project stripe and common stock, and lends stock between stripes with a payback (
src/cluster_erp/modules/inventory/stock/services.py:542-800). - Tracks batches with origin, expiry, and status, and serial numbers with status (
src/cluster_erp/modules/inventory/batches/models.py:14-125). - Serves a warehouse dashboard read model (
src/cluster_erp/modules/inventory/warehouses/services.py:145).
Sub-modules¶
| Sub-module | Purpose | Main entities | Router | Source |
|---|---|---|---|---|
warehouses |
Warehouses, storage locations, dashboard | Warehouse, StorageLocation |
/warehouses, /locations |
src/cluster_erp/modules/inventory/warehouses/routes.py:33-34 |
stock |
Stock levels, movements, reservations, usage decisions, loans | StockLevel, GoodsMovement, StockReservation, UsageDecision, ProjectStockLoan |
/stock, /movements, /reservations, /usage-decisions |
src/cluster_erp/modules/inventory/stock/routes.py:47-53 |
batches |
Batches and serial numbers | Batch, SerialNumber |
/batches, /serial-numbers |
src/cluster_erp/modules/inventory/batches/routes.py:33-34 |
All routers are mounted under the module prefix with the app entitlement dependency require_app("inventory") (src/cluster_erp/modules/inventory/module.py:52-62).
Data model¶
| Entity | Table | Key fields | Notes | Source |
|---|---|---|---|---|
Warehouse |
inventory_warehouses |
code, name, address, is_active, extra |
code unique among live rows |
src/cluster_erp/modules/inventory/warehouses/models.py:13-46 |
StorageLocation |
inventory_storage_locations |
warehouse_id, code, name, zone, is_active, extra |
src/cluster_erp/modules/inventory/warehouses/models.py:48-71 |
|
StockLevel |
inventory_stock_levels |
product_id, product_model_id, product_version_id, warehouse_id, storage_location_id, batch_id, owner_project_id, custodian_party_id, six quantity columns |
one live row per composite key | src/cluster_erp/modules/inventory/stock/models.py:25-123 |
GoodsMovement |
inventory_goods_movements |
movement_no, movement_type, product breakdown, warehouse_id, to_warehouse_id, qty, batch_id, serial_number_id, owner_project_id, reference_type, reference_id, waybill_no, posted_at, posted_by, is_reversed, original_movement_id |
movement_no unique |
src/cluster_erp/modules/inventory/stock/models.py:125-268 |
UsageDecision |
inventory_usage_decisions |
decision_no, movement_id, product breakdown, qty, source_stock_type, decision_code, target_stock_type, follow_up_movement_id |
src/cluster_erp/modules/inventory/stock/models.py:270-345 |
|
StockReservation |
inventory_stock_reservations |
product breakdown, warehouse_id, qty, reference_type, reference_id, status (default active), consumed_at, cancelled_at |
src/cluster_erp/modules/inventory/stock/models.py:347-413 |
|
ProjectStockLoan |
inventory_project_stock_loans |
loan_no, lender_project_id, borrower_project_id, product breakdown, qty, qty_paid, status (default open), due_date |
src/cluster_erp/modules/inventory/stock/models.py:415-474 |
|
Batch |
inventory_batches |
product breakdown, batch_number, origin_type, party_id, expiry_date, status |
batch_number unique per product among live rows |
src/cluster_erp/modules/inventory/batches/models.py:14-76 |
SerialNumber |
inventory_serial_numbers |
product breakdown, serial_number, batch_id, status |
serial_number unique among live rows |
src/cluster_erp/modules/inventory/batches/models.py:78-125 |
The stock key always carries the product breakdown. The partial unique index uq_stock_level_composite covers product, model, version, and warehouse, plus location, batch, owning project, and custodian with NULL coalesced, on live rows only (src/cluster_erp/modules/inventory/stock/models.py:27-47). Quantities are Numeric(14, 4) (src/cluster_erp/modules/inventory/stock/models.py:91-122). The module stores no money column; the analytics docstring states this (src/cluster_erp/modules/inventory/analytics_facts.py:17-18). The product breakdown is checked through the EventBus with ensure_product_breakdown, not by database foreign keys (src/cluster_erp/core/product_breakdown.py:86, src/cluster_erp/modules/inventory/stock/services.py:370-376).
Screens and metadata¶
get_metadata declares a sidebar group Inventory (Turkish Envanter) with eight sections: Warehouse Dashboard (custom widget warehouse_dashboard), Warehouses, Stock Levels, Goods Movements, Batches, Serial Numbers, Usage Decisions, and Stock Reservations (src/cluster_erp/modules/inventory/module.py:536-626). The 14 entity keys include drill-down views such as warehouse_stock_level, batch_movement, and product_stock_level (src/cluster_erp/modules/inventory/module.py:628-643). The web dashboard calls one read model, GET /inventory/warehouses/dashboard (gated_apps/inventory/app.js:7-11, src/cluster_erp/modules/inventory/warehouses/routes.py:99). The web app renders the rest generically; see generic list and gated app bundles. The desktop dashboard is described on desktop module screens.
Status and actions¶
Stock has no status field; movements change quantities. A reservation is active, consumed, or cancelled (src/cluster_erp/modules/inventory/stock/services.py:1950, src/cluster_erp/modules/inventory/stock/services.py:2015, src/cluster_erp/modules/inventory/stock/services.py:2068). A movement carries is_reversed (src/cluster_erp/modules/inventory/stock/models.py:253-257).
| Action | Label | Condition | Endpoint | Permission | Source |
|---|---|---|---|---|---|
goods_receipt |
Goods Receipt | none | POST /stock/goods-receipt |
goods_movement.create |
src/cluster_erp/modules/inventory/stock/metadata.py:675-770, src/cluster_erp/modules/inventory/stock/routes.py:156-183 |
goods_issue |
Goods Issue | none | POST /stock/goods-issue |
goods_movement.create |
src/cluster_erp/modules/inventory/stock/metadata.py:771-866 |
subcontract_issue, subcontract_return, subcontract_consume |
Issue to Subcontractor, Return from Subcontractor, Consume at Subcontractor | none | POST /stock/subcontract-issue, /subcontract-return, /subcontract-consume |
goods_movement.create |
src/cluster_erp/modules/inventory/stock/metadata.py:867-1004, src/cluster_erp/modules/inventory/stock/routes.py:598-633 |
transfer |
Transfer | none | POST /stock/transfer |
goods_movement.create |
src/cluster_erp/modules/inventory/stock/metadata.py:1005-1083 |
adjustment |
Adjustment | none | POST /stock/adjustment |
goods_movement.create |
src/cluster_erp/modules/inventory/stock/metadata.py:1084-1139 |
gr_blocked_receipt |
GR Blocked Receipt | none | POST /stock/gr-blocked-receipt |
goods_movement.create |
src/cluster_erp/modules/inventory/stock/metadata.py:1140-1220 |
stock_category_transfer |
Category Transfer | none | POST /stock/stock-category-transfer |
goods_movement.create |
src/cluster_erp/modules/inventory/stock/metadata.py:1221-1301 |
return_to_vendor |
Return to Vendor | none | POST /stock/return-to-vendor |
goods_movement.create |
src/cluster_erp/modules/inventory/stock/metadata.py:1302-1390 |
scrap |
Scrap | none | POST /stock/scrap |
goods_movement.create |
src/cluster_erp/modules/inventory/stock/metadata.py:1391-1456 |
reverse |
Reverse | is_reversed is false |
POST /movements/{id}/reverse |
goods_movement.create |
src/cluster_erp/modules/inventory/stock/metadata.py:1457-1472 |
create_reservation |
Create Reservation | none | POST /reservations/ |
stock_reservation.create |
src/cluster_erp/modules/inventory/stock/metadata.py:1593-1639 |
consume, cancel |
Consume, Cancel | status is active |
POST /reservations/{id}/consume, /cancel |
stock_reservation.create |
src/cluster_erp/modules/inventory/stock/metadata.py:1640-1661 |
create_usage_decision |
Record Usage Decision | none | POST /usage-decisions/ |
usage_decision.create |
src/cluster_erp/modules/inventory/stock/metadata.py:2633-2698, src/cluster_erp/modules/inventory/stock/routes.py:579-597 |
The ActionMeta class has no permission field; the permission comes from the route's require_permission (src/cluster_erp/core/metadata.py:418-434). The routes for ownership transfer and project loans have no ActionMeta (src/cluster_erp/modules/inventory/stock/routes.py:217-289).
Business rules¶
- Every posting first asks
GetProductByIdand refuses a product that is not stockable (src/cluster_erp/modules/inventory/stock/services.py:2119-2134). - A batch-managed product needs a batch, and a serial-tracked product needs a serial number. A blocked or expired batch is refused (
src/cluster_erp/modules/inventory/stock/services.py:2136-2193). - A goods receipt adds to the unrestricted quantity of the matching stock row and creates the row when it is missing. The row is read with a row lock (
src/cluster_erp/modules/inventory/stock/services.py:2195-2286). - A goods receipt that names a purchase order as reference is refused; purchase order receipts go through the procurement receive flow (
src/cluster_erp/modules/inventory/stock/routes.py:166-178). - A decrease takes the requested quantity from the given project stripe. A goods issue may take the rest from common stock and then writes one movement per stripe; the other decreases refuse a shortfall (
src/cluster_erp/modules/inventory/stock/services.py:2288-2363,src/cluster_erp/modules/inventory/stock/services.py:473-490). - A transfer between two project stripes is refused unless the setting
allow_project_to_project_stock_transferis on (src/cluster_erp/modules/inventory/stock/services.py:552-566). - A project loan needs two different sides, at least one of them a project (
src/cluster_erp/modules/inventory/stock/services.py:615-640). - An adjustment sets the unrestricted quantity to the new value and records the difference as the movement quantity (
src/cluster_erp/modules/inventory/stock/services.py:1051-1100). - Stock categories move only along nine allowed pairs, for example
gr_blockedtounrestrictedorqualitytoblocked(src/cluster_erp/modules/inventory/stock/services.py:112-122). - A usage decision applies only to a goods receipt or a blocked goods receipt, and not beyond the undecided quantity.
acceptandconditional_acceptmove to unrestricted,rejectreturns to the vendor,reworkmoves to blocked, andscrapposts a scrap movement (src/cluster_erp/modules/inventory/stock/services.py:124-135,src/cluster_erp/modules/inventory/stock/services.py:1496-1625). - A reservation moves quantity from unrestricted to reserved. Consuming removes it from reserved; cancelling returns it to unrestricted (
src/cluster_erp/modules/inventory/stock/services.py:1901-2080). - A movement can be reversed once, and a reversal movement cannot be reversed (
src/cluster_erp/modules/inventory/stock/services.py:1730-1760). - Batch origin is
customer,supplier, orinternal(src/cluster_erp/modules/inventory/batches/services.py:35). - Movement numbers come from the yearly sequence
goods_movement, decisions fromusage_decision, and loans fromstock_loan(src/cluster_erp/modules/inventory/stock/services.py:383).
Events and queries¶
The module consumes 14 contracts and produces 20, counted as on the parties module page. The full list is on the contracts reference.
| Direction | Contract class | Handler or emitter | Source |
|---|---|---|---|
| emits | GoodsReceived, GoodsIssued, StockTransferred, StockAdjusted, GrBlockedReceived, StockCategoryTransferred, ReturnedToVendor, ScrapPosted, UsageDecisionRecorded, GoodsMovementReversed |
the posting methods of StockService |
src/cluster_erp/modules/inventory/stock/services.py:431-447, src/cluster_erp/modules/inventory/stock/services.py:527-1879 |
| emits | StockReserved, StockReservationConsumed, StockReservationCancelled |
reservation methods | src/cluster_erp/modules/inventory/stock/services.py:1957, src/cluster_erp/modules/inventory/stock/services.py:2020, src/cluster_erp/modules/inventory/stock/services.py:2073 |
| emits | BatchCreated |
BatchService.create |
src/cluster_erp/modules/inventory/batches/services.py:135 |
| answers query | GetStockLevel, GetWarehouseById, GetBulkStockLevels, GetConsumptionHistory, GetBatchExpirySchedule, GetBatchesByProduct, IsStockAvailable |
handlers in module.py |
src/cluster_erp/modules/inventory/module.py:86-110 |
| answers query | ReserveStock, ConsumeReservation, CancelReservation, PostGoodsReceipt, PostGoodsIssue |
handlers in module.py |
src/cluster_erp/modules/inventory/module.py:111-115, src/cluster_erp/modules/inventory/module.py:392-495 |
| subscribes | DescribeFieldConsumers |
_describe_field_consumers |
src/cluster_erp/modules/inventory/module.py:68, src/cluster_erp/modules/inventory/module.py:646-696 |
| subscribes | DescribeAnalyticsFacts |
describe_analytics_facts |
src/cluster_erp/modules/inventory/module.py:119-124 |
The query handlers for reservations and postings commit their own session (src/cluster_erp/modules/inventory/module.py:410, src/cluster_erp/modules/inventory/module.py:465, src/cluster_erp/modules/inventory/module.py:494). The field-consumer answer marks the product flag is_stockable as a hard dependency and batch_managed and serial_tracked as soft ones (src/cluster_erp/modules/inventory/module.py:646-696); see tenant fields.
Permissions¶
| Permission key | Used by | Source |
|---|---|---|
warehouse.read, .create, .update, .delete |
warehouse routes | src/cluster_erp/modules/inventory/warehouses/routes.py:73-149 |
storage_location.read, .create, .update, .delete |
location routes and the warehouse location list | src/cluster_erp/modules/inventory/warehouses/routes.py:168-274 |
stock_level.read |
stock level, availability, and loan reads | src/cluster_erp/modules/inventory/stock/routes.py:69-136, src/cluster_erp/modules/inventory/stock/routes.py:273 |
goods_movement.read, goods_movement.create |
movement reads and all postings | src/cluster_erp/modules/inventory/stock/routes.py:163-437, src/cluster_erp/modules/inventory/stock/routes.py:601-627 |
stock_reservation.read, stock_reservation.create |
reservation routes | src/cluster_erp/modules/inventory/stock/routes.py:460-520 |
usage_decision.read, usage_decision.create |
usage decision routes | src/cluster_erp/modules/inventory/stock/routes.py:540-586 |
batch.*, serial_number.* |
batch and serial routes | src/cluster_erp/modules/inventory/batches/routes.py:58-217 |
Privacy, retention, and history¶
The module has no privacy_handlers.py. Warehouse, location, batch, and serial deletes set deleted_at (src/cluster_erp/modules/inventory/warehouses/services.py:137-143, src/cluster_erp/modules/inventory/batches/services.py:178-184). Movements have no delete route; a wrong movement is reversed by a counter movement (src/cluster_erp/modules/inventory/stock/routes.py:376-450, src/cluster_erp/modules/inventory/stock/services.py:1730). Changes are captured by change history.
Analytics facts¶
analytics_facts.py contributes one fact set, goods_movement, over inventory_goods_movements. Its dimensions are movement type, product, warehouse, party, reference type, reversal flag, and posting date. Its measures are movement_count and total_qty, read with goods_movement.read (src/cluster_erp/modules/inventory/analytics_facts.py:29-104). Stock levels are not contributed; the docstring gives the reason (src/cluster_erp/modules/inventory/analytics_facts.py:1-8). See analytics facts.
Settings and reference data¶
The module has no settings section. The project-to-project transfer switch is the server setting allow_project_to_project_stock_transfer, default False, read through Settings (src/cluster_erp/config/settings.py:184, src/cluster_erp/modules/inventory/stock/services.py:561); see settings reference.
Tests¶
Found by a grep for modules.inventory in tests/ (no hit) and for the path /inventory/ in web-client/_e2e_*.mjs.
| Kind | File | Covers |
|---|---|---|
| e2e | web-client/_e2e_inventory_app.mjs |
the inventory app |
| e2e | web-client/_e2e_procurement_gr.mjs |
goods receipt from purchase orders |
| e2e | web-client/_e2e_project_stock_phase2a.mjs, web-client/_e2e_project_pegging_phase2b.mjs |
project stock stripes and pegging |
| e2e | web-client/_e2e_make_or_buy_stock.mjs |
cut-from-stock issues |
| e2e | web-client/_e2e_mrp_hygiene_faz0.mjs and the other _e2e_mrp_* suites |
stock read by the MRP run |
| e2e | web-client/_e2e_unique_soft_delete.mjs |
live-row uniqueness |
Discrepancies¶
Terms introduced¶
| Term | Code identifier | Definition | Turkish term | Source |
|---|---|---|---|---|
| stock level | StockLevel |
The stock quantities of one product breakdown at one place, batch, owning project, and custodian. | Stok seviyesi | src/cluster_erp/modules/inventory/stock/models.py:25 |
| goods movement | GoodsMovement |
One posted change of stock with a type, a quantity, and a reference. | Mal hareketi | src/cluster_erp/modules/inventory/stock/models.py:125 |
| stock category | _VALID_STOCK_CATEGORIES |
One of the six quantity buckets of a stock level. | Stok kategorisi | src/cluster_erp/modules/inventory/stock/services.py:94-101 |
| usage decision | UsageDecision |
The decision on a received quantity that moves it to a target category or out of stock. | Kullanım kararı | src/cluster_erp/modules/inventory/stock/models.py:270 |
| stock reservation | StockReservation |
Unrestricted stock set aside for a reference. | Stok rezervasyonu | src/cluster_erp/modules/inventory/stock/models.py:347 |
| project stripe | owner_project_id |
The part of a stock level owned by one project; NULL is common stock. |
Proje şeridi | src/cluster_erp/modules/inventory/stock/models.py:79 |
| project stock loan | ProjectStockLoan |
Stock lent from one stripe to another with a payback obligation. | Proje stok ödüncü | src/cluster_erp/modules/inventory/stock/models.py:415 |
Related pages¶
- Modules index
- Procurement module
- Production module
- Product breakdown and stock conversion
- Soft delete and deactivation
- API reference for this module
Verified against commit 92f8051 on 2026-11-13.