$rows * @return ResourceSkill[] */ public function replace(User $user, ClinicResource $resource, array $rows): array { $validated = $this->validate($user, $rows); $this->resourceSkills->deleteForResource($resource); $resource->getSkills()->clear(); $created = []; foreach ($validated as $row) { $assignment = new ResourceSkill($resource, $row['skill'], $row['level']); $this->em->persist($assignment); $resource->getSkills()->add($assignment); $created[] = $assignment; } $this->em->flush(); return $created; } /** * @param array $rows * @return list */ private function validate(User $user, array $rows): array { $seen = []; $out = []; foreach ($rows as $row) { if (!is_array($row) || !is_string($row['skill_uuid'] ?? null)) { throw new AppException(ErrorCodes::ERR_VALIDATION_002, 'فیلد skill_uuid الزامی است', 422, 'skill_uuid'); } $uuid = $row['skill_uuid']; if (isset($seen[$uuid])) { throw new AppException(ErrorCodes::ERR_VALIDATION_001, 'یک مهارت دو بار فرستاده شده است', 422, 'skill_uuid'); } $seen[$uuid] = true; // مهارت محیط دیگر → ۴۰۴، پیش از هر تغییری در دیتابیس. $skill = $this->context->skill($user, $uuid); $level = $row['level'] ?? ResourceSkill::MIN_LEVEL; if (!is_numeric($level) || (int) $level < ResourceSkill::MIN_LEVEL || (int) $level > ResourceSkill::MAX_LEVEL) { throw new AppException(ErrorCodes::ERR_VALIDATION_001, 'سطح مهارت باید بین ۱ تا ۵ باشد', 422, 'level'); } $out[] = ['skill' => $skill, 'level' => (int) $level]; } return $out; } /** * مهارتی که روی منبعی نشسته حذف نمی‌شود — اول باید از منابع برداشته شود، وگرنه * `ON DELETE RESTRICT` خطای خام دیتابیس می‌داد. */ public function deleteSkill(Skill $skill): void { $inUse = $this->resourceSkills->countForSkill($skill); if ($inUse > 0) { throw new AppException( ErrorCodes::ERR_VALIDATION_001, sprintf('این مهارت به %d منبع داده شده است؛ اول از آن‌ها برداشته شود', $inUse), 422, ); } $this->em->remove($skill); $this->em->flush(); } }