05 · Purchasing — purchase orders, receipts, returns, and vendor payments
Updated
Solution layer. Depends on Accounting,
Common, Inventory,
Organization, and Tax. Purchasing covers
goods inflow: supplier order, receipt into the store, accounts-payable settlement,
and the mirror correction documents: purchase return and purchase credit note.
Purpose#
PurchaseOrder moves through Draft → Ordered → Received. On Received it
moves Stock, calculates input VAT through the
Tax engine, recognizes the vendor liability in Payable, and
creates a PutAwayTask for storage. Receipt cancellation is
split into two documents with different responsibility. PurchaseReturn returns
physical goods and the net amount to the supplier: store and net payable.
PurchaseCreditNote adjusts only input VAT and the tax part of the payable. They
complement each other; they do not replace each other. VendorPayment settles
the payable as a separate document, not as an order subtype. It mirrors
CustomerPayment in Sales and PayrollPayment in
HR.
Process description for analysts: Sales and purchasing. This page is the object-level reference.
What is created (object map)#
| Kind | Name | Purpose |
|---|---|---|
| Model | Purchasing |
module model, Solution(2) layer |
| NumberSequence | PurchaseOrderSeq, PurchaseReturnSeq, PurchaseCreditNoteSeq, VendorPaymentSeq, SupplierInvoiceSeq, PurchasingSettingsSeq |
numbering |
| Dictionary | PurchasingSettings |
module settings (singleton): auto-receipt and default payment term |
| TablePart | PurchaseOrderLines, PurchaseReturnLines, PurchaseCreditNoteLines, VendorPaymentLines, SupplierInvoiceLines |
lines |
| Document | PurchaseOrder |
order/receipt (Draft→Ordered→Received) |
| Document | PurchaseReturn |
return to supplier: store minus and payable minus (net) |
| Document | PurchaseCreditNote |
input VAT and the tax part of the payable, without stock movement |
| Document | SupplierInvoice |
the supplier's own paper (Draft→Registered): books only the difference from what the receipt accrued |
| Document | VendorPayment |
payment to supplier, separate document |
| Register | Payable |
accounts payable by supplier |
| Analytic | Supplier |
reused analytic over the Supplier dictionary, for Payable |
| Service | PurchaseReceiptService |
order receipt, including partial receipt |
| Service | SupplierInvoiceMatchService |
accrued vs invoiced totals and the tolerance check |
| Menu | menu.json |
module menu |
| — reused — | Item/Supplier/StoreCell/Stock/IStoreCellService/IStockAvailabilityService/IPricingService (Inventory), Money (Accounting), PaymentTerm/IRecordDefaults/IPaymentDueService (Common), LegalEntity (Organization), tax engine (Tax) |
not created here |
Total: 1 model, 6 number sequences, 1 dictionary (singleton), 5 table parts,
5 documents, 1 register, 2 services, 8 commands, and menu. The model has no
own EDTs and no link tables. All reference fields use EDTs from other models;
document links use the common document-reference graph, not LT_* objects.
model.json#
Solution(2) layer. Dependencies: Accounting (≥1.0.0),
Common (≥1.10.0), Inventory (≥1.0.0),
Organization (≥1.0.0), Tax (≥1.0.0).
The dependency on Accounting is only for the Money EDT. Purchasing itself does
not depend on Accounting just for the type any more: receipt, return and
payment write their own ledger legs (PurchaseReceiptGLTx, PurchaseReturnGLTx,
VendorPaymentGLTx) through IGeneralLedgerService.EntryAsync; see
Who writes the general ledger.
Number sequences#
PurchaseOrderSeq, PurchaseReturnSeq, PurchaseCreditNoteSeq,
VendorPaymentSeq, SupplierInvoiceSeq, PurchasingSettingsSeq (the singleton
dictionary also has a sequence).
Table parts#
PurchaseOrderLines: Item→Item (required), Quantity
Qty (required, unit is Unit, normalized into hidden BaseQuantity),
UnitPrice Money (required), ReceiveQty Qty (optional, “receive now” for
partial receipt; see PurchaseReceiptService).
SupplierInvoiceLines: Item (required), Quantity (required), UnitPrice
(required) — the supplier's own figures, which are allowed to disagree with the
order the invoice is linked to.
PurchaseReturnLines, PurchaseCreditNoteLines: Item (required), Quantity
(required), UnitPrice (required). They have no unit field and no
BaseQuantity: the line repeats the original receipt position, where unit
conversion has already happened.
VendorPaymentLines: Supplier→Supplier (required),
Amount Money (required). Supplier is on the line, not the header: one
payment can close debt for several suppliers.
EDT#
None. RefSupplier/RefStoreCell/RefItem/RefUnitOfMeasure/Qty come from
Inventory, RefLegalEntity from
Organization, RefPaymentTerm from
Common, TaxRateValue from Tax, and Money from
Accounting.
Dictionaries#
PurchasingSettings#
One record per tenant (isSingleton, isCached).
AutoReceiveOnOrder Boolean (optional): when an order is confirmed, it tries to
receive immediately; without the flag, receipt is a separate step. See the
PlaceOrder command. DefaultPaymentTerm→PaymentTerm
(optional), DefaultPaymentTermDays Integer (optional, hidden): stamped from
DefaultPaymentTerm.Days in OnBeforeSaveAsync. If the link is empty,
OnAfterLoadAsync restores DefaultPaymentTerm by reverse lookup on
DefaultPaymentTermDays. This is the compatibility path for settings stored
before the link field existed.
InvoiceMatchTolerance Decimal 18,2 (optional): the largest difference
RegisterSupplierInvoice accepts between the invoice and what the receipts
accrued. Zero means no limit.
Documents#
PurchaseOrder#
- Header:
Supplier→Supplier (required) ·Location→StoreCell (required, caption “Cell”) ·PaymentTerm→PaymentTerm (optional) ·DueDateDateTime (optional, stamped on each save from the contract/module payment term) ·ExpectedReceiptDateTime (optional, expected shelf date, not derived fromDueDate) ·LegalEntity→LegalEntity (optional, buyer; normally stamped by the server from the cell chain onReceived, not entered manually) ·NonRecoverableVatDecimal(18,2) (optional, hidden: the input VAT share that cannot be deducted and is capitalized into stock cost by Costing). - TP-Lines (
PurchaseOrderLines): see Table parts. - Subtypes:
Draft(initial) →Ordered→Received(isReadOnly). The metadata declares onlyDraft→Ordered.Ordered→Receivedhappens programmatically insidePurchaseReceiptService, which is legal for services; the user path goes through a command. - Events:
OnBeforeCreateAsync: fillsLegalEntity/PaymentTerm/DueDatethroughIRecordDefaults.SeedAsync, if they are empty.OnBeforeSaveAsync: rereadsExpectedReceiptfrom the raw save package beforenext()because otherwise a partial update of one subtype erases the field. It stampsDueDatethroughIPaymentDueServicefrom the contract payment term or the module default (PurchasingSettings.DefaultPaymentTermDays).OnBeforePostAsync: refuses empty lines or lines withQuantity ≤ 0. Only onReceived,Locationmust be a receiving cell (IStoreCellService.IsCellAllowedForAsync(…, Receiving), the store discipline); a tax rate must exist on the date if a tax code is specified. It calculates and stampsNonRecoverableVat. IfLegalEntityis still a placeholder, it resolves it from the receipt cell chain Cell→Zone→Store→Division throughIStoreCellService.GetLegalEntityAsync. This is a point header-only update, not a full save.OnAfterPostAsync(Received): opens an INPUT tax calculation (ITaxService.CreateCalculationAsync, tax point = document date) and links it to the order through the document-reference graph (IDocumentManager.AddLinkAsync, not a link table). If store discipline is enabled, it idempotently creates aDraftPutAwayTask with lines from the order, searched by the same reference graph.
- Posting subtype
Received(executionOrder):GoodsReceiptStockTx(Purchasing,+Stock byItem/Cell=Location) →GoodsReceiptPayableTx(Purchasing,+PayablebySupplier) →ReceiptCostTx/ReceiptFifoTx(Costing, open theInventoryValue/ItemCostFifolayer) →UaPurchaseReceiptTx(22-Ukraine, first event).
PurchaseReturn#
Only physical goods and the net amount. It does not touch the tax part; that is
the job of PurchaseCreditNote.
- Header:
Supplier(required) ·Location→StoreCell (required, caption “Return Cell”) ·OriginalOrder— plainGuid, not a Reference EDT. This is the only known deviation in the whole model from the rule “every reference to an entity uses a Reference EDT”. Fix it on the next object edit; do not silently document it as an EDT. - TP-Lines (
PurchaseReturnLines):Item,Quantity,UnitPrice(all required). - Subtypes:
Draft→Posted(isReadOnly). - Events:
OnBeforeSaveAsynccopiesSupplier/Locationand all lines fromOriginalOrderif it is specified and the return has no lines yet.OnBeforePostAsync(Posted) refuses no lines, lines withQuantity ≤ 0, and missingSupplier/Location. It checks physical balance for every position throughIStockAvailabilityService.HasSufficientStockAsyncand refuses with “need/have” when stock is short. - Posting subtype
Posted:PurchaseReturnStockTx(−Stock) →PurchaseReturnPayableTx(−PayablebySupplier).
PurchaseCreditNote#
Only the tax part and its share in the payable for an already received order.
The order stays Received; it is not cancelled. Stock and net amount are handled
by PurchaseReturn. The two documents solve different parts of the same “wrong
receipt” problem. They are usually issued together, but independently.
- Header:
Supplier(required) ·LegalEntity(optional, “buyer's legal entity”) ·OriginalOrder, the same plainGuiddeviation as inPurchaseReturn·TaxRateApplied→TaxRateValue(Tax) optional. - TP-Lines (
PurchaseCreditNoteLines):Item,Quantity,UnitPrice(all required). These lines are the base for VAT recalculation, not a physical return. - Subtypes:
Draft→Posted(isReadOnly). - Events:
OnBeforeCreateAsyncseedsLegalEntity.OnBeforeSaveAsync, whenOriginalOrderis set, pullsSupplierfrom the order if it is empty; resolvesLegalEntityfrom the receipt cell while the current value is still the module placeholder (otherwise the tenant default would be stamped instead of the real buyer); stampsTaxRateAppliedwith the current rate on the order date (ITaxService.ResolveDefaultTaxCodeAsync/ResolveRateAsync) if it is not already set; and copies lines from the order if lines are empty.OnBeforePostAsync(Posted) refuses missingOriginalOrder; the order must exist, beReceived, and have the sameSupplier; whileLegalEntityis still a placeholder, it recalculates and rewrites the header only, then refuses if it is still empty; refuses no lines or lines withQuantity ≤ 0; refusesTaxRateApplied ≤ 0with the meaning “there was no VAT on the receipt, so there is nothing to credit.”OnAfterPostAsyncbuilds the tax context (legal entity, supplier, item group), callsITaxService.CreateReversalAsyncin the INPUT direction, and links the calculation to the note. - Posting subtype
Posted:PurchaseCreditNotePayableTx(Purchasing) calculates VAT from the line base usingTaxRateApplied, writes−PayableforSupplier, and skips zero/negative rates. 22-Ukraine writes a first-event reversal. The note has no own GL extension: the GL effect goes through the same tax reversal seam (TaxCalculation) as any other VAT. See Tax.
SupplierInvoice#
The supplier's own paper, entered after the goods are in. It exists because the receipt and the invoice are different events: the receipt accrues the payable at the ordered price, the invoice arrives later and may disagree.
It books only the difference. GoodsReceiptPayableTx already added the net
line amounts to Payable on Received, and the purchase tax seam added input
VAT on top; booking the invoice in full would owe the supplier twice.
- Header:
Supplier(required) ·LegalEntity(optional) ·SupplierNumber(String 50, indexed, not required at field level) ·InvoiceDate·DueDate·PaymentTerm·TaxRateApplied·MatchedAmount·Difference(both Decimal 18,2, stamped by the command). - TP-Lines (
SupplierInvoiceLines):Item,Quantity,UnitPrice. - Subtypes:
Draft→Registered(isReadOnly). - The order is not a
Guidcolumn. Invoice and order are joined through the document-family graph (IDocumentManager.AddLinkAsync), so one invoice can cover several receipts. This is deliberately unlikePurchaseCreditNote/PurchaseReturn, which still carry the plainOriginalOrderdeviation. SupplierNumberis required by the command, not by the field. The draft is created from a received order before the supplier's paper exists, so a required field would makeEnterSupplierInvoiceunable to save what it creates.RegisterSupplierInvoiceis the only door intoRegistered, so the invariant has exactly one enforcement point.- Empty dates are clamped to
1901-01-01inOnBeforeSaveAsync, the same sentinelPurchaseOrderEventHandleruses: an optional documentDateTimeis non-nullable and unset reads as0001-01-01, which overflowsSqlDateTime.DueDateis computed fromPaymentTermthroughIPaymentDueServicewhen it is not already set.TaxPointOftreats anything below 1902 as empty, so the sentinel never becomes the day a rate is read on. - Events:
OnBeforeCreateAsyncseedsLegalEntityfromIRecordDefaults.OnBeforeSaveAsyncclamps the dates and stampsTaxRateAppliedfrom the default tax code on the invoice date.OnBeforePostAsyncre-reads the document (the subtype UPDATE carries only the subtype) and refuses a missing supplier, a blankSupplierNumber, no lines,Quantity ≤ 0, a supplier that disagrees with a linked order, and anyMatchProblemAsync.OnAfterPostAsynccreates aTaxCalculationin the INPUT direction for a positive difference or a reversal for a negative one, and links it.OnBeforeUnpostAsync/OnBeforeDeleteAsyncwithdraw and delete those calculations. - Posting subtype
Registered:SupplierInvoicePayableTx(order 1) adds the gross difference — difference plus tax atTaxRateApplied— toPayablefor the supplier, and writes nothing when the difference is zero.SupplierInvoiceGLTx(order 7) booksMath.Abs(difference)throughIGeneralLedgerService.EntryAsync, swapping debit and credit for a negative difference because the service refuses a non-positive amount. - The tax seam that adds input VAT to
Payableonly fires when it can resolve aPurchaseOrderbehind the calculation, so it does not double the invoice's own tax.
Example
A purchase order for 4 bolts at 25 is received. VAT is 15%.
| Step | Payable |
MatchedAmount |
Difference |
TaxCalculation |
|---|---|---|---|---|
| Receipt | 115.00 | — | — | +15.00 |
| Invoice for 4 × 25 | 115.00 | 100.00 | 0.00 | none |
| Invoice for 4 × 30 | 138.00 | 100.00 | 20.00 | +3.00 |
| Invoice for 4 × 20 | 92.00 | 100.00 | −20.00 | −3.00 |
A standalone invoice with no linked order accrues nothing, so the difference is the whole invoice: 2 × 50 becomes 115.00 of payable.
Partial invoicing. SupplierInvoiceMatchService.AccruedAmountAsync matches
each invoice line against the receipt price of the same quantity (several
order lines of one item average), so a receipt can be invoiced in parts.
DoubleBillingProblemAsync refuses an invoice that, with the Registered
invoices on the same receipts, bills more of an item than was received;
RemainingToInvoiceAsync is what Enter supplier invoice drafts.
VendorPayment#
- Header:
LegalEntity→LegalEntity (optional). Empty leaves the payable repaid, but without GL posting. Supplier is not on the header; it is on the line. - TP-Lines (
VendorPaymentLines):Supplier(required),Amount(required). - Subtypes:
Draft→Paid(isReadOnly). - Events:
OnBeforeCreateAsyncseedsLegalEntity.OnBeforePostAsync(Paid) refuses no lines and lines withAmount ≤ 0. There is no balance limit:Payableintentionally allows a negative balance, because overpayment and advance payments are valid. The engine does not cut payment to the balance. - Posting subtype
Paid:VendorPaymentTx(−Payablefor eachline.Supplier, not for a header supplier). 22-Ukraine writes the full tax-included amount, unlikeReceived, where the first event writes the tax-exclusive base.
Exchange differences: Supplier.Currency, ExchangeRate, PayableRevaluation#
Purchasing has no contract, so the supplier's billing currency is the
Currency field of the Supplier.Purchasing extension on the supplier card.
An extension field is not on the typed class; PurchaseFx reads it through
IDataService. Empty, or equal to the buyer entity's currency, means rate 1.
PurchaseOrder stamps ExchangeRate (supplier currency to the entity's, on
the receipt date) in OnBeforePost on Received and refuses without a rate.
SupplierInvoice stamps the rate on its invoice date; PurchaseReturn and
PurchaseCreditNote take the original order's. Payable keeps Amount in the
supplier's currency and AmountFunc in the entity's; the receipt ledger leg,
InventoryValue / ItemCostFifo (Costing), the input-tax base
and NonRecoverableVat are amount × rate. The tax seam posts the
calculation's tax to Payable as Amount = tax / rate, AmountFunc = tax.
VendorPaymentLines stamp ExchangeRate (payment date) and CarryingAmount
(the supplier slice's average); VendorPaymentGLTx books the gap to
FxGainAccountCode / FxLossAccountCode — paying more than carried is a loss.
PayableRevaluation (Draft → Posted; commands FillPayableRevaluation,
PostPayableRevaluation, CancelPayableRevaluation) revalues open foreign
payables at the period-end rate: a rising rate is a loss.
Payable carries a LegalEntity analytic (optional; older movements have
none and count only in the supplier's total), written by every payable
document from its legal entity; PurchaseFx slices by supplier × legal entity,
so each entity's payables are revalued and paid in its own currency. A
supplier's Currency cannot change while the supplier is owed anything
(SupplierPurchasingEventHandler, an extension of Supplier). Process view: exchange
differences.
Example#
Supplier Vendor-1, item Widget, receipt cell Cell-2. Before the scenario,
Cell-2 has no goods and payable to Vendor-1 is 0.00. Tax entries are omitted
for clarity: the receipt actually writes the INPUT tax calculation and the
ItemCostFifo/InventoryValue entry. See Costing and
Tax.
| Document | Action | Stock (Widget, Cell-2) |
Payable (Vendor-1) |
|---|---|---|---|
| — | before scenario | 0 | 0.00 |
PurchaseOrder PO-1 |
Received: 50 at 8.00 |
50 | 400.00 |
PurchaseReturn RET-1 |
return 5 pcs (net −40.00) | 45 | 360.00 |
PurchaseCreditNote CN-1 |
VAT on PO-1 was overstated by 20.00, without stock movement | 45 | 340.00 |
VendorPayment PAY-1 |
300.00 | 45 | 40.00 |
Scenario result: 45 units of Widget remain in the receipt cell, and payable to
Vendor-1 is 40.00. Notice that PurchaseReturn and PurchaseCreditNote hit
different columns: return moves stock and payable; the note moves only payable.
Registers#
Register dimensions and resources: see Payable in Objects below.
Written by: GoodsReceiptPayableTx (+, receipt), PurchaseReturnPayableTx
(−, net return), PurchaseCreditNotePayableTx (−, VAT share only),
VendorPaymentTx (−, payment by line). allowNegativeBalance: true means
overpayment and advance payment to a supplier are valid; the engine does not
block them.
Report PayableBySupplier (Reports/Views/) shows turnover by Supplier with
opening/closing balance and nonZeroBalance: true.
Services#
PurchaseReceiptService#
ValidateReceiveAsync(orderId) → string? (null means receipt is possible):
the order exists, has lines, all Quantity > 0, Location is a receiving cell,
and a tax rate exists on the date when a tax code is present.
ReceiveAsync(orderId) → string? (null means success): runs
ValidateReceiveAsync, then performs partial receipt. Lines with
ReceiveQty filled are split into a new remainder order by SplitRemainderAsync.
Lines without a partial-receipt mark go to the remainder in full; marked lines
leave only ReceiveQty on the current order, and the difference goes to the
remainder. The remainder order is copied by Supplier/Location/DocumentDate/
LegalEntity/PaymentTerm/ExpectedReceipt, saved, moved to Ordered, and
linked to the original order through the document-reference graph. If all
ReceiveQty ≤ 0, there is no split: everything is received. After the split, the
current order moves to Received.
Cross-model services (IStoreCellService, ITaxService) are injected lazily
through ScriptServices.Get<T>(), not through the constructor. This is the same
pattern as SalesFulfillmentService in Sales, so the factory
does not refuse to start.
SupplierInvoiceMatchService#
The single matching predicate; both transaction scripts and
RegisterSupplierInvoice ask it rather than repeating the arithmetic.
MatchedOrdersAsync(invoiceId) → the Received purchase orders in the
invoice's document family. AccruedAmountAsync(invoiceId) → the sum of their
line amounts. InvoicedAmountAsync(invoiceId) → the sum of the invoice's own
lines. Both use IPricingService.LineAmount, the same formula the receipt and
the credit note use. ToleranceAsync() → the absolute
PurchasingSettings.InvoiceMatchTolerance. MatchProblemAsync(difference) →
null when the difference is acceptable; a zero tolerance means no limit.
Commands#
PlaceOrder (“Place order”, Draft→Ordered) refuses no lines or lines with
Quantity ≤ 0. If PurchasingSettings.AutoReceiveOnOrder is off, it only moves
to Ordered. If it is on, it runs PurchaseReceiptService.ValidateReceiveAsync
while the document is still Draft, so a blocked receipt does not leave the
order suspended in Ordered. Only then it moves to Ordered and immediately
calls ReceiveAsync, which may receive everything or split the remainder.
ReceiveOrder (“Receive order”, Ordered) delegates to
PurchaseReceiptService.ReceiveAsync. The message text distinguishes full
receipt from receipt with a separated remainder.
FillPurchasePrices (“Fill prices”, Draft) prices lines with UnitPrice ≤ 0
through IPricingService.ResolvePurchasePriceAsync(Item, Unit, Supplier, DocumentDate).
Prices entered manually are not changed.
PayVendor (“Pay vendor”, Draft→Paid on VendorPayment) refuses no lines or
lines with Amount ≤ 0; there is no balance limit.
PostPurchaseReturn (“Post purchase return”, Draft→Posted on
PurchaseReturn) refuses no lines or lines with Quantity ≤ 0, then
IStockAvailabilityService checks each position and refuses with “need X, have
Y” when stock is short.
EnterSupplierInvoice (“Enter supplier invoice”, Received on
PurchaseOrder) creates a SupplierInvoice draft, copies supplier, document
date, payment term, due date and the order lines, resolves LegalEntity from
the receiving cell (falling back to the order header), links the two documents,
and opens the new one.
RegisterSupplierInvoice (“Register”, Draft→Registered on
SupplierInvoice) re-reads the document, refuses a missing supplier, a blank
SupplierNumber, no lines, Quantity ≤ 0, and a difference beyond the
tolerance. It then stamps MatchedAmount and Difference with a plain save
while the document is still Draft, and only afterwards moves the subtype, so
the operator sees the number before it is posted. The transaction scripts do not
depend on those stamps — they re-ask the service — so a programmatic transition
posts the same amounts.
PostPurchaseCreditNote (“Post purchase credit note”, Draft→Posted on
PurchaseCreditNote) refuses missing OriginalOrder, no lines, or lines with
Quantity ≤ 0. Deeper checks, such as the order being Received and the rate
being positive, belong to the document OnBeforePostAsync; the command does not
duplicate them.
Business logic and invariants#
Ordered→Received is not a declarative transition. Metadata declares only
Draft→Ordered. A service moves the order to Received programmatically. The
user path still goes through a command (ReceiveOrder, or auto-receipt inside
PlaceOrder), but there is no separate transition arc in the subtype table.
Partial receipt is implemented. ReceiveQty on an order line plus
SplitRemainderAsync in PurchaseReceiptService moves the unreceived balance
into a new linked Ordered order. Earlier versions of this document called it an
open question; it is now the working path.
Return and credit note are not alternatives. PurchaseReturn moves stock and
net payable. PurchaseCreditNote moves only the tax part of payable. A wrong
receipt is usually covered by both documents; neither substitutes for the other.
Payable has no ceiling. Payable.allowNegativeBalance = true. Overpayment
and advance payment to a supplier are valid; VendorPayment does not block them.
Historical integrity. Price and tax rate are fixed on the line at receipt or note time. Later edits to the dictionary or rate do not change an already posted document.
Document links use the graph, not link tables. Order → PutAwayTask, order
→ TaxCalculation, order → remainder order, and note → tax reversal are all
edges in the common document graph (IDocumentManager.AddLinkAsync/
GetDocumentFamilyAsync). Purchasing has no own link table.
The supplier invoice books the difference, not the invoice. The receipt already accrued the payable, so posting the invoice in full would owe the supplier twice. A matching invoice posts nothing at all. See SupplierInvoice.
A command-enforced invariant is enforced once. SupplierNumber is optional
at field level because EnterSupplierInvoice creates the draft before the
supplier's paper exists. RegisterSupplierInvoice is the only door into
Registered, so the requirement has exactly one place to live.
An empty optional document date is 1901-01-01. The column is non-nullable
and a DateTime parameter is sent as SqlDbType.DateTime whatever the column
type is, so DateTime.MinValue overflows in the driver before SQL Server ever
sees it. PurchaseOrder, SupplierInvoice and DeliveryTrip all clamp to the
same sentinel and all test emptiness with Year < 1902.
Known deviation. PurchaseReturn.OriginalOrder and
PurchaseCreditNote.OriginalOrder are plain Guid fields, not Reference EDTs.
This is the only place in the model where that is true.
Menu#
The Purchasing group → Documents (PurchaseOrder, SupplierInvoice,
VendorPayment, PurchaseReturn, PurchaseCreditNote) · Dictionaries (Supplier, whose
menu item lives here although the object belongs to Inventory) ·
Registers (Payable) · Settings (PurchasingSettings).
Links with other modules#
- Outgoing: Inventory —
Item/Supplier/StoreCell/Stock,IStoreCellService/IStockAvailabilityService/IPricingService; Tax — rate engine,TaxCalculation; Organization —LegalEntity; Common —PaymentTerm,IRecordDefaults,IPaymentDueService; Accounting — only theMoneyEDT. - Incoming: Costing writes
InventoryValue/ItemCostFifoonPurchaseOrder.Received(ReceiptCostTx/ReceiptFifoTx); Purchasing's own transaction scripts post receipt (Dr Inventory / Cr Payable, with theNonRecoverableVatshare), return (Dr Payable / Cr Inventory), and payment (Dr Payable / Cr Cash, only ifLegalEntityis filled) toGL; Purchasing'sTaxCalculationPayableSeamEventHandleradds input VAT of a receipt to the supplier'sPayable; 22-Ukraine writesUaPurchaseFirstEventfor receipt, notes, and payment — the input-VAT first event.
Objects#
Generated from workspace metadata by node tools/gen-reference.mjs; edit the metadata, not this block.
Dictionaries#
LegacyPayableBasis — Legacy payable bases
Supplier order of the predecessor system with open debt, not migrated as a document. MetaId equals the Basis used in PayableByBasis.
| Field | Caption | Type | Required |
|---|---|---|---|
DocumentDate |
Document date | DateTime | Yes |
ID |
Code | String(50) | Yes |
Number |
Number | String(50) | No |
Source |
Source | String(100) | No |
SourceKey |
Source key | String(100) | No |
Supplier |
Supplier | → Supplier |
Yes |
SupplierContract |
Supplier contract | → SupplierContract |
No |
PurchasingSettings — Purchasing settings
| Field | Caption | Type | Required |
|---|---|---|---|
ApprovalThreshold |
Approval threshold | Decimal(18,2) | No |
AutoReceiveOnOrder |
Auto-receive on order | Boolean | No |
DefaultPaymentTerm |
Default payment term | → PaymentTerm |
No |
DefaultPaymentTermDays |
Default payment term (days) | Integer | No |
ID |
ID | String(50) | Yes |
InvoiceMatchTolerance |
Invoice match tolerance | Decimal(18,2) | No |
SupplierContract — Supplier contracts
Contract with a supplier: the debt to the supplier is kept per contract (Payable, PayableByBasis). A supplier may hold several live contracts at once.
| Field | Caption | Type | Required |
|---|---|---|---|
Currency |
Currency | → Currency |
No |
EffectiveFrom |
Effective from | DateTime | No |
EffectiveTo |
Effective to | DateTime | No |
ID |
Code | String(50) | Yes |
IsDisabled |
Disabled | Boolean | No |
LegalEntity |
Buying legal entity | → LegalEntity |
No |
Name |
Name | String(160) | Yes |
Notes |
Notes | String(2000) | No |
Number |
Contract No. | String(50) | No |
Supplier |
Supplier | → Supplier |
Yes |
Documents#
PayableRevaluation — Payable revaluation
Period-end revaluation of foreign-currency payables. Fill revaluation lists every supplier billing in a currency other than the legal entity's, with an open payable, at the rate on the document date; Post revaluation moves Payable.AmountFunc to the revalued amount and books the unrealised exchange difference. The revalued carrying value stays: the next payment's realised difference is measured against it.
Header fields:
| Field | Caption | Type | Required |
|---|---|---|---|
ID |
Number | String(50) | Yes |
LegalEntity |
Legal entity | → LegalEntity |
Yes |
Subtypes:
| Subtype | Caption |
|---|---|
Draft |
Draft |
Posted |
Posted |
Commands:
| Command | Caption | Target subtype |
|---|---|---|
CancelPayableRevaluation |
Cancel revaluation | — |
FillPayableRevaluation |
Fill revaluation | — |
PostPayableRevaluation |
Post revaluation | — |
PurchaseCreditNote — Purchase credit note
Header fields:
| Field | Caption | Type | Required |
|---|---|---|---|
Contract |
Supplier contract | → SupplierContract |
No |
ExchangeRate |
Exchange rate | Decimal(18,8) | No |
ID |
Number | String(50) | Yes |
LegalEntity |
Buying legal entity | → LegalEntity |
No |
OriginalOrder |
Original order | Guid | No |
Supplier |
Supplier | → Supplier |
Yes |
TaxRateApplied |
Applied tax rate | TaxRateValue |
No |
Subtypes:
| Subtype | Caption |
|---|---|
Draft |
Draft |
Posted |
Posted |
Commands:
| Command | Caption | Target subtype |
|---|---|---|
PostPurchaseCreditNote |
Post purchase credit note | — |
PurchaseOrder — Purchase order
Header fields:
| Field | Caption | Type | Required |
|---|---|---|---|
ApprovedAmount |
Approved amount | Decimal(18,2) | No |
Contract |
Supplier contract | → SupplierContract |
No |
DueDate |
Due date | DateTime | No |
ExchangeRate |
Exchange rate | Decimal(18,8) | No |
ExpectedReceipt |
Expected receipt | DateTime | No |
ID |
Number | String(50) | Yes |
LegalEntity |
Legal entity | → LegalEntity |
No |
Location |
Receiving location | → StoreCell |
Yes |
NonRecoverableVat |
Non-recoverable VAT | Decimal(18,2) | No |
Notes |
Notes | String(2000) | No |
PaymentTerm |
Payment term | → PaymentTerm |
No |
Supplier |
Supplier | → Supplier |
Yes |
VendorDocumentDate |
Supplier document date | DateTime | No |
VendorDocumentNumber |
Supplier document No. | String(50) | No |
Subtypes:
| Subtype | Caption |
|---|---|
Draft |
Draft |
Ordered |
Ordered |
Received |
Received |
Commands:
| Command | Caption | Target subtype |
|---|---|---|
PlaceOrder |
Place order | — |
ReceiveOrder |
Receive order | — |
ApprovePurchaseOrder |
Approve order | — |
FillPurchasePrices |
Fill prices | — |
EnterSupplierInvoice |
Enter supplier invoice | — |
PurchaseReturn — Purchase return
Header fields:
| Field | Caption | Type | Required |
|---|---|---|---|
Contract |
Supplier contract | → SupplierContract |
No |
ExchangeRate |
Exchange rate | Decimal(18,8) | No |
ID |
Number | String(50) | Yes |
Location |
Return from | → StoreCell |
Yes |
OriginalOrder |
Original order | Guid | No |
Supplier |
Supplier | → Supplier |
Yes |
Subtypes:
| Subtype | Caption |
|---|---|
Draft |
Draft |
Posted |
Posted |
Commands:
| Command | Caption | Target subtype |
|---|---|---|
PostPurchaseReturn |
Post purchase return | — |
SupplierInvoice — Supplier invoice
Header fields:
| Field | Caption | Type | Required |
|---|---|---|---|
Contract |
Supplier contract | → SupplierContract |
No |
Difference |
Difference | Decimal(18,2) | No |
DueDate |
Due date | DateTime | No |
ExchangeRate |
Exchange rate | Decimal(18,8) | No |
ID |
Number | String(50) | Yes |
InvoiceDate |
Supplier's date | DateTime | No |
LegalEntity |
Legal entity | → LegalEntity |
No |
MatchedAmount |
Accrued by receipts | Decimal(18,2) | No |
PaymentTerm |
Payment term | → PaymentTerm |
No |
Supplier |
Supplier | → Supplier |
Yes |
SupplierNumber |
Supplier's number | String(50) | No |
TaxRateApplied |
Tax rate | TaxRateValue |
No |
Subtypes:
| Subtype | Caption |
|---|---|
Draft |
Draft |
Registered |
Registered |
Commands:
| Command | Caption | Target subtype |
|---|---|---|
RegisterSupplierInvoice |
Register | — |
SupplierPriceSetting — Supplier price setting
Header fields:
| Field | Caption | Type | Required |
|---|---|---|---|
ID |
Number | String(50) | Yes |
Notes |
Notes | String(1000) | No |
PriceType |
Price type | → PriceType |
No |
Supplier |
Supplier | → Supplier |
Yes |
Subtypes:
| Subtype | Caption |
|---|---|
Draft |
Draft |
Posted |
Posted |
Commands:
| Command | Caption | Target subtype |
|---|---|---|
PostSupplierPriceSetting |
Post supplier prices | — |
VendorPayment — Vendor payment
Header fields:
| Field | Caption | Type | Required |
|---|---|---|---|
BankAccount |
Bank account | → BankAccount |
No |
CashDesk |
Cash desk | → CashDesk |
No |
ID |
Number | String(50) | Yes |
LegalEntity |
Legal entity | → LegalEntity |
No |
Notes |
Notes | String(2000) | No |
Subtypes:
| Subtype | Caption |
|---|---|
Draft |
Draft |
Paid |
Paid |
Commands:
| Command | Caption | Target subtype |
|---|---|---|
PayVendor |
Pay vendor | — |
VendorRefund — Supplier refund
Money a supplier pays back into a cash desk or a bank account (overpayment, returned goods). Posting raises the payable to the supplier back toward zero, raises the desk or account balance and debits its ledger account against payables.
Header fields:
| Field | Caption | Type | Required |
|---|---|---|---|
Amount |
Amount | Money |
Yes |
BankAccount |
Bank account | → BankAccount |
No |
Basis |
Basis | Guid | No |
CashDesk |
Cash desk | → CashDesk |
No |
Contract |
Supplier contract | → SupplierContract |
No |
ExchangeRate |
Exchange rate | Decimal(18,8) | No |
ID |
Number | String(50) | Yes |
LegalEntity |
Legal entity | → LegalEntity |
Yes |
Notes |
Notes | String(2000) | No |
Supplier |
Supplier | → Supplier |
Yes |
Subtypes:
| Subtype | Caption |
|---|---|
Draft |
Draft |
Paid |
Paid |
Commands:
| Command | Caption | Target subtype |
|---|---|---|
ReceiveVendorRefund |
Receive refund | — |
Registers#
Payable — Accounts payable
Amount owed to suppliers, by supplier; by supplier contract when the document names one (empty contract — no contract analytic)
Resources:
| Resource | Caption | Type | Required |
|---|---|---|---|
Amount |
Amount | Money |
— |
AmountFunc |
Amount in functional currency | Money |
— |
Analytics:
| Analytic | Caption | Type | Required |
|---|---|---|---|
Payable-LegalEntity |
Payable-LegalEntity | — | No |
Payable-Supplier |
Payable-Supplier | — | Yes |
Payable-SupplierContract |
Payable-SupplierContract | — | No |
PayableBasis — Accounts payable by basis
Companion of Payable: the same debt to a supplier split by the purchase order it belongs to. Empty basis is an advance.
Dimensions:
| Dimension | Caption | Type | Required |
|---|---|---|---|
Basis |
Basis | PayableBasisKey |
— |
Supplier |
Supplier | → Supplier |
— |
Resources:
| Resource | Caption | Type | Required |
|---|---|---|---|
Amount |
Amount | Money |
— |
PayableByBasis — Accounts payable by basis
Companion of Payable: the same debt to a supplier split by the purchase order it belongs to. Empty basis is an advance. The supplier contract is an optional analytic, not a dimension: the balance table stays keyed by supplier x basis.
Dimensions:
| Dimension | Caption | Type | Required |
|---|---|---|---|
Basis |
Basis | PayableBasisKey |
— |
Supplier |
Supplier | → Supplier |
— |
Resources:
| Resource | Caption | Type | Required |
|---|---|---|---|
Amount |
Amount | Money |
— |
Analytics:
| Analytic | Caption | Type | Required |
|---|---|---|---|
PayableByBasis-SupplierContract |
PayableByBasis-SupplierContract | — | No |
Purchases — Purchases
Turnover of goods received from suppliers: quantity and amount by item and supplier. Not on-hand stock — Stock stays item and cell. A return reverses the same cut.
Resources:
| Resource | Caption | Type | Required |
|---|---|---|---|
Amount |
Amount | Money |
— |
AmountFunc |
Amount in functional currency | Money |
— |
Qty |
Qty | Qty |
— |
Analytics:
| Analytic | Caption | Type | Required |
|---|---|---|---|
Purchases-Cell |
Purchases-Cell | — | No |
Purchases-Item |
Purchases-Item | — | Yes |
Purchases-LegalEntity |
Purchases-LegalEntity | — | No |
Purchases-Supplier |
Purchases-Supplier | — | Yes |
Purchases-SupplierContract |
Purchases-SupplierContract | — | No |
Table parts#
PayableRevaluationLines — Lines
Used by: PayableRevaluation.
| Field | Caption | Type | Required |
|---|---|---|---|
AmountCur |
Open amount | Decimal(18,2) | No |
Carrying |
Carrying amount | Decimal(18,2) | No |
Currency |
Currency | → Currency |
Yes |
Difference |
Difference | Decimal(18,2) | No |
Rate |
Rate | Decimal(18,8) | No |
Revalued |
Revalued amount | Decimal(18,2) | No |
Supplier |
Supplier | → Supplier |
Yes |
SupplierContract |
Supplier contract | → SupplierContract |
No |
PurchaseCreditNoteLines — Lines
Used by: PurchaseCreditNote.
| Field | Caption | Type | Required |
|---|---|---|---|
Item |
Item | → Item |
Yes |
Quantity |
Quantity | Qty |
Yes |
UnitPrice |
Unit price | Money |
Yes |
PurchaseOrderLines — Lines
Used by: PurchaseOrder.
| Field | Caption | Type | Required |
|---|---|---|---|
Amount |
Amount | Money |
No |
BaseQuantity |
Qty in base unit | Qty |
No |
Item |
Item | → Item |
Yes |
Location |
Warehouse cell | → StoreCell |
No |
PackQty |
Packages | Qty |
No |
PackUnit |
Package unit | → UnitOfMeasure |
No |
Quantity |
Quantity | Qty |
Yes |
ReceiveQty |
Receive now | Qty |
No |
SupplierPrice |
Supplier price | Money |
No |
TaxAmount |
VAT amount | Money |
No |
TaxCode |
VAT rate | → TaxCode |
No |
Unit |
Unit | → UnitOfMeasure |
No |
UnitPrice |
Unit price | Money |
Yes |
PurchaseReturnLines — Lines
Used by: PurchaseReturn.
| Field | Caption | Type | Required |
|---|---|---|---|
Amount |
Amount | Money |
No |
BaseQuantity |
Qty in base unit | Qty |
No |
Item |
Item | → Item |
Yes |
Location |
Warehouse cell | → StoreCell |
No |
OriginalReceipt |
Receipt | Guid | No |
PackQty |
Packages | Qty |
No |
PackUnit |
Package unit | → UnitOfMeasure |
No |
Quantity |
Quantity | Qty |
Yes |
TaxAmount |
VAT amount | Money |
No |
TaxCode |
VAT rate | → TaxCode |
No |
Unit |
Unit | → UnitOfMeasure |
No |
UnitPrice |
Unit price | Money |
Yes |
SupplierInvoiceLines — Lines
Used by: SupplierInvoice.
| Field | Caption | Type | Required |
|---|---|---|---|
Amount |
Amount | Money |
No |
BaseQuantity |
Qty in base unit | Qty |
No |
Item |
Item | → Item |
Yes |
PackQty |
Packages | Qty |
No |
PackUnit |
Package unit | → UnitOfMeasure |
No |
Quantity |
Quantity | Qty |
Yes |
TaxAmount |
VAT amount | Money |
No |
TaxCode |
VAT rate | → TaxCode |
No |
Unit |
Unit | → UnitOfMeasure |
No |
UnitPrice |
Unit price | Money |
Yes |
SupplierPriceSettingLines — Prices
Used by: SupplierPriceSetting.
| Field | Caption | Type | Required |
|---|---|---|---|
Item |
Item | → Item |
Yes |
Price |
Price | Money |
Yes |
Unit |
Unit | → UnitOfMeasure |
No |
VendorPaymentLines — Lines
Used by: VendorPayment.
| Field | Caption | Type | Required |
|---|---|---|---|
Amount |
Amount | Money |
Yes |
Basis |
Basis | Guid | No |
CarryingAmount |
Carrying amount | Decimal(18,2) | No |
Contract |
Supplier contract | → SupplierContract |
No |
ExchangeRate |
Exchange rate | Decimal(18,8) | No |
Supplier |
Supplier | → Supplier |
Yes |