This guide explains which journal entries Odoo 19 posts under perpetual inventory valuation with FIFO costing when a vendor bill arrives before the goods, or a customer is invoiced before the goods ship, and how to keep the stock account in line. The short answer: a vendor bill posted early needs no fix, because the bill books the stock account and the receipt later supplies the matching stock value. A customer invoice posted early is the case to watch. Its cost of goods sold is worked out once, when the invoice is posted, from whatever cost Odoo holds at that moment, and nothing recalculates it when the delivery is validated.
It is written for accountants and owners setting up Odoo 19.
Before you start
- Version and edition: Odoo 19. The entries on bills and invoices work in Community and Enterprise. The Inventory Valuation report, the stock closing, the Billed Not Received list and the company-level valuation settings are Enterprise only.
- Apps: Inventory, Purchase, Sales and Accounting (called Invoicing in Community).
- Settings: in the Inventory app, go to Configuration > Products > Categories, open the category, and set Costing Method to First In First Out (FIFO) and Inventory Valuation to Perpetual (at invoicing). Products must have Track Inventory ticked. The Inventory Valuation field is visible only to Inventory Administrators and accounting users.
- Terms used: Perpetual (at invoicing) posts inventory value through bills and invoices instead of at period end. FIFO (first in, first out) costs each sale at the oldest units in stock. COGS is cost of goods sold. On the category, Stock Account is the inventory asset account and Stock Variation absorbs differences at closing. A stock closing is a journal entry that aligns the Stock Account with the value of stock on hand.
What Odoo 19 posts at each step
Take 10 units bought at 12.00 each and later sold. With FIFO and perpetual valuation:
- Vendor bill posted: the product line is booked to the Stock Account, so the bill debits stock by 120.00 and credits the vendor's payable. FIFO products get no price difference lines; those apply to standard price products only.
- Receipt validated: no journal entry. The Stock report shows 10 units worth 120.00, valued from the posted bill. A receipt done before its bill is valued at the purchase order price, then revalued when the bill is posted.
- Customer invoice posted: Odoo adds two lines, debiting the product's expense account (COGS) and crediting the Stock Account with the cost it calculates at that moment.
- Delivery validated: no journal entry.
Odoo 19 no longer uses separate stock input and output (interim) accounts. Moves to or from a location with its own valuation account, such as inventory loss, still post at the time of the move.
When the vendor bill comes first
To bill goods from a purchase order before they arrive, the product's Control Policy on the Purchase tab must be On ordered quantities. On received quantities, the default for goods, a bill created from the order has nothing to bill until the receipt is done.
Until the receipt, the Stock Account is 120.00 higher than the Stock report. In Enterprise, Accounting > Review > Purchases > Billed Not Received lists the purchase lines billed beyond what has been received. If a stock closing is generated in that window, it credits the Stock Account and debits the Stock Variation account by 120.00. Each closing posts the gap between stock value and the account balance at its date, so the next closing after the receipt moves it back. Validating the receipt is all that is needed.
When the customer invoice comes first
If the goods have not shipped, Odoo prices the invoice's COGS from stock on hand, oldest units first. Any quantity beyond what is on hand is priced at the unit cost of the newest receipt counted, and with nothing on hand at all Odoo uses the product's Cost. When that cost is zero, for example a new product never received, Odoo adds no COGS lines at all rather than lines at zero.
The delivery posts nothing and the invoice is not revisited. The Stock report drops by the delivered value while the Stock Account still holds it, so the Inventory Valuation report proposes a credit to the Stock Account and a debit to Stock Variation. If Stock Variation is a current asset account, the cost waits on the balance sheet; if it is an expense account, the cost reaches profit and loss, but on that account rather than as COGS on the invoice. Scheduled Daily or Monthly closings do not run for a company whose default valuation is Perpetual, so this entry exists only when someone generates it.
How to handle pre-sold stock
Option 1: take the advance as a down payment.
- Open the Sales app, open the confirmed order and click Create Invoice.
- Set Create Invoice to Down payment (percentage) or Down payment (fixed amount), enter the amount and click Create Draft. Post the invoice. Its line carries the order's taxes but no product, so no COGS is posted.
- After the delivery, click Create Invoice again and choose Regular invoice. The final invoice deducts the down payment and posts COGS from the delivered units' FIFO cost.
Setting the product's Invoicing Policy to Delivered quantities stops a regular invoice from being raised for goods not yet shipped.
Option 2: reset the early invoice and post it again. After the delivery, open the invoice from Accounting > Customers > Invoices, click Reset to Draft, then Confirm. Resetting deletes the COGS lines and confirming rebuilds them from the delivery's cost. If a closing had already moved that cost to Stock Variation, the next closing moves it back. Reset to Draft is not offered on journals that lock posted entries with a hash, lock dates can block it, and Odoo detaches the invoice's stored PDF, so test the cycle on a duplicate database first, especially for paid invoices.
Option 3: rely on the closing. Leave the invoice and post the closing from Accounting > Review > Inventory Valuation with Generate Entry, using an expense account as Stock Variation if the cost should reach profit and loss.
Check that it worked
- Open the final or reposted invoice and the Journal Items tab. Two product lines should debit the expense account and credit the Stock Account for the delivered quantity at its FIFO cost.
- In the Inventory app, open Reporting > Stock and compare the Total Value sum with the Stock Account balance.
- In Enterprise, open Accounting > Review > Inventory Valuation and click Generate Entry. The message "Everything is correctly closed" means the two already agree.
A note on Odoo 20
Odoo 20 reorganizes how COGS lines are built, so the entries above were verified for Odoo 19 only. Check them again on an Odoo 20 database before relying on them there.
Steps verified on Odoo 19. Menus and labels can differ on other versions.
Common questions
What journal entry does Odoo 19 post when a vendor bill is posted before the receipt?
With perpetual valuation, the bill line for a stock-tracked product is booked to the category's Stock Account, against the vendor's payable. The receipt itself posts no entry and is valued from the posted bill.
Why is there no COGS line on a customer invoice in Odoo 19?
COGS is calculated when the invoice is posted. If the goods have not shipped, nothing is on hand and the product's Cost is zero, Odoo skips the COGS lines entirely.
Does Odoo 19 update COGS on an invoice after the delivery is validated?
No. The delivery posts no journal entry and the posted invoice is not revisited. Resetting the invoice to draft and confirming it again rebuilds the COGS lines from the delivery's cost.
Does Odoo 19 still use stock interim accounts?
No. Odoo 19 removed the stock input and output accounts. Differences between stock value and the Stock Account are handled by the stock closing, which posts them against the Stock Variation account.
How do I invoice customers in advance in Odoo without losing COGS?
Use a down payment invoice for the advance. It carries the order's taxes but no product, and the regular invoice issued after delivery posts COGS from the delivered units' cost while deducting the down payment.
Talk to the team.
If you are setting this up, or Odoo is not behaving the way this guide describes, book a 30-minute call. We will look at your configuration and tell you what it needs. Calgary studio, in-house team, no offshore handoffs.
Book the call →