- Added a new 'category' field to the InventoryItem entity and updated the database schema. - Replaced free-text input for 'unit' and 'category' with select dropdowns in the AddItemModal. - Introduced a new API endpoint to fetch metadata for units and categories. - Updated inventory filtering logic to use the new 'category' field instead of 'consumable'. - Enhanced validation for item creation and updates to ensure valid unit and category values. - Updated tests to cover new functionality and ensure proper validation.
5.0 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 <= 0 → out_of_stock; stock <= alertThreshold → low_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": "جراحی",
"category": "لوازم مصرفی و تزریقات",
"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 category values actually in use by the tenant — powers the
filter dropdown. For the full list of allowed categories use inventory-meta.
Response 200
{ "success": true, "data": ["دارو", "لوازم آزمایشگاهی"] }
GET /api/v1/inventory-meta
Backend-owned option lists for the item form. Single source of truth — the admin never hardcodes units/categories. Values are plain Persian strings stored as-is; the create/update endpoints validate against these lists.
Response 200
{
"success": true,
"data": {
"units": ["عدد", "بسته", "…", "سیسی", "میلیلیتر", "…"],
"categories": ["دارو", "لوازم مصرفی و تزریقات", "…", "سایر"]
}
}
POST /api/v1/inventory-item
Create an item.
Body
| Field | Type | Required | Notes |
|---|---|---|---|
name |
string | ✅ | نام کالا |
consumable |
string | ❌ | «مصرفی» — یادداشت آزاد (سازگاری عقبرو) |
category |
string | ❌ | دستهبندی؛ باید یکی از inventory-meta.categories باشد. خالی → null. فرم ادمین آن را الزامی میکند |
unit |
string | ❌ | باید یکی از inventory-meta.units باشد. خالی → پیشفرض عدد |
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 خالی است (field name) |
422 |
ERR_VALIDATION_001 |
unit خارج از لیست مجاز (field unit) |
422 |
ERR_VALIDATION_001 |
category خارج از لیست مجاز (field category) |
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.