Skip to main content

Overview

Inventory Locations represent physical or logical zones within an Operating Unit where stock is held. Each location maintains separate stock records and enables precise tracking of inventory movement between zones.
Example: A branch might have MAIN (warehouse), KITCHEN (prep area), BAR (beverage station), and WASTE (spoilage tracking) as separate locations.

Location Types

SushiGo supports six predefined location types, each with specific operational semantics:

MAIN - Primary Warehouse

type
enum
default:"MAIN"
Primary storage location for the operating unit
Characteristics:
  • Typically set as is_primary = true
  • Receives opening balances and purchase receipts
  • Source for transfers to other locations
  • Highest priority in allocation logic
Example: Main warehouse, central storage room

KITCHEN - Food Preparation

type
enum
default:"KITCHEN"
Prep area where ingredients are consumed for production
Characteristics:
  • Receives transfers from MAIN
  • Source for production consumption
  • May have multiple instances (Kitchen 1, Kitchen 2)
Example: Sushi prep station, hot kitchen, salad station

BAR - Beverage Station

type
enum
default:"BAR"
Dedicated location for beverage inventory
Characteristics:
  • Receives transfers from MAIN
  • Tracks cocktail ingredients, bottled drinks, etc.
  • Separate from food prep for control
Example: Main bar, service bar, cocktail station

TEMP - Temporary Location

type
enum
default:"TEMP"
Temporary holding location (events, catering)
Characteristics:
  • Used for events or temporary operations
  • Stock typically returned to MAIN after event closure
  • Time-bounded (start_date, end_date on Operating Unit)
Example: Event booth, catering truck, pop-up location

RETURN - Return/Quarantine

type
enum
default:"RETURN"
Holding area for returned or quarantined items
Characteristics:
  • Receives customer returns or damaged goods
  • Quality inspection before moving to MAIN or WASTE
  • Separate costing and reporting
Example: Returns bay, QC quarantine, damaged goods hold

WASTE - Spoilage/Loss

type
enum
default:"WASTE"
Virtual location for tracking spoilage, breakage, and loss
Characteristics:
  • Receives stock via CONSUMPTION movements
  • Does not transfer out (one-way only)
  • Used for loss analysis and variance reports
Example: Spoiled items, broken equipment, expired food

Location Fields

operating_unit_id
integer
required
Parent operating unit ID (branch or event)
name
string
required
Location name (max 255 chars)Examples: Main Warehouse, Kitchen - Prep 1, Bar Central
type
enum
required
Location type: MAIN, TEMP, KITCHEN, BAR, RETURN, WASTE
is_primary
boolean
default:"false"
Whether this is the primary location for the operating unit
Each operating unit should have exactly one primary location
is_active
boolean
default:"true"
Active status (inactive locations hidden from selection)
priority
integer
default:"0"
Display/allocation priority (higher = first)Used for sorting in UI and stock allocation logic
notes
text
Additional notes or instructions
meta
jsonb
default:"{}"
Custom metadata (capacity, address, contact, etc.)

API Endpoints

Create Location

POST /api/v1/inventory-locations
Authorization: Bearer {token}
Content-Type: application/json

{
  "operating_unit_id": 1,
  "name": "Main Warehouse",
  "type": "MAIN",
  "is_primary": true,
  "is_active": true,
  "priority": 100,
  "notes": "Central storage facility - refrigerated section available"
}
{
  "status": 201,
  "data": {
    "id": 1,
    "operating_unit_id": 1,
    "name": "Main Warehouse",
    "type": "MAIN",
    "is_primary": true,
    "is_active": true,
    "priority": 100,
    "notes": "Central storage facility - refrigerated section available",
    "meta": {},
    "created_at": "2026-03-06T10:00:00Z",
    "updated_at": "2026-03-06T10:00:00Z"
  }
}

List Locations

GET /api/v1/inventory-locations?operating_unit_id=1&type=KITCHEN&is_active=true
Authorization: Bearer {token}
operating_unit_id
integer
Filter by operating unit
type
enum
Filter by location type
is_primary
boolean
Filter primary locations
is_active
boolean
Filter active/inactive locations
{
  "status": 200,
  "data": [
    {
      "id": 1,
      "operating_unit_id": 1,
      "name": "Main Warehouse",
      "type": "MAIN",
      "is_primary": true,
      "is_active": true,
      "priority": 100,
      "operating_unit": {
        "id": 1,
        "name": "SushiGo Centro - Main Inventory",
        "type": "BRANCH_MAIN"
      }
    },
    {
      "id": 2,
      "operating_unit_id": 1,
      "name": "Kitchen - Prep 1",
      "type": "KITCHEN",
      "is_primary": false,
      "is_active": true,
      "priority": 50
    }
  ]
}

Show Location with Stock

GET /api/v1/inventory-locations/{id}
Authorization: Bearer {token}
Returns location details with current stock summary.
{
  "status": 200,
  "data": {
    "id": 1,
    "name": "Main Warehouse",
    "type": "MAIN",
    "is_primary": true,
    "stock_summary": {
      "total_variants": 45,
      "total_value": 125340.50,
      "low_stock_variants": 3
    },
    "recent_movements": [
      {
        "id": 123,
        "item_variant": "Salmon 1kg",
        "qty": 10.0,
        "reason": "TRANSFER",
        "posted_at": "2026-03-06T09:45:00Z"
      }
    ]
  }
}

Update Location

PUT /api/v1/inventory-locations/{id}
Authorization: Bearer {token}
Content-Type: application/json

{
  "name": "Main Warehouse - Expanded",
  "priority": 150,
  "notes": "Expanded capacity with new freezer units"
}
Cannot change type or operating_unit_id after creation

Delete Location

DELETE /api/v1/inventory-locations/{id}
Authorization: Bearer {token}
Soft deletes the location (requires zero stock and no pending movements).

Real-World Examples

Example 1: Single Branch Setup

Operating Unit: SushiGo Centro (BRANCH_MAIN) Locations:
[
  {
    "name": "Main Warehouse",
    "type": "MAIN",
    "is_primary": true,
    "priority": 100
  },
  {
    "name": "Kitchen - Sushi Bar",
    "type": "KITCHEN",
    "priority": 50
  },
  {
    "name": "Kitchen - Hot Kitchen",
    "type": "KITCHEN",
    "priority": 50
  },
  {
    "name": "Bar",
    "type": "BAR",
    "priority": 40
  },
  {
    "name": "Waste & Spoilage",
    "type": "WASTE",
    "priority": 0
  }
]
Daily Flow:
  1. Opening balances registered at MAIN
  2. Morning prep: transfer ingredients MAIN → KITCHEN
  3. Service: consumption from KITCHEN (reason: CONSUMPTION)
  4. Spoilage: transfer KITCHEN → WASTE (reason: CONSUMPTION)

Example 2: Event Setup

Operating Unit: Festival Nikkei 2026 (EVENT_TEMP) Locations:
[
  {
    "name": "Event Main Storage",
    "type": "MAIN",
    "is_primary": true,
    "priority": 100,
    "notes": "Refrigerated truck onsite"
  },
  {
    "name": "Prep Tent A",
    "type": "TEMP",
    "priority": 50
  },
  {
    "name": "Sales Booth 1",
    "type": "TEMP",
    "priority": 50
  },
  {
    "name": "Sales Booth 2",
    "type": "TEMP",
    "priority": 50
  }
]
Event Flow:
  1. Pre-event: transfer from branch MAIN → event MAIN
  2. During event: transfers event MAIN → prep tents → booths
  3. Post-event: count remaining stock, transfer back to branch MAIN
  4. Event closure: generate profitability report

Example 3: Multi-Kitchen Branch

Operating Unit: SushiGo Polanco (Large Format) Locations:
[
  {
    "name": "Central Warehouse",
    "type": "MAIN",
    "is_primary": true,
    "priority": 100
  },
  {
    "name": "Sushi Bar - Station 1",
    "type": "KITCHEN",
    "priority": 60,
    "meta": { "capacity": "2 chefs", "station_type": "nigiri" }
  },
  {
    "name": "Sushi Bar - Station 2",
    "type": "KITCHEN",
    "priority": 60,
    "meta": { "capacity": "2 chefs", "station_type": "rolls" }
  },
  {
    "name": "Hot Kitchen",
    "type": "KITCHEN",
    "priority": 55,
    "meta": { "station_type": "hot_dishes" }
  },
  {
    "name": "Main Bar",
    "type": "BAR",
    "priority": 50
  },
  {
    "name": "Service Bar",
    "type": "BAR",
    "priority": 45
  },
  {
    "name": "Returns & QC",
    "type": "RETURN",
    "priority": 10
  },
  {
    "name": "Waste Tracking",
    "type": "WASTE",
    "priority": 0
  }
]

Location Workflows

Workflow 1: Initial Location Setup

1

Create Primary Location

POST /api/v1/inventory-locations with type=MAIN, is_primary=true
2

Create Operational Locations

Add KITCHEN, BAR locations with lower priority
3

Create Support Locations

Add RETURN, WASTE as needed
4

Set Priorities

Assign priorities for display order (MAIN=100, KITCHEN=50, WASTE=0)

Workflow 2: Stock Allocation Flow

1

Receive at Primary

Register opening balance or receipt at MAIN location
2

Transfer to Working Locations

Move stock MAIN → KITCHEN/BAR for daily operations
3

Consume from Working Locations

Record consumption (SALE or CONSUMPTION) from KITCHEN/BAR
4

Track Waste

Move spoiled items to WASTE location for reporting

Workflow 3: End-of-Day Count

1

Count Each Location

Physical count at each active location
2

Record Variances

Register adjustments (reason: COUNT_VARIANCE)
3

Consolidate Stock (Optional)

Transfer unused stock from KITCHEN/BAR back to MAIN
4

Generate Reports

Variance report, consumption analysis, loss summary

Business Rules

One Primary per Unit

Each operating unit must have exactly one primary location

Type Immutability

Location type cannot be changed after creation

Zero Stock Deletion

Can only delete locations with zero stock

WASTE One-Way

WASTE locations only receive (never transfer out)

Priority Guidelines

TypeSuggested PriorityReasoning
MAIN100Default source for allocations
KITCHEN40-60Active working locations
BAR40-50Active working locations
TEMP30-50Event-specific, temporary
RETURN10-20Inspection/quarantine area
WASTE0Tracking only, no allocations

Database Schema

inventory_locations Table

ColumnTypeConstraints
idbigintPK, auto-increment
operating_unit_idbigintFK → operating_units.id, NOT NULL
namevarchar(255)NOT NULL
typeenumIN (‘MAIN’, ‘TEMP’, ‘KITCHEN’, ‘BAR’, ‘RETURN’, ‘WASTE’)
is_primarybooleanDEFAULT false
is_activebooleanDEFAULT true
priorityintegerDEFAULT 0
notestextNULLABLE
metajsonbDEFAULT ''
created_attimestamp
updated_attimestamp
deleted_attimestampNULLABLE
Indexes:
  • idx_inventory_locations_operating_unit on (operating_unit_id)
  • idx_inventory_locations_type on (type)
  • idx_inventory_locations_priority on (priority DESC, name ASC)
Constraints:
  • Unique: (operating_unit_id, name)
  • Check: priority >= 0

Integration Points

With Stock

Each location has many Stock records (one per item variant):
SELECT 
  il.name AS location,
  iv.name AS variant,
  s.on_hand,
  s.reserved,
  s.available
FROM inventory_locations il
JOIN stock s ON s.inventory_location_id = il.id
JOIN item_variants iv ON iv.id = s.item_variant_id
WHERE il.id = 1 AND s.available > 0
ORDER BY iv.name;

With Stock Movements

Locations are source and target for movements:
-- Movements FROM this location (outbound)
SELECT * FROM stock_movements WHERE from_location_id = 1;

-- Movements TO this location (inbound)
SELECT * FROM stock_movements WHERE to_location_id = 1;

-- All movements (in or out)
SELECT * FROM stock_movements 
WHERE from_location_id = 1 OR to_location_id = 1
ORDER BY posted_at DESC;

Common Queries

Get Stock Summary by Location

SELECT 
  il.id,
  il.name,
  il.type,
  COUNT(DISTINCT s.item_variant_id) AS total_variants,
  SUM(s.on_hand * s.weighted_avg_cost) AS total_value,
  COUNT(DISTINCT s.id) FILTER (WHERE s.on_hand <= iv.min_stock) AS low_stock_count
FROM inventory_locations il
LEFT JOIN stock s ON s.inventory_location_id = il.id
LEFT JOIN item_variants iv ON iv.id = s.item_variant_id
WHERE il.operating_unit_id = 1
GROUP BY il.id, il.name, il.type
ORDER BY il.priority DESC, il.name;

Find Primary Location for Operating Unit

SELECT * FROM inventory_locations
WHERE operating_unit_id = 1 AND is_primary = true
LIMIT 1;

Get Active Locations by Type

SELECT * FROM inventory_locations
WHERE operating_unit_id = 1 
  AND type = 'KITCHEN'
  AND is_active = true
ORDER BY priority DESC, name;

Validation Rules

Location Creation

  • operating_unit_id (must exist)
  • name (max 255)
  • type (valid enum)

Business Validations

  • Cannot set is_primary=true if operating unit already has a primary location
  • Cannot delete location with on_hand > 0 or reserved > 0
  • Cannot change type or operating_unit_id after creation
  • Cannot deactivate location with active stock movements

Best Practices

Name locations clearly: “Kitchen - Sushi Bar” instead of “K1”
Use priority to control allocation order: MAIN (100) > KITCHEN (50) > WASTE (0)
Always create a WASTE location to measure loss and spoilage
Create a RETURN location for items that need inspection before re-stocking
Transfer unused stock back to MAIN before physical counts for accuracy

Next Steps

Stock Movements

Record transfers and consumption between locations

Items & Variants

Define items to track in these locations

Build docs developers (and LLMs) love