Lists and inventory
Shared lists (ValueList, LookupValue, LookupService)
A shared list has one row of its own (ValueList): its name, its order (most used, or the window's line order), what a
value a requester types does (ListEntry: added for everyone, kept on its request, or refused), optional detail
columns (ColumnsJson: text, number, yes/no, or a value of another list) and two optional roles: held values
and stock items.
- A list is written by one method,
LookupService.SaveListAsync: new lines are added, removed lines switched off (a request keeps its words), and a line whose text changed keeps its row (renames, audited aslookup.renamed). - A value a published rule reads is never switched off or deleted; a column a rule reads never goes or changes type.
- The branches' and the rooms' lists follow their own pages: their values change only there.
- A list read by a published service, a draft or a stopped service is not deleted.
Held values (HeldValuesListener)
A list marked «قيمها تُحجز» lends its values (a car, a projector). A value is taken while an open request's form holds it (waiting for approval, in progress or returned), and free once that request closes. Nothing is stored: the forms are the record. Every stored version of a service is read, so a request on an older version still holds its value. Writes refuse a taken value under an advisory lock; publishing refuses a held list in a table column.
The stock ledger (modules/requests/Stock)
flowchart LR
V[Voucher\nreceipt, issue, transfer, correction] --> P[StockService.PostAsync]
T[Task «حركة مخزون»\nStockListener.StepDoneAsync] --> P
R[Return / reject / cancel\nReverseRequestAsync] --> P
P --> M[(StockMove\nthe ledger)]
P --> B[(StockBalance\nper item and branch)]
P --> L[(StockLot\nper expiry batch)]
P --> N[Low-stock notice]
- Any list may hold stock («قيمها أصناف مخزون»,
ValueList.StockJson,StockList): an item's unit and minimum are two of the list's own detail columns. A warehouse is a branch's. - One writer.
StockService.PostAsyncwrites the movement, the balance and the batch in its caller's save. A balance is never edited;StockTestschecks every balance equals the sum of its ledger. - Weighted average cost, one rule for every line (
AverageAfter): a receipt weighs its cost in; a line out at the average leaves it unchanged; a receipt taken back leaves at its own cost. - Vouchers (
SaveVoucherAsync) are saved with their lines in one transaction. «عكس السند» posts the opposite lines at their own costs; «حذف السند» is allowed only while nothing moved the voucher's items after it, then the balances are recounted from the ledger. - From a request. A task's «حركة مخزون» (
StepDef.Stock: a table, its item and quantity columns, a direction, a warehouse field) posts when its holder presses «تم». A way out and a way in over one table on one step are a transfer, posted both sides or neither. A return, a rejection or a cancellation puts back what the request moved, and a resubmission moves it again. A correction never changes a warehouse a step already moved stock in. - Warn, then allow. An issue above the balance is allowed; the confirm before «تم» (
WarningsAsync) says each shortfall and each row that will not move (an unknown item, an unknown branch, a list that holds no stock). - Batches. An item whose list expires moves by batch (
StockLot): a receipt dates it, a way out takes the batch the holder picks (ChoicesAsync, nearest unexpired first). A line with no known date goes to «بلا تاريخ» (StockService.NoDate), so batches always add up to the balance; a list that starts expiring puts its stock there. - Notices go to every holder of
ManageStock: once when a balance crosses its minimum (StockLow), and from the daily sweep (StockExpirySweep) once before a batch expires and once when it does (StockExpiring). - Audit keys:
stock.received,stock.issued,stock.transferred,stock.adjusted,stock.voucher_cancelled,stock.voucher_deleted.
The audit of 2026-10-13 and its fixes: docs/checklists/stock-lists-audit-2026-10-13.md.