branches->pair($user); $status = $request->query->get('status'); $status = is_string($status) && $status !== '' ? $status : null; return $this->success(array_map( static fn (TreatmentCase $c): array => $c->toArray(), $this->cases->findForTenant($entityType, $entityId, $status), )); } #[Route('/api/v1/treatment-case/{uuid}', name: 'treatment_case_show', methods: ['GET'])] public function show(#[CurrentUser] User $user, string $uuid): JsonResponse { return $this->success($this->requireCase($user, $uuid)->toArray(withSessions: true)); } /** * صفِ «جلسات بدون نوبت» — کاری که منشی باید انجام دهد، نه رفتاری که خودکار اتفاق بیفتد. * * جلسه‌ای که سررسیدش رسیده و کسی رزروش نکرده اینجا دیده می‌شود؛ بدون این، جلسهٔ * فراموش‌شده در سکوت می‌ماند تا بیمار خودش زنگ بزند. */ #[Route('/api/v1/treatment-sessions/unbooked', name: 'treatment_sessions_unbooked', methods: ['GET'])] public function unbooked(#[CurrentUser] User $user, Request $request): JsonResponse { [$entityType, $entityId] = $this->branches->pair($user); $withinDays = (int) $request->query->get('within_days', 7); $until = time() + max(0, min($withinDays, 90)) * 86400; return $this->success(array_map( static fn (TreatmentSession $s): array => $s->toArray() + [ 'case_uuid' => $s->getTreatmentCase()->getUuid(), 'service_name' => $s->getTreatmentCase()->getServiceItem()->getName(), ], $this->sessions->findUnbookedDue($entityType, $entityId, $until), )); } /** * اسلات‌های پیشنهادی برای این جلسه. * * پیشنهاد است، نه رزرو: منشی با بیمار هماهنگ می‌کند و بعد از مسیر عادی ثبت نوبت * یکی را می‌گیرد. */ #[Route('/api/v1/treatment-session/{uuid}/slot-suggestions', name: 'treatment_session_slots', methods: ['GET'])] public function slotSuggestions(#[CurrentUser] User $user, string $uuid, Request $request): JsonResponse { $session = $this->requireSession($user, $uuid); $resourceUuid = $request->query->get('resource_uuid'); $resource = is_string($resourceUuid) && $resourceUuid !== '' ? $this->resources->findByUuid($resourceUuid) : $this->slotFinder->preferredResource($session->getTreatmentCase()); if ($resource === null) { return $this->error( ErrorCodes::ERR_VALIDATION_002, 'منبعی برای پیشنهاد وقت مشخص نیست؛ resource_uuid بفرستید', 422, 'resource_uuid', ); } [$entityType, $entityId] = $this->branches->pair($user); if (!$this->ownership->belongsToPair($entityType, $entityId, $resource)) { return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'منبع یافت نشد', 404, 'resource_uuid'); } $days = (int) $request->query->get('days', NextSessionSlotFinder::DEFAULT_HORIZON_DAYS); return $this->success($this->slotFinder->suggest($session, $resource, $days) + [ 'session' => $session->toArray(), ]); } private function requireCase(User $user, string $uuid): TreatmentCase { [$entityType, $entityId] = $this->branches->pair($user); $case = $this->cases->findByUuid($uuid); if (!$this->ownership->belongsToPair($entityType, $entityId, $case)) { throw new AppException(ErrorCodes::ERR_NOT_FOUND_001, 'پروندهٔ درمان یافت نشد', 404); } return $case; } private function requireSession(User $user, string $uuid): TreatmentSession { [$entityType, $entityId] = $this->branches->pair($user); $session = $this->sessions->findByUuid($uuid); if (!$this->ownership->belongsToPair($entityType, $entityId, $session)) { throw new AppException(ErrorCodes::ERR_NOT_FOUND_001, 'جلسهٔ درمان یافت نشد', 404); } return $session; } }