Inventory & Warehouses
Overview
TaxMTD includes a full inventory management system for businesses that hold physical stock. Track products across multiple warehouse locations, move stock between sites with transfer orders, reconcile physical counts with stock takes, and monitor value and margins with built-in valuation reports.
Products
Create and manage products with SKU, barcode, pricing, and stock levels.
Warehouses
Multiple locations with independent stock levels and reorder points.
Transfers
Move stock between warehouses with a draft/ship/complete workflow.
Stock Takes
Reconcile physical inventory against system records.
Products
Each product in TaxMTD tracks the following fields:
| Field | Description |
|---|---|
| Name | Product name |
| SKU | Auto-generated unique stock-keeping unit code |
| Barcode | Optional barcode (EAN, UPC, etc.) |
| Category | Product grouping for reports and filtering |
| Unit Price | Selling price per unit (£) |
| Cost Price | Purchase/cost price per unit (£) |
| VAT Rate | VAT percentage applied to the product |
| Unit | Unit of measure (e.g. each, kg, metre) |
| Low Stock Threshold | Global alert threshold for this product |
| Notes | Optional internal notes |
Creating a Product
- Navigate to Inventory > Products
- Click Add Product
- Fill in the product details - only Name is required
- Set an initial stock quantity if applicable
- Save the product
Deactivating Products
Products can be deactivated rather than deleted. Deactivated products are excluded from stock take counts, low stock alerts, and transfer order line selection, but their movement history is preserved.
Warehouse Locations
Warehouses represent physical locations where stock is held - this could be a warehouse, shop floor, storage unit, or van.
| Field | Description |
|---|---|
| Name | Location name (e.g. "Main Warehouse", "London Shop") |
| Address | Optional address |
| Status | Active or inactive |
Per-Warehouse Stock Levels
Each product has an independent stock level at every warehouse. This means the same product can have 50 units in your main warehouse and 12 units in your shop - tracked separately.
Stock levels per warehouse also include:
- Quantity - current stock on hand
- Reorder Point - warehouse-specific threshold that triggers a low stock alert
- Reserved Quantity - stock reserved by in-transit transfer orders
Warehouse Dashboard
The inventory overview at Inventory shows KPI cards for:
- Total products
- Stock value at cost
- Number of warehouses
- Low stock alerts count
Each warehouse page shows product counts, total units, cost value, and retail value for that location.
Stock Movements
Every change to stock is recorded as a movement with a full audit trail.
Movement Types
| Type | Description |
|---|---|
| Purchase | Stock received from a supplier |
| Sale | Stock sold to a customer |
| Adjustment | Manual correction (positive or negative) |
| Return | Stock returned by a customer or to a supplier |
| Transfer In | Stock arriving from another warehouse |
| Transfer Out | Stock leaving to another warehouse |
| Stock Take | Adjustment generated by a stock take variance |
Recording a Movement
- Navigate to Inventory > Movements
- Click Add Movement
- Select the product, warehouse location, and type
- Enter the quantity (negative for reductions)
- Optionally add a reference and notes
- Save - the stock level at that warehouse updates automatically
Filtering Movements
The movements table supports filtering by:
- Product
- Warehouse location
- Movement type
- Date range
Transfer Orders
Transfer orders move stock between two warehouse locations with a controlled workflow.
Workflow
Draft → In Transit → Completed
│
└──→ Cancelled
- Draft - Create the order, select source and destination warehouses, add product lines with quantities. No stock is affected yet.
- Ship (Draft → In Transit) - Validates that the source warehouse has sufficient stock for every line. Deducts stock from the source location and creates
transfer_outmovements. - Complete (In Transit → Completed) - Adds stock to the destination location and creates
transfer_inmovements. Records the completion date. - Cancel - A draft order can be cancelled at any time. An in-transit order that is cancelled will reverse the source deductions.
Transfer Order Fields
| Field | Description |
|---|---|
| Number | Auto-generated TO number (e.g. TO-001) |
| Source | Warehouse the stock is leaving |
| Destination | Warehouse the stock is arriving at |
| Lines | Products and quantities to transfer |
| Notes | Optional notes for the transfer |
| Status | Draft, In Transit, Completed, or Cancelled |
Stock Takes
Stock takes reconcile your system records with a physical count at a specific warehouse.
Process
- Navigate to Inventory > Stock Takes
- Click New Stock Take and select a warehouse
- TaxMTD auto-populates all active products held at that location with their current system quantities
- Enter the counted quantity for each product during the physical count
- Any products not counted can be left blank (they are excluded from adjustments)
- Click Complete to finalise the stock take
What Happens on Completion
When a stock take is completed, TaxMTD compares the counted quantity against the system quantity for every line. For any variance:
- A stock_take movement is automatically created with the difference (positive or negative)
- The warehouse stock level is updated to match the counted quantity
- The variance is recorded in the stock take history for audit purposes
Stock Take Statuses
| Status | Description |
|---|---|
| Draft | Created but counting has not started |
| In Progress | Counting is underway, counts are being entered |
| Completed | Count finalised, adjustments applied |
| Cancelled | Abandoned without applying any adjustments |
Stock Valuation
The valuation report at Inventory > Valuation provides a real-time view of your stock value.
Grouping Options
View the report grouped by:
- Warehouse - total value held at each location
- Product - value across all locations per product
- Category - value aggregated by product category
Valuation Method
TaxMTD uses the weighted average cost method. Each product's cost price is multiplied by the quantity on hand to determine the cost value.
Summary KPIs
| Metric | Description |
|---|---|
| Total Cost Value | Sum of (quantity x cost price) across all stock |
| Total Retail Value | Sum of (quantity x unit price) across all stock |
| Total Units | Total units held across all locations |
| Average Margin | Average percentage margin across products |
Filtering
The valuation report can be filtered by warehouse location. When a location is selected, only stock at that warehouse is included in the calculations.
Low Stock Alerts
TaxMTD provides two levels of stock alerting:
Global Product Threshold
Every product has a low stock threshold field. When the total quantity across all warehouses falls to or below this threshold, the product appears in the low stock alerts panel on the inventory dashboard.
Per-Warehouse Reorder Points
Each product-warehouse combination can have its own reorder point. When stock at a specific warehouse drops to or below the reorder point, an alert is raised for that location.
Where Alerts Appear
- Inventory dashboard - a dedicated low stock alerts card showing product name, current quantity, and threshold
- KPI badge - the low stock alerts count is displayed prominently in the dashboard KPI cards
- Warehouse pages - per-location low stock counts are shown on each warehouse's detail view
API
// List products
const products = await $fetch('https://dev.taxmtd.uk/api/products?entityId=...')
// Create a product
await $fetch('https://dev.taxmtd.uk/api/products', {
method: 'POST',
body: {
name: 'Widget A',
sku: 'WID-001',
category: 'Components',
unit_price: 24.99,
cost_price: 12.50,
stock_qty: 100,
low_stock_threshold: 10,
unit: 'each',
entity_id: '...'
}
})
// Record a stock movement
await $fetch('https://dev.taxmtd.uk/api/stock-movements', {
method: 'POST',
body: {
product_id: 1,
qty: 50,
type: 'purchase',
location_id: 'warehouse-uuid',
reference: 'PO-2026-042',
notes: 'Restock from supplier'
}
})
// Create a transfer order
await $fetch('https://dev.taxmtd.uk/api/transfer-orders', {
method: 'POST',
body: {
source_location_id: 'warehouse-a-uuid',
destination_location_id: 'warehouse-b-uuid',
entity_id: '...',
lines: [
{ product_id: 'product-uuid', quantity: 20 }
]
}
})
// Get stock valuation report
const report = await $fetch('https://dev.taxmtd.uk/api/reports/stock-valuation?entityId=...')
# List products
products = requests.get(
"https://dev.taxmtd.uk/api/products",
params={"entityId": "..."},
cookies=session_cookies,
).json()["data"]
# Create a product
requests.post(
"https://dev.taxmtd.uk/api/products",
json={
"name": "Widget A",
"sku": "WID-001",
"category": "Components",
"unit_price": 24.99,
"cost_price": 12.50,
"stock_qty": 100,
"low_stock_threshold": 10,
"unit": "each",
"entity_id": "...",
},
cookies=session_cookies,
)
# Record a stock movement
requests.post(
"https://dev.taxmtd.uk/api/stock-movements",
json={
"product_id": 1,
"qty": 50,
"type": "purchase",
"location_id": "warehouse-uuid",
"reference": "PO-2026-042",
"notes": "Restock from supplier",
},
cookies=session_cookies,
)
# Create a transfer order
requests.post(
"https://dev.taxmtd.uk/api/transfer-orders",
json={
"source_location_id": "warehouse-a-uuid",
"destination_location_id": "warehouse-b-uuid",
"entity_id": "...",
"lines": [{"product_id": "product-uuid", "quantity": 20}],
},
cookies=session_cookies,
)
# Stock valuation report
valuation = requests.get(
"https://dev.taxmtd.uk/api/reports/stock-valuation",
params={"entityId": "..."},
cookies=session_cookies,
).json()["data"]
// List products
$products = Http::withCookies($session)
->get('https://dev.taxmtd.uk/api/products', ['entityId' => '...'])
->json()['data'];
// Create a product
Http::withCookies($session)
->post('https://dev.taxmtd.uk/api/products', [
'name' => 'Widget A',
'sku' => 'WID-001',
'category' => 'Components',
'unit_price' => 24.99,
'cost_price' => 12.50,
'stock_qty' => 100,
'low_stock_threshold' => 10,
'unit' => 'each',
'entity_id' => '...',
]);
// Record a stock movement
Http::withCookies($session)
->post('https://dev.taxmtd.uk/api/stock-movements', [
'product_id' => 1,
'qty' => 50,
'type' => 'purchase',
'location_id' => 'warehouse-uuid',
'reference' => 'PO-2026-042',
'notes' => 'Restock from supplier',
]);
// Create a transfer order
Http::withCookies($session)
->post('https://dev.taxmtd.uk/api/transfer-orders', [
'source_location_id' => 'warehouse-a-uuid',
'destination_location_id' => 'warehouse-b-uuid',
'entity_id' => '...',
'lines' => [['product_id' => 'product-uuid', 'quantity' => 20]],
]);
// Stock valuation report
$valuation = Http::withCookies($session)
->get('https://dev.taxmtd.uk/api/reports/stock-valuation', ['entityId' => '...'])
->json()['data'];
// List products
let products = client
.get("https://dev.taxmtd.uk/api/products?entityId=...")
.send().await?
.json::<serde_json::Value>().await?;
// Create a product
client.post("https://dev.taxmtd.uk/api/products")
.json(&serde_json::json!({
"name": "Widget A",
"sku": "WID-001",
"category": "Components",
"unit_price": 24.99,
"cost_price": 12.50,
"stock_qty": 100,
"low_stock_threshold": 10,
"unit": "each",
"entity_id": "..."
}))
.send().await?;
// Record a stock movement
client.post("https://dev.taxmtd.uk/api/stock-movements")
.json(&serde_json::json!({
"product_id": 1,
"qty": 50,
"type": "purchase",
"location_id": "warehouse-uuid",
"reference": "PO-2026-042",
"notes": "Restock from supplier"
}))
.send().await?;
// Create a transfer order
client.post("https://dev.taxmtd.uk/api/transfer-orders")
.json(&serde_json::json!({
"source_location_id": "warehouse-a-uuid",
"destination_location_id": "warehouse-b-uuid",
"entity_id": "...",
"lines": [{"product_id": "product-uuid", "quantity": 20}]
}))
.send().await?;
// Stock valuation report
let valuation = client
.get("https://dev.taxmtd.uk/api/reports/stock-valuation?entityId=...")
.send().await?
.json::<serde_json::Value>().await?;
# List products
curl "https://dev.taxmtd.uk/api/products?entityId=..."
# Record a stock movement
curl -X POST https://dev.taxmtd.uk/api/stock-movements \
-H "Content-Type: application/json" \
-d '{"product_id":1,"qty":50,"type":"purchase","location_id":"uuid","reference":"PO-2026-042"}'
# Create a transfer order
curl -X POST https://dev.taxmtd.uk/api/transfer-orders \
-H "Content-Type: application/json" \
-d '{"source_location_id":"uuid-a","destination_location_id":"uuid-b","entity_id":"...","lines":[{"product_id":"uuid","quantity":20}]}'
# Stock valuation
curl "https://dev.taxmtd.uk/api/reports/stock-valuation?entityId=..."
Importing Products
Import your product catalogue from Stripe, Shopify, WooCommerce, Xero, QuickBooks, Sage, Zoho Books, or any CSV file. SKUs, prices, and descriptions are preserved.
See Migrating from Other Platforms for details.