WMSSeptember 2026 · 5 min read

Warehouse Database Design

By go2ismail · Published · .NET 10

At a glance

Separate master data, business documents, and movement records. This WMS uses explicit product and warehouse foreign keys, plus module references that identify a movement’s source document.

Design around business meaning

Warehouse database design should let you explain both the present balance and the operation behind each change. In Indotalent’s Blazor WMS Source Code, master data describes products and warehouses, business documents record purchasing and sales activities, and inventory transactions hold movement-level details. These responsibilities are connected, but they should not be collapsed into a single product table.

This guide examines the source model and its EF configurations. It is a logical design walkthrough, not an exported schema from a running production database. The inventory database schema article goes deeper into individual property types and configured indexes.

Keep master data reusable

Product references UnitMeasure and ProductGroup. Those references allow multiple products to share a unit or grouping. Warehouse is a separate entity with a name, description, and nullable SystemWarehouse flag. A product can participate in movements at multiple warehouses without storing a single warehouse identifier on the product itself.

A system warehouse represents a special movement role in the calculation helper. For example, receiving uses VENDOR as its source and transfers use TRANSIT. This is different from assuming every Warehouse record is a physical building. Preserve that distinction when adding a location selector or calculating a report for operational sites.

Read the core relationships

Core foreign-key relationships: each arrow runs from the referencing entity to its parent.
ProductUnitMeasureId → UnitMeasure.IdProductGroupId → ProductGroup.Id
InventoryTransactionProductId → Product.IdWarehouseId → Warehouse.IdWarehouseFromId → Warehouse.IdWarehouseToId → Warehouse.Id
PurchaseOrderItemPurchaseOrderId → PurchaseOrder.IdProductId → Product.Id
GoodsReceivePurchaseOrderId → PurchaseOrder.Id
TransferInTransferOutId → TransferOut.Id

The diagram lists foreign-key properties from the model. Many child records can reference the same parent, and the listed foreign-key properties are nullable in the C# entities. InventoryTransaction has three separate relationships to Warehouse: the warehouse whose stock is being recorded, the movement origin, and the movement destination. The configuration names each relationship explicitly.

Distinguish order lines from inventory movement lines

PurchaseOrderItem contains a purchase-order reference, product reference, quantity, unit price, and total. GoodsReceive references a PurchaseOrder. Movement details associated with receiving are represented through InventoryTransaction. The commercial order and the physical stock movement therefore have different roles even when they involve the same product.

This separation gives a useful design question for any extension: does the new information belong to the agreement, the receiving event, or each product movement? A supplier reference may belong on a commercial document; an actual received quantity belongs to the operational flow. Placing everything on Product makes those distinctions difficult to preserve.

Understand the document reference in InventoryTransaction

The fields ModuleId, ModuleName, ModuleCode, and ModuleNumber describe a movement’s originating module and document. Unlike ProductId and the warehouse references, ModuleId is not mapped in the inspected configuration as a foreign key to every possible source document. It is a generic reference interpreted by application code.

That provides a common movement shape for multiple modules, but the distinction must remain visible in a database diagram. Drawing one enforced foreign key from ModuleId to every document table would misrepresent the implementation. When adding a new module, review how its handlers create, query, update, and delete the associated movements.

Review lifecycle rules as part of the design

Business entities inherit an Id, audit fields, and IsDeleted from BaseEntity. AppDbContext changes entity deletion into a soft-delete update for participating entities and installs a filter that normally excludes deleted rows. The configured transaction relationships use DeleteBehavior.NoAction. Soft deletion and relational delete behavior are different mechanisms: a soft delete updates a flag rather than executing a database DELETE.

Consider what a historical report should display if master data is retired. Keeping transaction records does not, by itself, guarantee that every query will still show the same related names after filters are applied. This is a reporting requirement to test with the actual query and relationship configuration.

Extend only the dimensions your workflow needs

The inspected core model does not establish a bin hierarchy, serial-number ledger, or lot-expiry allocation system. Those can be design extensions, but each adds relationships and operational rules. For bin-level stock, for example, determine whether location becomes part of a movement’s identity and report grouping before adding a free-text bin field.

Similarly, the helper’s stock calculation is based on confirmed signed contributions. A reserved quantity or available-to-promise figure would need a defined source and calculation; it should not be inferred from the existing Stock field. Start with a concrete business question and trace which entity, operation, and report must answer it.

Use the design to review the complete application

A useful review follows one product from its unit and group to an order item, then to a confirmed movement and stock report. That path exposes whether the design supports the workflow you need. The Blazor WMS product page provides the complete source-code offer behind this model, while the EF Core implementation guide explains how it is configured and queried.