Skip to content

Anglo-Saxon Accounting in Odoo v19 — Full Investigation

A method that controls when COGS is recognised:

MethodCOGS fires atUsed in
ContinentalVendor bill validationFrance, Germany, most of Europe
Anglo-SaxonDelivery + 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.


08b62a4bbcc6f9a391b2cc00a621ef4c76100229
Author: amoyaux — August 9, 2025
Title: [REF] stock_account: New inventory valuation
Files: 220 changed

Official 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 on stock.move
  • New product.value model for manual adjustments only
  • Value priority: manual > invoice/bill > PO/SO > standard price

  • anglo_saxon_accounting boolean on res.company gates 3 behaviours:
    1. COGS journal lines on customer invoices (via stock_output interim account)
    2. Purchase bill lines redirected to stock_input interim account
    3. Reconciliation between interim account lines at invoice posting
  • stock.valuation.layer (SVL) created on every _action_done() — tracks value, links to journal entry
  • Journal entries fire at both delivery and receipt
  • stock.valuation.layer completely removed
  • anglo_saxon_accounting field still exists on res.company but 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)

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()
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 delivery
Eventv18 (Anglo-Saxon ON)v19 (always)
Goods receiptDR Stock Valuation / CR Stock Inputnothing
Customer deliveryDR Stock Output / CR Stock Valuationnothing
Vendor billDR Stock Input / CR AP + reconcileDR Stock Valuation / CR AP
Customer invoiceDR COGS / CR Stock Output + reconcileDR 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 MethodValuationJournal firesCOGS basis
Standardmanual_periodicNeverManual closing
Standardreal_timeInvoice postingstandard_price
AVCOreal_timeInvoice postingCurrent rolling average
FIFOreal_timeInvoice postingConsumed 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.


Every item below was confirmed by reading the v18 source and the commit diff.

"stock_in", "Stock Interim (Received)", "1102", asset_current, reconcile=True
"stock_out", "Stock Interim (Delivered)", "1103", asset_current, reconcile=True
property_stock_account_input_categ_id # Many2one account.account, company_dependent
property_stock_account_output_categ_id # Many2one account.account, company_dependent

Keys 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.

  • _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 reconciles stock_output
  • _stock_account_prepare_anglo_saxon_out_lines_vals() — builds COGS line pairs using stock_output interim (replaced by _stock_account_prepare_realtime_out_lines_vals() using stock_valuation directly)
  • _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 — called prepare_anglo_saxon_out_lines_vals() then reconcile_valuation()
  • _compute_account_id() purchase branch — redirected vendor bill lines to stock_input when 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
  • _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

stock.valuation.layer — the table, the model, all methods, and stock_valuation_layer_ids on account.move.


Checked all OCA repositories on 19.0 branch:

RepoModuleVerdict
account-financial-toolsaccount_usabilityShows the toggle in CE UI only — zero functional restore
stock-logistics-workflowstock_move_valuation_usageTracks FIFO layer sources, no COGS/reconciliation
All othersNothing relevant

No OCA module restores Anglo-Saxon in v19. The gap is open.


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:

  1. _stock_account_anglo_saxon_reconcile_valuation() references stock_valuation_layer_ids by name — same model name means minimal adaptation
  2. v19 fresh install has no stock_valuation_layer table — zero collision
  3. Clean upgrade path: if Odoo officially restores Anglo-Saxon, uninstall the module, done
  4. 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 journal

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 logic

No try/except. No logic before the guard. Standard Odoo runs unchanged.


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.xml
  1. Company flag + category accounts + COA data — low risk, validate config first
  2. Lean SVL model — just the fields, no logic yet
  3. _get_product_accounts() keys restored — unblocks all downstream methods
  4. Delivery journal entries (Standard + AVCO first, FIFO after)
  5. Test Standard + AVCO purchase/sale flows
  6. FIFO value-lock — store value on SVL at delivery, use it at invoice time
  7. COGS line account overridestock_valuationstock_output
  8. Reconciliation engine — adapt v18’s _stock_account_anglo_saxon_reconcile_valuation()
  9. Returns + credit notes
  10. Multi-currency exchange difference handling
  11. Dropship exclusion
  12. Period-end closing compatibility audit
  13. Full test suite

Do not go live on FIFO before step 6 is complete and tested.


RiskSeverityWhat breaksFix
Partial delivery + partial invoiceVery HighQty mismatch → dangling interim linesProportional reconciliation by move
FIFO value drift (receipt between delivery and invoice)Very HighInterim never zerosLock value at delivery on SVL
AVCO average shifts between delivery and invoiceHighSame as FIFO driftSame fix — use SVL value at invoice
Returns in wrong orderHighUnreconciled credit note linesOverride _reverse_moves()
Multi-currency exchange differenceHighReconcile fails on FX mismatchAdd exchange diff handling before reconcile
Dropship (no physical delivery)MediumInterim credit with no debitSkip Anglo-Saxon for is_dropship moves
v19 period-end closing reportsMediumDouble-counting delivery journalsAudit closing queries, exclude interim entries
Landed costs after deliveryMediumSVL value stale vs adjusted costRecompute 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.


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 reconciliation
SELECT SUM(debit) - SUM(credit) AS balance
FROM account_move_line
WHERE account_id IN (<stock_input_id>, <stock_output_id>)
AND reconciled = TRUE;
-- No unreconciled lines older than 30 days
SELECT COUNT(*) FROM account_move_line
WHERE account_id IN (<stock_input_id>, <stock_output_id>)
AND reconciled = FALSE
AND date < NOW() - INTERVAL '30 days';

TaskDaysRisk
Config (flag, accounts, category fields)1Low
Lean SVL model0.5Low
Delivery journal entries (Standard + AVCO)2Medium
FIFO value-lock mechanism2Very High
COGS account override1Medium
Reconciliation engine adaptation3Very High
Returns / credit notes2High
Multi-currency2High
Dropship exclusion0.5Low
Period-end closing audit2High
Test suite5
Total~21 daysSenior dev, knows both v18 and v19