Anglo-Saxon Accounting in Odoo v19 — Full Investigation
What Is Anglo-Saxon Accounting?
Section titled “What Is Anglo-Saxon Accounting?”A method that controls when COGS is recognised:
| Method | COGS fires at | Used in |
|---|---|---|
| Continental | Vendor bill validation | France, Germany, most of Europe |
| Anglo-Saxon | Delivery + invoice (two-step) | UK, US, Australia |
In v17/v18 this was a company toggle: Accounting → Settings → Use Anglo-Saxon Accounting. In v19 it was removed entirely by a single refactor commit.
The Commit
Section titled “The Commit”08b62a4bbcc6f9a391b2cc00a621ef4c76100229Author: amoyaux — August 9, 2025Title: [REF] stock_account: New inventory valuationFiles: 220 changedOfficial message:
- Real-time valuation only at invoice/bill posting (removed valuation at delivery/receipt)
- Elimination of input/output interim accounts
- Removed
stock.valuation.layer— value now stored onstock.move- New
product.valuemodel for manual adjustments only- Value priority: manual > invoice/bill > PO/SO > standard price
What Changed Per Version
Section titled “What Changed Per Version”v17/v18 — Anglo-Saxon fully functional
Section titled “v17/v18 — Anglo-Saxon fully functional”anglo_saxon_accountingboolean onres.companygates 3 behaviours:- COGS journal lines on customer invoices (via
stock_outputinterim account) - Purchase bill lines redirected to
stock_inputinterim account - Reconciliation between interim account lines at invoice posting
- COGS journal lines on customer invoices (via
stock.valuation.layer(SVL) created on every_action_done()— tracks value, links to journal entry- Journal entries fire at both delivery and receipt
v19 — Anglo-Saxon stripped
Section titled “v19 — Anglo-Saxon stripped”stock.valuation.layercompletely removedanglo_saxon_accountingfield still exists onres.companybut controls nothing- No journal entry at delivery or receipt
- COGS always active via
display_type='cogs'lines — no flag gate - Accounts moved from product to location (
location_id.valuation_account_id)
The Core Architecture Difference
Section titled “The Core Architecture Difference”v18 Flow
Section titled “v18 Flow”stock.move._action_done() → stock.valuation.layer created (product, qty, value, stock_move_id, account_move_id) → svl._validate_accounting_entries() → stock.move._account_entry_move() → account.move posted ← fires at delivery → if anglo_saxon_accounting: invoices._stock_account_anglo_saxon_reconcile_valuation()v19 Flow
Section titled “v19 Flow”stock.move._action_done() → stock.move._set_value() ← FIFO / AVCO / Standard computed here → stock.move._create_account_move() → _should_create_account_move() → _get_account_move_line_vals() ← accounts from location, not product → account.move posted ← fires at invoice, NOT deliveryJournal Entry Comparison
Section titled “Journal Entry Comparison”| Event | v18 (Anglo-Saxon ON) | v19 (always) |
|---|---|---|
| Goods receipt | DR Stock Valuation / CR Stock Input | nothing |
| Customer delivery | DR Stock Output / CR Stock Valuation | nothing |
| Vendor bill | DR Stock Input / CR AP + reconcile | DR Stock Valuation / CR AP |
| Customer invoice | DR COGS / CR Stock Output + reconcile | DR COGS / CR Stock Valuation |
The interim accounts are the mechanism. No interim = no Anglo-Saxon.
Product Category Costing — How It Affects Valuation
Section titled “Product Category Costing — How It Affects Valuation”Two fields on product.category control everything:
property_cost_method = Selection(['standard', 'fifo', 'average'], company_dependent=True)property_valuation = Selection(['manual_periodic', 'real_time'], company_dependent=True)| Cost Method | Valuation | Journal fires | COGS basis |
|---|---|---|---|
| Standard | manual_periodic | Never | Manual closing |
| Standard | real_time | Invoice posting | standard_price |
| AVCO | real_time | Invoice posting | Current rolling average |
| FIFO | real_time | Invoice posting | Consumed layer cost |
_should_create_account_move() gates on product.valuation == 'real_time' — manual_periodic products never generate automatic journals.
AVCO and FIFO risk: In v19, COGS is computed at invoice time using the current average or FIFO state. If a new receipt arrived between delivery and invoice, the value will differ from what was on the delivery. With Anglo-Saxon interim accounts, this mismatch leaves the interim account unreconciled. Value must be locked at delivery time.
What Was Deleted — Exact Inventory
Section titled “What Was Deleted — Exact Inventory”Every item below was confirmed by reading the v18 source and the commit diff.
Accounts (from generic COA)
Section titled “Accounts (from generic COA)”"stock_in", "Stock Interim (Received)", "1102", asset_current, reconcile=True"stock_out", "Stock Interim (Delivered)", "1103", asset_current, reconcile=TrueFields on product.category
Section titled “Fields on product.category”property_stock_account_input_categ_id # Many2one account.account, company_dependentproperty_stock_account_output_categ_id # Many2one account.account, company_dependentKeys removed from _get_product_accounts() on product.template
Section titled “Keys removed from _get_product_accounts() on product.template”stock_input and stock_output keys no longer returned. Every downstream call to accounts['stock_input'] now fails silently or KeyErrors.
Methods deleted from product.product
Section titled “Methods deleted from product.product”_stock_account_get_anglo_saxon_price_unit(uom)— UOM-aware standard price for COGS lines_create_fifo_vacuum_anglo_saxon_expense_entries(vacuum_pairs)— handles negative stock at delivery time (FIFO vacuum): if product delivered while out of stock, then received at a different price, creates a correcting expense entry and reconcilesstock_output
Methods deleted from account.move
Section titled “Methods deleted from account.move”_stock_account_prepare_anglo_saxon_out_lines_vals()— builds COGS line pairs usingstock_outputinterim (replaced by_stock_account_prepare_realtime_out_lines_vals()usingstock_valuationdirectly)_stock_account_anglo_saxon_reconcile_valuation()— the full reconciliation engine, matched interim lines from delivery with COGS lines from invoice, handled exchange differences and correction layers_post()Anglo-Saxon branch — calledprepare_anglo_saxon_out_lines_vals()thenreconcile_valuation()
Methods deleted from account.move.line
Section titled “Methods deleted from account.move.line”_compute_account_id()purchase branch — redirected vendor bill lines tostock_inputwhen Anglo-Saxon ON_stock_account_get_anglo_saxon_price_unit()— for credit notes, reads price from original invoice COGS line instead of current standard price
Methods deleted from stock.move
Section titled “Methods deleted from stock.move”_account_entry_move()— generated journal entry vals (now replaced by_create_account_move())_prepare_anglosaxon_account_move_vals()— dropship-specific Anglo-Saxon entries_generate_valuation_lines_data()— built debit/credit line dicts_get_src_account()/_get_dest_account()— account selection per move direction
Entire model removed
Section titled “Entire model removed”stock.valuation.layer — the table, the model, all methods, and stock_valuation_layer_ids on account.move.
OCA Research
Section titled “OCA Research”Checked all OCA repositories on 19.0 branch:
| Repo | Module | Verdict |
|---|---|---|
account-financial-tools | account_usability | Shows the toggle in CE UI only — zero functional restore |
stock-logistics-workflow | stock_move_valuation_usage | Tracks FIFO layer sources, no COGS/reconciliation |
| All others | — | Nothing relevant |
No OCA module restores Anglo-Saxon in v19. The gap is open.
Module Strategy
Section titled “Module Strategy”Use the Same Model Name: stock.valuation.layer
Section titled “Use the Same Model Name: stock.valuation.layer”Yes — recreate it with the same _name. Reasons:
_stock_account_anglo_saxon_reconcile_valuation()referencesstock_valuation_layer_idsby name — same model name means minimal adaptation- v19 fresh install has no
stock_valuation_layertable — zero collision - Clean upgrade path: if Odoo officially restores Anglo-Saxon, uninstall the module, done
- Developer familiarity — anyone from v15–v18 knows the name
But keep it lean — the old SVL had 20+ fields and owned FIFO stack logic. v19’s stock.move._set_value() already computes the correct value. The new SVL just records it and links delivery to journal entry.
class StockValuationLayer(models.Model): _name = 'stock.valuation.layer' _description = 'Stock Valuation Layer (Anglo-Saxon Bridge)' _order = 'create_date, id'
product_id = fields.Many2one('product.product', required=True, index=True) company_id = fields.Many2one('res.company', required=True) description = fields.Char() quantity = fields.Float(digits='Product Unit of Measure') value = fields.Monetary(currency_field='currency_id') currency_id = fields.Many2one('res.currency', related='company_id.currency_id') stock_move_id = fields.Many2one('stock.move', index=True) # delivery account_move_id = fields.Many2one('account.move', index=True) # delivery journalNon-Invasive Design Rule
Section titled “Non-Invasive Design Rule”Every override must fall through to super() when the flag is OFF:
def any_override(self, ...): if not self.company_id.anglo_saxon_accounting: return super().any_override(...) # pure standard v19 # Anglo-Saxon logicNo try/except. No logic before the guard. Standard Odoo runs unchanged.
Implementation Plan
Section titled “Implementation Plan”Module Structure
Section titled “Module Structure”custom_stock_anglo_saxon/├── models/│ ├── stock_valuation_layer.py # lean SVL bridge model│ ├── res_company.py # restore anglo_saxon_accounting as functional│ ├── product_category.py # restore stock_input / stock_output fields│ ├── product_template.py # restore _get_product_accounts() keys│ ├── product_product.py # restore _stock_account_get_anglo_saxon_price_unit()│ │ # _create_fifo_vacuum_anglo_saxon_expense_entries()│ ├── stock_move.py # hook _action_done() → create SVL + delivery journal│ │ # restore _prepare_anglosaxon_account_move_vals() (dropship)│ ├── account_move.py # restore stock_valuation_layer_ids field│ │ # restore _post() Anglo-Saxon branch│ │ # restore _stock_account_prepare_anglo_saxon_out_lines_vals()│ │ # restore _stock_account_anglo_saxon_reconcile_valuation()│ └── account_move_line.py # restore _compute_account_id() purchase branch│ # restore _stock_account_get_anglo_saxon_price_unit()├── data/│ └── account_data.xml # Stock Interim (Received) + (Delivered) accounts└── views/ ├── product_category_views.xml # stock_input / stock_output fields on form └── res_config_settings_views.xmlBuild Order
Section titled “Build Order”- Company flag + category accounts + COA data — low risk, validate config first
- Lean SVL model — just the fields, no logic yet
_get_product_accounts()keys restored — unblocks all downstream methods- Delivery journal entries (Standard + AVCO first, FIFO after)
- Test Standard + AVCO purchase/sale flows
- FIFO value-lock — store value on SVL at delivery, use it at invoice time
- COGS line account override —
stock_valuation→stock_output - Reconciliation engine — adapt v18’s
_stock_account_anglo_saxon_reconcile_valuation() - Returns + credit notes
- Multi-currency exchange difference handling
- Dropship exclusion
- Period-end closing compatibility audit
- Full test suite
Do not go live on FIFO before step 6 is complete and tested.
Risk Assessment
Section titled “Risk Assessment”| Risk | Severity | What breaks | Fix |
|---|---|---|---|
| Partial delivery + partial invoice | Very High | Qty mismatch → dangling interim lines | Proportional reconciliation by move |
| FIFO value drift (receipt between delivery and invoice) | Very High | Interim never zeros | Lock value at delivery on SVL |
| AVCO average shifts between delivery and invoice | High | Same as FIFO drift | Same fix — use SVL value at invoice |
| Returns in wrong order | High | Unreconciled credit note lines | Override _reverse_moves() |
| Multi-currency exchange difference | High | Reconcile fails on FX mismatch | Add exchange diff handling before reconcile |
| Dropship (no physical delivery) | Medium | Interim credit with no debit | Skip Anglo-Saxon for is_dropship moves |
| v19 period-end closing reports | Medium | Double-counting delivery journals | Audit closing queries, exclude interim entries |
| Landed costs after delivery | Medium | SVL value stale vs adjusted cost | Recompute SVL on landed cost posting |
The critical adaptation: _stock_account_anglo_saxon_reconcile_valuation() references
stock_valuation_layer_ids on account.move. In v19 that field/relation is gone.
Must replace with stock_move_ids lookups — this is the single hardest rewrite in the project.
Test Matrix
Section titled “Test Matrix”Run every row for Standard, AVCO, and FIFO separately. All must pass before go-live.
Purchase flows
- PO → full receipt → full bill
- PO → partial receipt → bill → remaining receipt → remaining bill
- PO → receipt → bill at higher price (price difference account)
- PO → receipt → vendor return → credit note
- Dropship PO
Sale flows
- SO → full delivery → full invoice
- SO → partial delivery → invoice → remaining delivery → remaining invoice
- SO → delivery → customer return → credit note
- SO → delivery → invoice → return → credit note (reversed order)
- Dropship SO
Special cases
- FIFO vacuum — deliver while out of stock, receive later at different price
- AVCO receipt between delivery and invoice shifts average
- Standard price change while stock on hand
- Multi-currency (delivery rate ≠ invoice rate)
- Landed cost applied after delivery but before invoice
- Period-end closing report — no double-counting
After every test, verify:
-- Interim accounts must net to zero after reconciliationSELECT SUM(debit) - SUM(credit) AS balanceFROM account_move_lineWHERE account_id IN (<stock_input_id>, <stock_output_id>) AND reconciled = TRUE;
-- No unreconciled lines older than 30 daysSELECT COUNT(*) FROM account_move_lineWHERE account_id IN (<stock_input_id>, <stock_output_id>) AND reconciled = FALSE AND date < NOW() - INTERVAL '30 days';Effort
Section titled “Effort”| Task | Days | Risk |
|---|---|---|
| Config (flag, accounts, category fields) | 1 | Low |
| Lean SVL model | 0.5 | Low |
| Delivery journal entries (Standard + AVCO) | 2 | Medium |
| FIFO value-lock mechanism | 2 | Very High |
| COGS account override | 1 | Medium |
| Reconciliation engine adaptation | 3 | Very High |
| Returns / credit notes | 2 | High |
| Multi-currency | 2 | High |
| Dropship exclusion | 0.5 | Low |
| Period-end closing audit | 2 | High |
| Test suite | 5 | — |
| Total | ~21 days | Senior dev, knows both v18 and v19 |