API (api-v2)
Inventory & locations
Stock is held per variant per location, and every change is an immutable adjustment with a reason. Locations carry shelves, tills and drawers. Places proxies address lookup, and Jarde applies spreadsheet stock counts.
Inventory
Tables:
inventory_item: cost,tracked,min_stock(reorder point)inventory_level: per location, with a free-textshelfinventory_adjustment: the immutable movement ledger
| Route | Guard |
|---|---|
/merchant/inventory/v1/list, adjust, movements, transfer, transfer/bulk | perm inventory.write (also applied to reads, because there's no inventory.read code) |
adjusttakes the absolute on-hand quantity and records the delta.transferwrites atransfer_out/transfer_inpair sharing atransferGroupId.transfer/bulk(bulkTransferStock, PR #84) moves many lines in one transaction: every line moves or none does. It returns a result per line.inventory-screenis an orchestrator that composes the list view across modules.
Adjustment reasons
order_placed, pos_sale, refund_restock, return_restock, order_cancelled, delivery_failed, manual, stock_count, transfer_out, transfer_in
Gaps
- Staff location scope isn't enforced on writes here (documented in the router).
Locations & shelves
Tables: location, location_shelf
| Route | Guard |
|---|---|
/merchant/locations/v1/get-shop-locations | perm products.view |
get-locations | oScoped |
create | wide locations.write |
update, delete | at locations.write |
get-shelves | at products.view |
add-shelf, remove-shelf | app shelves + at locations.write |
- Coordinates are validated. There's no plan cap on locations.
- Without the
shelvesapp, the dashboard lists shelf labels found oninventory_level.shelf. Declared shelves are still marked In progress (.scratch/location-shelves/spec.md). - A till is a (shop, location) pair. There's no device or terminal entity.
Places
| Route | Guard |
|---|---|
/merchant/places/v1/autocomplete, get-place, geocode, reverse-geocode | authenticated |
- A proxy over Google Places (New), Geocoding and Time Zone, keyed by
GOOGLE_MAPS_API_KEY. - Every lookup fails soft, returning empty results instead of errors.
- Results are biased toward Beirut (
PLACES_BIAS_CENTER,PLACES_BIAS_RADIUS_M). - The dashboard renders maps with MapLibre and keyless OpenFreeMap tiles (dashboard ADR 0003).
Jarde (stock count app)
| Route | Guard |
|---|---|
/merchant/jarde/v1/plan, apply | app jarde + inventory.write |
- Input: rows of barcode, quantity, and optionally branch name. The dashboard parses CSV or XLSX, and accepts English or Arabic headers (barcode, ISBN or EAN).
planmatches rows and returns a preview: counts, net change, shortages, and per-row problems. The pure planner isjarde.plan.ts.applywritesstock_countadjustments. Counted quantities replace on-hand stock. Rows absent from the sheet are untouched.- Rows at branches the caller can't write to are refused one row at a time.