What a warehouse system for a Cambodian distributor looks like.
Four screens from a stock and distribution build: the operations dashboard, the day’s delivery runs, the catalogue across four warehouses, and the analytics a manager opens before deciding what to buy. It walks itself through all four; clicking the navigation takes it over. Seven further modules are named in the sidebar and locked; hover one to read what it does.
Dashboard
Live · Phnom PenhMock dataShipments by mode
Freight billed by channel
Destinations
This monthShipments by month
Stock value by site
Imported · USDLocal · KHR$765,400 on hand · 60% of it held at a USD cost
Latest movements
Today- 09:42Goods receipt · Kirirom water 1.5L · 240 pcsPP · Node 3
- 09:31Pick · Mixed, 4 lines · 38 pcsSHV · Port
- 09:18Transfer out · Bayon lager 330ml · 60 pcsPP → BTB
- 08:57Count adjustment · Tonlé iced tea 450ml · −4 pcsPP · Node 3
- 08:40Goods receipt · Mekong jasmine rice 50kg · 120 pcsBTB · Depot
Interface mock. Every figure, address and product line above is invented for illustration. It is not a screenshot of a customer’s system, and no part of it is licensed software you would be resold. A build of this shape is scoped, quoted and written for your operation, and the source code is yours.
Dashboard
Shipments, freight billed and on-time rate against the prior period, volume by mode and channel, stock value split by the currency it was bought in, and the movements as they are written.
Orders
The day's runs with one order open beside them: address, both currencies at the order's rate, cargo weight, driver, and what is on the truck.
Warehouse
Stock by category across four sites, counted in packs, each line carrying thirteen weeks of issue and the days of cover that count implies.
Stock analytics
The four questions a count cannot answer on its own: what the stock is worth, how fast it turns, which lines deserve attention, and where the money is stuck.
What does inventory software actually change in a Cambodian business?
Inventory software removes the second stock list, the notebook kept because nobody trusts the screen, by making on-hand the sum of dated movements.
Somewhere in most Cambodian warehouses there is a notebook, or a spreadsheet on one person's laptop, recording what is actually on the racks. It is kept because the system has been wrong before and nobody could say why. An inventory management system has done its job on the day that notebook is closed and nobody feels any need to open it again.
The symptom is specific enough to name. A physical count disagrees with the screen. It is found at month end, when someone finally has to value the stock, and it is settled the only way it can be settled under time pressure: believe the count, edit the system to match, move on.
Nobody records why the two differed. The next count then starts from a figure that was typed rather than earned, the gap comes back, and after two or three rounds of that the warehouse stops treating the screen as evidence at all. The notebook is not laziness. It is a rational response to a number nobody can account for.
For one record to be worth trusting, the system has to be able to say where every number came from. That means each movement is a row of its own: a receipt, a transfer, a pick, an adjustment, each carrying who did it and when. On-hand is not a field anyone sets; it is the sum of the rows.
Nothing is edited in place. A mistake is corrected by posting a counter-movement that names the person correcting it and the reason, so the correction joins the history instead of replacing it. A stock figure that can be typed over will be typed over, and the second list exists because someone on the floor worked that out years ago.
Which leads to the question most owners are really asking: an ERP, or just inventory software? The split is about which number is failing you.
Take one order of 200 packs through the eight stations it passes: purchase order, shipment, goods receipt, put-away, pick, pack, dispatch, invoice. Four of them write a row in the stock ledger.
The goods receipt adds 200 packs on hand; put-away moves them from one bin to another for no net change; the pick moves them from on hand to allocated; the dispatch takes them out and recognises the cost of goods sold. The other four write nothing at all, and two of those are the documents management actually looks at.
Between the purchase order and the goods receipt the stock is on order: paid for, and not countable. Between the pick and the dispatch it is allocated: countable, and not sellable. Most arguments about stock figures are really arguments about which of those two states somebody was looking at.
Two routes get you to that record, and we build both. Scale Inventory on Odoo, where the flows are ordinary and a maintained base is worth having, or purpose-built, where the way you move stock is the way you compete. Which one fits is a discovery question rather than a sales one, and neither pays us more in a way that should interest you. The comparison at the end of this page sets out what actually separates them.
- If the stock number and the money number disagree and month end is where you find out, the problem is the ledger, not the counting. A stock module inside one ledger is the fix, and how one ledger carries purchasing, inventory, sales and accounting sets out what that means structurally.
- If the books already close cleanly and the failure is physical (several sites, goods moving between them, counts done on the floor by people who never open the accounting system), a standalone stock system is enough, and it is the cheaper of the two by a wide margin.
- If you are actually replacing three systems and the spreadsheets between them, this is the smaller half of that decision. What an ERP replaces in a Cambodian business is the page to read before scoping anything here.
- If the pain starts before the stock arrives (supplier lead times, what a container really cost by the time it cleared), that is the buy side, and it is covered in purchase orders, landed cost and goods receipt.
Multi-warehouse stock: Phnom Penh, the provinces, and what is in transit
Multi-warehouse stock replaces the company total with a figure per site, plus stock in transit, which belongs to neither the sending nor the receiving warehouse.
On-hand stops being one number and becomes four: Phnom Penh · Node 3 as the central DC, Sihanoukville · Port for import and bond, Battambang · Depot upcountry, Kampot · Cross-dock for goods only passing through. A company total is still the right number for valuation. It is the wrong one for deciding whether tomorrow's run can be loaded.
The figure most systems lose is the one on the truck. A transfer from the central DC to Battambang is no longer stock at Node 3 the moment it is loaded, and it is not stock at the depot until someone there receives it against the transfer.
For the hours it spends on National Road 5 it belongs to neither end, and it has to exist in its own right: an in-transit quantity with a source, a destination, a dispatch row and a receipt row. Skip that state and you are left choosing between two wrong answers.
Decrement on dispatch and Battambang cannot see what is coming. Increment on dispatch and the depot can sell packs that are still on the road. Neither survives the moment a discrepancy appears and someone has to work out where the stock went. A warehouse management system that cannot show what is in transit is a single-site stock list with four filters on it.
The second distinction is between what you hold and what you can promise. A case allocated to tomorrow's delivery run is still physically in the building. It is countable, it belongs in the valuation, and it is not sellable. Show one number and a salesperson takes an order against stock the loader is about to put on a truck, and the argument that follows happens in front of a customer. Committed and available are two columns rather than one, and every allocation is the row that moved a pack from the second column to the first.
Reorder points belong to the item and the site together, not to the item alone. The same SKU needs a different trigger in each place because the sites do different jobs, and stock is counted in packs (a 24 × 330ml case is one thing you count, not twenty-four), so the trigger is set in packs too, which is also the unit the person holding the clipboard is looking at.
The rule itself is plain: it reads movements that have already been posted and raises a flag when the level is crossed. Where the levels themselves come from is how supplier lead times set them on the buy side.
- Phnom Penh · Node 3 holds the depth. It absorbs a container and every other site draws from it, so its trigger has to cover its own picking plus the days a replenishment spends reaching the other three.
- Sihanoukville · Port holds goods that have landed but are not all yours to sell yet. Bonded and cleared are two states of the same pack, and one combined on-hand figure across both will overstate what you can promise a customer.
- Battambang · Depot is far enough that a mistake costs a day. A replenishment that leaves short arrives too late to fix before the weekend, which is why the in-transit figure matters more upcountry than it does across town.
- Kampot · Cross-dock is a place stock passes through rather than sits in. Days of cover is the honest metric there, and a level that would read healthy at the central DC reads as a blockage at a cross-dock.
Barcode and QR on the warehouse floor
Warehouses use EAN-13 on imported goods, Code 128 for internal codes, 2D for lot and expiry; KHQR is a payment code, not a product barcode.
Three cases cover almost every item in a Cambodian warehouse. An imported carton arrives already carrying a GTIN barcode, and you scan what the manufacturer printed. A locally produced item has no code the world agrees on, so you assign one. And plenty of goods carry nothing at all, in which case you print the label yourself and the code becomes yours to control.
The two symbologies worth telling apart take a sentence each. EAN-13 is the thirteen-digit retail barcode on a consumer product, and it encodes a GTIN issued to the manufacturer through GS1, which is what makes it mean the same thing outside your building as inside it. A retail barcode carries an identity and nothing else: no lot, no expiry.
Code 128 encodes whatever text you give it (your own SKU, a bin location, a pallet number), which is why it is the right choice for anything internal, where you are not asking the rest of the world to recognise the number. A barcode scanner reads both without knowing the difference; the difference is entirely in what the number means to the stock system on the other side of it.
A 2D code earns its place when one scan has to carry more than an identity: lot and expiry alongside the SKU, so a picker taking a case off the rack confirms the batch and the date in the same movement instead of reading them off the carton and typing them in.
One correction worth making plainly, because it comes up in almost every conversation about scanning here. KHQR is not a product barcode. It is Bakong's payment standard: the code identifies a merchant account so a customer's banking app knows who to pay.
KHQR carries no product identity: no SKU, no lot, no quantity, nothing a stock system can use. Point a warehouse scanner at a KHQR sticker and you get a payment string back, and the stock record does not move. The two codes share a counter and do unrelated jobs; what the till does with KHQR is covered in how payment is taken at the counter.
The usual sales line about connectivity is also wrong, and worth replacing with the real risk. The national network is not the bottleneck: 4G coverage is wide and mobile data here is cheap. The bottleneck is this building, right now. A concrete stockroom that kills the signal two aisles in. A power cut that takes the router down while the generator catches. Ten seconds of dropout in the middle of a count.
A scanning build is held to one specification: a scan is a local write first, so the device records the movement, keeps its own queue, and syncs when it can, with the row carrying the time the scan happened rather than the time it reached the server. There is no inventory handheld to put in your hands today. This is the architecture the build is written against, and it is a fair thing to hold any vendor to.
Labels have a typographic problem in Khmer that they do not have in English, and it belongs in a scope document as a requirement rather than in a demo as a feature. Khmer script stacks: subscript consonants and vowel signs sit above and below the base line, so the same product name is taller than its Latin equivalent at the same point size, and on a narrow thermal label the height runs out before the width does.
A long name cannot be shortened by cutting bytes either: cut in the wrong place and you split a cluster and print something that is not a word. Sorting is a second requirement. Khmer does not sort the way a byte comparison sorts it, so an alphabetical product list has to be collated properly or it comes back in an order no Khmer reader would call alphabetical. Line breaking, cluster-safe truncation and collation are the three things a Khmer-first build has to answer for before the first label prints.
Counting: cycle counts, variance, and the trail an auditor can follow
Cycle counting takes a slice of the warehouse weekly, and every variance posts as a dated adjustment carrying a reason code and the counter's name.
Weighting is what makes a cycle count work. A handful of lines are counted at a time, with the fast-moving class A lines coming round most often and the slow tail left on a longer loop, so the effort lands where an error costs money. Nobody stops shipping, and a wrong number surfaces within weeks of the mistake instead of eleven months after it.
The annual shutdown has one advantage: it is simple to organise. Everything else about it is bad. You lose two days of shipping, you count with people who do not count for a living, and when the number comes back wrong you are looking at a difference that built up across twelve months of receipts, picks, transfers and returns. There is no way to trace it, so it gets posted as one large correction and the cause is never found. The following year it happens again.
Cycle counting spreads the same work across the year and weights it by what is worth getting right. The fast-moving, high-value class A lines come round every few weeks. The slow tail comes round once or twice a year. A counter takes a short list of lines, counts what is on the shelf, and enters what they found. Nothing stops. The warehouse keeps shipping while it is being counted, which is the only reason anyone does it often enough for it to work.
When the count disagrees with the system, the count wins. The stock is what is on the shelf; the system is a claim about the stock. What matters is what happens next.
The difference is written as a stock adjustment: a dated movement carrying the item, the site, the quantity in the unit stock is held in, a reason code, and the name of the person who counted. It is never a silent edit of the on-hand figure. Anyone who later asks why a beverage line at Phnom Penh · Node 3 dropped by six packs on a Tuesday gets an answer with a person attached to it.
The trail matters more than any single adjustment. One correction tells you nothing. A hundred of them, sorted by reason code, by site and by person, tell you where the process leaks. Damage in the aisle, short deliveries from one supplier, picks entered against the wrong line, packs broken open to sell loose units: all of those look identical on the on-hand figure and completely different in the reason codes. That is the gap between knowing your count was out and knowing which four things put it out, and only the second version is something you can act on.
It matters again at year-end. An external auditor is not testing whether your stock figure is right to the pack. They are testing whether it can be supported. A valuation you can walk through line by line, with adjustments that each name a reason and a person, is a short conversation. A valuation that arrived as one round correction in December is a long one, and the questions stop being about arithmetic. What the stock system itself produces here is a valuation and a COGS figure the accounts consume; it files nothing with anybody. Where the accounting side is the thing that needs replacing, what an accounting migration actually covers is a separate scope.
- Counted and expected, side by side: both numbers held in the unit the stock actually moves in, so a variance of one pack is never read as one bottle.
- A reason code chosen from a short list: damage, miscount, short delivery, mispick, sample, write-off. A free-text box nobody fills in is not a reason code.
- The counter's name and the time: the person who stood in the aisle, not the login of whoever typed the sheet into the system that evening.
- The value effect on the valuation: what the adjustment did to stock value, in the currency the books run in, visible to finance in the period the warehouse made it.
- A blind count sheet: the counter is not shown the expected quantity before counting, because the expected quantity is otherwise what gets written down.
- An approval step above a variance level you set: small differences post on their own, large ones wait for a second pair of eyes before they touch the valuation.

Lot, serial and expiry: what changes by industry
Pharmacy tracks lot and expiry, food distribution picks first expired first out, electronics tracks serial numbers, and garment inputs track the roll and dye lot.
The mechanism is the same in every industry; the rule you run on it is not. A lot number groups units that were made or received together. A serial number identifies one unit. An expiry date belongs to the lot, not to the product. What you capture at goods receipt decides what you can answer a year later, when something has to be found and pulled back.
Pharmacy and medical supply run on lot plus expiry, and the reason is recall. A recall has to run in both directions off the same record. Forwards: a manufacturer names a batch, and you need every customer, order and delivery note that batch reached, including whatever is still on a shelf at Battambang · Depot.
Backwards: a customer arrives holding a strip of tablets and a complaint, and you need the batch it came from and everywhere else that batch went. Where the lot is captured at receipt, both of those are queries. Where it lives on a whiteboard, both are phone calls to every customer on your list, and you still will not know whether you reached them all.
Expiry then adds a second job that runs quietly all year: what is short-dated, and where is it sitting. That is a question about dates already recorded, and it decides whether short-dated stock goes back to the supplier in time or gets written off.
FMCG and food distribution run FEFO, and both acronyms are worth spelling out because they get used interchangeably by people who mean different things. FIFO is first in, first out: the unit that arrived earliest leaves earliest. FEFO is first expired, first out: the unit with the nearest expiry date leaves earliest, whenever it arrived.
Most weeks the two agree. They stop agreeing the moment a delivery turns up with a shorter remaining life than the pallet already in the rack, and that happens whenever a supplier ships older stock, whenever two production runs carry different shelf lives, and whenever a transfer moves stock that has been standing at another site. A picker following arrival order in that situation walks to the wrong pallet, and nobody finds out until the short-dated one surfaces at the back with a week left on it.
Those two rules live in different places, and confusing them causes real arguments between the warehouse and finance. They are independent of one another: a system can run FEFO on the floor every day and still value stock on weighted average in the books, which is a normal, defensible arrangement rather than an inconsistency to be fixed. Agree the valuation method with your accountant, set the picking rule with your warehouse manager, and stop expecting either one to explain the other.
Electronics and equipment need serial numbers, because every question is about an individual unit. Which unit did this customer buy, on what date, and is it inside its warranty period. Is the unit on the RMA bench one we sold. Where does it go next: repair, replacement, return to the supplier, or scrap.
A serialised return sitting in the stockroom is not sellable stock, and where the system cannot hold that distinction somebody eventually sells it to a walk-in customer. Serial capture costs more effort at receipt and at the point of sale than lot capture does, and it is the only thing that turns a warranty claim into a lookup instead of an argument between your counter staff and someone holding a broken machine.
Garment and manufacturing inputs track the roll and the dye lot. Two rolls of the same fabric in the same colour code, dyed in different lots, are not interchangeable. Cut panels from both into one garment and the difference shows in daylight, on a finished order, after the cutting is done and the fabric cannot be recovered.
The stock unit is the roll, carrying its dye lot and its remaining length, rather than a total number of metres held under one colour. Cutting reserves named rolls, not a quantity. The same logic covers coil, resin batch, yarn lot and anything else where two units of identical specification behave differently in production. A system that holds only a quantity per SKU cannot express any of this, and the shortfall shows up as a rejected shipment rather than as a stock problem.
All four rules depend on the same moment: the number being recorded as stock enters the building, by whoever is standing there with the delivery. Fitting a lot number to stock already in the rack is guesswork, and a serial copied off a box weeks later is a transcription with nothing to check it against. That moment belongs to goods receipt on the buy side, and everything on this page assumes it happened properly.
Moving stock off a spreadsheet
Migrating stock off a spreadsheet is a cleaning job, not an import job: freeze the sheet, count the building, de-duplicate, reconcile, load opening on-hand only.
A spreadsheet that has run a business for six years holds the same product under three names, codes that changed when a supplier changed, and costs typed in whichever currency the invoice arrived in. Load it as it stands and you have rebuilt the mess in a database, where it is harder to see and harder to argue with. The eight steps below exist to stop that.
The hardest part of the sequence below is not technical. The import itself runs in an afternoon. The two things that fill the calendar are de-duplication, deciding which of three rows is the real product, and reconciliation, explaining every difference between what a line cost and what was counted.
The scarce resource on a migration is the person who remembers why the data looks like this, not the person who writes the script, and their time has to be booked like anyone else's. Be wary of any quote for a stock migration given without someone asking to see the sheet first, because the sheet is the entire variable.
Run the steps in this order. Each one assumes the one before it finished, and the reconciliation at step six is the one people are most tempted to skip, which is how a business ends up with an opening stock figure nobody in the room can defend. Where the migration is one part of a wider implementation, the same cleaning work sits on its critical path: what actually drives the scope of an ERP project sets out where it lands.
- Freeze the spreadsheet: name a cut-off date, take a copy, and treat that copy as the only version anything is measured against. Movements after the cut-off keep happening in the live file and are entered into the new system afterwards; they are never merged back into the migration data.
- Count physically against that cut-off: count the building in as tight a window as you can manage, with movements paused or logged separately while it runs. What you are migrating is the count. The spreadsheet is only there to tell you how far off it was.
- De-duplicate the products: the same item exists as three rows because three people typed it in over three years. Decide which row survives, merge the quantity and the history into it, and keep the mapping. Someone will ask why last year's figure for a product does not match this year's.
- Map the SKUs that changed code: a supplier reissues a code, a category gets renumbered, and one physical item carries three identities across six years. One column, old code against new code, maintained by a person who knows the history. It is unglamorous, and it decides whether your old data is worth anything after the move.
- Split KHR and USD costs into flagged columns: a single mixed cost column is common and invisible until you total it. Every cost gets a currency flag and the date of the rate used. Where the number should have been the landed cost rather than the invoice line, fix that in the same pass; how landed cost is built on the buy side covers what belongs in it.
- Reconcile before you import: total cost in the cleaned sheet against the value of what was counted, and resolve every difference rather than the large ones. A difference you cannot explain is not rounding. It is a product you have not found, a duplicate you have not merged, or stock that left without a document. Finding it now costs an afternoon; finding it after go-live costs the new system its credibility.
- Import opening on-hand only, with no back-dating: the new system starts at the cut-off with a quantity and a value per item and per site. It does not receive six years of movements dated into the past. Back-dated history produces valuations that change retrospectively and a stock ledger that argues with books you have already closed. Keep the old file as an archive and query it there.
- Run both for one month, and reconcile weekly: everything gets entered twice, which is unpopular and worth it. A weekly reconciliation catches a process gap while it is one week old and the people involved can still remember the transaction. A parallel run reconciled only at the end is not a parallel run; it is a month of double entry followed by a disagreement nobody can unpick.
The words this page uses, in English and Khmer
Inventory vocabulary belongs in both languages, starting with the pair English-first vendors merge: ឃ្លាំង is the building, ស្តុក is the goods held inside it.
A vendor who uses ឃ្លាំង and ស្តុក interchangeably in a Khmer demo has translated a screen rather than built for this market, and it is the fastest test you can run in a meeting. The rest is the vocabulary this page runs on, in both languages, so a warehouse manager and a finance lead can argue about the same number.
A glossary earns its place here because half of these words reach a Cambodian business through an English system nobody translated, and the other half through whoever trained the last warehouse manager. When the two do not line up, a stock meeting spends twenty minutes agreeing what a word means before it can discuss a number. ERP systems for Cambodian businesses covers what the same ambiguity does further downstream, once it reaches the ledger.
Three entries below carry a caveat rather than a clean Khmer term. Lot and serial number are left in English, because Cambodian warehouses in practice say the English word or read the number straight off the label, and the nearest Khmer glosses are general enough to cover both, which destroys the one distinction those two words exist to draw.
តម្លៃដើមមធ្យម reads as average cost, not weighted average cost: the weighting by quantity is carried in the English definition instead, because there is no single settled Khmer phrase for it that we would defend in writing. Inventing terminology your team will then quietly ignore is worse than leaving the gap visible.
- Warehouse versus stock (ឃ្លាំង / ស្តុក): the building and the goods in it, and the software should keep them as far apart as Khmer does. A site is a place with an address, a manager and a door. Stock is a quantity that belongs to exactly one of those places at any moment.
- SKU (លេខកូដទំនិញ): the code that identifies one sellable thing precisely. Bayon lager 330ml in a 24-pack is one SKU; the same beer in a different pack size is another, because you count, price and reorder them separately.
- On-hand (ស្តុកនៅក្នុងឃ្លាំង): the quantity physically present at one site right now, counted in packs rather than loose units. It is a statement about goods, not a promise about them, and almost every other number on this page is derived from it.
- Available versus committed (អាចលក់បាន / កក់ទុក): on-hand minus what is already promised to open orders. Two hundred packs on the floor with a hundred and eighty committed is twenty packs available, and a system that shows only the two hundred is how a sales team keeps selling what the warehouse cannot ship.
- Reorder point (កម្រិតបញ្ជាទិញឡើងវិញ): the level at which an item is flagged for buying again. It is a threshold a person sets and another person can question, never a prediction; supplier lead times and the buy side that sets them is where the number comes from.
- Lot: a quantity received or produced together and tracked as one group, usually because it shares an expiry date or arrived on one consignment. If a lot is bad, the question is which customers received it, and that is a recall question long before it is a stock question.
- Serial number: one number for one physical unit, where a lot number covers many at once. Worth the labour on generators, handsets and anything under warranty; wasted labour on cans of soft drink.
- Cycle count (ការរាប់ស្តុកតាមវដ្ត): counting a slice of the warehouse on a schedule instead of closing the whole site once a year. A rolling count finds a variance while someone still remembers the delivery it came from, which is the difference between a correction and a mystery.
- FIFO, first in first out (ចូលមុន ចេញមុន): issue the oldest stock first. It is a picking discipline and a costing method at the same time, which is why the floor and the accountant can both be following it and still disagree about which pallet went out.
- Weighted average cost (តម្លៃដើមមធ្យម): one cost per item, recalculated on each receipt and weighted by the quantity received, so a large cheap delivery moves the average further than a small expensive one. Easier to run than FIFO across many small receipts, and harder to explain to whoever asks why the margin moved.
- Perpetual inventory (ការកត់ត្រាស្តុកជាបន្តបន្ទាប់): the stock figure updates on every movement rather than being established once a year by a physical count. It is what makes an on-hand number worth reading on a Tuesday afternoon.
- Inventory turnover (អត្រាវិលជុំស្តុក): how many times a year the stock sells through, cost of goods sold divided by average stock value. Slow turnover in a rented Phnom Penh warehouse is rent being paid to store something nobody has ordered.
Scale Inventory two ways: built on Odoo, or built from scratch
Scale Inventory is built on Odoo where the flows are ordinary, or from scratch where your stock movements are unusual; neither route is the default.
The choice is yours to make with us in discovery rather than ours to make for you in a pitch, and we take no referral fee from any vendor named below. Against the two Scale Inventory routes sit the two alternatives every distributor already has: a spreadsheet somebody maintains, or a packaged WMS bought off the shelf. The table sets out what separates the four.
Neither route is our default and neither pays us more in a way that should interest you. On Odoo we configure the warehouse properly, check the Khmer screens your staff will actually use, with them, before signature, and hand you a system whose base is maintained by somebody other than us. Purpose-built, we write the stock model against the way you already work.
| Decision | Spreadsheet | Packaged WMS | Scale Inventory on Odoo | Scale Inventory purpose-built |
|---|---|---|---|---|
| Per-user licensing | No fee, and no limit on who opens it. | Usually per user or per site, with a minimum. | Community: none. Enterprise: per named user, per year. | No per-user line. Hosting and support recur instead. |
| Khmer interface | Renders any font. Sorts Khmer however the tool likes. | Usually English plus one regional language, rarely Khmer. | Community translation. Coverage varies by module and version. | Khmer sort, search and label rules go in the contract. |
| Multi-warehouse and stock in transit | A column per site, until two tabs disagree. | Built for it. Transfers and in-transit are core. | Handles it properly. Often the better answer here. | Sites modelled as you run them: DC, port, depot, cross-dock. |
| Lot, serial and FEFO | Records a lot number. Cannot enforce one. | Core function, and generally done well. | Tracks lots and serials, with FEFO removal strategies. | For unusual rules: a split lot traced through a repack. |
| Working when the connection drops | Keeps working on the laptop. Stops being shared. | Server application. The back aisle is where it struggles. | Server application. The fix is networking in the building. | Specified: a local write that settles on reconnect. Not built yet. |
| KHR and USD cost on one item | One rate somebody typed in March, applied to everything. | Often single-currency. Valuation handed back to the ERP. | Multi-currency with a daily rate. Check which date applies. | Both currencies on the stock line, at the receipt date. |
| Who owns the code | Nobody owns it, and everybody edits it. | Closed. Changes queue behind the vendor's roadmap. | Community is open source. Enterprise is a rented licence. | Source, schema and documentation handed over at go-live. |
| Cost shape over five years | Arrives late, as a wrong count or a stockout. | Front-loads licence and implementation in the same year. | Community: no licence. Enterprise renews, and grows with headcount. | Heavier first year, nothing to renew after it. |
The first is faster to stand up and cheaper to begin. The second answers things a package argues with: bonded stock at the port that cannot be valued like domestic stock, a repack that turns one SKU into a different one, consignment sitting in a customer's shop that is still yours until it sells. How purchasing, inventory, sales and accounting run as one ledger is the architecture both routes are held to.
Where the flows are ordinary, receive, put away, pick, transfer and count, a configured Odoo will serve a distributor for years. Two of the table's rows are recommendations rather than feature ticks: Odoo handles multiple warehouses with stock in transit properly, and it tracks lots and serials with FEFO removal. On both, where your rules are unexceptional, Odoo is the answer and the cheaper route, and we say so even though the build is the larger engagement.
One version-specific fact worth carrying into any Odoo demo, ours included: the Cambodian localisation ships natively from Odoo 18, and earlier versions have none. Anything sold to you as a Cambodian Odoo on 16 or 17 is a partner's own module, so ask who maintains it when the base version moves, and what happens to it if that partner loses interest.
It is also a chart-of-accounts and tax localisation rather than a warehouse one. It does not translate the stock screens, and it is not a filing connection. That last point is where inventory demos oversell, and it is worth being blunt about.
A stock system produces a valuation and a cost-of-goods-sold figure. It files nothing. Filing to GDT stays a separately scoped adapter on every option in the table, an Odoo build and a purpose-built one alike. Any vendor blurring those two is describing an adapter that does not exist yet, and the question to ask is not whether they can do it but whether it is in the contract you are being handed.
The licensing row compresses a lot. A packaged WMS minimum, not its unit rate, is what excludes a mid-size distributor. Odoo's Enterprise per-named-user line bites hardest when several warehouse staff need nothing more than read access, and Community code is yours to host and modify provided you can hire people who know the framework. A caveat on the currency row: Odoo will pull a daily rate, but whether your team uses the rate that applied on the invoice date is a process question, not a capability one.
On the five-year shape, the verdict the table implies is worth stating: purpose-built is a worse first year and, on a five-year view, usually a better fifth. Odoo Community carries no licence fee and still has to be implemented, hosted and maintained by somebody; an Enterprise renewal grows with headcount rather than with the value the system returns, and a rented licence can be repriced at renewal. The Cambodian ERP cost calculator gives an order-of-magnitude range before you speak to anyone.
Common questions about inventory systems
Cambodian distributors most often ask about stock in transit, count variances, Khmer screens, riel and dollar valuation, offline scanning, and code ownership.
Can we run inventory without replacing our accounting system?
A stock system can run alongside your existing accounting software, and that is the usual starting point rather than the exception. It produces the two things your books need, a valuation at a date and a COGS figure when a unit sells, and hands them across as a periodic journal rather than taking over the ledger. Your accountant keeps working where they work now. No stock system files anything: the monthly return still comes out of the accounting system. If the accounting side is also creaking, what an accounting migration actually covers is a separate scope, better kept apart from this one.
Can it track stock across multiple warehouses?
Stock is tracked per site, and a site is a first-class row in the data rather than a column bolted onto an item. The mock on this page runs four: Phnom Penh · Node 3 as the central DC, Sihanoukville · Port for import and bond, Battambang · Depot upcountry, and Kampot · Cross-dock.
What happens to stock in transit between our warehouses?
Stock on a truck between two of your sites sits in an in-transit location that belongs to neither warehouse until someone receives it. A transfer is two movements rather than one: it leaves the origin at dispatch and arrives at the destination on receipt. That makes a shortfall visible: forty packs dispatched, thirty-eight received, two in transit with a name and a date on them rather than quietly missing.
Will the system tell me when stock needs reordering?
The system tells you when an item crosses a reorder point you set, which is a rule rather than a prediction. Minimums, maximums and low-stock alerts work the same way, reading movements that have already been posted. It knows nothing about Pchum Ben, or a customer about to double an order, unless somebody sets the level to account for it. That is a hard limit rather than a soft one. How reorder rules sit against supplier lead times is covered on the buy side.
What happens when a stock count disagrees with the system?
A physical count beats the system record, and the disagreement is posted as its own movement rather than quietly erased. The adjustment carries a reference, a reason, a value and the name of whoever approved it, so the variance stays a line you can query months later instead of a number that changed on its own. It has a second consequence people forget: the valuation moves, so the difference reaches the books as shrinkage. The working discipline is counting fast lines often and slow lines rarely, by class, rather than shutting the warehouse once a year for a count nobody trusts by the second day.
Does the stock system work offline when the internet drops?
A scan is written locally first, carries its own identifier, and reconciles when the link returns, with conflicts surfaced rather than resolved silently. There is no inventory handheld to hand you today, so that rule is a specification rather than a demo. The risk is not the country, where mobile data is cheap and 4G coverage is wide. It is the building: a power cut, a concrete stockroom with no signal in the back aisle, ten seconds of dropout mid-count. Offline selling at a counter is a different problem: how the till keeps working when the line drops.
Can warehouse staff use the system in Khmer?
Warehouse screens run in Khmer and English, with the language treated as a build requirement rather than a translation pack added at the end. That distinction is where these systems break. Item names have to sort in Khmer order rather than by code point. Search has to normalise two visually identical spellings to one match, because two storemen will type them differently. Labels have to break across lines correctly in a script that does not put spaces between words. Ask any vendor to show you a receiving screen in Khmer, driven by a storeman rather than a salesperson, with your own item names in it.
How is stock valued when the same item is bought in riel and in dollars?
Each receipt keeps the currency it was paid in and the NBC daily rate for the day the stock arrived. The cost on that line therefore traces to a real rate on a real day, and the valuation reports in both currencies, neither a re-conversion of the other at month-end. On imported goods the number that belongs on the stock line is not the invoice line at all: how landed cost is calculated on an import is the buy side's part of it.
Can we track lot numbers and expiry dates on stock?
Yes, and the catch is that lot tracking has to be scoped before the build starts, because retrofitting a lot onto stock already received means recounting the shelf. If you handle food, drink, pharmaceuticals or anything with a shelf life, three things follow: the receipt captures the lot and the date at the moment of receipt, picking defaults to first-expiry-first-out, and the ageing view is by lot rather than by item. The last of those is what people discover late. An item can show plenty of cover in total and still have a quarter of that cover expiring inside a month.
Do we own the code and the data?
Yes on a build, and the handover is source code, database schema, migrations and a data dictionary, with the data in standard PostgreSQL. Any engineer you hire afterwards can query and export every movement without asking us for permission or a format. Where we instead configure a platform you licence, you own your data and rent the software, which behaves very differently across five years, so get it explicit which of the two you are buying before you sign. The data model every build is held to sets out what one record means in practice.
How long does an inventory implementation take?
Eight to fifteen weeks is the band a stock module is scoped in for a single-entity SMB, and stock sits at the harder end of it. The opening count is a physical job rather than a data job. The phases are discovery and process mapping, master clean-up on items and sites, build or configuration, the opening count and its reconciliation, then parallel running until the system and the shelf agree. If the module lands inside a wider programme the shape changes again: what a full ERP engagement runs to.
What do we need to have ready before an inventory implementation starts?
A decision on the unit you count in is the first thing to settle, made once and then enforced. A case of 24 × 330ml counted sometimes as one pack and sometimes as twenty-four cans is the most common reason a count never reconciles. After that: an item master somebody will vouch for, with the dead SKUs marked dead; a site list with the role of each site written down; a cost per item, and a note of where it came from; and a named person who can settle it when two records disagree, because that call is needed weekly and it is not ours to make.
What should we ask an inventory vendor that a demo will not answer?
The question that separates vendors is what happens to stock that has left one site and not yet arrived at the next, so ask to see the record. Then ask for an adjustment from a count that went the wrong way, with the reason and the approver still on it. Ask what is written where when the connection drops mid-scan, and what happens when it comes back. Ask who owns the source code and the schema at go-live, in writing. And ask what this does that your spreadsheet does not, answered without the word "integrated". A vendor who cannot answer these on a whiteboard will not answer them in production.