🔄 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).
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→Approvecreates the realization inReserved; Release realization sets Released and the orderDelivered. 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.