feat(treatment): run a session from the staff panel, area by area
The operator opens the session, treats each body area on its own device and records what that device was set to. Readings are validated against the resource type's field schema, so a laser form and an RF form each enforce their own rules without this code naming either. Finishing is allowed with areas still open — the operator is standing in front of a patient and must not be trapped by the software — but the count comes back so the panel can warn. Session state mirrors onto the appointment (salon, then completed) while its slot times are never rewritten: those are the reservation's promise and the input to occupancy, whereas how long it actually took belongs to the session. Overwriting them would destroy the comparison between the two. Who performed it is recorded on the session rather than inferred from the appointment's planned staff: when a colleague covers a sick operator, the medical record must say who actually held the device. Endpoints live under /api/v1/dashboard/staff because StaffRouteGuardSubscriber closes everything else to staff-only users. Opening a second door through its allowlist would put the access boundary in two places. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+134
-1
@@ -1,6 +1,7 @@
|
||||
# Treatment API
|
||||
|
||||
> **Prefix:** `/api/v1/service-item/{uuid}/treatment-protocol`
|
||||
> **Prefixes:** `/api/v1/service-item/{uuid}/treatment-protocol` · `/api/v1/treatment-case[s]` ·
|
||||
> `/api/v1/treatment-session[s]` · `/api/v1/dashboard/staff/…`
|
||||
|
||||
A **treatment protocol** is the "طول درمان" of a service: it says a course of that service runs over
|
||||
several sessions, when each one falls due, which doctor supervises it, and which staff may perform
|
||||
@@ -184,3 +185,135 @@ single-session again. Idempotent: deleting a service that has no protocol still
|
||||
```json
|
||||
{ "success": true, "data": null }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Treatment cases
|
||||
|
||||
## GET `/api/v1/treatment-cases`
|
||||
|
||||
پروندههای درمانِ محیط جاری، تازهترین اول.
|
||||
|
||||
**Permission:** `IS_AUTHENTICATED_FULLY`, محدود به محیط جاری.
|
||||
|
||||
| Query | توضیح |
|
||||
|---|---|
|
||||
| `status` | `active` \| `completed` \| `abandoned` — نبودش یعنی همه |
|
||||
|
||||
```json
|
||||
{
|
||||
"uuid": "…",
|
||||
"status": "active",
|
||||
"total_sessions": 4,
|
||||
"completed_sessions": 1,
|
||||
"opened_at": 1785660000,
|
||||
"closed_at": null,
|
||||
"service": { "uuid": "…", "name": "لیزر توتال" },
|
||||
"supervisor": { "uuid": "…", "name": "دکتر ناظر" },
|
||||
"areas": [ { "uuid": "…", "name": "بیکینی" } ]
|
||||
}
|
||||
```
|
||||
|
||||
## GET `/api/v1/treatment-case/{uuid}`
|
||||
|
||||
همان شکل، بهعلاوهٔ `sessions`. پروندهٔ محیط دیگر `404` میگیرد.
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/treatment-sessions/unbooked`
|
||||
|
||||
صفِ «جلسات بدون نوبت» — جلسهای که سررسیدش رسیده و کسی رزروش نکرده.
|
||||
|
||||
رزرو جلسهٔ بعد عمداً خودکار نیست: سیستم نمیداند بیمار پنجشنبهها سر کار است و «اولین
|
||||
وقت آزاد» معمولاً بدترین وقت است چون کسی نخواستهاش. پس کار در صفی دیده میشود که منشی
|
||||
از رویش عمل میکند، نه رفتاری که بیصدا اتفاق بیفتد.
|
||||
|
||||
| Query | پیشفرض | توضیح |
|
||||
|---|---|---|
|
||||
| `within_days` | `7` | تا چند روز آینده؛ سقف ۹۰ |
|
||||
|
||||
جلسهای که از قبل نوبت دارد در این فهرست نمیآید.
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/treatment-session/{uuid}/slot-suggestions`
|
||||
|
||||
اسلاتهای آزادِ منبع، از سررسید جلسه به بعد. **پیشنهاد است، نه رزرو**؛ ثبت نوبت از مسیر
|
||||
عادی انجام میشود.
|
||||
|
||||
| Query | پیشفرض | توضیح |
|
||||
|---|---|---|
|
||||
| `resource_uuid` | منبعِ جلسهٔ قبلی | ادامهٔ دوره روی همان دستگاه، هم یکنواختتر است هم یک انتخاب کمتر |
|
||||
| `days` | `14` | افق جستوجو؛ سقف ۶۰ |
|
||||
|
||||
سررسیدِ گذشته یعنی بیمار دیر کرده، پس جستوجو از امروز شروع میشود نه از تاریخی که رد شده.
|
||||
مدتِ نوبت روی همان منبع حل میشود، نه از پیشفرض خام سرویس.
|
||||
|
||||
**۴۲۲** وقتی نه `resource_uuid` آمده و نه جلسهای از قبل رزرو شده — فهرست خالی برنمیگردد،
|
||||
چون «منبعی مشخص نیست» با «وقتی نیست» یکی نیست. **۴۰۴** برای منبع محیط دیگر.
|
||||
|
||||
---
|
||||
|
||||
# Staff panel — running a session
|
||||
|
||||
همهٔ این مسیرها زیر `/api/v1/dashboard/staff` هستند چون `StaffRouteGuardSubscriber` کاربرِ
|
||||
فقط-پرسنل را بیرون از همان پیشوند میبندد؛ باز کردن راهِ تازه با allowlist یعنی مرزِ
|
||||
دسترسی در دو جا تعریف شود.
|
||||
|
||||
**Permission:** `ROLE_STAFF` **بهعلاوهٔ** ردیف فعالِ پرسنل در محیط جاری. نقش بهتنهایی کافی
|
||||
نیست: توکن تا انقضا معتبر میماند و غیرفعالشدنِ پرسنل باید همان لحظه دسترسی را ببندد.
|
||||
|
||||
| Method | Path | کار |
|
||||
|---|---|---|
|
||||
| GET | `/dashboard/staff/treatment-sessions` | جلسات امروزِ همین پرسنل |
|
||||
| GET | `/dashboard/staff/treatment-session/{uuid}` | جزئیات جلسه + نواحی + `forms` |
|
||||
| POST | `/dashboard/staff/treatment-session/{uuid}/start` | شروع جلسه |
|
||||
| POST | `/dashboard/staff/treatment-session/{uuid}/finish` | اتمام جلسه |
|
||||
| POST | `/dashboard/staff/session-area/{uuid}/start` | شروع یک ناحیه |
|
||||
| POST | `/dashboard/staff/session-area/{uuid}/complete` | ثبت خواندههای دستگاه |
|
||||
| POST | `/dashboard/staff/session-area/{uuid}/skip` | صرفنظر از ناحیه |
|
||||
|
||||
### شروع جلسه
|
||||
|
||||
رکوردِ هر ناحیهٔ پرونده یک بار ساخته میشود، پس فراخوانی دوباره ناحیهٔ تکراری نمیسازد.
|
||||
پرسنلِ فراخوان بهعنوان **انجامدهندهٔ واقعی** ثبت میشود — ممکن است با پرسنلِ
|
||||
برنامهریزیشدهٔ نوبت فرق کند، و سابقهٔ پزشکی باید بگوید چه کسی واقعاً دستگاه را دست گرفت.
|
||||
|
||||
وضعیت نوبت به `salon` میرود. **زمان نوبت هرگز بازنویسی نمیشود**: `slot_start`/`slot_end`
|
||||
تعهدِ رزرو و ورودیِ محاسبهٔ اشغالاند، و «چقدر طول کشید» در `started_at`/`finished_at` جلسه
|
||||
مینشیند. بازنویسی گذشته یعنی مقایسهٔ پیشبینی با واقعیت برای همیشه از بین میرود.
|
||||
|
||||
### ثبت یک ناحیه
|
||||
|
||||
```json
|
||||
{
|
||||
"resource_uuid": "…",
|
||||
"parameters": { "energy": 18, "pulse": 3, "shots": 212 },
|
||||
"note": "بدون عارضه"
|
||||
}
|
||||
```
|
||||
|
||||
`parameters` با `field_schema`ِ **نوع همان منبع** سنجیده میشود — قواعدش در
|
||||
[resource.md](./resource.md#فرم-ثبت-درمان). کلید ناشناخته، مقدار خارج از گزینهها و فیلد
|
||||
الزامیِ نیامده هر سه `422` میگیرند.
|
||||
|
||||
ناحیهای که یک بار بسته شده (`completed` یا `skipped`) دوباره بسته نمیشود ⇒ `422`.
|
||||
|
||||
### اتمام جلسه
|
||||
|
||||
```json
|
||||
{ "note": "یادداشت کلی جلسه" }
|
||||
```
|
||||
|
||||
**ناحیهٔ ناتمام مانع نیست** — اپراتور جلوی بیمار ایستاده و نباید در نرمافزار گیر کند — ولی
|
||||
تعدادشان در پاسخ میآید تا پنل هشدار بدهد:
|
||||
|
||||
```json
|
||||
{ "unsettled_areas": 2 }
|
||||
```
|
||||
|
||||
وضعیت نوبت `completed` میشود. اگر گذار مجاز نباشد (منشی وضعیت را دستی عوض کرده) جلسه
|
||||
بسته میشود و نوبت دستنخورده میماند؛ ماجرا لاگ میشود، خطای ۵۰۰ داده نمیشود.
|
||||
|
||||
سپس `TreatmentWorkflow` حوزهٔ فعالیت سررسید جلسهٔ بعد را از **تاریخ واقعیِ همین جلسه**
|
||||
حساب میکند، و اگر جلسهٔ دیگری نمانده باشد دوره بسته میشود.
|
||||
|
||||
Reference in New Issue
Block a user