Odoo Inventory for UAE Distributors: Lots, Serials and Real Traceability

Most UAE trading and distribution businesses I walk into can tell me what is sitting in the warehouse. Few can tell me which supplier batch a damaged carton came from, which customer received a specific serial number, or why the stock valuation on the balance sheet does not match what a physical count just found. That gap is structural, built or missed in the first weeks of an Odoo Inventory setup. Here is how I build it so the traceability still holds up months later.

Lot tracking vs serial tracking, chosen per product

Odoo tracks a product one of two ways, chosen on the product once Lots & Serial Numbers is switched on under Inventory, Configuration, Settings. A lot number identifies a batch received, stored or made together, useful for grouping units sharing a supplier or a container. A serial number is a unique identifier for one unit, used to trace that exact item through its own life in the supply chain.

For a UAE distributor the split is usually obvious. Cartons of imported foodstuffs, cosmetics or building materials belong on lots: one lot number per container keeps data entry light while still giving a recall trail. Generators, pumps, compressors and anything under individual warranty belong on serial numbers, since a warranty claim is about one unit, not the batch it arrived in. Track a bulk commodity by serial for no payoff, or leave pharmaceuticals untracked and a recall question goes unanswered. Where those units also sell through a shop floor, the same number has to survive the till, which is the layer I build into the point-of-sale systems I set up for UAE retail and multi-branch operators.

Expiry and shelf life: the dates behind FEFO

Food, pharmaceutical and cosmetics distributors need a layer on top of lot tracking: expiry. Once Lots & Serial Numbers is active, Odoo's Expiration Dates feature adds four fields to a tracked product, set as a number of days: the Expiration Date, after which goods may be dangerous to use; the Best Before Date, after which goods deteriorate without yet being dangerous; the Removal Date, by which stock should be pulled; and the Alert Date, which warns before that happens. Odoo calculates all four automatically from the day a batch is received or made.

Removal strategies: FIFO by default, FEFO when it matters

Odoo needs a rule for which units it picks first when an order ships: the removal strategy, set on a location or, with higher priority, a product category. Left unset, Odoo defaults to First In, First Out (FIFO), pulling whichever stock arrived earliest. That is not enough for perishables, since the batch that arrived first is not always the one that expires first — different production dates across shipments can put a newer arrival ahead of an older one. First Expired, First Out (FEFO) fixes that by sorting on the removal date, not arrival date, so whatever is closest to going bad leaves first.

One warning I give every food and pharma client: FEFO sets the priority, it does not enforce a hard stop. A lot past its removal date can still be picked for a delivery if nobody has removed it from stock, so the alert dates only work if someone acts on them. The diagram below is the shape I walk every client through, from goods-in to the figure in the ledger.

From goods-in to the ledger: how one receipt becomes traceable, correctly valued stock Flowchart. Goods are received on a purchase order. The flow splits into two boxes that both feed forward: a lot or serial number is captured, and landed costs such as freight, duty and insurance are allocated onto the unit cost. Both merge into Stock on hand, valued and traceable by warehouse and location. A removal strategy then picks the unit for a sale or delivery, FIFO by arrival order by default, or FEFO by expiry order when expiry matters, after which stock valuation updates and a cycle count confirms it into the general ledger. A parallel dashed path shows what happens if a location is never cycle counted: the counted quantity drifts from the system quantity, unnoticed, and arrives at the same final box labelled instead as silently wrong, since a location that was never counted cannot actually reconcile. Goods received on a purchase order Posted as an incoming transfer lot / serial landed cost Lot or serial captured By lot number, or by unique serial number Landed costs allocated Freight, duty, insurance onto the unit cost Stock on hand Valued and traceable, by warehouse and location Removal strategy picks the unit FIFO by default · FEFO for expiry No cycle count on this location Valuation updates COGS posted, remaining units revalued Quantity drifts, unnoticed cycle count confirms it Reconciled to the general ledger Physical count confirms the quantity Costing method matches the balance sheet Skipped counts arrive here silently wrong instead
How one receipt becomes traceable, correctly costed stock — and what skipping a cycle count quietly does to the number in the ledger.

Multi-warehouse and multi-location structure

A warehouse in Odoo is the physical building or site; a location is the shelf, zone or floor inside it, and Storage Locations with Multi-Step Routes let a warehouse have more than one default location. Turn Multi-Step Routes on and a warehouse can receive and ship in one, two or three steps — which matters the moment a container needs a customs or quality check before release into sellable stock.

A distributor running a bonded store, a main warehouse and branch showrooms can hold several warehouses in one database and move stock between them as ordinary transfers, with a Resupply From setting letting one location draw from another automatically. It is the same question multi-branch and online sellers face from the other side, matching stock across locations to what is promised to a customer — the ground connecting e-commerce, multi-company inventory and branch POS for UAE retailers is built on.

Landed costs: so the import margin is actually true

A purchase order price is not the real cost of an imported item, and if Odoo never sees the rest, every margin report downstream is quietly wrong. The Landed Costs feature, switched on under Inventory, Configuration, Settings, Valuation, lets you take a completed receipt and add shipment, insurance, customs duty or other charges onto it; Odoo recalculates each received unit's value to include its share, and posts the adjustment to the accounting journal you choose. One condition catches people out: the category needs a costing method of AVCO or FIFO — landed costs cannot apply to a category still on Standard cost.

For a distributor clearing goods through Jebel Ali or another UAE port, that duty and freight line is part of what the product actually cost, not overhead — I set landed costs up before the first container lands, not after. Where freight forwarding is the business itself, the same discipline extends further, which is what the freight and customs costing work I do for UAE logistics operators is built around.

Stock aging and dead-stock visibility

Knowing what is in stock is not the same as knowing how long it has sat there. Odoo's Valuation report, under Inventory, Reporting, gives list, pivot and graph views of the same figures; the pivot view works as a stock aging report, breaking on-hand quantity and value down by receipt date. That is where dead stock becomes visible — product sitting since a purchase date months back, tying up space and cash while newer batches move around it. For a trading or distribution business carrying a wide SKU range across more than one warehouse, that pivot view is usually the first place I look when margins read fine on paper but cash is tight — the wider purchasing and stock structure behind it is what I cover in my guide to running a trading and distribution company on Odoo in the UAE.

Cycle counts versus the annual stocktake

By default Odoo schedules one full count a year, on 31 December unless the Annual Inventory Day and Month setting is changed, with every product on every location counted at once. Cycle counts differ: performed through Inventory, Operations, Physical Inventory, they let specific locations be counted on their own schedule, as often as needed. A single annual count suits a low-value, slow-moving warehouse; it is a poor fit where top-value or fastest-moving lines can drift all year unnoticed. I set cycle counts on those locations and leave the rest on the annual cycle.

Reconciling stock valuation to the ledger

Every section above eventually has to agree with one number on the balance sheet, and that is where the most expensive-looking small mistakes turn up. Odoo prices stock using one of three costing methods on the product category: Standard, a fixed cost updated manually; Average Cost (AVCO), a weighted average recalculated after each receipt; or FIFO, where each batch keeps its own cost until consumed. The category is also set to periodic or perpetual accounting; perpetual, the norm here, capitalises stock on receipt and expenses it on sale. Worth knowing on Odoo 19: perpetual valuation now posts at invoice level plus one closing entry, instead of a real-time entry per stock move — a different ledger shape from earlier versions. Every one of those costing methods only works from a correct starting point — an opening stock quantity or valuation seeded wrong on day one is an error every report above will quietly inherit, which is why I treat it as inseparable from getting an Odoo data migration right the first time.

Landed costs change the valuation layer; a cycle count adjustment posts its own entry; the costing method decides how both get priced. None of that reconciles itself. I tie the Valuation report back to the stock account at every close, the same discipline I apply to reconciling VAT control accounts before a return is filed, since an unreconciled figure tends to be found by an auditor before anyone on the team.

The practical rule I give every UAE distributor: nothing enters or leaves stock without a document. No adjusting a quantity by typing over a number on a report, no receiving against a bill that skips the actual transfer. If a unit's history cannot be reconstructed from its transfers alone, the traceability was never real — it just looked real until it mattered.

Frequently asked questions

What is the difference between a lot number and a serial number in Odoo?
A lot number identifies a batch of products received, stored or manufactured together, so one lot number can cover an entire container or production run. A serial number identifies one individual unit and must be unique, used when you need to trace that exact item through its own life cycle, such as a warranty claim or a recall on one specific unit. You choose one, the other, or no tracking at all, per product, in the product's Tracking field.
Does Odoo automatically pull expired stock out of sellable inventory?
No, and this is a common misunderstanding. Odoo's removal strategies decide the pick order, and First Expired, First Out (FEFO) prioritises stock closest to its removal date, but a lot past its removal date is not physically blocked from being picked for a delivery if it has not been removed from stock. FEFO sets the priority; someone still has to act on the alert and take expired stock out.
Can Odoo trace a recalled batch back to every customer who received it?
Yes, if the product was tracked by lot or serial number at receipt. Odoo's traceability report, under Inventory, Products, Lots or Serial Numbers, follows a batch or unit through every transfer from the original receipt to the sales order or delivery it left on. What Odoo gives you is the trace; running the actual recall notice to those customers is still your process to execute.
Does Odoo calculate the true landed cost of an imported item after freight and customs duty?
Yes, through the Landed Costs feature. You take a completed receipt, add the shipment, insurance, customs duty or other charges, and Odoo reallocates that cost across the received units, which changes their stock valuation. One condition to check first: the product's category needs a costing method of AVCO or FIFO, since landed costs cannot be applied to a category using the Standard costing method.
How often should a UAE distributor run a full stock count versus cycle counts?
Odoo defaults to one full count a year, scheduled for 31 December unless you change the Annual Inventory Day and Month setting, and it supports cycle counts on top of that for counting specific locations more often. In my experience, waiting for a single annual count on a fast-moving or high-value warehouse lets a year of small discrepancies build up unnoticed, so I set cycle counts on the top-value or fastest-moving locations and leave the rest on the annual cycle.

Related reading

More guides from my desk: what's new in Odoo 19 — including the valuation change behind the ledger shift above; Odoo Community vs Enterprise — how to choose — the edition decision underneath which inventory and traceability tooling you actually get; what actually drives Odoo implementation cost in the UAE — where warehouse and inventory setup sits inside the wider project budget; and what UAE businesses actually customise in Odoo — inventory and warehouse builds made up one in ten of the 140 modules in that count.

Want your warehouse setup checked by someone who has done it before?

I'm Muhammad Salman Ali Khan, an Odoo Techno-Functional Consultant in Dubai and Head of Projects at Techbot Information Technology LLC, a UAE Odoo partner — 10+ years, 100+ implementations, certified across Odoo v13, v14, v15, v16, v18 and v19, and I presented Odoo 19's new features at Odoo Experience 2025 in Brussels. Tell me about your warehouse and I'll give you a scoped, honest view of what getting your inventory and traceability right in Odoo actually involves — at no cost.

Get a free business evaluation