SaaS Development

Custom Inventory Management System Development: Build, Extend, or Buy?

Custom inventory management system development explained: build vs buy, version-one features, ledger data model, oversell prevention and cost drivers.

Ảnh đại diện Long Nguyen

Long Nguyen

Lập trình viên Fullstack · Kỹ sư AI · Nhà nghiên cứu

• • 5 phút đọc •

What does custom inventory management system development involve?

Custom inventory management system development is the work of building stock software around the way your goods actually move: your locations, your units of measure, your sales channels and your accounting rules. A packaged product asks you to adapt to its model. A custom build starts from yours.

Whatever the industry, a working system has four parts:

  • A movement record: every receipt, sale, transfer, return and adjustment, stored as its own row.
  • Balances: the current quantity per item, location and state, calculated from that record.
  • Integrations: the code that sends quantities to each sales channel and brings orders back.
  • Screens and scanners: what your staff use to receive, pick, count and correct.

Buyers usually arrive with a list of screens. Most of the engineering effort goes into the movement record and the integrations, and those two decide whether the numbers can still be trusted a year in. This guide covers when a custom build makes sense, what version one should contain, how the data should be modelled, how overselling is prevented, and what moves the price.

Build, extend, or buy: which route fits your business?

There are three routes, and the right one depends on how far your process sits from the standard one.

Buy a packaged appExtend an ERPBuild custom
Fits whenYour process matches the product's model and your channels have supported connectorsYour process is mostly standard, with a few rules of your ownYour stock rules are unusual, or they are what you compete on
You give upControl over the data model and the sync logicSome freedom: you work inside the ERP's conceptsSpeed to first use; nothing exists until it is built
Ongoing costSubscription, often scaling with users, orders or locationsHosting plus maintenance of your custom modulesHosting plus maintenance of everything
Main riskOutgrowing it and migrating laterCustomising so heavily that upgrades become hardScope creep and under-specified integrations

A custom build is usually justified when at least one of these is true:

  • Your units don't fit a standard catalog: fabric sold by the metre from rolls, kits assembled to order, serialised parts, consignment stock you hold but don't own.
  • You sell on a channel that packaged tools don't connect to, or their connector syncs too slowly for your order volume.
  • Allocation rules are part of how you compete, such as holding stock back for wholesale accounts or setting a different buffer per channel.
  • Per-user or per-order fees are growing faster than the business.

It is usually not justified for a short catalog in one location selling through one channel. The inventory built into your store platform, or a well-kept spreadsheet, does that job.

The middle route deserves more attention than it gets. An open-source ERP already has receipts, transfers, reordering and valuation, so the custom work shrinks to the rules that are yours alone. If your process is mostly standard, Odoo development with custom modules will usually reach a working system sooner than an empty repository will.

Which features belong in version one?

Version one should be the smallest system your team can run the business on. One rule sorts the list: anything that changes the shape of the movement record gets decided now, even if its screens ship later.

CapabilityWhenReason
Item and variant catalog with units of measureVersion oneEverything else refers to it
Movement record and balances per locationVersion oneThe core of the system
Receiving against purchase ordersVersion oneStock has to enter with a cost and a source
Order reservation and fulfilmentVersion oneThis is where overselling is prevented
Sync with your highest-volume sales channelVersion oneProves the integration pattern on real orders
Adjustments with reason codes, and cycle countsVersion oneReality and the system will disagree; you need a clean way to correct it
Low-stock alerts and reorder pointsVersion oneCheap once balances exist
Lot, serial or expiry trackingDecide nowIt adds a dimension to every movement and is expensive to retrofit
Costing methodDecide nowIt determines what each receipt has to store
Additional channels and warehousesLater, one at a timeEach one is its own integration and test cycle
Barcode scanning and mobile appsLater, unless you already scanAn input method on top of the same movements
Demand forecastingLaterIt needs months of clean movement history to work from

How should the inventory data model be designed?

The most common design mistake is a quantity column on the product table that gets overwritten on every sale. It works until someone asks why the system says 37 and the shelf says 34, and there is no history to answer with.

The design that holds up is a ledger. Every change in stock is appended as a movement row with a signed quantity, a reason, and a reference to the document that caused it. Nothing is edited or deleted; a mistake is corrected with another movement. Balances are a running total kept beside the ledger for fast reads, and they can always be rebuilt from it.

Stock is several numbers, not one

'How many do we have?' has more than one answer. Units on the shelf, units you can sell, and units already promised to a customer are three different quantities. Shopify's inventory states documentation is a useful reference model because it names eight of them:

StateMeaning
incomingOn its way to a location, not yet physically there
on_handTotal units physically at a location
availableUnits that can be sold
committedUnits set aside, for example for unfulfilled orders
reservedOn-hand units temporarily set aside
damagedOn-hand units that can't be sold or used
safety_stockOn-hand units held back to guard against overselling
quality_controlOn-hand units currently in inspection

In that model, on hand equals the sum of available, committed, reserved, damaged, safety stock and quality control. Incoming sits outside it. A first version rarely needs all eight, but it does need to keep physical stock and sellable stock apart from day one. Systems that merge them are the ones that oversell.

A ledger schema to start from

Two PostgreSQL tables carry the design:

CREATE TABLE stock_movement (
    id              bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    sku_id          bigint      NOT NULL,
    location_id     bigint      NOT NULL,
    state           text        NOT NULL,
    quantity        integer     NOT NULL CHECK (quantity <> 0),
    reason          text        NOT NULL,
    reference       text        NOT NULL,
    idempotency_key text        NOT NULL UNIQUE,
    created_at      timestamptz NOT NULL DEFAULT now()
);

CREATE TABLE stock_balance (
    sku_id      bigint  NOT NULL,
    location_id bigint  NOT NULL,
    state       text    NOT NULL,
    quantity    integer NOT NULL CHECK (quantity >= 0),
    PRIMARY KEY (sku_id, location_id, state)
);

The reference column ties each movement to its source: a purchase order, a sales order, a count sheet. Shopify's inventory mutations accept a reference document URI for the same audit-trail purpose. The CHECK (quantity >= 0) on balances is a last line of defence: if application code ever tries to sell stock that isn't there, the database refuses.

How do you prevent overselling across sales channels?

Overselling has three separate causes, and each needs its own fix.

CauseWhat it looks likeFix
Two orders race for the last unitBoth succeed and stock goes negativeCheck and deduct in one guarded database statement
The same event arrives twiceAn order is deducted twice after a webhook retryA unique idempotency key per event
A channel shows an old quantityA marketplace sells a unit that sold elsewhere minutes earlierPush on every change, reconcile on a schedule, hold a buffer on slow channels

Reserve stock in one guarded transaction

The first two causes are solved inside your own database. This transaction reserves two units of item 42 for an incoming order:

BEGIN;

INSERT INTO stock_movement
    (sku_id, location_id, state, quantity, reason, reference, idempotency_key)
VALUES
    (42, 1, 'available', -2, 'order_reserved', 'shopify:order:1001', 'shopify:1001:line1:out'),
    (42, 1, 'reserved',   2, 'order_reserved', 'shopify:order:1001', 'shopify:1001:line1:in');

UPDATE stock_balance
   SET quantity = quantity - 2
 WHERE sku_id = 42 AND location_id = 1 AND state = 'available'
   AND quantity >= 2;
-- 0 rows updated means not enough stock: ROLLBACK and reject the line

INSERT INTO stock_balance (sku_id, location_id, state, quantity)
VALUES (42, 1, 'reserved', 2)
ON CONFLICT (sku_id, location_id, state)
DO UPDATE SET quantity = stock_balance.quantity + EXCLUDED.quantity;

COMMIT;

If the UPDATE reports zero rows, there was not enough stock, and the order line is rejected before anything is committed. If the same webhook is delivered again, the first INSERT fails on the unique key and the whole transaction rolls back, so nothing is counted twice. Both behaviours were tested on PostgreSQL 16.

Make one system the source of truth

The third cause is an integration problem. Your inventory system should own the available number. Channels receive it, and orders come back as reservations. If staff can also edit quantities by hand inside a marketplace dashboard, the two sides drift, and sync code cannot repair that.

Writes to a channel need the same care as writes to your own database. Shopify's inventorySetQuantities mutation takes a compareQuantity value and only applies the change if the stored quantity still matches it. From API version 2026-04 it also requires an idempotency key on every call. Where a channel's API offers neither protection, a scheduled full reconciliation has to catch what the event-driven pushes miss, and it belongs in the design either way.

Which costing method should the system support?

Costing decides what each unit on the shelf is worth in your accounts, and it shapes the data model as much as lot tracking does. Under IAS 2 Inventories, the international accounting standard, inventories are measured at the lower of cost and net realisable value. Cost is assigned by specific identification for items that are not ordinarily interchangeable, and by first-in, first-out (FIFO) or weighted average for items that are.

MethodWhat the system must storeSuits
Specific identificationThe actual cost of each individual unit, tied to its serial numberOne-off or serialised goods such as vehicles, machinery or artwork
FIFOCost layers: each receipt keeps its unit cost and the quantity still unsoldGoods with changing purchase prices, and perishables, where it mirrors the physical flow
Weighted averageA running average cost per item, recalculated on every receiptLarge volumes of identical items where layer tracking adds little

LIFO is not among the formulas IAS 2 offers. US tax rules still accept it, so a company that reports only in the United States may ask for it. Confirm the method with your accountant before development starts, because switching later means recalculating the cost of every historical movement.

One trap to settle in the specification is the backdated receipt. If a supplier invoice is entered after goods from that delivery have already been sold, FIFO layers and running averages both have to be recalculated from that date forward. Decide whether the system recalculates automatically or blocks entries dated inside a closed accounting period.

What drives the cost and timeline of a custom build?

No one can price an inventory system from its name. The estimate comes from a short list of drivers, and you can read your own project off this table before talking to any developer.

DriverEffect on effortWhy
Number of sales channels to syncHighEach has its own API, authentication, rate limits, error formats and sometimes an app approval process
Lot, serial or expiry trackingHighAdds a dimension to every movement, every screen and every report
Kits, bundles and unit conversionsMedium to highOne sale has to deduct several components, or a fraction of a larger unit
Data migrationMedium, often underestimatedDuplicate SKUs, missing units and inconsistent names have to be cleaned before import
Number of locations and transfersMediumStock in transit is its own state with its own rules
Barcode hardware and mobile screensMediumDevice testing, offline behaviour and label printing
Costing and valuation reportsMediumThe figures must agree with your accounting system
User roles and permissionsLowStandard work unless approval chains are involved

Two points protect your budget. First, ask for the estimate split by phase, with version one priced on a fixed scope. A fixed price for the whole system, quoted before anyone has looked at your item list and your channels, is a guess. Second, budget for maintenance from the start. Sales platforms revise their APIs on a schedule; Shopify releases a new API version every quarter and retires old ones, so integration code needs regular attention after launch.

What does the development process look like, step by step?

  1. Map the movements. Follow one unit from purchase order to customer delivery and back through a return. Every change of place or status is a movement type the system must record.
  2. Clean the catalog. Unique SKUs, agreed units of measure and working barcodes come before any code. Software cannot fix an item that exists under three names.
  3. Build the ledger and balances. Include automated tests that fire concurrent orders at the last unit, because that is the failure you most need to rule out.
  4. Add receiving, adjustments and counts. At this point the system can already replace a stock spreadsheet.
  5. Connect the first channel in shadow mode. The system reads orders and calculates quantities but does not push them. Compare its numbers with reality until they agree.
  6. Go live from a physical count. The count becomes the opening movements, and pushing quantities to the channel is switched on.
  7. Add channels and locations one at a time. Each gets its own shadow period before it is trusted.

Shadow mode is the step most often skipped under deadline pressure, and it is the cheapest place to find sync bugs, because nothing a customer can see depends on the new system yet.

What should you prepare before asking for a quote?

A developer can give a realistic estimate once these six things are on the table:

  • An export of your item list, with variants and units of measure.
  • Your stock locations, including any third-party warehouse.
  • Every sales channel you use and its rough monthly order volume.
  • Stock rules that are specific to you: lots, serials, kits, consignment, per-channel buffers.
  • The costing method your accountant wants.
  • What your current tool or spreadsheet gets wrong, with a recent example.

The last item matters most. A concrete failure, such as an order that oversold last month, tells a developer more about the system you need than a feature list does.

Netalith builds inventory and multichannel systems as custom software and SaaS projects, priced to scope. Send the list above and you will get a phased estimate for your catalog and channels.

CÂU HỎI THƯỜNG GẶP

Câu hỏi thường gặp

What is the difference between an inventory management system and a warehouse management system?

An inventory management system tracks how much stock you own, where it is, what it cost and what is available to sell. A warehouse management system directs the physical work inside a building: bin locations, pick paths, wave picking and labour. Most small and mid-sized sellers need the first and only add warehouse features once a single site becomes hard to run by eye.

Can a custom inventory system work with my existing Shopify, Amazon or eBay store?

Yes. The custom system connects to each store through its API, receives orders as they are placed and sends updated quantities back. Your storefronts and listings stay where they are. The usual design makes the custom system the single source of truth for available stock, with each channel receiving that number rather than keeping its own.

Is Odoo a good base for a custom inventory management system?

It is when your process is mostly standard. Odoo already covers receipts, transfers, reordering rules, lots and valuation, so custom modules only need to add the rules specific to your business. A from-scratch build suits cases where the stock logic itself is unusual, or where the system is a product you plan to sell to others.

Do I need barcode scanning in the first version?

Only if your team already scans today. Scanning is an input method on top of the same stock movements, so it can be added later without changing the data model. What should be in place from the start is a unique, clean identifier for every item, because scanners depend on it.

How do I move from spreadsheets to a custom inventory system without losing data?

Clean the item list first: one row per SKU, agreed units, no duplicates. Import it as the catalog, then take a physical count and load the result as opening movements, so the system starts from verified quantities instead of spreadsheet figures. Keep the spreadsheet read-only for a period as a reference, and do not run both as live records.

Who owns the code and data in a custom inventory system?

That depends on the contract, so settle it in writing before work starts. A typical arrangement gives the client ownership of the source code and all data once the project is paid for, with hosting accounts registered in the client's name. Ask for repository access during the build instead of a handover at the end.

Cập nhật cùng Netalith

Nhận kiến thức công nghệ, cập nhật sản phẩm và ưu đãi đặc biệt qua email.