From 23590370b2979b993f1c22fc25ac4117bb24b80e Mon Sep 17 00:00:00 2001 From: hamed <15238-genius.ha@users.noreply.drupalcode.org> Date: Sat, 11 Jul 2026 19:56:58 +0330 Subject: [PATCH] feat(doctor): prevent duplicate doctor imports based on medical system code --- docs/api/doctor-import.md | 9 ++++++++ src/Doctor/Service/DoctorImportService.php | 9 ++++++++ tests/Doctor/DoctorImportTest.php | 26 ++++++++++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/docs/api/doctor-import.md b/docs/api/doctor-import.md index 7403bdbc..085c7c2a 100644 --- a/docs/api/doctor-import.md +++ b/docs/api/doctor-import.md @@ -39,6 +39,10 @@ - اگر وجود داشته باشد و `owner_status != claimed` → **به‌روزرسانی** می‌شود (`200`). - اگر وجود داشته باشد و `owner_status == claimed` → **رد** می‌شود (`200`, `skipped: "claimed"`) تا دادهٔ مالک واقعی بازنویسی نشود. +- **تکرار بین منابع:** اگر پزشکی با همان `medical_system_code` ولی `source` متفاوت + (مثلاً ثبت دستی در پنل) وجود داشته باشد → **رد** می‌شود (`200`, `skipped: "duplicate"`)؛ + نه رکورد جدیدی ساخته می‌شود و نه رکورد موجود بازنویسی می‌شود. `uuid` همان رکورد موجود + برگردانده می‌شود (تست: `DoctorImportTest::testManualDoctorWithSameCodeIsNeverDuplicated`). --- @@ -108,6 +112,11 @@ { "success": true, "data": { "uuid": "…", "created": false, "skipped": "claimed" } } ``` +### `200 OK` (رد به‌دلیل کد نظام پزشکی تکراری با منبع دیگر) +```json +{ "success": true, "data": { "uuid": "…", "created": false, "skipped": "duplicate" } } +``` + ### `422` (اعتبارسنجی) ```json { "success": false, "data": null, "errors": [ { "code": "…", "message": "کد نظام پزشکی الزامی است", "field": "medical_system_code" } ] } diff --git a/src/Doctor/Service/DoctorImportService.php b/src/Doctor/Service/DoctorImportService.php index 13c7800a..22f5a807 100644 --- a/src/Doctor/Service/DoctorImportService.php +++ b/src/Doctor/Service/DoctorImportService.php @@ -65,6 +65,15 @@ class DoctorImportService return new DoctorImportResult($doctor, false, 'claimed'); } + if ($doctor === null) { + // جلوگیری از تکرار بین منابع: اگر همین کد نظام پزشکی با هر source دیگری + // (مثلاً ثبت دستی) وجود دارد، ایمپورت نه می‌سازد و نه بازنویسی می‌کند. + $existing = $doctorRepo->findOneBy(['medicalSystemCode' => $code]); + if ($existing !== null) { + return new DoctorImportResult($existing, false, 'duplicate'); + } + } + if ($doctor === null) { // کاربر جانشینِ یکتا و غیرفعال؛ شناسهٔ مصنوعی قطعی از روی کد نظام پزشکی $synthetic = 'imp_' . substr(md5($source . ':' . $code), 0, 14); // ≤ ۱۸ کاراکتر، ASCII، یکتا diff --git a/tests/Doctor/DoctorImportTest.php b/tests/Doctor/DoctorImportTest.php index cd9dde43..3c2f4198 100644 --- a/tests/Doctor/DoctorImportTest.php +++ b/tests/Doctor/DoctorImportTest.php @@ -164,6 +164,32 @@ class DoctorImportTest extends ApiTestCase $this->assertSame('مطب دکتر صفورا حجازی نیا', $doctor->getAddresses()->first()->getName()); } + public function testManualDoctorWithSameCodeIsNeverDuplicated(): void + { + $admin = $this->createUser(['ROLE_USER', 'ROLE_ADMIN']); + $code = $this->freshCode(); + + $owner = $this->createUser(['ROLE_USER', 'ROLE_DOCTOR']); + $manual = new Doctor($owner, 'پزشک دستی'); + $manual->setMedicalSystemCode($code); + $this->em->persist($manual); + $this->em->flush(); + + $data = $this->authJson('POST', '/api/v1/admin/doctors/import', $admin, $this->importPayload($code)); + + $this->assertSame(200, $this->responseCode()); + $this->assertFalse($data['data']['created']); + $this->assertSame('duplicate', $data['data']['skipped']); + $this->assertSame($manual->getUuid(), $data['data']['uuid']); + + $this->em->clear(); + $count = $this->em->getRepository(Doctor::class)->count(['medicalSystemCode' => $code]); + $this->assertSame(1, $count); + $doctor = $this->em->getRepository(Doctor::class)->findOneBy(['medicalSystemCode' => $code]); + $this->assertSame('پزشک دستی', $doctor->getName()); + $this->assertSame('manual', $doctor->getSource()); + } + public function testValidationErrors(): void { $admin = $this->createUser(['ROLE_USER', 'ROLE_ADMIN']);