feat(treatment): endpoint for a course's calendar and its recorded work
GET /api/v1/treatment-case/{uuid}/plan returns every session with a date and an
is_estimate flag, plus each session's area records — the device readings a staff
member actually logged. Until now nothing exposed either: due_at existed only
for the next session, and TreatmentCase::toArray() serialised sessions without
their areas, so 'what was done' was unreachable outside the staff panel.
Kept separate from GET /treatment-case/{uuid}; that response feeds the edit
modal, which needs neither the calendar nor the areas.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -282,6 +282,62 @@ single-session again. Idempotent: deleting a service that has no protocol still
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## GET `/api/v1/treatment-case/{uuid}/plan`
|
||||||
|
|
||||||
|
تقویمِ **کل** دوره بهعلاوهٔ آنچه در هر جلسه انجام شده. مصرفکنندهاش تب «نوبتهای
|
||||||
|
بعدی» در پروندهٔ بیمار است.
|
||||||
|
|
||||||
|
جدا از `GET /treatment-case/{uuid}` است نه اضافه به آن: آن پاسخ مصرفکنندهٔ دیگری
|
||||||
|
دارد (مودال ویرایش) که نه تقویم لازم دارد نه نواحی.
|
||||||
|
|
||||||
|
| فیلد هر جلسه | توضیح |
|
||||||
|
|---|---|
|
||||||
|
| `planned_at` | زمان جلسه — قطعی یا تخمینی |
|
||||||
|
| `is_estimate` | `false` یعنی به واقعیتی گره خورده، `true` یعنی محاسبهٔ لحظهٔ نمایش |
|
||||||
|
| `areas[]` | نواحی با `parameters` (خواندههای دستگاه)، `resource`، `note`، زمانها |
|
||||||
|
|
||||||
|
**`planned_at` ذخیره نمیشود.** `TreatmentScheduler` فقط سررسید جلسهٔ بعدی را
|
||||||
|
مینویسد؛ بقیهٔ زنجیره را `TreatmentPlanProjector` در لحظهٔ خواندن میسازد. لنگرِ هر
|
||||||
|
جلسه بهترتیب: زمان اتمام، زمان نوبت، `due_at` نوشتهشده. دورهای که هیچکدام را
|
||||||
|
ندارد از `opened_at` شروع میشود.
|
||||||
|
|
||||||
|
یعنی تاریخهای تخمینی ممکن است بین دو بار خواندن عوض شوند — این عمدی است و در UI با
|
||||||
|
برچسب «تخمینی» اعلام میشود.
|
||||||
|
|
||||||
|
### Response `200` (خروجی واقعی)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"case": { "uuid": "2a8b1e28-…", "status": "active", "patient": { "name": "محمد رستمی" } },
|
||||||
|
"sessions": [
|
||||||
|
{
|
||||||
|
"session_number": 1,
|
||||||
|
"status": "done",
|
||||||
|
"planned_at": 1786106340,
|
||||||
|
"is_estimate": false,
|
||||||
|
"areas": [
|
||||||
|
{ "area": { "name": "دست" }, "status": "completed",
|
||||||
|
"parameters": { "energy": 8, "pulse": 3, "shots": 23 } }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ "session_number": 2, "status": "planned", "planned_at": 1787402340, "is_estimate": false, "areas": [] },
|
||||||
|
{ "session_number": 3, "status": "planned", "planned_at": 1789994340, "is_estimate": true, "areas": [] }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Errors:**
|
||||||
|
|
||||||
|
| Code | HTTP | شرط |
|
||||||
|
|---|---|---|
|
||||||
|
| ERR_NOT_FOUND_001 | 404 | پرونده یافت نشد یا مال محیط دیگری است |
|
||||||
|
| ERR_AUTH_001 | 401 | بدون توکن |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## GET `/api/v1/treatment-session/{uuid}`
|
## GET `/api/v1/treatment-session/{uuid}`
|
||||||
|
|
||||||
یک جلسه بهتنهایی، بهعلاوهٔ `case_uuid`، `service` و `patient` — برای فرمِ «ثبت نوبت این
|
یک جلسه بهتنهایی، بهعلاوهٔ `case_uuid`، `service` و `patient` — برای فرمِ «ثبت نوبت این
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ use App\Treatment\Repository\TreatmentSessionRepository;
|
|||||||
use App\Treatment\Service\NextSessionSlotFinder;
|
use App\Treatment\Service\NextSessionSlotFinder;
|
||||||
use App\Treatment\Service\TreatmentCaseEditor;
|
use App\Treatment\Service\TreatmentCaseEditor;
|
||||||
use App\Treatment\Service\TreatmentCaseOpener;
|
use App\Treatment\Service\TreatmentCaseOpener;
|
||||||
|
use App\Treatment\Service\TreatmentPlanProjector;
|
||||||
use OpenApi\Attributes as OA;
|
use OpenApi\Attributes as OA;
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
@@ -37,6 +38,7 @@ class TreatmentCaseController extends BaseController
|
|||||||
private readonly AddressResolver $branches,
|
private readonly AddressResolver $branches,
|
||||||
private readonly TreatmentCaseOpener $opener,
|
private readonly TreatmentCaseOpener $opener,
|
||||||
private readonly TreatmentCaseEditor $editor,
|
private readonly TreatmentCaseEditor $editor,
|
||||||
|
private readonly TreatmentPlanProjector $planner,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
#[Route('/api/v1/treatment-cases', name: 'treatment_case_list', methods: ['GET'])]
|
#[Route('/api/v1/treatment-cases', name: 'treatment_case_list', methods: ['GET'])]
|
||||||
@@ -142,6 +144,32 @@ class TreatmentCaseController extends BaseController
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* تقویمِ کل دوره + جزئیات آنچه انجام شده.
|
||||||
|
*
|
||||||
|
* جدا از `GET /treatment-case/{uuid}` است نه اضافه به آن: آن پاسخ مصرفکنندهٔ
|
||||||
|
* دیگری دارد (مودال ویرایش) که نه تقویم لازم دارد نه نواحی، و بزرگترش کردن یعنی
|
||||||
|
* هزینهٔ بیمصرف روی همان مسیر.
|
||||||
|
*
|
||||||
|
* `planned_at` تخمین است نه دادهٔ ذخیرهشده — `is_estimate` تکلیفش را روشن میکند.
|
||||||
|
*/
|
||||||
|
#[Route('/api/v1/treatment-case/{uuid}/plan', name: 'treatment_case_plan', methods: ['GET'])]
|
||||||
|
public function plan(#[CurrentUser] User $user, string $uuid): JsonResponse
|
||||||
|
{
|
||||||
|
$case = $this->requireCase($user, $uuid);
|
||||||
|
|
||||||
|
return $this->success([
|
||||||
|
'case' => $case->toArray(),
|
||||||
|
'sessions' => array_map(
|
||||||
|
static fn (array $row): array => $row['session']->toArray(withAreas: true) + [
|
||||||
|
'planned_at' => $row['planned_at'],
|
||||||
|
'is_estimate' => $row['is_estimate'],
|
||||||
|
],
|
||||||
|
$this->planner->project($case),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* یک جلسه بهتنهایی — برای فرمِ «ثبت نوبت این جلسه».
|
* یک جلسه بهتنهایی — برای فرمِ «ثبت نوبت این جلسه».
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -204,4 +204,37 @@ class TreatmentCaseEndpointsTest extends ApiTestCase
|
|||||||
|
|
||||||
self::assertSame(404, $this->responseCode());
|
self::assertSame(404, $this->responseCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** تقویم دوره: همهٔ جلسات با تاریخ، و جزئیات نواحیِ جلسهٔ انجامشده. */
|
||||||
|
public function testPlanReturnsEverySessionWithADate(): void
|
||||||
|
{
|
||||||
|
$s = $this->scenario();
|
||||||
|
|
||||||
|
$body = $this->authJson('GET', '/api/v1/treatment-case/' . $s['case']->getUuid() . '/plan', $s['user']);
|
||||||
|
|
||||||
|
self::assertSame(200, $this->responseCode());
|
||||||
|
self::assertSame(
|
||||||
|
$s['case']->getTotalSessions(),
|
||||||
|
count($body['data']['sessions']),
|
||||||
|
'تقویم باید همهٔ جلسات را بدهد، نه فقط آنهایی که سررسید نوشته دارند',
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($body['data']['sessions'] as $row) {
|
||||||
|
self::assertArrayHasKey('planned_at', $row);
|
||||||
|
self::assertArrayHasKey('is_estimate', $row);
|
||||||
|
// نواحی باید بیایند — «چه کاری انجام شد» همین است.
|
||||||
|
self::assertArrayHasKey('areas', $row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** حالت مرزی: پروندهٔ محیط دیگر — تقویم هم مثل بقیه ۴۰۴ میگیرد نه ۴۰۳. */
|
||||||
|
public function testPlanOfAnotherTenantIs404(): void
|
||||||
|
{
|
||||||
|
$mine = $this->scenario();
|
||||||
|
$other = $this->scenario();
|
||||||
|
|
||||||
|
$this->authJson('GET', '/api/v1/treatment-case/' . $other['case']->getUuid() . '/plan', $mine['user']);
|
||||||
|
|
||||||
|
self::assertSame(404, $this->responseCode());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user