ZuloOne
English
Sign in
Menu

⚙️ Document commands and subtypes

Updated

← Business process wiki

How a user posts a document, why "there is a Posted subtype" does not yet mean the document can be posted from the card, and what happens to the postings when the state changes. How the contours run — the process map.

A command is the only transition button#

The subtype is the document's current state (Draft, Confirmed, Posted…). There is no subtype field on the card: a document is born already in the initial subtype (IsInitial). If there is no initial subtype, or several are marked, Create in the journal first asks which subtype to start in. After that only a command changes the subtype: picking "Posted" from a list is not possible.

A document can only be posted from the UI by a command. The command is bound to the source subtype (for example, only to Draft). When pressed it:

  1. Runs the checks through the domain services, without copying their formulas into the command: the balance (IStockAvailabilityService, ISalesFulfillmentService), the cell assignment (IStoreCellService), the quantity in the base unit (IItemQuantityConverter), the tax rate and amount (ITaxService), the period and the accounts (IGeneralLedgerService). If a check fails, the client gets a message (ClientAction.Message), execution stops, and the subtype does not change.
  2. Sets the target subtype (document.Subtype = SalesOrder.Subtypes.Confirmed) and saves.
  3. The engine posts the document in its new state: the OnBeforePost / OnAfterPost events run, and so do the target subtype's transaction scripts — the postings into the registers, the reservation, the ledger.

A transition command does not call the services' mutating methods (CreateCalculationAsync, InvoiceOrderAsync, CompleteTripAsync, CreateAccrualAsync…): OnAfterPost already runs them. From a command — reading and validation only.

Without such a command the document is a draft forever as far as the card is concerned, no matter how many scripts are attached to "Confirmed". How to make the button — the zuloone-new-command skill. How subtypes and postings work — zuloone-new-document.

The programmatic path (document.Subtype = …, SetSubtypeAsync, the /subtype API) is for services, events and tests, when the checks have already been made higher up. It is not a substitute for the user's button.

A command that changes no subtype (filling in prices, expanding a BOM) does not post the document.

Postings do not accumulate: Mix semantics (replacement, not a sum)#

Yes: on a transition the engine removes every movement the document has already written and writes afresh what the scripts of the target subtype only produce. In the ZuloOne code this is called MIQS replace semantics — that is how Mix worked.

In Mix, on every save DocumentManager took the current SubtypeID's scripts (GetTransactionScripts), computed the new set of postings and handed it to TransactionProcessor.SaveDocumentTransactions: registers no longer in the set went to DeleteDocumentTransactions, the rest to MergeDocumentTransactions (old rows absent from the new set are deleted). Changing the subtype did not "add a layer" — it substituted the document's postings with what the new subtype knows how to write. Commands there were bound to a subtype as well (DocumentCommandToSubtype): the button is visible only on its own state and, once the checks pass, sets the new subtype + Save.

The typical Draft → Confirmed path feels like "just post the document": a draft has no scripts, there is nothing to remove, and the confirmation's postings are written.

But if the states form a chain and each has its own set of registers:

Realized  →  the script writes the realization
Done      →  the script writes something else, no realization in it

then moving Realized → Done will remove the realization's postings. Only what the "Done" subtype wrote stays in the registers. A rollback to draft (a subtype with no scripts) removes the postings entirely — the document is "empty" again.

That is why "pay" must not be made a subtype of the same sales realization: an Issued → Paid transition would erase the stock, the revenue and the receivables along with the "paid" flag. A payment is a document of its own, see obligations and the ledger.

If a later state has to keep an earlier one's effect, the later subtype's scripts write both sets (the realization and "done"), or the second effect lives in another document. The current state's movements = what has to be true now, not the sum of the transition history.

The same transaction script can be attached to several subtypes (in Mix — DocumentSubtypeTransactionScript on different SubtypeIDs). The later state then keeps no "layer" of the earlier one but recomputes the same postings. If the target subtype has no binding, the register rows are deleted.

Transition commands#

One command, one edge. The binding is to the source subtype. The programmatic document.Subtype = … in tests and services is still legitimate; from the card a button is needed.

Stores#

Document Command Transition What happens
PutAwayTask Confirm put-away (ConfirmPutAway) Draft → Confirmed store: receiving → storage
PickTask Confirm pick (ConfirmPick) Draft → Confirmed store: storage → picking
StockAdjustment Post adjustment (PostStockAdjustment) Draft → Posted write-off / write-on
StockTransfer Post transfer (PostStockTransfer) Draft → Posted cell ↔ cell
StockCount Post stock count (PostStockCount) Draft → Posted discrepancies
GoodsIssue Post goods issue (PostGoodsIssue) Draft → Posted an issue out of the store

Sales and purchasing#

Document Command Transition What happens
SalesLead Qualify lead (QualifyLead) New → Qualified interest, no postings
SalesLead Create quotation (ConvertLead) Qualified → Quoted a Draft SalesQuotation, key SourceLead
SalesLead Mark lost (LoseLead) New / Qualified → Lost no quotation is created
SalesQuotation Issue quotation (IssueQuotation) Draft → Issued a commercial offer, no postings
SalesQuotation Create sales order (ConvertQuotation) Issued → Converted a Draft SalesOrder, key SourceQuotation
SalesQuotation Cancel quotation (CancelQuotation) Draft / Issued → Cancelled no order is created
PurchaseOrder Place order (PlaceOrder) Draft → Ordered an obligation towards the supplier, no stock yet. With PurchasingSettings.AutoReceiveOnOrder — immediately Ordered and then Received, as with Receive order; receiving is checked while still in Draft, and a refusal leaves the draft
PurchaseOrder Receive order (ReceiveOrder) Ordered → Received stock+, payables, tax, a put-away task
VendorPayment Pay vendor (PayVendor) Draft → Paid settles payables
PurchaseReturn Post purchase return (PostPurchaseReturn) Draft → Posted stock−, payables− (net); another document's cell reserve is not written off; input VAT is not reversed
PurchaseCreditNote Post purchase credit note (PostPurchaseCreditNote) Draft → Posted reverses input VAT and the tax part of the payable; the order stays Received
SalesOrder Submit order (SubmitSalesOrder) Draft → Submitted a commercial request, no postings. With SalesSettings.ConfirmOrderOnSubmit — Draft → Confirmed straight away and a sales realization, as with Approve order
SalesOrder Approve order (ApproveSalesOrder) Submitted → Confirmed creates the realization in Reserved (does not write the reservation itself); the Manager phone: ApproveOrder
SalesOrder Reject order (RejectSalesOrder) Submitted → Draft RejectReason is mandatory; the agent corrects it and submits again. The Manager phone: RejectOrder
SalesOrder Cancel order (CancelSalesOrder) Submitted / Confirmed → Cancelled an unclosed realization is removed
SalesRealization Start picking (StartPicking) Reserved → Picking the same reservation
SalesRealization Mark packed (MarkPacked) Picking → Packing the same reservation
SalesRealization Mark shipped (MarkShipped) Packing → Shipped the same reservation
SalesRealization Release realization (ReleaseRealization) Shipped → Issued stock−, debt+, revenue+, tax, loyalty; the reservation is released
SalesRealization Issue realization (IssueInvoice) Draft → Issued POS / without an order: the same Issued set straight away
SalesReturn Post sales return (PostSalesReturn) Draft → Posted stock+, debt− (net), revenue−
SalesCreditNote Post sales credit note (PostSalesCreditNote) Draft → Posted reverses the VAT and the tax part of the debt; the original realization stays Issued
SalesDebitNote Post sales debit note (PostSalesDebitNote) Draft → Posted additional VAT and the tax part of the debt; the lines are not copied from the original realization; that realization stays Issued
CustomerPayment Receive payment (ReceiveCustomerPayment) Draft → Paid settles receivables

Issued in the UI is Released. Issued → Paid on a realization does not exist: payment is made by CustomerPayment. There is no Mark delivered on a realization.

DeliveryTrip stays an option for consolidated delivery, not the axis: FillTripFromRoute (collects the Confirmed orders of the route's stops), DispatchTrip (Draft → Dispatched, checks the crew and the capacity), CompleteTrip (closes the orders: a refusal → Cancelled, otherwise Delivered). The global PlanDeliveryWave creates the drafts from the day's schedule. The global PlanPickWave (Gather pick wave, warehouse ribbon) folds draft PickTasks for several orders from one storage cell into one task. An order becomes Delivered when the realization is Released, or when a trip completes. The usual path is order → realization, with no trip. The process — sales and purchasing.

Production, accounting, HR, taxes, loyalty#

Document Command Transition What happens
ProductionOrder Release to shop (ReleaseProduction) Draft → Released the components are written off
ProductionOrder Finish production (FinishProduction) Released → Finished the write-off + the output
JournalEntry Post journal entry (PostJournalEntry) Draft → Posted Dr=Cr balance, the period on the date, a closed month refuses
TimeSheet Approve timesheet (ApproveTimeSheet) Draft → Approved otherwise "Accrue payroll" is not visible
PayrollAccrual Post payroll accrual (PostPayrollAccrual) Draft → Posted an accrual created by hand; from a timesheet the same is done by AccruePayroll
PayrollAccrual Void payroll accrual (VoidPayrollAccrual) Posted → Voided Mix removes the payroll, the liability and the GL; the linked contributions go first; a net already paid refuses
PayrollPayment Pay payroll (PayPayroll) Draft → Paid the payout
PayrollPayment Void payroll payment (VoidPayrollPayment) Paid → Voided Mix brings the liability back
SocialInsuranceAccrual Post social insurance (PostSocialInsuranceAccrual) Draft → Posted the Posted transition: SocialInsuranceAccrualTx; usually spawned from an accrual
SocialInsuranceAccrual Void social insurance accrual (VoidSocialInsuranceAccrual) Posted → Voided Mix removes the register and the withholding; amounts already paid to the fund refuse
SocialInsurancePayment Pay social insurance (PaySocialInsurance) Draft → Paid the Paid transition: SocialInsurancePaymentTx
SocialInsurancePayment Void social insurance payment (VoidSocialInsurancePayment) Paid → Voided Mix brings SocialInsurance back; after that the contribution accrual can be voided
TaxReturn File tax return (FileTaxReturn) Draft → Filed the filing + a TaxSubmission row
TaxPayment Pay tax (PayTax) Draft → Paid the Paid transition: TaxPaymentTx
TaxCalculation Finalize tax calculation (FinalizeTaxCalculation) Draft → Finalized usually set by the tax service
LoyaltyRedemption Redeem points (RedeemLoyalty) Draft → Redeemed the points are written off

Commands without a subtype change#

Document Command What for
TimeSheet Fill standard week (FillTimeSheet) on Draft Mon–Fri at the 40h norm, the gaps out of TimeOff, manual ticks are not overwritten
TimeSheet Accrue payroll (AccruePayroll) on Approved creates a PayrollAccrual and sets it to Posted straight away; the salary, or hours × rate
SalesRealization / PurchaseOrder Fill prices fills in the lines' prices
ProductionOrder Expand bill of materials (ExpandBom) fills the components from the BOM
ProductionOrder Schedule operations (ScheduleProduction) on Draft and Released sets the day of each operation by sequence and remaining capacity

Rolling back (posted → draft) the engine can do by itself. No document has a separate "Back to draft" command.

How not to confuse the three mechanisms#

What What for
The command the button: the checks + the subtype change. The only path from the UI
The transition map (subtypeTransitions) which edges are permitted at all. An empty map = everything is permitted
The transaction scripts what to write into the registers once the subtype has already changed. They are attached to a subtype, not to the document as a whole
The events (OnBeforePost / OnAfterPost) validation and side effects of any transition, including one made from the API

A map with no command gives no button. Scripts with no command do not run from the card. Events with no command only fire if the subtype was changed programmatically.

What's not there yet#

  • There is no subtype selector on a saved card; a visible command is the only user transition.
  • There is no generic Back to draft command for posted documents.
  • A sales realization has no paid state; payment is a separate customer payment.

Where next#

Was this page helpful?