🏦 The AccountingSettings account profile
Updated
IGeneralLedgerService.EntryAsync resolves the legs by code
(ChartOfAccounts.Code + IsPostable). The ArAccount, RevenueAccount, …
references on the AccountingSettings singleton are what the form shows. On
saving, AccountingSettingsEventHandler stamps the paired *AccountCode. An
empty reference does not wipe the string: tests and older stands still write the
code directly.
Do not rename and do not drop the *AccountCode columns: removing a field from
the JSON does not drop the column, and renaming leaves orphans.
isVisible: false on the strings; the references are what is visible.
The AccountingAccountProfileTest test (3 cases): the stamp after a reload by
MetaId (the event mutates the DataService dictionary, not the same POCO that
IDictionaryManager.SaveRecordAsync returned), a string with no reference, a
group being rejected.
WipAccount is the same pair. ProductionOrderEventHandler (Production) after
posting: on Released the amount is this document's negative ItemCostFifo,
Dr WIP / Cr inventory, written as the order's own GL movements
(IInventoryCostGLService.PostIssuedCostAsync). Moving to Finished reverses
all the order's movements — the WIP leg included — and books only absorbed
labour (Dr inventory / Cr LaborAbsorbedAccountCode): materials out and the
product in are neutral on the one inventory account. The ProductionWipGLTest
test.
PurchasePriceVarianceAccount is the same pair, and it is the one leg that must
not be pointed at the inventory account. SupplierInvoiceGLTx books the gap
between the supplier's sheet and what the receipts accrued, and the receipt
already priced the goods in InventoryValue and in the ItemCostFifo
layers. Neither can be revalued afterwards from a script: a layer is addressed by
the engine's LotNo, which is absent from the generated entity, and a movement
with Quantity = 0 is poison — FifoTotalDriver.CalculateOutcomes hands its
whole amount to the next issue whatever that issue's size, while the AVG branch
of CostingValuationTotalDriver gives the layer share = 0 and then drops it
from the survivors, so the amount never leaves stock. Debiting the inventory
ACCOUNT while the registers stay put therefore opens a permanent ledger↔subledger
gap and breaks the invariant CostingIssueTotalDriver rests on: the value it
writes off is exactly what the engine removed from the layers. The policy is
standard costing — stock keeps the receipt price, the gap is a result of the
period, and re-costing goods already in a cell stays StockAdjustment's job. An
empty code is lawful: no leg, Payable still moves. The
SupplierInvoiceVarianceTest test (4 cases) guards both halves — the variance
account moved, InventoryValue and ItemCostFifo did not.
Two doors for a document's ledger leg#
| Door | When | Why |
|---|---|---|
EntryAsync in the document's transaction script |
the amount is known from the document (revenue, payments, payroll, VAT) | the leg joins the posting chain: atomic, reversed with the document, compared on re-save |
PostForDocumentAsync from OnAfterPost |
the amount is a fact of the posting (cost the CostingIssue driver took off the layers) |
GetTransactions is synchronous and runs BEFORE the drivers — the cost is not there yet |
Trap: the after-post event can run twice in one posting. A driver that appends movements through the totals manager re-enters the document's events.
PostForDocumentAsynctakes alegKey(stored as the movement'sScriptMetaId) and skips a leg that is already there. Pass a fixed GUID per leg kind, neverGuid.NewGuid()— a fresh key makes the guard useless and the cost doubles silently.
Trap: set all six GL resources.
Movementalways writesDebit,Credit,ManagementDebit,ManagementCredit,TaxDebit,TaxCredit, zeros included. A hand-builtRegisterMovementSpec("GL")that omits one gets a different movement signature from the stored row, and the document re-posts on every save.
Trap: a transaction script's bind row lives in the OWNER's object.json. A new GL tx on
SalesRealizationis bound inSales/Documents/SalesRealization/SalesRealization.object.json(subtypeTransactionScripts). Applying the object before the script exists fails with an FK onMetaScripts— apply the script first, then the object.
Same topic for other readers: Analyst · User