denyUnlessGranted($user, 'view'); [$entityType, $entityId] = $this->context->pair($user); $addressUuid = $request->query->get('address_uuid'); $typeUuid = $request->query->get('type_uuid'); $activeParam = $request->query->get('active'); $filters = [ 'address' => is_string($addressUuid) && $addressUuid !== '' ? $this->context->address($user, $addressUuid) : null, 'type' => is_string($typeUuid) && $typeUuid !== '' ? $this->context->type($user, $typeUuid) : null, 'active' => $activeParam === null || $activeParam === '' ? null : filter_var($activeParam, FILTER_VALIDATE_BOOL), 'skillUuid' => $request->query->get('skill_uuid') ?: null, ]; // مهارتِ محیط دیگر نباید بی‌صدا «هیچ نتیجه» بدهد؛ ۴۰۴ صریح است. if ($filters['skillUuid'] !== null) { $this->context->skill($user, $filters['skillUuid']); } return $this->success(array_map( static fn (ClinicResource $r): array => $r->toArray(), $this->resources->findForPair($entityType, $entityId, $filters), )); } #[Route('/api/v1/resource', name: 'resource_create', methods: ['POST'])] public function create(#[CurrentUser] User $user, Request $request): JsonResponse { $this->denyUnlessGranted($user, 'update'); $data = json_decode($request->getContent(), true); if (!is_array($data) || !is_string($data['address_uuid'] ?? null)) { return $this->error(ErrorCodes::ERR_VALIDATION_002, 'فیلد address_uuid الزامی است', 422, 'address_uuid'); } if (!is_string($data['type_uuid'] ?? null)) { return $this->error(ErrorCodes::ERR_VALIDATION_002, 'فیلد type_uuid الزامی است', 422, 'type_uuid'); } $address = $this->context->address($user, $data['address_uuid']); $type = $this->context->type($user, $data['type_uuid']); return $this->success($this->service->create($address, $type, $data)->toArray(), 201); } #[Route('/api/v1/resource/{uuid}', name: 'resource_show', methods: ['GET'])] public function show(#[CurrentUser] User $user, string $uuid): JsonResponse { $this->denyUnlessGranted($user, 'view'); $resource = $this->context->resource($user, $uuid); // شمار نوبت‌های آینده کنار خودِ منبع می‌آید تا پنل بتواند پیش از خاموش‌کردنش // هشدار بدهد. در فهرست نمی‌آید — آنجا یک کوئری per ردیف می‌شد. return $this->success($resource->toArray() + [ 'upcoming_appointments' => $this->occupancy->countUpcomingAppointments((int) $resource->getId()), ]); } #[Route('/api/v1/resource/{uuid}', name: 'resource_update', methods: ['PATCH'])] public function update(#[CurrentUser] User $user, string $uuid, Request $request): JsonResponse { $this->denyUnlessGranted($user, 'update'); $data = json_decode($request->getContent(), true); if (!is_array($data)) { return $this->error(ErrorCodes::ERR_VALIDATION_001, 'بدنهٔ درخواست نامعتبر است', 422); } $resource = $this->context->resource($user, $uuid); if (is_string($data['type_uuid'] ?? null)) { $resource->setType($this->context->type($user, $data['type_uuid'])); } return $this->success($this->service->update($resource, $data)->toArray()); } #[Route('/api/v1/resource/{uuid}', name: 'resource_delete', methods: ['DELETE'])] public function delete(#[CurrentUser] User $user, string $uuid): JsonResponse { $this->denyUnlessGranted($user, 'update'); $this->service->delete($this->context->resource($user, $uuid)); return $this->success(null); } /** جایگزینی کامل مهارت‌های منبع: مهارتی که در بدنه نیست، برداشته می‌شود. */ #[Route('/api/v1/resource/{uuid}/skills', name: 'resource_skills_replace', methods: ['PUT'])] public function replaceSkills(#[CurrentUser] User $user, string $uuid, Request $request): JsonResponse { $this->denyUnlessGranted($user, 'update'); $data = json_decode($request->getContent(), true); if (!is_array($data) || !is_array($data['skills'] ?? null)) { return $this->error(ErrorCodes::ERR_VALIDATION_002, 'فیلد skills الزامی است', 422, 'skills'); } $resource = $this->context->resource($user, $uuid); $this->skills->replace($user, $resource, $data['skills']); return $this->success($resource->toArray()); } }