# Centrex Inventory API Documentation

Read-only inventory/stock API for the mobile app, reading from the same
Oracle `CSP` schema as the HR reports. **Every query is a `SELECT` —
nothing here ever writes to that database.**

All requests require a valid Authorization Bearer token (same
`requireAuthToken()` mechanism as the rest of Centrex — call
`login_admin.php` first). All numeric IDs (`store_id`, `item_id`) are
returned as strings — they're natural keys from a legacy schema, not
auto-increment integers.

Response envelope matches the rest of Centrex: `{"error": 0, "error_msg":
"...", ...}` on success, `{"error": 1, "error_msg": "...", "error_code":
N}` on failure. A missing resource (bad `store_id`/`item_id`) returns
HTTP 404 in addition to `error_code: 404`.

Stock numbers come from one shared ledger (`src/inventory/lib/ledger.php`)
that unions posted (`STATUS = 2`) GRN, store returns, store transfers
(in/out), and store issues. There is no "debit note / return to
supplier" flow in this system.

---

## 1. Stores

**Endpoint:** `GET /inventory_stores.php`

**Params:** `store_id` (optional) — returns a single store, 404 if not found.

```json
{
  "error": 0,
  "error_msg": "Stores retrieved successfully",
  "data": [
    { "id": "2", "name": "CC STORE", "code": "02010010001", "status": "A",
      "total_in": 666873.482, "total_out": 682342.7496, "balance": -15469.2676 }
  ]
}
```

---

## 2. Items

**Endpoint:** `GET /inventory_items.php`

**List params:** `store_id`, `search` (matches name or barcode), `status`
(`in_stock` | `out_of_stock`), `per_page` (capped at 100), `page`.

**Detail mode:** pass `item_id` — also accepts `store_id`, `from_date`,
`to_date` (YYYY-MM-DD), which only scope the `movements` list; `stock.by_store`
is always all stores. 404 if the item doesn't exist.

```json
{
  "error": 0,
  "error_msg": "Items retrieved successfully",
  "data": [
    { "id": "976", "name": "CHICKEN BREAST (BONELESS)", "barcode": null,
      "type": "RAW MATERIAL", "class": "GROCERY", "uom": "Kilo Gram",
      "cost_price": 670.48, "stock": 145.887, "status": "in_stock" }
  ],
  "meta": { "current_page": 1, "per_page": 25, "total": 5017, "last_page": 201 },
  "filters": { "store_id": "", "search": "", "status": "" }
}
```

Note: there is no minimum-stock/reorder-point column in this schema —
`status` is only ever `in_stock` or `out_of_stock` (balance ≤ 0).

---

## 3. Stock Report

**Endpoint:** `GET /inventory_stock.php`

**Params:** `store_id`, `item_type` (ITEM_TYPE_ID), `class_id` (CLS_ID),
`low_stock_only` (`1`/`true`/`yes` — means balance ≤ 0, same definition
as `out_of_stock` above; there's no separate low-stock threshold in this
data), `per_page` (capped at 100), `page`.

Each row includes a `by_store` breakdown computed only for that page's
items (never a full-catalogue `IN (...)`). `summary` is one aggregate
query over the whole filtered set, not paginated.

```json
{
  "error": 0,
  "error_msg": "Stock report retrieved successfully",
  "data": [
    { "id": "976", "name": "CHICKEN BREAST (BONELESS)", "type": "RAW MATERIAL",
      "class": "GROCERY", "cost_price": 670.48, "total_in": 337.657,
      "total_out": 191.77, "balance": 145.887,
      "by_store": [ { "store_id": "1", "store_name": "BEACON PRIDE STORE", "balance": 35 } ] }
  ],
  "meta": { "current_page": 1, "per_page": 25, "total": 5017, "last_page": 201 },
  "summary": { "total_items": 5017, "zero_or_below_stock": 4543, "total_value": 8295586.29 },
  "filters": { "store_id": "", "item_type": "", "class_id": "", "low_stock_only": false }
}
```

---

## 4. Expiry Report

**Endpoint:** `GET /inventory_expiry.php`

**Params:** `store_id` (optional).

Matches GRN batches carrying an expiry date to how much of that exact
batch has since been issued (`SIN_DTL.VNO_GRN = GRN_DTL.VNO`, posted
issues only). **Currently returns an empty list** — as of writing, no
GRN line in this database has `EXPIRE_DATE` set. That's expected, not a
bug; it will start populating once receiving staff record expiry dates.

```json
{
  "error": 0,
  "error_msg": "Expiry report retrieved successfully",
  "data": [
    { "item_id": "1171", "item_name": "WASHING LIQUID", "store_id": "3",
      "store_name": "SNACK SHACK STORE", "grn_no": "3260052",
      "received_qty": 12, "issued_qty": 4, "remaining_qty": 8,
      "expiry_date": "20-MAR-26", "days_remaining": 12, "status": "expiring_soon" }
  ],
  "filters": { "store_id": "" }
}
```

---

## 5. Dashboard

**Endpoint:** `GET /inventory_dashboard.php`

**Params:** `store_id` (optional — scopes `kpis`, `value_by_category`,
`movement_last_6_months`, `negative_stock`; `kpis.total_items` and
`balance_by_store` are never store-scoped).

```json
{
  "error": 0,
  "error_msg": "Dashboard retrieved successfully",
  "data": {
    "kpis": { "total_items": 5017, "stock_value": 8295586.29, "negative_stock_items": 90 },
    "value_by_category": [ { "category": "GROCERY", "value": 5019301.91 } ],
    "movement_last_6_months": [ { "month": "2026-09", "received": 110818.25, "issued": 36968.47 } ],
    "balance_by_store": [ { "store_id": "2", "store_name": "CC STORE", "balance": -15469.2676 } ],
    "negative_stock": [ { "item_id": "976", "name": "CHICKEN BREAST (BONELESS)", "on_hand": -35 } ]
  },
  "filters": { "store_id": "" }
}
```
