- Added SecretaryAccessChecker to manage resource access for secretaries. - Integrated permission checks for payments, inventory, and tags in relevant controllers. - Updated PaymentController and PaymentMethodController to enforce secretary permissions. - Enhanced TenantTagController to check permissions for tag management actions. - Introduced tests for secretary resource enforcement, ensuring proper access control. - Updated DoctorSecretary entity to include inventory and tags permissions. - Created a comprehensive audit document for secretary permissions coverage and enforcement. - Fixed potential crashes in SecretaryDashboard when rendering without doctor data.
160 lines
5.4 KiB
Markdown
160 lines
5.4 KiB
Markdown
# Inventory API
|
|
|
|
> **Prefix:** `/api/v1/inventory-*`
|
|
|
|
> **دسترسی منشی:** برای `ROLE_SECRETARY` روی منبع `inventory` اعمال میشود (`SecretaryAccessChecker`): GET→`view`, POST→`create`, PATCH→`update`, DELETE→`delete`؛ نبودِ مجوز → `403`. پیشفرضِ منشی برای این منبع همه `false` است. جزئیات: [secretary.md](secretary.md).
|
|
|
|
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`
|
|
```json
|
|
{
|
|
"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`
|
|
```json
|
|
{ "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`
|
|
```json
|
|
{
|
|
"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`
|
|
```json
|
|
{ "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`
|
|
```json
|
|
{
|
|
"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.
|