feat(catalog): manage global categories from settings

Categories are the taxonomy both services and resources select from, but
until now they could only be reached through the service catalog, so every
environment ended up with its own spelling of "whole body".

- Settings > Categories page: global CRUD plus the "includes" edge
- POST/GET/DELETE /api/v1/service-category/{uuid}/includes — a DAG, kept
  separate from `parent` because "hand" sits under both "whole body" and
  "upper limb"; a cycle is refused with 422
- PUT /api/v1/resource/{uuid}/categories — full replacement, and a category
  from another environment is rejected explicitly since the uuid arrives in
  the request body where TenantFilter does not reach

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
hamed
2026-08-02 12:58:26 +03:30
co-authored by Claude Opus 5
parent f8e8a63ae8
commit 03d8d7d68a
13 changed files with 865 additions and 0 deletions
+33
View File
@@ -335,6 +335,39 @@
> **اتمی است.** اعتبارسنجی کل فهرست پیش از هر حذفی انجام می‌شود، پس یک ردیف نامعتبر
> در انتهای فهرست، مهارت‌های درستِ قبلی را پاک نمی‌کند و بعد ۴۲۲ برگرداند.
### `PUT /api/v1/resource/{uuid}/categories`
مجوز: `appointment_settings.update`.
دستهٔ منبع از **کاتالوگ سراسری** انتخاب می‌شود (`CatalogCategory`) — همان دسته‌هایی که سرویس
هم از آن‌ها استفاده می‌کند. ساخت دسته اینجا ممکن نیست؛ فقط در «تنظیمات ← دسته‌بندی‌ها»
([`clinic-services.md`](clinic-services.md)).
جایگزینی **کامل**، مثل `skills`: `{"category_uuids":[]}` همه را پاک می‌کند.
```json
{ "category_uuids": ["8ae755b5-5f27-404e-9b63-1b29693a9039"] }
```
پاسخ ۲۰۰ کلِ منبع است؛ بخش `categories` آن (خروجی واقعی):
```json
{
"success": true,
"data": {
"uuid": "ce070910-7038-4f50-9f7a-1b35ec1a67f7",
"name": "اتاق ۱",
"categories": [
{ "uuid": "8ae755b5-5f27-404e-9b63-1b29693a9039", "name": "دست (doc)" }
]
}
}
```
**۴۲۲:** نبودِ `category_uuids` (`{"code":"ERR_VALIDATION_002","message":"فیلد category_uuids الزامی است","field":"category_uuids"}`)
· دستهٔ محیط دیگر. uuid از بدنهٔ درخواست می‌آید و `TenantFilter` رویش اعمال نمی‌شود، پس محیطِ
هر دسته صریحاً با محیط منبع مقایسه می‌شود.
---
## استخر منابع