Odoo COGS Explained: How Cost of Goods Sold Is Calculated and Posted
Odoo COGS explained for Odoo 19 and 20: Anglo-Saxon vs Continental timing, FIFO, AVCO and standard cost, journal entries, closing entries and reports.
Most articles on cost of goods sold in Odoo stop at the textbook formula. This guide shows what Odoo actually does: which setting decides the timing, how each costing method computes the number, and which journal lines appear in Odoo 19 and 20. Our Odoo consulting services start with this design.
It is written for finance managers, controllers and owners who want margins they can trust, and for consultants configuring product categories. If the Accounting app is new to you, read how Odoo Accounting works first.
What is COGS in Odoo and where does the number come from?
In Odoo, cost of goods sold is the value removed from stock when goods leave the company, computed by the Inventory app and posted to the ledger by the Accounting app. The two apps work on different triggers, which causes most of the confusion.
The Inventory app values every stock move in real time: a receipt adds value at the purchase order price, a delivery removes value at the unit cost your costing method produces. Odoo's valuation of stock movements guide lists every operation that changes stock value.
The Accounting app does not post an entry per move. It updates the Inventory and COGS accounts when bills and invoices are posted, and a closing entry covers the gap between what was invoiced and what was received or delivered. Odoo adopted this design in version 19 and kept it in Odoo 20.
So "Odoo COGS" rests on three settings you must choose on purpose:
- Costing method: Standard Price, AVCO or FIFO, set on the product category or in Accounting settings.
- Valuation method: Perpetual (at invoicing) or Periodic (at closing), which decides when cost becomes an expense.
- Accounts: the Stock (Inventory) account, its Variation account, and the Expense or COGS account on the product or category.
Anglo-Saxon vs Continental: when does Odoo post COGS?
Odoo posts cost of goods sold at the customer invoice when you choose Perpetual (at invoicing), and expenses the vendor bill when you choose Periodic (at closing). Odoo's docs pair perpetual with Anglo-Saxon practice and periodic with Continental practice, but you can combine them.
In Odoo 19 and 20 there is no Anglo-Saxon checkbox. You pick the valuation method under Accounting, Configuration, Settings, Inventory Valuation, as Odoo's inventory valuation documentation describes. The table uses the account names from Odoo's cheat sheet.
| Setup | Vendor bill posts | Customer invoice posts | Closing entry does |
|---|---|---|---|
| Continental, Periodic | Debit Goods for Resale expense, credit Suppliers | Revenue only, no cost line | Stock vs Variation (stock change reduces expense) |
| Continental, Perpetual | Debit Stock, credit Suppliers | Debit Goods for Resale expense, credit Stock | Stock vs Expense, then Variation |
| Anglo-Saxon, Periodic | Debit COGS, credit Accounts Payable | Revenue only, no cost line | Stock vs Variation, optional Shrinkage |
| Anglo-Saxon, Perpetual | Debit Inventory, credit Accounts Payable | Debit COGS, credit Inventory | Inventory vs Inventory Variations |
Odoo's valuation cheat sheet calls periodic "the best practice in Europe" and perpetual the practice in Anglo-Saxon countries "like the USA and India". Choose by your reporting standard, not by habit.
How does Odoo calculate COGS under Standard, AVCO and FIFO?
Odoo calculates COGS as the delivered quantity multiplied by the unit cost your costing method assigns to that outbound move, so the same shipment can carry three different costs. The example uses the figures from Odoo's cheat sheet and is illustrative.
You receive 8 units at $10, then 4 units at $16, then deliver 10 units. Standard Price is set to $10.
| Costing method | Unit cost used for the 10 delivered | COGS on the invoice | Stock value left (2 units) |
|---|---|---|---|
| Standard Price ($10) | $10 for every unit | $100 | $20 |
| Average Cost (AVCO) | $12 weighted average ($144 / 12) | $120 | $24 |
| FIFO | 8 units at $10, then 2 units at $16 | $112 | $32 |
Three rules from Odoo's documentation shape these numbers:
- Standard Price never recalculates. Receipts at $16 enter stock at $10; with Perpetual valuation the gap can go to a Price Difference account on the category.
- AVCO changes only on receipts. Deliveries do not move the average; the next receipt at a new price does.
- FIFO keeps each receipt layer. Remaining units keep their own value, while the Cost field on the product form still shows an average.
Removal strategies such as LIFO or FEFO only decide which units are picked, not how they are valued. For the FIFO vs AVCO decision, see our guide to Odoo 20 inventory valuation changes.
Which journal entries does Odoo post for COGS in Odoo 19 and 20?

With Anglo-Saxon perpetual valuation, Odoo 19 and 20 post the cost of goods sold line on the customer invoice itself, while receipts and deliveries only change the Inventory app value. The table follows Odoo's cheat sheet: a $50 product with 9% tax, sold for $100 plus 9% tax.
| Event | Inventory app value | Accounting entry (Anglo-Saxon perpetual) |
|---|---|---|
| Receipt validated | +$50 | None; the gap shows as Stock Variation |
| Vendor bill posted | No change | Debit Inventory $50, debit Tax Paid $4.50, credit Accounts Payable $54.50 |
| Delivery validated | minus $50 | None; the gap shows as Stock Variation |
| Customer invoice posted | No change | Debit Receivable $109, credit Sales $100, credit Tax $9, debit COGS $50, credit Inventory $50 |
| Closing entry generated | No change | Inventory against Inventory Variations for the open gap |
The COGS line uses the Expense or COGS account from the product or its category, and the Inventory line uses the Stock account on the category. If a bill arrives after month end, goods have left stock but nothing has hit the ledger; the closing and accrual entries below fix that.
What changed from Odoo 18: stock interim accounts to one closing entry
Odoo 18 and earlier posted a journal entry for every stock move through Stock Interim accounts; since Odoo 19 the perpetual method posts at the invoice and uses one closing entry. Odoo made the change for performance, ledger clarity and auditability.
Under the old automated valuation in the Odoo 18 valuation configuration, a receipt debited Stock Valuation and credited Stock Interim (Received); the bill cleared it. A delivery debited Stock Interim (Delivered); the invoice cleared it to COGS.
Busy warehouses got thousands of journal items and interim balances that never reached zero. One forum user asked how to clear Stock Interim (Delivered) for POS sales under FIFO because the balance kept sitting on the balance sheet.
In Odoo 19 and 20 the stock input and output accounts are no longer used. Any balance left in them must move to the Stock Valuation account during the upgrade, and Odoo ships a rebalance server action for it. Our Odoo 20 accounting upgrade checklist covers the sequence.
How do returns, scrap and cost changes affect Odoo COGS?
Returns reverse the original cost, scrap posts to a loss account, and cost changes revalue stock rather than creating a sale. Each case is a common source of wrong COGS.
- Customer return: created from the original delivery, the goods re-enter stock at the unit cost at which they left. The credit note reverses the invoice lines, including the COGS and Inventory pair.
- Scrap: with a Loss Account on the Inventory Loss location, scrap debits Inventory Loss Expense and credits Inventory; Odoo's scrapped goods guide shows the setup.
- Manual cost change: revalues the quantity on hand. In Odoo 18 this posted to the expense account, which one user saw as a COGS entry with no sale. In Odoo 19 and 20 it shows as stock variation and is settled at closing.
- Negative stock: deliver before the receipt is recorded and Odoo values the move at the last known cost, then corrects it when the receipt lands.
- Landed costs: freight, duty and insurance added through Odoo's landed costs feature raise the value of received goods, for AVCO or FIFO categories only.
Odoo 20 adds a retroactive COGS update. When a receipt's value changes after its goods were delivered, Odoo replays the valuation, recomputes the delivery value and updates the COGS lines on the posted invoice. Our Odoo 20 Inventory release notes show the Moves Analysis view where this happens.
How is manufacturing COGS built in Odoo?
For manufactured goods, COGS on a sale is the finished product's unit cost, which Odoo builds from consumed components plus work center and employee labor when the manufacturing order is validated. The sale then behaves like any other delivery and invoice.
Odoo's example: an order consumes components valued at $78, $180 and $20 and records $200 of labor, so the finished good enters stock at $478. Components leave stock at their own costing method.
With Anglo-Saxon perpetual valuation and a Cost of Production account on the Production location, validating the order debits Manufactured Products Inventory and credits Raw Materials Inventory, both against that account. Without it, production variations are recorded at closing.
Overhead, scrap during production and work order costing are covered in our post on Odoo manufacturing cost accounting.
How do you close the month so Odoo COGS matches the stock report?
At closing, the accounting stock value and the Inventory app stock value must match, and Odoo gets there with one closing entry plus accrual entries from the Inventory Valuation report. This routine replaces the old interim accounts.
- Post what you can. Post draft vendor bills and customer invoices for goods already received or delivered, so the open gap is small.
- Open the report. Go to Accounting, Review, Inventory Valuation. It shows Initial Balance, Stock Variation and Ending Stock.
- Book accruals. Use Bill To Receive, Invoices To Be Issued, Billed Not Received and Invoiced Not Delivered. In Anglo-Saxon perpetual, Invoices To Be Issued also debits COGS and credits Inventory Variations.
- Generate the closing entry. Click Generate Entry, review the draft Stock Closing entry and post it. Inventory and Inventory Variations are updated in the general ledger.
- Check the P&L. COGS for the period should equal the value of goods delivered, whether invoiced or accrued.
Odoo suggests Manual closing for perpetual setups, with the entry generated at period end, unless you want daily or monthly automation. In Odoo 20 the accruals sit inside the valuation closing report, as our Odoo 20 Accounting release notes describe.
To explain a margin, read the Profit and Loss for the COGS total, the Inventory Valuation report for stock variation not yet posted, and Moves Analysis for the value per delivery.
Which Odoo COGS problems come up most, and how do you fix them?
Most Odoo COGS problems trace back to wrong category accounts, an unsuitable valuation method, skipped closing entries, stale costs, or custom code written for the old interim model. The fixes are configuration and routine, not new modules.
| Symptom | Likely cause | Fix |
|---|---|---|
| COGS missing from the P&L | Periodic valuation, or Expense account pointing to a balance sheet account | Set the category to Perpetual (at invoicing) and point Expense to a COGS account |
| COGS posted twice | Old customization booking cost at delivery and at invoice | Remove the override; Odoo 19 and 20 post at the invoice only |
| Stock Interim balances on the balance sheet | Database upgraded from Odoo 18 or earlier | Run the rebalance server action, then generate the closing entry |
| Expense with no sale | Manual cost change on the product form | Adjust the value of specific receipt layers, not the product field |
A user on Odoo 11 asked how to generate COGS entries when the sales invoice is created, because the trigger was the delivery back then. In Odoo 19 and 20 the invoice is the trigger by design.
Not sure which case applies to your database? Our free Odoo health check tool is a quick first pass before a consultant reads the ledger.
Which industries need special attention on Odoo COGS?
Distributors, importers, manufacturers and retailers each stress a different part of Odoo's COGS logic. The table shows where the design time usually goes.
| Industry | Pain or cost driver | Odoo approach | How iVentureTeam helps |
|---|---|---|---|
| Wholesale and distribution | High volume, vendor price changes | AVCO per category, perpetual valuation, monthly closing | Category design, closing routine, margin reports |
| Importers | Freight and duty billed after goods ship | Landed costs on FIFO or AVCO, Odoo 20 retroactive COGS update | Landed cost products and split methods |
| Manufacturing | Labor and overhead in finished cost | Work center costs, Cost of Production account | Routing costs, BOM reviews, account mapping |
| Retail and POS | Many small sales, frequent returns | AVCO, perpetual valuation, returns from the original delivery | POS to accounting reconciliation |
For trading companies the biggest win is usually a correct category structure, which we cover in our guide to Odoo for trading companies.
How iVentureTeam helps with Odoo COGS setup and reporting

iVentureTeam designs the valuation, costing and account setup behind your Odoo COGS, then proves it with a dry month-end close before go-live. We have delivered 150+ ERP projects across 35+ countries with 40+ Odoo specialists over 13+ years.
- Valuation design: perpetual or periodic per company, costing method per product category, aligned with your reporting standard.
- Account mapping: Stock, Variation, COGS, Price Difference, Inventory Loss and Cost of Production accounts, as part of our Odoo implementation services.
- Upgrade clean-up: interim account rebalancing and custom COGS code review when moving from Odoo 18 or earlier to Odoo 19 or 20.
- Closing routine: a written month-end checklist for the Inventory Valuation report, accruals and sign-off.
- Training: finance teams learn the reports through our Odoo ERP training.
Odoo COGS case study: Mechanical Products IL
Accurate cost of goods sold starts with accurate cost prices, and that was the problem a US precision manufacturer brought to us. Mechanical Products IL machines and assembles custom components and ran its product lifecycle on manual Excel imports into Odoo 17.
Sales and cost prices did not update on import, and purchase orders created from sales orders did not pick up the latest vendor price, so every pricing error eroded margin.
We built a custom product-lifecycle module that refreshes standard and cost prices from the uploaded sheet, creates missing products and archives dropped ones. Scheduled actions keep the standard_price field in sync, and purchase methods were overridden so POs always pull the latest vendor price.
The project took 6 weeks. The client reports 90% less manual import effort, 100% vendor-price accuracy on POs and zero pricing mismatches since go-live. Read the Mechanical Products Odoo product data automation case study.
The bottom line on Odoo COGS
Odoo COGS is only as good as three settings, the costing method, the valuation method and the accounts on the product category, plus a closing routine someone actually runs. Get those right and the P&L margin matches the stock report every month.
In Odoo 19 and 20 the model is simpler: invoices move Inventory and COGS, one closing entry handles the gap, and Odoo 20 corrects COGS when late costs arrive. The work is in choosing the settings on purpose and testing them on your own products before the first real close.
Ready to get your Odoo COGS and margins right?

Have a consultant review your valuation settings, accounts and closing routine. Request an Odoo COGS and valuation review, call +91-90235-15208, or email business@iventureteam.com.
Frequently Asked Questions
How does Odoo calculate cost of goods sold?
+
Odoo multiplies the delivered quantity by the unit cost assigned by the product category's costing method: Standard Price, Average Cost or FIFO. The Inventory app values the delivery, and the Accounting app posts it to the COGS account when the customer invoice is posted under perpetual valuation.
When does Odoo post the COGS journal entry?
+
With Perpetual (at invoicing) valuation, the COGS debit and Inventory credit are posted on the customer invoice. With Periodic (at closing), the vendor bill is expensed and a closing entry adjusts stock value. Receipts and deliveries themselves do not create journal entries in Odoo 19 and 20.
Where is the Anglo-Saxon accounting setting in Odoo 19 and 20?
+
There is no separate checkbox any more. Go to Accounting, Configuration, Settings, Inventory Valuation and choose Perpetual (at invoicing) for Anglo-Saxon behavior or Periodic (at closing) for Continental behavior. The setting can be overridden per product category.
What happened to Stock Interim accounts in Odoo?
+
Since Odoo 19 the stock input and output interim accounts are no longer used. When upgrading from Odoo 18 or earlier, any balance in them must be moved to the Stock Valuation account, either with Odoo's rebalance server action after the upgrade or with a manual journal entry.
Why does Odoo show COGS in the P&L when nothing was sold?
+
In Odoo 18 and earlier, changing the Cost field on a product with automated valuation posted the revaluation to the Expense account, often a COGS account. Adjust specific receipt layers instead. In Odoo 19 and 20 the change appears as stock variation and is settled by the closing entry.
Does Odoo update COGS when a landed cost arrives after the sale?
+
In Odoo 20, yes. When a receipt's value changes after its goods were delivered, Odoo replays the valuation, recomputes the delivery value and updates the COGS lines on the posted customer invoice under perpetual accounting. Odoo 19 and earlier do not update sold goods retroactively.
How do customer returns affect COGS in Odoo?
+
A return created from the original delivery brings goods back at the unit cost at which they left, reversing the delivery value. The linked credit note reverses the invoice, including the COGS and Inventory lines, so margin for the period is corrected without revaluing the goods.
Free written answer from a certified consultant
Getting stock under control in Odoo?
Tell us how stock moves through your business. We map warehouses, routes and replenishment in Odoo and show what to configure first.
Free, with no obligation. A senior consultant replies within 12 business hours.
Free Odoo tools
All tools- Odoo Health CheckVersion, modules, findings and upgrade cost
- Odoo App SelectorWhich apps you need, by industry
- Odoo Project EstimateIndicative cost and timeline in minutes
- Odoo Implementation Cost CalculatorImplementation cost and timeline, in minutes
- Odoo Migration Cost CalculatorVersion jump or legacy ERP, priced in minutes
Continue reading
Odoo Manufacturing Cost Accounting: Work Order Costing and Overhead
How Odoo 19 and 20 cost a manufacturing order: work center rates, BOM cost roll-up, overhead absorption, WIP and production journal entries, and variance.
Odoo 20 Inventory Valuation: Changes, FIFO vs AVCO and Upgrade Checks
Odoo 20 inventory valuation explained: what changed since Odoo 18 and 19, FIFO vs AVCO, Anglo-Saxon perpetual setup, reports and upgrade checks to run.
Odoo Saudi Bank Integration: Statement Sync, Reconciliation, SADAD
Odoo Saudi bank integration explained: bank sync coverage, CAMT and Excel statement import, SAR auto-reconciliation, ISO 20022, WPS and SADAD in Odoo 19/20.
