Files
clinicpro/docs/api/inventory.md
T
hamedandClaude Opus 4.8 519bc8d7f6 feat: port inventory (انبارداری) page from tauri to admin dashboard
Add a per-tenant (doctor/clinic) Inventory domain and admin page, ported
from clinic-pro-tauri /inventory (which was static/mock) into a real feature.

Backend (src/Inventory/):
- Entities InventoryItem, InventoryPackage, InventoryPackageItem, scoped via
  entity_type/entity_id like TenantTag. Item status is derived, package total
  and availability derived at read time.
- InventoryService (stats, package assembly, availability), thin
  InventoryController with CRUD for items and packages + categories endpoint.
- Migration + docs/api/inventory.md + functional tests (10 tests, 42 assertions).

Frontend (assets/admin/):
- InventoryPage with two tabs (کالاهای مصرفی / پکیج), stat cards, items table
  (desktop + mobile cards), packages accordion, add/edit item and package
  modals, search + category filter — pixel-matched to the tauri source.
- useInventory hook (TanStack Query), route + sidebar link for doctor/clinic.
- Vitest coverage (real data, empty state, modal, packages tab).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 13:48:29 +03:30

3.9 KiB

Inventory API

Prefix: /api/v1/inventory-*

Per-tenant (doctor/clinic) consumable-stock management: items and packages (bundles of items). Every row is scoped to the caller's resolved entity (doctor / clinic), exactly like Tenant Tags — a tenant only ever sees and mutates its own inventory. Prices are stored and returned in Rial (integer).

Permission (all routes): IS_AUTHENTICATED_FULLY (roles doctor, clinic; secretary resolves to its active clinic/doctor context).

Item status is derived, never stored: stock <= 0out_of_stock; stock <= alertThresholdlow_stock; else in_stock.


Items

GET /api/v1/inventory-items

List the tenant's items plus the four derived stat counters.

Response 200

{
  "success": true,
  "data": {
    "items": [
      {
        "uuid": "…",
        "name": "دستکش جراحی",
        "consumable": "جراحی",
        "unit": "عدد",
        "price": 250000,
        "stock": 150,
        "alertThreshold": 20,
        "status": "in_stock"
      }
    ],
    "stats": { "total": 1, "low": 0, "inStock": 1, "outOfStock": 0 }
  }
}

GET /api/v1/inventory-categories

Distinct non-empty consumable values for the tenant — powers the filter dropdown.

Response 200

{ "success": true, "data": ["جراحی", "دندانپزشکی"] }

POST /api/v1/inventory-item

Create an item.

Body

Field Type Required Notes
name string نام کالا
consumable string «مصرفی» / گروه فیلتر
unit string Default عدد
price integer Rial, Default 0
stock integer Default 0
alertThreshold integer Default 0

Response 201

{ "success": true, "data": { "uuid": "…", "name": "دستکش جراحی", "status": "in_stock", "...": "..." } }

Errors

Status Code Cause
422 ERR_VALIDATION_001 name خالی است
403 ERR_FORBIDDEN_001 پروفایل tenant یافت نشد

PATCH /api/v1/inventory-item/{uuid}

Partial update. Any of the create fields may be sent. Returns 200 with the item, 404 ERR_NOT_FOUND_001 if the item does not belong to the caller, 422 on empty name.

DELETE /api/v1/inventory-item/{uuid}

Delete an item. 200 with { "message": "کالا حذف شد" }, or 404 if not owned. Deleting an item also removes it from any package lines (FK ON DELETE CASCADE).


Packages

A package's total (Rial) and available (boolean) are derived at read time from its component items — available is true only if every component item has stock >= amount.

GET /api/v1/inventory-packages

Response 200

{
  "success": true,
  "data": [
    {
      "uuid": "…",
      "title": "پکیج شماره یک",
      "items": [
        { "itemUuid": "…", "name": "ژل", "unit": "سی‌سی", "price": 1200000, "amount": 2 }
      ],
      "total": 2400000,
      "available": true
    }
  ]
}

POST /api/v1/inventory-package

Body

Field Type Required Notes
title string نام پکیج
items array [{ "itemUuid": "…", "amount": 2 }] — references not owned by the caller are silently skipped

Response 201 — the serialized package (same shape as list). Errors: 422 ERR_VALIDATION_001 (empty title), 403 ERR_FORBIDDEN_001.

PATCH /api/v1/inventory-package/{uuid}

Partial update. title renames; sending items replaces all component lines. 200 with the package, 404 ERR_NOT_FOUND_001 if not owned, 422 on empty title.

DELETE /api/v1/inventory-package/{uuid}

200 with { "message": "پکیج حذف شد" }, or 404 if not owned.