Inventory & Warehouses

Multi-warehouse stock tracking with transfer orders, stock takes, valuation reports, and low stock alerts.

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:

FieldDescription
NameProduct name
SKUAuto-generated unique stock-keeping unit code
BarcodeOptional barcode (EAN, UPC, etc.)
CategoryProduct grouping for reports and filtering
Unit PriceSelling price per unit (£)
Cost PricePurchase/cost price per unit (£)
VAT RateVAT percentage applied to the product
UnitUnit of measure (e.g. each, kg, metre)
Low Stock ThresholdGlobal alert threshold for this product
NotesOptional internal notes

Creating a Product

  1. Navigate to Inventory > Products
  2. Click Add Product
  3. Fill in the product details - only Name is required
  4. Set an initial stock quantity if applicable
  5. Save the product
SKU codes are auto-generated when you create a product. You can override the generated SKU if you have your own numbering scheme.

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.

FieldDescription
NameLocation name (e.g. "Main Warehouse", "London Shop")
AddressOptional address
StatusActive 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

TypeDescription
PurchaseStock received from a supplier
SaleStock sold to a customer
AdjustmentManual correction (positive or negative)
ReturnStock returned by a customer or to a supplier
Transfer InStock arriving from another warehouse
Transfer OutStock leaving to another warehouse
Stock TakeAdjustment generated by a stock take variance

Recording a Movement

  1. Navigate to Inventory > Movements
  2. Click Add Movement
  3. Select the product, warehouse location, and type
  4. Enter the quantity (negative for reductions)
  5. Optionally add a reference and notes
  6. Save - the stock level at that warehouse updates automatically
All movements are immutable - they cannot be edited or deleted after creation. To correct an error, create a new adjustment movement. This preserves the full audit trail.

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
  1. Draft - Create the order, select source and destination warehouses, add product lines with quantities. No stock is affected yet.
  2. Ship (Draft → In Transit) - Validates that the source warehouse has sufficient stock for every line. Deducts stock from the source location and creates transfer_out movements.
  3. Complete (In Transit → Completed) - Adds stock to the destination location and creates transfer_in movements. Records the completion date.
  4. 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

FieldDescription
NumberAuto-generated TO number (e.g. TO-001)
SourceWarehouse the stock is leaving
DestinationWarehouse the stock is arriving at
LinesProducts and quantities to transfer
NotesOptional notes for the transfer
StatusDraft, In Transit, Completed, or Cancelled
Shipping a transfer order will fail if any line quantity exceeds available stock at the source warehouse. Reduce the quantity or add stock before retrying.

Stock Takes

Stock takes reconcile your system records with a physical count at a specific warehouse.

Process

  1. Navigate to Inventory > Stock Takes
  2. Click New Stock Take and select a warehouse
  3. TaxMTD auto-populates all active products held at that location with their current system quantities
  4. Enter the counted quantity for each product during the physical count
  5. Any products not counted can be left blank (they are excluded from adjustments)
  6. 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

StatusDescription
DraftCreated but counting has not started
In ProgressCounting is underway, counts are being entered
CompletedCount finalised, adjustments applied
CancelledAbandoned 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

MetricDescription
Total Cost ValueSum of (quantity x cost price) across all stock
Total Retail ValueSum of (quantity x unit price) across all stock
Total UnitsTotal units held across all locations
Average MarginAverage 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

JavaScript
// List products
const products = await $fetch('https://taxmtd.uk/api/products?entityId=...')

// Create a product
await $fetch('https://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://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://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://taxmtd.uk/api/reports/stock-valuation?entityId=...')
Python
# List products
products = requests.get(
    "https://taxmtd.uk/api/products",
    params={"entityId": "..."},
    cookies=session_cookies,
).json()["data"]

# Create a product
requests.post(
    "https://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://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://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://taxmtd.uk/api/reports/stock-valuation",
    params={"entityId": "..."},
    cookies=session_cookies,
).json()["data"]
PHP
// List products
$products = Http::withCookies($session)
    ->get('https://taxmtd.uk/api/products', ['entityId' => '...'])
    ->json()['data'];

// Create a product
Http::withCookies($session)
    ->post('https://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://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://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://taxmtd.uk/api/reports/stock-valuation', ['entityId' => '...'])
    ->json()['data'];
Rust
// List products
let products = client
    .get("https://taxmtd.uk/api/products?entityId=...")
    .send().await?
    .json::<serde_json::Value>().await?;

// Create a product
client.post("https://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://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://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://taxmtd.uk/api/reports/stock-valuation?entityId=...")
    .send().await?
    .json::<serde_json::Value>().await?;
cURL
# List products
curl "https://taxmtd.uk/api/products?entityId=..."

# Record a stock movement
curl -X POST https://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://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://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.