04 · Inventory — items, stores, and stock balances
Updated
Solution layer. Depends on Organization for sites. The model stores arbitrary items and store logistics by the MIQS model Store → Zone → Cell. It owns the Qty EDT and the Store, Cell, Item, Customer, and CustomerOutlet analytics.
Purpose#
Inventory covers items (raw materials, semi-finished goods, finished goods, and merchandise), units of measure and item packs, counterparties (Supplier/Customer plus contacts and outlets), pricing (PriceType), stores (Store) → zones (StoreZone) → cells (StoreCell) with cell types and roles, the Stock register with balances by cell, and optional store discipline for receiving → storage → picking. Store discipline is off by default. The model also provides dictionaries and attributes, such as customer type and item tax category, that Sales and Purchasing use to build the tax context.
The process description is in Store and stock. This page does not repeat it. It lists exact object names, field names, and links.
What is created#
| Kind | Name | Purpose |
|---|---|---|
| Model | Inventory |
Module model on the Solution layer |
| NumberSequence | 21 sequences; see Number sequences | Numbering |
| EDT | Qty (scalar) + 12 Ref EDTs; see EDT |
Types |
| Dictionary | ItemGroup, Item, Brand, ItemUnit, PriceType, Supplier, Customer, CustomerContact, CustomerOutlet, Store, StoreZone, StoreCellType, StoreCell, InventorySettings (singleton) |
Items, pricing, counterparties, stores, zones, cells, and settings |
| TablePart | PutAwayTaskLines, PickTaskLines, StockAdjustmentLines, StockTransferLines, StockCountLines, GoodsIssueLines |
WMS document lines |
| Document | PutAwayTask, PickTask, StockAdjustment, StockTransfer, StockCount, GoodsIssue |
Movements, stock count, and issue |
| Register | Stock |
Balances by cell |
| Analytic | Store, Cell, Item, Customer, CustomerOutlet |
Register sections |
| Service | IStoreCellService |
Cell resolution and store discipline |
| Script | tx (PutAwayTaskTx/PickTaskTx/StockAdjustmentTx/StockTransferTx/StockCountTx/GoodsIssueTx), commands (ConfirmPutAway/ConfirmPick/PostStockAdjustment/PostStockTransfer/PostStockCount/PostGoodsIssue), dictionary and document events |
Postings, transitions, and validation |
| Menu | menu.json |
Module menu |
Reused from Common and the platform kernel |
UnitOfMeasure, UnitClass (Common), Division (Organization), Money (Accounting) |
Do not create them here |
Total: 1 model, 21 number sequences, 13 EDTs (1 scalar + 12 Ref), 14 dictionaries (one singleton settings dictionary), 6 table parts, 6 documents, 1 register, 5 analytics, 1 service, scripts, and menu entries.
Sales extends PickTask on top of Inventory. See PickTask for reservations.
model.json#
Solution(2) layer. Dependency: Organization. Money comes from Accounting, so the model also declares a dependency on Accounting.
Number sequences#
ItemSeq, ItemGroupSeq, BrandSeq, ItemUnitSeq, PriceTypeSeq, SupplierSeq, CustomerSeq, CustomerContactSeq, CustomerOutletSeq, StoreSeq, StoreZoneSeq, StoreCellTypeSeq, StoreCellSeq, InventorySettingsSeq, PutAwayTaskSeq, PickTaskSeq, StockAdjustmentSeq, StockTransferSeq, StockCountSeq, GoodsIssueSeq.
The sequence files still contain UnitConversionSeq, but the model no longer has a UnitConversion object. Units were rebuilt on UnitClass and RatioToBase. See Store and stock. The sequence is an unused tail.
EDT#
The model owns the scalar Qty EDT: Decimal(18,4). It also owns 12 reference EDTs: RefItemGroup, RefItem, RefBrand, RefPriceType, RefSupplier, RefCustomer, RefCustomerContact, RefCustomerOutlet, RefStore, RefStoreZone, RefStoreCellType, and RefStoreCell.
RefUnitOfMeasure is not part of Inventory. Units of measure and their EDTs belong to Common. Inventory only references them through Item.UnitOfMeasure.
Dictionaries#
ItemGroup#
Code String(16), required, unique · Name String(64), required. The description is a free text hint such as RawMaterial/SemiFinished/FinishedGood/Merchandise. It is not a closed list. Groups are records, not enum values.
Item#
Fields: see Item in Objects below.
| Field | Business note |
|---|---|
UnitOfMeasure |
Base unit for the item. |
Barcode |
Indexed. Used by the form scan widget. |
DefaultPurchasePrice |
Fallback when the supplier or customer has no PriceType. |
DefaultSalePrice |
Sale fallback price. |
TaxProductCategory |
Product/category key for TaxMapping with SourceType=Product/Category. |
Referenced by Purchasing, Production, and Sales lines; the Item analytic; Stock, Cost, and Revenue; and ItemUnit. In the UI, SKU is ID or Barcode; there is no separate SKU column.
Brand#
Name String(256), required. A flat dictionary without hierarchy. Referenced by optional field Item.Brand.
ItemUnit#
An item pack stores the factor from a specific item and unit to the item base unit. The factor belongs to the item + unit pair, not to the unit itself: one item may have 12 pieces per box, another item may have 6. Fields: see ItemUnit in Objects below.
| Field | Business note |
|---|---|
Unit |
Pack unit, for example BOX. |
QtyInBaseUnit |
Number of Item.UnitOfMeasure base units in one pack. |
ItemUnit is cached and logged. It is used by the ItemQuantityConverter implementation of IQuantityConverter on every quantity line. See Store and stock.
PriceType#
A price type applies to a transaction side. It is assigned by PriceType on Customer or Supplier and participates in price selection. Fields: see PriceType in Objects below.
| Field | Business note |
|---|---|
Direction |
Sale prices are never selected for supplier orders, and purchase prices are never selected for sales. |
Kind |
Base stores manually entered history through LT_PriceTypeHistory on the item card. Calculated derives from BasePriceType with MarkupPercent. |
BasePriceType |
Required for Kind=Calculated; must be empty for Kind=Base. The handler checks it. |
MarkupPercent |
May be negative for a discount. Must be 0 for Kind=Base. |
IsDisabled |
Disabled price types are excluded from selection. Empty means active. The field is named from the disabled case so the optional Boolean default does not disable every type. |
Supplier#
Fields: see Supplier in Objects below.
| Field | Business note |
|---|---|
TaxRegistrationNumber |
VAT number for the tax context. |
PriceType |
Purchase price type. Empty means item defaults are used. |
Referenced by Store.InventoryAgent and Purchasing.
Customer#
Fields: see Customer in Objects below.
| Field | Business note |
|---|---|
TaxRegistrationNumber |
Buyer VAT number. In KSA, this chooses clearance or reporting instead of CustomerType. |
CustomerType |
B2B/B2C commercial classification. It does not choose the KSA clearance/reporting branch. |
PriceType |
Sale price type. Empty means item defaults are used. |
CRM extends Customer with loyalty fields. The record is referenced by the Customer analytic, CustomerContact, CustomerOutlet, and Sales.
CustomerContact#
A customer contact person. It is not a shipping address; shipping addresses are CustomerOutlet records. Fields: see CustomerContact in Objects below.
Customer is indexed for contact lookup by customer.
CustomerOutlet#
Ship-to point of a customer: address and contact. Commercial terms live on SalesContract in Sales, not here. Fields: see CustomerOutlet in Objects below.
Customer is indexed for ship-to lookup by customer. CustomerOutlet is logged and is referenced by the CustomerOutlet analytic and by Sales.
Store#
Store (MIQS Store). Fields: see Store in Objects below.
| Field | Business note |
|---|---|
Division |
Site or office that owns the store. |
InventoryAgent |
Materially responsible counterparty or depot agent. |
IsSimple |
Marks a simple store without zones and cells. |
IsSalesPoint |
Marks a point of sale used by Sales. |
OnAfterSaveAsync: if store discipline is already enabled (InventorySettings.EnforceWarehouseTasks), a new store immediately gets its yard. See IStoreCellService. Referenced by StoreZone and the Store analytic.
StoreZone#
Store zone. It is cached and logged. Fields: see StoreZone in Objects below.
| Field | Business note |
|---|---|
Store |
Owner store. |
IsBarcodeTracking |
Enables barcode tracking in the zone. |
Referenced by StoreCell.
StoreCellType#
Cell type. It is cached. Fields: see StoreCellType in Objects below.
| Field | Business note |
|---|---|
Purpose |
Role of the type: Receiving, Storage, Picking, or Unspecified. The closed list replaces the legacy string-name check for Receiving; a typo in the type name no longer disables address discipline silently. |
A cell type with Purpose that does not match the required role is not allowed when discipline is enabled. Cells of that type fail the single IsCellAllowedForAsync guard. Referenced by StoreCell.Type.
StoreCell#
Store cell. It is logged. The address is zone → line → rack → shelf → cell. Fields: see StoreCell in Objects below.
| Field | Business note |
|---|---|
StoreZone |
The store is derived through StoreZone.Store. |
Type |
Cell role is read through Type.Purpose. |
IsPickable |
Hidden in the UI (isVisible:false). Code does not read it, because picking is determined by Type.Purpose = Picking. |
MaxQty |
Reference capacity value. Automatic validation does not use it. |
Referenced by the Cell analytic, Stock, and all WMS documents.
InventorySettings (singleton)#
One record, cannot be deleted, and opens directly for editing. Fields: see InventorySettings in Objects below.
| Field | Business note |
|---|---|
DefaultReceivingLocation |
Requires an already open cell. Configure it after the store. |
EnforceWarehouseTasks |
Empty means false: cells are free, and receipts/issues can use any cell directly without put-away or pick tasks. The field is intentionally named from the requirement, because the optional Boolean is generated as non-nullable and the working default must be off. |
FulfillmentScheme |
Only matters when EnforceWarehouseTasks is enabled. Details and a numeric example are in Store and stock. |
Enabling EnforceWarehouseTasks on existing data is safe and idempotent. OnAfterSaveAsync calls PrepareAllYardsAsync(). The service sets Purpose by role name and creates missing receiving, storage, and picking cells for each store. Repeating the call does not create duplicates.
Master data setup order#
Inventory objects have hard dependencies, where a field must point to an existing record, and soft dependencies, where you may leave a field empty and fill it later. If you skip a hard dependency, the next object is not saved. If you skip a soft dependency, the record is saved but some functions do not work until the field is filled.
ItemGrouprecords are item groups. They need no other records. You cannot create anItemwithout one, becauseItemGroupis required.Brandrecords are optional.Item.Brandis optional, so you can add brands later without changing existing items.Itemis the item master. It requiresItemGroupand referencesUnitOfMeasurefrom Common. If the base units do not exist in Common, create them there first.ItemUnitrecords are optional packs for a specific item. They requireItem. Missing packs, for examplebox = 12 pcs, simply are not available for that item and do not cause errors.PriceTypeis optional, but you need it before you want prices separate fromItem.DefaultSalePriceandItem.DefaultPurchasePrice. CreateKind=Baseprice types first, thenKind=Calculatedprice types that need an existingBasePriceType.Supplier/CustomerplusCustomerContactandCustomerOutletare independent of stores, but must exist before the first documents with a counterparty, such asGoodsIssue.CustomerorStore.InventoryAgent.PriceTypeon the card is a soft dependency and can be filled later.Store→StoreZone→StoreCellType→StoreCellis a hard chain. A zone requires an existing store; a cell requires an existing zone and type. For anIsSimplestore without detailed addressing, one zone and one cell per store are enough.InventorySettingsis configured last.DefaultReceivingLocationpoints to an existingStoreCell.EnforceWarehouseTaskscan be enabled any time after that.PrepareAllYardsAsyncfills missing role cells and setsPurposeon existing types by name. Enabling the flag after data already exists is safe.
What to decide before setup#
- Simple store or zones/cells?
Store.IsSimpleis a decision for the whole store. You can change it later, but cell data for the earlier simple period does not appear retroactively. - Do you need address discipline from day one, or later? Retroactive enablement is safe; see step 8. You can start with discipline off and enable it when the storekeeper process is ready.
- Name standard cell types by role. Use
Receiving,Storage, orPicking.PrepareAllYardsAsyncmatchesPurposeby name case-insensitively and independent of the register. Arbitrary type names require manualPurposesetup.
Analytics#
Store (RefStore), Cell (RefStoreCell), Item (RefItem), Customer (RefCustomer), and CustomerOutlet (RefCustomerOutlet) are used by Stock, Cost, Revenue, and LoyaltyPoints. Stock itself does not consume analytics; see below. They are needed by other registers of this model and by other models.
Registers#
Stock#
Balances in cells. The section is defined by two dimensions (dimensions), not by analytics:
{
"kind": "Register",
"object": {
"registerEngineType": "Standard",
"totalDriverMetaId": "<GUID Inventory-TotalDriver>",
"isDoubleEntry": false, "allowNegativeBalance": true, "useBalanceTable": true,
"metaId": "<GUID Reg.Stock>", "name": "Stock",
"modelId": "<GUID Model.Inventory>"
},
"dimensions": [
{ "fieldName": "Cell", "edtMetaId": "<GUID EDT.RefStoreCell>", "isOperational": true },
{ "fieldName": "Item", "edtMetaId": "<GUID EDT.RefItem>", "isOperational": true }
],
"resources": [ { "fieldName": "Qty", "edtMetaId": "<GUID EDT.Qty>", "isOperational": true } ]
}
- The section is the cell. Store and zone are derived from
StoreCell.StoreZone.Store. allowNegativeBalance: truemeans the CAM engine does not reject an issue over balance. Document event handlers apply balance checks where needed; the check is not a register property.- The register has its own
totalDriverMetaId. The balance is calculated by the standard accumulation register engine.
Example. At a random moment, the register stores one line for each cell/item pair:
| Cell | Item | Qty |
|---|---|---|
| Storage-A1 | Milk 1L | 150 |
| Picking-01 (Store-1) | Milk 1L | 26 |
| Picking-01 (Store-2) | Milk 1L | 10 |
The numbers come from the end-to-end document example below: this is the final state after all six operations on one milk batch.
Documents (WMS)#
The example below follows one item, Milk 1L, through all six documents. The storekeeper receives 200 pcs; part is damaged, and part is shipped to the customer. Each document example continues the previous one.
None of the six documents uses transactionPairs.Add. All of them post to Stock with transactions.Add(new RegisterMovementSpec("Stock")…):
- Two consecutive calls with the same
BaseQuantity: one minus from a cell and one plus to another cell. Used byPutAwayTask,PickTask, andStockTransfer. - One signed call:
GoodsIssueuses a minus only.StockAdjustmentuses the signed line quantity.StockCountuses the signed calculated difference.
A document line may carry its own cell (line.FromCell, line.ToCell, or line.Cell, depending on the document). If it is empty, the line uses the header cell.
PutAwayTask#
Receiving → storage. Header FromCell → StoreCell is required and means the receiving cell. Table part Lines: Item, Quantity/BaseQuantity, ToCell. Subtypes: Draft (initial, editable) → Confirmed (read-only). The confirmed document is frozen. One tx script runs on Confirmed. Command: ConfirmPutAway.
OnBeforePostAsync requires Subtype == "Confirmed" and checks two things:
- Physical stock always: for each line, the balance on
FromCellfromITotalsManager.GetBalanceAsyncmust cover the requested quantity. - Policy only when
EnforceWarehouseTasksis enabled:FromCellmust havePurpose=Receiving, and eachline.ToCellmust havePurpose=Storage, checked throughIStoreCellService.IsCellAllowedForAsync.
Example. The supplier brought 200 Milk 1L units into the receiving cell. The storekeeper puts the whole lot into permanent storage:
| Cell | Role | Balance before | Movement | Balance after |
|---|---|---|---|---|
| Receiving-01 | Receiving | 200 | −200 | 0 |
| Storage-A1 | Storage | 0 | +200 | 200 |
PickTask#
Storage → picking. Header FromCell → StoreCell is required and means the storage cell. Subtypes: Draft (editable) → Confirmed (read-only). Explicit transitions exist in both directions: Draft→Confirmed and Confirmed→Draft, so confirmation can be cancelled. Command: ConfirmPick.
OnBeforePostAsync checks physical stock on FromCell always. When EnforceWarehouseTasks is enabled, it also checks policy: FromCell must have Purpose=Storage, and each line.ToCell must have Purpose=Picking.
There are three tx scripts, not one script per subtype:
Draft→PickTaskDraftReserveTxin the Sales extensionPickTask.Saleswith[ExtensionOf]. A draft pick does not moveStock; it writes+qtytoReservedStockonFromCell(storage) so a second order does not see the same cell as free.Confirmed,executionOrder1 →PickTaskTxin Inventory. It movesStock: minusFromCell, plusline.ToCell.Confirmed,executionOrder2 →PickTaskFollowReserveTxin Sales. If the task serves a sales order (ISalesFulfillmentService.PickServesSalesOrderAsync), it moves the reserve to the new cell (line.ToCell). Manual picking without an order creates no reserve.
The black reserve on FromCell is removed by the platform automatically when the document exits the Draft subtype. No script clears it manually. Reserve (ReservedStock) and balance are read separately by ICellFreeStock as physical balance minus someone else's reserve. The Reserved stock by item and cell layout is described in Store and stock.
Example. 50 items are picked from the storage cell for a customer order:
| Cell | Role | Balance before | Movement | Balance after |
|---|---|---|---|---|
| Storage-A1 | Storage | 200 | −50 | 150 |
| Picking-01 (Store-1) | Picking | 0 | +50 | 50 |
If this task serves a sales order, the reserve on Storage-A1 (+50, created by draft PickTask) is removed and transferred to Picking-01. This operation does not change the Stock balance; it is a separate Sales register operation.
StockAdjustment#
Write-off or surplus acceptance. Header Cell → StoreCell is required. Reason is optional String(128). Table part Lines: Item, Quantity. Subtypes: Draft → Posted, and both are editable. Unlike Pick, Put-away, and Goods Issue, the posted document remains editable. One tx script runs on Posted. Command: PostStockAdjustment.
OnBeforePostAsync checks balance sufficiency only for negative lines, which write stock off. Positive lines, which add surplus, are always allowed.
Example. In the picking cell, the storekeeper found damage of 3 pcs and one extra unit during recount. Two separate documents would be excessive, so one StockAdjustment has two lines:
| Line | Reason | Qty (signed) | Balance before | Balance after |
|---|---|---|---|---|
| 1 | Breakage | −3 | 50 | 47 |
| 2 | Extra unit found | +1 | 47 | 48 |
StockTransfer#
Free movement between cells. Header FromCell and ToCell are both required; they are the defaults for the lines. Table part Lines: Item, Quantity, and optional FromCell / ToCell — a filled line cell overrides the header for that line, so one transfer can gather from several cells or spread into several. Subtypes: Draft → Posted, and both are editable. One tx script runs on Posted. Command: PostStockTransfer.
OnBeforePostAsync checks only physical stock on the source cell (line.FromCell or the header). There is no Purpose validation, unlike Pick and Put-away. Free movement is not part of target store discipline. Skipping the balance check here is more dangerous than in a normal issue: the movement is net zero, the cost driver does not see it, and an unchecked transfer can create a phantom balance without a cost layer at the destination.
Example. Part of the batch is moved to a second store, for example for an opened outlet. This is free movement without roles and without a reservation:
| Cell | Balance before | Movement | Balance after |
|---|---|---|---|
| Picking-01 (Store-1) | 48 | −10 | 38 |
| Picking-01 (Store-2) | 0 | +10 | 10 |
StockCount#
Cell inventory count. Header Cell → StoreCell is required. CountDate is required DateTime, not an EDT. Table part Lines: required Item, required CountedQty → Qty. Subtypes: Draft → Posted, and both are editable. One tx script runs on Posted. Command: PostStockCount.
This is not a guard; it is a delta calculation. OnBeforePostAsync calculates, for each line, delta = counted − onHand from Stock[Cell,Item] balance, uses BaseQuantity with fallback to CountedQty, and writes the result to TP_StockCountLines.QtyDelta directly through IDataService.UpdateAsync. It bypasses the normal save because draft line writes may not reach the database before the tx script runs. The tx script (StockCountTx) reads the already calculated QtyDelta and posts one signed Stock movement per line. delta = 0 creates no movement.
OnBeforeSaveAsync sets header.DocumentDate = CountDate before posting, so movements use DocumentDate and not CountDate directly. You can enter Item with a scanner by Barcode; see the form scan widget.
Example. Planned count of the Picking-01 (Store-1) cell: the storekeeper counts the physical stock and enters the actual quantity for each item. The document calculates the difference and posts only that difference:
| Item | System balance (on hand) | Counted (CountedQty) |
Delta (QtyDelta) |
Movement on Stock |
|---|---|---|---|---|
| Milk 1L | 38 | 36 | −2 | −2 (shortage write-off) |
| Kefir 1L | 15 | 14 | −1 | −1 (shortage) |
| Yogurt 200g | 5 | 5 | 0 | no movement |
Milk and kefir have shortages, perhaps because someone took stock without a document or because of a previous error. Yogurt matches. The line is saved, but Stock is unchanged. After posting on Picking-01 (Store-1), Milk 1L = 36.
GoodsIssue#
Issue. Header Customer → Customer is optional. Header FromCell → StoreCell is required and means the picking cell. Table part Lines: Item, Quantity. Subtypes: Draft (editable) → Posted (read-only). One tx script runs on Posted. Command: PostGoodsIssue.
OnBeforePostAsync rejects lines with qty <= 0, aggregates demand by (Cell, Item) through BaseQuantity with fallback to Quantity, and checks balance through ITotalsManager.GetBalanceAsync("Stock", …). The line cell is line.FromCell or the header cell.
Example. 10 pcs of milk are shipped directly from the picking cell to customer Camel LLC. Issue has no counter-movement, unlike the previous documents. It only subtracts stock:
| Cell | Balance before | Movement | Balance after |
|---|---|---|---|
| Picking-01 (Store-1) | 36 | −10 | 26 |
The final balance is 26 in the first store, plus 10 in the second store from the StockTransfer example. Those are the balances shown in the Stock register snapshot above.
Services#
IStoreCellService#
The model's only service resolves cells and owns the entire store discipline policy. The real contract in Inventory/Services/StoreCellService/StoreCellService.cs is broader than "choose the default cell":
GetDefaultReceivingCellAsync/GetDefaultPickingCellAsync/GetDefaultOutputCellAsync(store),SuggestPutAwayCellAsync(store, item),SuggestStorageCellAsync(...),GetStoreAsync(cell),GetCellsOfStoreAsync(store, purpose?): resolve cells for postings in Purchasing, Production, and Sales. The store is derived throughStoreCell.StoreZone.Store.IsStoreDisciplineOnAsync()readsInventorySettings.EnforceStoreTasks.GetCellPurposeAsync(cell)/GetCellByPurposeAsync(store, purpose)read the cell role throughStoreCell.Type.Purpose.IsCellAllowedForAsync(cell, purpose): when discipline is off, it always returnstruefor backward compatibility. When discipline is on, it verifies that the cellPurposematches the requested role. This is the only policy point; it is called fromPutAwayTaskandPickTaskevents and is not duplicated in each document.EnsureYardAsync(store)idempotently creates a start yard for a store: a main zone plus oneReceiving, oneStorage, and onePickingcell if they do not exist yet.PrepareAllYardsAsync()is a retroactive backfill. It setsPurposeon existingStoreCellTyperecords by name, matchingReceiving/Acceptance,Storage/Storage, andPicking/Selectioncase-insensitively, then callsEnsureYardAsyncfor each store.InventorySettingsEventHandler.OnAfterSaveAsynccalls it automatically whenEnforceStoreTasksis enabled.GetLegalEntityAsync(cell)bridges from cell to legal entity for taxes and GL:StoreCell → StoreZone → Store → Division → LegalEntity.
Physical balance checks, meaning "is there enough stock", are not in this service and not in an availability service. Each document's OnBeforePostAsync performs the needed check separately through ITotalsManager.GetBalanceAsync("Stock", …).
Business logic and invariants#
Storage hierarchy. Store → StoreZone by Store → StoreCell by StoreZone. Cell address is line / rack / shelf / cell plus type, and the role is read through Type.Purpose. An IsSimple store can operate without detailed addressing.
Balance is in the cell, not in the store. Store and zone are derived through StoreCell.StoreZone.Store. Stock has no separate store analytic. The register allows negative balance at the engine level (allowNegativeBalance:true). Balance checks over available quantity are application checks in document OnBeforePostAsync, and they differ by document: GoodsIssue checks all demand, StockAdjustment checks only write-offs, StockTransfer checks only the source cell, and StockCount is a delta calculation, not a guard.
Address discipline is optional and off by default. InventorySettings.EnforceStoreTasks = false means cells are free: receipts and issues may use any cell directly, and pick/put-away tasks are not required. When the flag is on, receipts go only to Purpose=Receiving, issues go only from Purpose=Picking, and PutAwayTask and PickTask are required between them. Switching on existing data is safe; see InventorySettings. The store does not need to be prepared in advance.
Items are not teleportable. Any movement between cells is a pair of operations: one minus and one plus. The documents implement this with two consecutive register calls, not with platform pair posting.
Cell resolution and cell-role policy are centralized. Postings in Purchasing, Production, and Sales get cells and check roles through IStoreCellService. Do not hardcode IDs or compare type names as strings.
Stock count posts the difference. StockCount does not rewrite the balance. It moves Stock by fact − system for a cell/item pair. The audit trail and cost stay consistent.
Reservations are not in Inventory. The ReservedStock register and ISalesFulfillmentService belong to Sales. Sales extends PickTask with additional tx scripts; see PickTask. Without the Sales model, PickTask still physically moves Stock, but there is no cell reservation between draft and confirmation.
Menu#
Inventory → Dictionaries (15 entries, including singleton InventorySettings) · Documents (PutAwayTask, PickTask, StockAdjustment, StockTransfer, StockCount, GoodsIssue) · Registers (Stock) · Settings (InventorySettings).
Links with other modules#
- Outgoing: Organization (
Division), Accounting (Money), and Common (UnitOfMeasure,UnitClass,Address). - Incoming: Purchasing, Production, and Sales use
Stock,IStoreCellService, counterparty dictionaries, and item dictionaries. Costing reads cost byStore/Itemand integratesStockCountwith the cost driver. CRM extendsCustomer. Sales extendsPickTaskwith two reservation tx scripts (PickTask.Sales).
Objects#
Generated from workspace metadata by node tools/gen-reference.mjs; edit the metadata, not this block.
Dictionaries#
Brand — Brands
| Field | Caption | Type | Required |
|---|---|---|---|
ID |
Code | String(50) | Yes |
Name |
Name | String(256) | Yes |
Customer — Customers
| Field | Caption | Type | Required |
|---|---|---|---|
Address |
Address | → Address |
No |
CreditLimit |
Credit limit | Money |
No |
CustomerType |
Customer type code | String(8) | No |
Email |
String(128) | No | |
FullName |
Full name | String(250) | No |
ID |
ID | String(50) | Yes |
IsDisabled |
Disabled | Boolean | No |
Kind |
Customer type | → CustomerType |
No |
Name |
Name | String(160) | Yes |
Notes |
Notes | String(2000) | No |
PaymentTerm |
Payment term | → PaymentTerm |
No |
Phone |
Phone | String(32) | No |
PriceType |
Price type | → PriceType |
No |
TaxRegistrationNumber |
Tax reg. number | String(64) | No |
CustomerContact — Customer contacts
| Field | Caption | Type | Required |
|---|---|---|---|
Customer |
Customer | → Customer |
Yes |
Email |
String(128) | No | |
ID |
Code | String(50) | Yes |
IsPrimary |
Primary contact | Boolean | No |
Name |
Name | String(256) | Yes |
Phone |
Phone | String(64) | No |
CustomerOutlet — Customer outlets
Ship-to of a customer: address and contact. Commercial terms live on SalesContract.
| Field | Caption | Type | Required |
|---|---|---|---|
Address |
Address | → Address |
No |
Contact |
Contact | → CustomerContact |
No |
Customer |
Customer | → Customer |
Yes |
ID |
ID | String(50) | Yes |
IsDisabled |
Disabled | Boolean | No |
Lat |
Latitude | Decimal(10,7) | No |
Lng |
Longitude | Decimal(10,7) | No |
Name |
Name | String(160) | Yes |
Phone |
Phone | String(32) | No |
WorkingHours |
Working hours | String(200) | No |
CustomerType — Customer types
B2B, B2C, Retail… — what the customer card's «Customer type» points at. The code is what other models compare (loyalty campaigns, the invoice's buyer.type); it is copied onto the customer's hidden CustomerType column.
| Field | Caption | Type | Required |
|---|---|---|---|
Code |
Code | String(8) | Yes |
ID |
Code | String(50) | Yes |
Name |
Name | String(128) | Yes |
InventorySettings — Inventory settings
| Field | Caption | Type | Required |
|---|---|---|---|
DefaultPurchasePriceType |
Default purchase price type | → PriceType |
No |
DefaultReceivingLocation |
Default receiving location | → StoreCell |
No |
DefaultSalePriceType |
Default sale price type | → PriceType |
No |
DefaultUnit |
Default unit of measure | → UnitOfMeasure |
No |
EnforceUnitConversion |
Enforce unit conversion | Boolean | No |
EnforceWarehouseTasks |
Enforce warehouse tasks | Boolean | No |
FulfillmentScheme |
Shipment scheme | WarehouseFulfillmentSchemeEdt |
No |
ID |
ID | String(50) | Yes |
Item — Items
Any item: raw material, semi-finished, finished good, merchandise
| Field | Caption | Type | Required |
|---|---|---|---|
Article |
Article | String(64) | No |
Barcode |
Barcode | String(64) | No |
BaseName |
Base name | String(200) | No |
Brand |
Brand | → Brand |
No |
CharacteristicTemplate |
Characteristic template | → CharacteristicTemplate |
No |
DefaultPurchasePrice |
Default purchase price | Money |
No |
DefaultSalePrice |
Default sale price | Money |
No |
Description |
Description | String(500) | No |
ID |
ID | String(50) | Yes |
Image |
Image | Image | No |
IsDisabled |
Discontinued | Boolean | No |
IsRawMaterial |
Raw material | Boolean | No |
IsSellable |
Sellable | Boolean | No |
ItemGroup |
Item group | → ItemGroup |
Yes |
Name |
Name | String(160) | Yes |
TaxCode |
VAT rate | → TaxCode |
No |
TaxProductCategory |
Tax product category | String(32) | No |
UnitOfMeasure |
Unit | → UnitOfMeasure |
Yes |
WeightGross |
Gross weight, kg | Decimal(15,3) | No |
WeightNet |
Net weight, kg | Decimal(15,3) | No |
ItemCategory — Item categories
Catalogue tree like a web shop: Paints → Interior. A category's characteristic template is inherited by its subcategories.
| Field | Caption | Type | Required |
|---|---|---|---|
ID |
Code | String(50) | Yes |
MemberCharacteristicTemplate |
Template for its items | → CharacteristicTemplate |
No |
Name |
Name | String(150) | Yes |
ParentId |
Parent | → ItemCategory |
No |
ItemGroup — Item groups
RawMaterial / SemiFinished / FinishedGood / Merchandise
| Field | Caption | Type | Required |
|---|---|---|---|
Code |
Code | String(16) | Yes |
ID |
ID | String(50) | Yes |
MemberCharacteristicTemplate |
Template for its items | → CharacteristicTemplate |
No |
Name |
Name | String(64) | Yes |
ParentId |
Parent | → ItemGroup |
No |
ItemUnit — Item units
| Field | Caption | Type | Required |
|---|---|---|---|
ID |
Code | String(50) | Yes |
Item |
Item | → Item |
Yes |
QtyInBaseUnit |
Quantity in base unit | Decimal(18,6) | Yes |
Unit |
Unit | → UnitOfMeasure |
Yes |
PriceListItem — Item prices
| Field | Caption | Type | Required |
|---|---|---|---|
EffectiveFrom |
Effective from | DateTime | No |
EffectiveTo |
Effective to | DateTime | No |
ID |
Code | String(50) | Yes |
Item |
Item | → Item |
Yes |
Price |
Price | Money |
Yes |
PriceType |
Price type | → PriceType |
Yes |
Unit |
Unit | → UnitOfMeasure |
Yes |
PriceType — Price types
| Field | Caption | Type | Required |
|---|---|---|---|
BasePriceType |
Base price type | → PriceType |
No |
Direction |
Direction | PriceDirectionEdt |
Yes |
ID |
ID | String(50) | Yes |
IsDisabled |
Disabled | Boolean | No |
Kind |
Kind | PriceTypeKindEdt |
Yes |
MarkupPercent |
Markup % | Percent |
No |
Name |
Name | String(160) | Yes |
RoundingStep |
Rounding step | Decimal(10,4) | No |
RoundUp |
Round up | Boolean | No |
Store — Stores
Store / warehouse (MIQS Store). Zones and cells hang off it.
| Field | Caption | Type | Required |
|---|---|---|---|
Division |
Division | → Division |
Yes |
ID |
Code | String(50) | Yes |
InventoryAgent |
Inventory agent | → Supplier |
No |
IsSalesPoint |
Sales point | Boolean | No |
IsSimple |
Simple store | Boolean | Yes |
Name |
Name | String(128) | Yes |
StoreCell — Store cells
Bin within a store zone (MIQS StoreCell); addressed by line/rack/shelf/cell. Stock is held per cell; store is derived via StoreZone.Store.
| Field | Caption | Type | Required |
|---|---|---|---|
CellNumber |
Cell | Integer | Yes |
ID |
Code | String(50) | Yes |
IsPickable |
Pickable | Boolean | No |
LineNumber |
Line | Integer | Yes |
MaxQty |
Capacity (max qty) | Qty |
No |
Name |
Name | String(64) | Yes |
RackNumber |
Rack | Integer | Yes |
ShelfNumber |
Shelf | Integer | Yes |
StoreZone |
Zone | → StoreZone |
Yes |
Type |
Cell type | → StoreCellType |
Yes |
StoreCellType — Store cell types
Receiving / Storage / Picking (MIQS StoreCellType)
| Field | Caption | Type | Required |
|---|---|---|---|
Code |
Code | String(16) | Yes |
ID |
Code | String(50) | Yes |
Name |
Name | String(64) | Yes |
Purpose |
Purpose | StoreCellPurposeEdt |
No |
StoreZone — Store zones
Zone within a store (MIQS StoreZone); groups cells. Store is the owner.
| Field | Caption | Type | Required |
|---|---|---|---|
ID |
Code | String(50) | Yes |
IsBarcodeTracking |
Barcode tracking | Boolean | Yes |
Name |
Name | String(128) | Yes |
Store |
Store | → Store |
Yes |
Supplier — Suppliers
| Field | Caption | Type | Required |
|---|---|---|---|
Email |
String(128) | No | |
FullName |
Full name | String(250) | No |
ID |
ID | String(50) | Yes |
IsDisabled |
Disabled | Boolean | No |
Name |
Name | String(160) | Yes |
Notes |
Notes | String(2000) | No |
Phone |
Phone | String(32) | No |
PriceType |
Price type | → PriceType |
No |
TaxRegistrationNumber |
Tax reg. number | String(64) | No |
Documents#
GoodsIssue — Goods issue
Header fields:
| Field | Caption | Type | Required |
|---|---|---|---|
Customer |
Customer | → Customer |
No |
FromCell |
From cell | → StoreCell |
Yes |
ID |
Number | String(50) | Yes |
Subtypes:
| Subtype | Caption |
|---|---|
Draft |
Draft |
Posted |
Posted |
Commands:
| Command | Caption | Target subtype |
|---|---|---|
PostGoodsIssue |
Post goods issue | — |
PickTask — Pick task
Header fields:
| Field | Caption | Type | Required |
|---|---|---|---|
FromCell |
From cell (storage) | → StoreCell |
Yes |
ID |
Number | String(50) | Yes |
Subtypes:
| Subtype | Caption |
|---|---|
Draft |
Draft |
Confirmed |
Confirmed |
Commands:
| Command | Caption | Target subtype |
|---|---|---|
ConfirmPick |
Confirm pick | — |
PutAwayTask — Put-away task
Header fields:
| Field | Caption | Type | Required |
|---|---|---|---|
FromCell |
From cell (receiving) | → StoreCell |
Yes |
ID |
Number | String(50) | Yes |
Subtypes:
| Subtype | Caption |
|---|---|
Draft |
Draft |
Confirmed |
Confirmed |
Commands:
| Command | Caption | Target subtype |
|---|---|---|
ConfirmPutAway |
Confirm put-away | — |
StockAdjustment — Stock adjustment
Header fields:
| Field | Caption | Type | Required |
|---|---|---|---|
Cell |
Cell | → StoreCell |
Yes |
ID |
Number | String(50) | Yes |
Reason |
Reason | String(128) | No |
Subtypes:
| Subtype | Caption |
|---|---|
Draft |
Draft |
Posted |
Posted |
Commands:
| Command | Caption | Target subtype |
|---|---|---|
PostStockAdjustment |
Post adjustment | — |
StockCount — Stock count
Header fields:
| Field | Caption | Type | Required |
|---|---|---|---|
Cell |
Cell | → StoreCell |
Yes |
CountDate |
Count date | DateTime | Yes |
ID |
Number | String(50) | Yes |
Subtypes:
| Subtype | Caption |
|---|---|
Draft |
Draft |
Posted |
Posted |
Commands:
| Command | Caption | Target subtype |
|---|---|---|
PostStockCount |
Post stock count | — |
StockTransfer — Stock transfer
Header fields:
| Field | Caption | Type | Required |
|---|---|---|---|
FromCell |
From location | → StoreCell |
Yes |
ID |
Number | String(50) | Yes |
Notes |
Notes | String(2000) | No |
ToCell |
To location | → StoreCell |
Yes |
Subtypes:
| Subtype | Caption |
|---|---|
Draft |
Draft |
Posted |
Posted |
Commands:
| Command | Caption | Target subtype |
|---|---|---|
PostStockTransfer |
Post transfer | — |
Registers#
Stock — Stock
On-hand quantity by location and item (single-entry accumulation register, MIQS-style). Receipts add +qty, issues/sales post -qty directly from the cell; transfers move between two cells. Balance = actual on-hand — no External counterparty bucket.
Dimensions:
| Dimension | Caption | Type | Required |
|---|---|---|---|
Cell |
Cell | → StoreCell |
— |
Item |
Item | → Item |
— |
Resources:
| Resource | Caption | Type | Required |
|---|---|---|---|
Qty |
Qty | Qty |
— |
Table parts#
GoodsIssueLines — Lines
Used by: GoodsIssue.
| Field | Caption | Type | Required |
|---|---|---|---|
BaseQuantity |
Qty in base unit | Qty |
No |
FromCell |
From cell | → StoreCell |
No |
Item |
Item | → Item |
Yes |
Quantity |
Quantity | Qty |
Yes |
Unit |
Unit | → UnitOfMeasure |
No |
PickTaskLines — Lines
Used by: PickTask.
| Field | Caption | Type | Required |
|---|---|---|---|
BaseQuantity |
Qty in base unit | Qty |
No |
Item |
Item | → Item |
Yes |
Quantity |
Quantity | Qty |
Yes |
ToCell |
To cell (picking) | → StoreCell |
Yes |
Unit |
Unit | → UnitOfMeasure |
No |
PutAwayTaskLines — Lines
Used by: PutAwayTask.
| Field | Caption | Type | Required |
|---|---|---|---|
BaseQuantity |
Qty in base unit | Qty |
No |
Item |
Item | → Item |
Yes |
Quantity |
Quantity | Qty |
Yes |
ToCell |
To cell (storage) | → StoreCell |
Yes |
Unit |
Unit | → UnitOfMeasure |
No |
StockAdjustmentLines — Lines
Used by: StockAdjustment.
| Field | Caption | Type | Required |
|---|---|---|---|
Amount |
Amount | Money |
No |
BaseQuantity |
Qty in base unit | Qty |
No |
Cell |
Cell | → StoreCell |
No |
Item |
Item | → Item |
Yes |
PackQty |
Packages | Qty |
No |
PackUnit |
Package unit | → UnitOfMeasure |
No |
Quantity |
Quantity | Qty |
Yes |
Unit |
Unit | → UnitOfMeasure |
No |
UnitPrice |
Unit price | Money |
No |
StockCountLines — Lines
Used by: StockCount.
| Field | Caption | Type | Required |
|---|---|---|---|
BaseQuantity |
Qty in base unit | Qty |
No |
Cell |
Cell | → StoreCell |
No |
CountedQty |
Counted qty | Qty |
Yes |
Item |
Item | → Item |
Yes |
QtyDelta |
Qty delta | Qty |
No |
Unit |
Unit | → UnitOfMeasure |
No |
StockTransferLines — Lines
Used by: StockTransfer.
| Field | Caption | Type | Required |
|---|---|---|---|
Amount |
Amount | Money |
No |
BaseQuantity |
Qty in base unit | Qty |
No |
FromCell |
From cell | → StoreCell |
No |
Item |
Item | → Item |
Yes |
PackQty |
Packages | Qty |
No |
PackUnit |
Package unit | → UnitOfMeasure |
No |
Quantity |
Quantity | Qty |
Yes |
ToCell |
To cell | → StoreCell |
No |
Unit |
Unit | → UnitOfMeasure |
No |
UnitPrice |
Unit price | Money |
No |