ZuloOne
English
Sign in
Menu

🔄 Sales and purchasing (the process)

Updated

Sales and purchasing are mirror contours: one document recognises a debt, another document settles it, and tax is calculated separately. Metadata — 07-Sales and 05-Purchasing. The ledger and obligations — obligations, payments and the general ledger. How the contours run — the process map (§8–10).

← Business process wiki

Transitions from the card are made by a command, not a subtype selector. The catalogue of buttons is commands and subtypes. Printing is Zr, with names resolved through the reference display and amounts through the pricing service's line amount: the order OrderZr, the sales realization InvoiceZr, the credit note CreditNoteZr, the debit note DebitNoteZr, the goods return ReturnZr (not a tax invoice — the VAT is reversed by the credit note), the payment CustomerPaymentZr, the trip DeliveryTripZr (driver, route, stops, no sales amounts). Credit and debit notes and returns print the original invoice. Purchasing: the order PurchaseOrderZr, the payment VendorPaymentZr, the return to the supplier PurchaseReturnZr (net, the original order; input VAT is reversed by PurchaseCreditNoteZr). The phone takes the order print-form id from SalesAgent.json layouts.SalesOrder.printForm.

Submit → Approve creates the realization in Reserved; Release realization sets Released and the order Delivered. Tax posts only after Released. There is no Mark delivered on a realization. The trip is an option, not the main process axis.

ABC/XYZ classification is not part of the sales axis: profiles and bands live in the Sales settings, the recalculation is a command, and the log is an information register. Process 22 in the process map; instructions for the user.

The rule: a payment is not a subtype of the sales realization or of the order#

A subtype transition removes the previous state's movements. "Paying" the sales realization or the order itself would erase the revenue and the stock along with the debt. That is why CustomerPayment and VendorPayment are documents of their own.

In the ribbon: Sales holds customers, orders, sales realizations, payments and returns; Purchasing holds suppliers, purchase orders and payments. Writing stock off lives on the Inventory tab, not in sales. The DeliveryTrip stays in the model as an option for consolidated delivery, not as the axis of the process.

A sale: order and realization are two documents#

A new tenant gets the BuySell profile after organisation setup — buy and sell, no production: a ProductionOrder cannot be saved in that mode. An explicit CommonSettings.TradeProfile = Full brings production back. The entry point is TradeProfileService. The same call installs the country packages by the legal entity's ISO2 code (vat-SA / vat-UA).

The customer is the debtor. Work is done against a trading outlet (CustomerOutlet), and the terms of the deal are on the customer's contract (SalesContract: currency, settlement kind, payment term, delivery term (Incoterms), price type, special prices and quantity breaks, a date window). A line's price is resolved by the pricing service from the contract's type and its overlays, not by a second sales-specific engine; the line carries a price explanation. The order, the realization and the return all carry a mandatory contract. Without a contract a document can be neither saved nor posted. A contract is signed with the customer; its Contract outlets list (SalesContractOutlet) limits it to some outlets, and an empty list covers them all. A customer may hold several contracts in force at once (for example, one per legal entity or per currency): the document fills the contract by itself only when exactly one of them covers its outlet on its date; with two or more it asks you to pick («The outlet has several contracts in force on the document date: choose the contract.»). The debt is recorded in Receivable by customer and contract. Country VAT (VatPayable / UaVatPayable) is broken down by customer × outlet × contract (the outlet comes from a contract limited to one outlet). A payment settles a segment of Receivable: a CustomerPayment line carries a mandatory contract too.

The order is a commercial promise — optionally preceded by a lead and a quotation. The realization (SalesRealization) is stock, debt and tax. Confirming the order creates the realization in Reserved immediately: the goods are held, the money and the VAT are not there yet.

The internal name of the realization's final subtype is Issued (existing rows, handlers, ZATCA). The UI caption is Released. The code still compares against "Issued".

Postings do not accumulate (Mix semantics): the target subtype writes its own set from scratch. The reservation exists on Picking, Packing and Shipped only because the same reserve script is attached to those subtypes as well. On Issued it is absent — the reservation is released and stock, revenue, receivables, loyalty and VAT are written instead.

Lead (CRM, optional)
  New → Qualified → Quoted     SalesLead
    no scripts
    ConvertLead → SalesQuotation Draft (SourceLead, no second quotation)

Quotation (CRM, optional)
  Draft → Issued → Converted     SalesQuotation
    no scripts
    ConvertQuotation → SalesOrder Draft (SourceQuotation, no second order)

order
  Draft → Submitted → Confirmed(=Approved)     SalesOrder
    no scripts (the reservation is not here)
    SalesSettings.ConfirmOrderOnSubmit (off by default) — Submit goes straight to Confirmed
    after-post on Confirmed:
      InvoiceOrderAsync → SalesRealization in Reserved
      repeating it by SourceOrder does not create a second one
      with EnforceWarehouseTasks — a draft PickTask
    Cancelled — from Submitted or Confirmed; an unclosed realization is removed too
    RejectSalesOrder — Submitted → Draft when RejectReason is filled (office and
    the Manager phone)

realization (from the order)
  Reserved → Picking → Packing → Shipped       SalesRealization
    script: SalesInvoiceReserveTx
    ReservedStock+   stock, debt and tax are NOT touched
    free = Stock − Reserved

  Shipped → Issued(=Released)
    on Issued: Stock−, Revenue+, Receivable+, Loyalty+, SaudiVat+
    no reservation — it is gone because the set was replaced
    before-post on Issued:
      the picking cell, the free balance (another reservation minus its own and minus this order's picking), recording LegalEntity and TaxRateApplied
      no rate while a tax is configured — refused, not a silent zero
    after-post on Issued:
      TaxCalculation OUTPUT plus the link to the sales realization
      the order → Delivered (a flag; the order has no scripts)

The revenue for this realization is the **Revenue by item, customer and contract**
layout (Sales → Totals → Revenue). Groups: item, customer, contract. The line
amount is quantity × the document's discounted price, without tax. Ten pieces
at 100 produce an outgoing **1000**. Returning two pieces at 100 leaves **800**.
A zero outgoing value is hidden. Tax is not in this line: it is in the debt and
in VAT.

POS / without an order
  Draft → Issued                               SalesRealization
    the same Issued set, tax straight away
    **Issue realization**: the same Issued set

  Draft → Posted                               SalesReturn
    Stock+, Receivable− (net), Revenue−
    GL: Dr revenue / Cr receivables
    does not touch VAT

  Draft → Posted                               SalesCreditNote
    **Original invoice** is mandatory; that sales realization stays Issued
    Receivable− (VAT), VatPayable−, TaxLedger reversal
    does not touch stock
    empty lines are copied from the original realization

  Draft → Posted                               SalesDebitNote
    **Original invoice** is mandatory; that sales realization stays Issued
    the lines are additional charges, not copied from the original realization
    Receivable+ (VAT), VatPayable+, TaxLedger accrual
    does not touch stock

  Draft → Paid                                 CustomerPayment
    on Paid: CustomerPaymentTx
    Receivable− (gross)

Issued → Paid on a realization does not exist. The Paid subtype is kept on SalesRealization for historical rows and is hidden in the card stepper. Payment is a separate CustomerPayment.

Issued (Released) is marked read-only: the header and the lines are frozen. Otherwise a quantity could be edited after the stock had been written off and the revenue recognised. Moving to another subtype — a cancellation, say — remains a command.

The stand: the default tax contour#

Tax on a realization is optional: with an empty default tax code the handler releases the sales realization with no TaxCalculation at all.

For new released realizations on a stand to carry VAT, the contour is installed by packages — data, not metadata:

Package When What
Tax/tax-directions once INPUT / OUTPUT / WITHHOLDING / OTHER
Tax/vat once the common VAT: the authority and the INTL jurisdiction, the VAT tax, the STANDARD / ZERO_RATED / EXEMPT / OUT_OF_SCOPE / REVERSE_CHARGE categories, the STD / ZERO / EXEMPT / OOS / RC codes. No rates — a rate is always a country's
LocalizationSaudiArabia/vat-SA country SA the Saudi tax of its own: authority ZATCA, jurisdiction SA, tax VAT-SA (not the common VAT) with its own STANDARD/ZERO_RATED/EXEMPT/OUT_OF_SCOPE categories, the VAT5 (2018–2020) and VAT15 (from 2020-07-01) rates, and the ZATCA S / Z / E / O codes

TaxSettings is a singleton dictionary: edit the existing row, do not insert a second. A rate in TaxRate is stored as a fraction (0.15), not a percentage.

Every money leg is summed the same way — the pricing service's line amount (quantity × price × the header discount). Without that, a discount would drift the registers away from the ledger.

The seller's legal entity and the tax rate are recorded only on Issued (cell → store → division; the rate is a dated TaxRate). The country-specific SaudiVatTx and the universal TaxLedger read TaxRateApplied off the document, not a constant. There is no recorded rate on Reserved through Shipped: otherwise a rate preview would be pretending to be an accrual.

Scripts by realization subtype#

Subtype Scripts
Draft none
Reserved, Picking, Packing, Shipped SalesInvoiceReserveTx
Issued SalesStockTx, SalesRevenueTx, SalesReceivableTx, SalesLoyaltyTx (CRM), SaudiVatTx (KSA). No reserve
Paid none; entry is forbidden

The master data without which the card is misleading#

On the customer: the address (Common.Address), the payment term, the credit limit, the contacts (CustomerContact). The delivery term (Incoterms EXW, FOB, DAP…) lives on the contract rather than the customer, and is copied to the order and the realization while the field is still empty. On the item: the image, the description, the brand. On the order and the realization: the contact, the payment term, the delivery term, notes; the order also carries a header discount. The invoice and the purchase order record a calendar DueDate = document date + the payment term's days (or the default days, if there is no term). A sales order does not compute a due date. The line amount and the VAT in the grid are computed, not columns.

Outstanding and year-to-date are a related-snapshot tile on the order and invoice cards: the limit from Customer.CreditLimit, the debt from the Receivable register, revenue since the start of the year from Revenue, the number of orders in the year and the share delivered no later than the delivery date. The feed of subtype changes is the activity tile. How to place tiles — widgets.

Purchasing#

End-to-end pages: purchase order, vendor payment, purchase return, and supplier invoice. The received purchase order recognises the payable; the supplier invoice books only the difference the supplier billed.

Draft → Ordered → Received        PurchaseOrder
  Stock+, Payable+ (net), ItemCostFifo+
  GL FIN+MGT: Dr stock / Cr payables
  TaxCalculation INPUT
    TaxLedger+, Payable+ tax
    GL FIN+TAX: Dr recoverable VAT / Cr payables
  draft PutAwayTask               (the goods are still in receiving)

Draft → Paid                      VendorPayment
  on Paid: VendorPaymentTx
  Payable− (gross)
  GL FIN+MGT: Dr payables / Cr cash

Draft → Posted                    PurchaseReturn
  Stock−, Payable− (net)
  GL: Dr payables / Cr stock
  input VAT is not reversed
  the return does not take another cell's reservation (`ICellFreeStock`)

Draft → Posted                    PurchaseCreditNote
  Payable− tax
  TaxCalculation INPUT reverse
  GL FIN+TAX: Dr payables / Cr recoverable VAT
  the order stays Received

Known issue on purchase credit notes: the TaxCalculation reversal uses contextual tax determination, while PurchaseCreditNotePayableTx reverses payable VAT by Applied tax rate recorded from the default tax code. With tax rules or mappings, the two rates can diverge.

Approving a purchase order#

A purchase order above a threshold cannot be placed until somebody approves it.

The threshold is the switch. Open Ribbon → Purchasing → Settings → Purchasing settings, field Approval threshold:

Value What happens
Empty or 0 Approval is off, and this is the default. Place order behaves exactly as before — a stand that never set a threshold does not change
A number, say 1000.00 An order whose lines total more than 1000.00 is refused by Place order until approved. Exactly 1000.00 is not above the threshold and goes through

Approve order sits on the draft and stamps Approved amount with the total it approved. The order is approved for a sum, not approved in general — add lines afterwards and the stamp no longer covers the new total, so placing is refused again. Without that, approving a small order and filling it up would be a silent way around the threshold.

There is no separate "approved on" date and no "approved by" field: when and who are in the document log, and a second copy of either would be free to disagree with it. Restricting who may press the button is a permissions question, not a field.

Example with numbers#

Threshold 1000.00.

Step Order total Place order says
Draft with 10 × 500.00 5,000.00 Refused: names both 5,000.00 and the threshold 1,000.00
Approve order 5,000.00 Approved amount becomes 5,000.00
Place order 5,000.00 Ordered
Another line of 90,000.00 added to a fresh copy after approval 95,000.00 Refused again: approval was for 5,000.00
An order of 100.00 100.00 Ordered; Approve order answers that approval is not required

Both buttons ask the same service (IPurchaseApproval), so "may I approve this" and "may I place this" cannot drift apart into an order that can be neither approved nor placed.

The Draft → Received route is forbidden: the transition table blocks the jump. PurchasingSettings.AutoReceiveOnOrder (off by default) does not jump: Place order writes Ordered, then Received. The receipt is still checked while in Draft (IPurchaseReceiptService is the same entry point as the Receive goods button), so a refusal leaves the document in Draft, not Ordered: half of the requested action is worse than doing nothing. The receipt increases stock, and the costing driver stays silent on the plus side — there is no doubled after-post problem here like Sales has with layers.

Each line has a Receive now field. Zero on every line means receiving the whole quantity, as before. If at least one line has a number above zero, only that quantity is received. Example: an order of 10 pieces at 3, receive now 4. The cell gets 4, payables are 12. The remaining 6 become a new order in Ordered, with the same supplier, cell and price; it does not move stock until it is received too. A line with zero, when a neighbouring line has a set quantity, is not included in this receipt and goes wholly into the residual order. A number above the line quantity rejects the receipt, and stock stays as it was.

Line amount, line discount and packages#

Every goods line — sales order, realization, sales return, purchase order, purchase return, supplier invoice, stock transfer, stock adjustment — carries:

Field Values Default Effect
Amount money quantity × price (less the line discount) The amount the line contributes to stock value, the receivable or payable and the books
DiscountPercent (sales lines only) percent, 15 = 15 % 0 Taken off the line before the document discount
PackQty / PackUnit packages / a unit of the item (box, pallet) empty Packages are base quantity ÷ the package size in the item's units

Editing a line works as in 1C: change quantity, price or discount — the amount is recalculated; type an amount — the price becomes amount ÷ quantity, to the kopeck, and the typed amount stays; type packages — the quantity follows.

The typed amount wins over quantity × price only while price rounding explains the difference — up to half a kopeck per unit plus half a kopeck. A supplier's receipt of 12 000 units at 36.91 with a document sum of 442 932 posts 442 932 to the payable, the books and the FIFO lots (12 000 × 36.91 = 442 920 would lose 12). An amount of 999 on a line of 10 × 3 is stale — someone changed the quantity after typing it — and the line posts 30.

The same rule holds when a line is saved again. Saving replays every field of the line, quantity and price included. A price that came from the typed amount does not overwrite it: 100 typed on 3 units gives a price of 33.33, and the line stays 100, not 3 × 33.33 = 99.99, however many times it is saved. A real edit breaks the link. Quantity 4 makes 133.32. A price of 40 makes 120.

On a positive stock adjustment the line price is the cost of the surplus: 5 units at 9 open a lot of 45, not 5 at the current average. A line without a price keeps the average.

Copying a line to the next document carries amount, discount, unit and package unit: order → realization, order → supplier invoice, the remainder of a partial receipt, receipt → purchase return. A copy with a different quantity drops the amount and recalculates it.

Line warehouse cell#

Realization, sales return, purchase order (receipt) and purchase return lines carry Location — the cell this line's goods move through (1C: Склад of the line). Empty means the header's location, which is how almost every document is filled.

Where Effect of a line cell
Stock movement the line's quantity moves in its own cell, the rest in the header's
Stock check on issue, purchase return each cell is checked for its own lines: 3 from the header cell with 10 on hand and 4 from cell B-02 with 5 on hand pass; 6 from B-02 is refused
Warehouse discipline (Inventory settings) a line cell must have the purpose of the operation — picking for a realization, receiving for a receipt and a sales return
Legal entity a line cell must belong to the same legal entity as the header's; ledger, tax and receivable follow the header

A receipt of 4 + 6 at 3 with the second line in cell R-02 puts 4 in the header cell and 6 in R-02; the payable is 30 for the document. A line cell of another legal entity's warehouse is refused before posting.

A receipt split into a partial receipt and a purchase return created from a receipt keep each line's cell.

Line VAT rate and VAT amount#

Sales order, realization, sales return, purchase order (receipt), supplier invoice and purchase return lines carry TaxCode (VAT rate, 1C: СтавкаНДС) and TaxAmount (VAT amount, 1C: СуммаНДС). The item (Item.TaxCode) supplies the default rate when it is picked.

Field Values Default Effect
Item.TaxCode a tax code empty Copied to the line when the item is chosen
TaxCode on a line a tax code from the item The line is taxed at this code's rate on the document date
TaxAmount on a line money net amount × rate Kept as typed when within a kopeck of the computed tax (1C rounds every line); otherwise recomputed

A line without a code is taxed as before lines had codes: the document's base goes through the tax rules and then the default code of the tax settings, and the realization keeps that rate in TaxRateApplied.

Example. Tax settings: default code 20 %. A realization with two lines of 10 × 10: the first line has code «Reduced 7 %», the second none.

Base VAT
Line 1, code 7 % 100 7
Line 2, document rate 20 % 100 20
Receivable 227

The tax calculation of the realization has two lines, one per code, and the tax ledger shows 7 under the 7 % code and 20 under the 20 % code. A line code without a rate on the document date stops the realization (and the receipt) before posting; the line itself shows zero VAT until a rate exists.

The Ukrainian tax invoice (UaEInvoice) prints each line's amount after discounts — 10 × 10 less 10 % is 90 at a price of 9, not 100 — and a coded line prints its own rate and VAT.

Credit and debit notes#

Sales credit and debit note lines carry the same TaxCode and TaxAmount. A credit note created from a realization copies each line's rate; on a debit note the rate comes from the item when it is picked. Coded lines are taxed at their rate on the note date; the others at the rate copied from the realization (TaxRateApplied). A note whose line rate has no rate in force on its date is not posted.

Example: a realization of 4 × 25 at 7 % and 2 × 25 without a code, document rate 15 %, gives a receivable of 100 + 7 + 50 + 7.5 = 164.5. A full credit note reverses the VAT only — 7 + 7.5 = 14.5 — leaving 150 for the sales return to clear. The credit note's tax calculation reverses each code separately: −7 under the 7 % code, −7.5 under the 15 % one, so the tax ledger nets to zero per code. A debit note of 2 × 10 at 7 % and 1 × 20 without a code adds 1.4 + 3 = 4.4; its tax calculation has a 7 % line (base 20, tax 1.4) and a 15 % line (base 20, tax 3).

Three accountings on this chain#

Ledger What it sees
Financial (Debit/Credit) the net transaction + VAT legs + payments
Management (Management*) the net transaction and payments, without VAT
Tax (Tax*) VAT accrual and payment

After tax calculation, the Receivable / Payable registers become gross: a payment of 115 closes a debt of 100 + 15. Without tax settings the debt stays 100. Before Issued there is no receivable — a reservation does not create it.

The settlement kind is set on the contract: Credit (the contract limit applies to this contract segment; if the limit is only on the customer, all that customer's contracts share it), Prepaid (advance on this contract: Receivable + amount ≤ 0), CashOnDelivery (do not check the limit). The contract currency must match the seller legal entity's currency. There are no exchange rates.

Delivery: fleet, route, trip#

The trip is optional. POS and customer pickup without DeliveryTrip are legal. A trip is a morning wave by trading outlets, not the sales axis: the debt still belongs to the customer, and the sales realization still comes from the order.

Catalogue#

Object Why
Vehicle plate number (unique), type (Van/Truck/Bike/Car), capacity in pieces/kg. CapacityQty = 0 means no check. IsDisabled switches the vehicle off
Driver full name, phone, licence (unique when filled). Not SalesPerson and not an HR Employee
DeliveryRoute name, departure store (Store), default vehicle/driver
DeliveryRouteStop route + sequence + outlet. Among live rows (Route, Sequence) and (Route, Outlet) are unique. The window and stop time are minutes from midnight
DeliverySchedule route + weekday (Mon=1…Sun=7) + departure hour/minute + crew. Among live rows (Route, Weekday) is unique

SalesPerson remains a sales agent (the Agents menu); they are not put behind the wheel.

Trip#

Header: date, route, vehicle, driver, store, planned departure, actual departure, actual completion. An empty crew is filled from the route, then from this weekday's schedule (IDeliveryService.ResolveStampAsync). Lines: order, outlet (copied from the order), stop sequence, issued quantity, the window and stop time from the stop card (minutes from midnight), StopOutcome (Unspecified / Delivered / Partial / Refused). Unspecified is treated as delivered when the trip is closed.

Commands:

Command From What
FillTripFromRoute Draft confirmed orders of the route's outlets on the trip date; an order on another Route is skipped; the line receives the stop window and stop time
DispatchTrip Draft → Dispatched ValidateTripAsync: vehicle, driver, lines, route outlets, crew free that day, capacity if set. PlannedDepart = trip date + the live schedule's hour/minute for that day (empty if there is no schedule). ActualDepart = now
CompleteTrip Dispatched → Completed (Draft → Completed also exists) the same check, ActualComplete = now before moving to Completed (the subtype is read-only), then ISalesFulfillmentService.CompleteTripAsync: Refused → order Cancelled, otherwise Delivered
PlanDeliveryWave global a draft trip for each live schedule on the day; a route that already has Draft/Dispatched for the date is skipped

There is one entry point: IDeliveryService. Handlers and commands do not scan stops or other trips themselves. Printing is DeliveryTripZr: driver, route, vehicle, departure store, planned/actual departure, stops (order, outlet, window, issued, outcome). There are no sales amounts on the sheet.

Example. Wednesday schedule 06:30, trip on 2026-09-16. Dispatch writes PlannedDepart = 2026-09-16 06:30 and ActualDepart at about "now". Completion writes ActualComplete in the same save as subtype Completed. Optimise stop order and per-stop arrival marks are on the trip document — delivery trips. Distances are straight-line; there is no road graph.

Multi-day trips and debt by outlet are not on this contour. Exchange differences follow the contract or supplier currency — FX.

What's not there yet#

  • There is no customer ageing report. A payment reduces debt; it does not match a particular invoice line.
  • A trip is optional. Multi-day trips and debt by outlet are not on the contour. Distances are straight-line; there is no road graph.
  • Line amounts are net. There is no price-including-VAT on a line.
  • The country-neutral tax submission is a mock. Country e-invoice channels are what each localization package ships — e-invoice envelope.
  • Put-away after a receipt starts from the header cell, not a line cell. Line VAT codes are not read by the supplier-invoice variance, the non-recoverable share of a receipt, or Saudi and Ukrainian credit notes.

Where next#

Was this page helpful?