Refactor doctor name handling across the application
- Removed the "دکتر" prefix from doctor names in various components and API responses to ensure consistency and clarity. - Updated the AppointmentDetailPage, CommentsPage, DashboardPage, RatingsPage, SecretariesPage, and other relevant files to reflect the changes in doctor name formatting. - Adjusted API documentation to align with the new naming conventions. - Implemented validation to prevent the creation of clinics without a name and restricted users to a single clinic. - Added tests to verify that doctor names are stored without titles and that clinic creation adheres to the new validation rules.
This commit is contained in:
@@ -30,6 +30,7 @@ use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\CurrentUser;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
use App\Shared\Util\PersianText;
|
||||
|
||||
#[OA\Tag(name: 'Admin')]
|
||||
#[IsGranted('ROLE_ADMIN')]
|
||||
@@ -457,7 +458,7 @@ class AdminApiController extends BaseController
|
||||
{
|
||||
$data = json_decode($request->getContent(), true) ?? [];
|
||||
$mobile = InputValidator::toEnglishDigits(trim((string) ($data['mobile'] ?? '')));
|
||||
$name = trim((string) ($data['name'] ?? ''));
|
||||
$name = PersianText::stripDoctorTitle((string) ($data['name'] ?? ''));
|
||||
|
||||
if ($mobile === '' || $name === '') {
|
||||
return $this->error(ErrorCodes::VALIDATION, 'موبایل و نام الزامی هستند', 422);
|
||||
|
||||
@@ -99,6 +99,19 @@ class ClinicController extends BaseController
|
||||
if (($err = $this->validateGallerySize($data)) !== null) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
// بدون نام، کلینیکِ بیهویت ساخته میشد که در هیچ لیستی قابلتشخیص نیست.
|
||||
if (trim((string) ($data['name'] ?? '')) === '') {
|
||||
return $this->error(ErrorCodes::ERR_VALIDATION_001, 'نام کلینیک الزامی است', 422, 'name');
|
||||
}
|
||||
|
||||
// یک کلینیک به ازای هر کاربر: ClinicRepository::findByUser() — که محیط کاری
|
||||
// کاربر از آن حل میشود — findOneBy است، پس کلینیک دوم به بعد هرگز انتخاب
|
||||
// نمیشود و به دادهٔ یتیم تبدیل میشود.
|
||||
if ($this->clinicRepo->findByUser($user) !== null) {
|
||||
return $this->error(ErrorCodes::ERR_CONFLICT_001, 'برای این کاربر کلینیک ثبت شده است', 409);
|
||||
}
|
||||
|
||||
$clinic = new Clinic($user);
|
||||
$this->hydrateClinic($clinic, $data);
|
||||
$this->clinicRepo->save($clinic);
|
||||
|
||||
@@ -13,11 +13,14 @@ use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
/**
|
||||
* اصلاح یکبارمصرف نامِ پزشکانِ ایمپورتشدهٔ IRIMC که با پیشوند «دکتر» ذخیره شدهاند.
|
||||
* فقط source='irimc' را دست میزند؛ پزشکان manual/seed را تغییر نمیدهد.
|
||||
* حذف پیشوند «دکتر» از نام پزشکانی که با عنوان ذخیره شدهاند. نام پزشک هرگز نباید
|
||||
* عنوان داشته باشد؛ لایهٔ نمایش خودش تصمیم میگیرد چطور نشانش دهد.
|
||||
*
|
||||
* پیشفرض فقط source='irimc' است. `--all` هر منبعی (seed/manual) را هم پاک میکند —
|
||||
* لازم است چون مسیرهای ثبتنام تا پیش از این عنوان را حذف نمیکردند.
|
||||
*
|
||||
* php bin/console app:doctors:fix-irimc-names --dry-run
|
||||
* php bin/console app:doctors:fix-irimc-names
|
||||
* php bin/console app:doctors:fix-irimc-names --all
|
||||
*/
|
||||
#[AsCommand(
|
||||
name: 'app:doctors:fix-irimc-names',
|
||||
@@ -33,19 +36,21 @@ class FixIrimcNamesCommand extends Command
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->addOption('dry-run', null, InputOption::VALUE_NONE, 'Report only, change nothing');
|
||||
$this->addOption('all', null, InputOption::VALUE_NONE, 'Every doctor, not just source=irimc');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$dryRun = (bool) $input->getOption('dry-run');
|
||||
$all = (bool) $input->getOption('all');
|
||||
|
||||
$qb = $this->em->getRepository(Doctor::class)->createQueryBuilder('d');
|
||||
if (!$all) {
|
||||
$qb->where('d.source = :src')->setParameter('src', 'irimc');
|
||||
}
|
||||
/** @var Doctor[] $doctors */
|
||||
$doctors = $this->em->getRepository(Doctor::class)->createQueryBuilder('d')
|
||||
->where('d.source = :src')
|
||||
->setParameter('src', 'irimc')
|
||||
->getQuery()
|
||||
->getResult();
|
||||
$doctors = $qb->getQuery()->getResult();
|
||||
|
||||
$fixed = 0;
|
||||
foreach ($doctors as $doctor) {
|
||||
@@ -63,7 +68,13 @@ class FixIrimcNamesCommand extends Command
|
||||
$this->em->flush();
|
||||
}
|
||||
|
||||
$io->success(sprintf('%d نام %s (از %d پزشک IRIMC).', $fixed, $dryRun ? 'قابل اصلاح' : 'اصلاح شد', count($doctors)));
|
||||
$io->success(sprintf(
|
||||
'%d نام %s (از %d پزشک %s).',
|
||||
$fixed,
|
||||
$dryRun ? 'قابل اصلاح' : 'اصلاح شد',
|
||||
count($doctors),
|
||||
$all ? 'بررسیشده' : 'IRIMC',
|
||||
));
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\CurrentUser;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
use App\Shared\Util\PersianText;
|
||||
|
||||
#[OA\Tag(name: 'Doctors')]
|
||||
class DoctorController extends BaseController
|
||||
@@ -105,7 +106,7 @@ class DoctorController extends BaseController
|
||||
}
|
||||
|
||||
$data = json_decode($request->getContent(), true) ?? [];
|
||||
$name = trim($data['title'] ?? $data['name'] ?? '');
|
||||
$name = PersianText::stripDoctorTitle($data['title'] ?? $data['name'] ?? '');
|
||||
|
||||
if ($name === '') {
|
||||
return $this->error(ErrorCodes::ERR_VALIDATION_001, 'نام دکتر الزامی است', 422, 'title');
|
||||
@@ -350,7 +351,7 @@ class DoctorController extends BaseController
|
||||
}
|
||||
|
||||
$data = json_decode($request->getContent(), true) ?? [];
|
||||
if (!empty($data['title'])) $doctor->setName($data['title']);
|
||||
if (!empty($data['title'])) $doctor->setName(PersianText::stripDoctorTitle($data['title']));
|
||||
|
||||
$this->hydrateDoctor($doctor, $data);
|
||||
$this->doctorRepo->save($doctor);
|
||||
|
||||
@@ -19,6 +19,7 @@ use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\CurrentUser;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
use App\Shared\Util\PersianText;
|
||||
|
||||
/**
|
||||
* اکشنهای پنل نماینده: افزودن پزشک/کلینیک، نوبتهای پزشکانِ زیرمجموعه، و پروفایل نمایندهی جاری.
|
||||
@@ -258,7 +259,7 @@ class RepresentationActionController extends BaseController
|
||||
{
|
||||
$data = json_decode($request->getContent(), true) ?? [];
|
||||
$mobile = InputValidator::toEnglishDigits(trim((string) ($data['mobile'] ?? '')));
|
||||
$name = trim((string) ($data['name'] ?? ''));
|
||||
$name = PersianText::stripDoctorTitle((string) ($data['name'] ?? ''));
|
||||
|
||||
if ($mobile === '' || $name === '') {
|
||||
return $this->error(ErrorCodes::ERR_VALIDATION_002, 'موبایل و نام الزامی هستند', 422);
|
||||
|
||||
@@ -333,7 +333,7 @@ class SeedDemoDataCommand extends Command
|
||||
$userRows[] = [
|
||||
'uuid' => Uuid::v4()->toRfc4122(),
|
||||
'mobile_number' => sprintf('09126%06d', $i),
|
||||
'real_name' => 'دکتر ' . $this->persianName($i),
|
||||
'real_name' => $this->persianName($i),
|
||||
'roles' => json_encode(['ROLE_USER', 'ROLE_DOCTOR']),
|
||||
'national_code_verified' => 0,
|
||||
'status' => 1,
|
||||
@@ -359,7 +359,7 @@ class SeedDemoDataCommand extends Command
|
||||
$doctorRows[] = [
|
||||
'uuid' => Uuid::v4()->toRfc4122(),
|
||||
'user_id' => $userId,
|
||||
'name' => 'دکتر ' . $this->persianName($i),
|
||||
'name' => $this->persianName($i),
|
||||
'gender' => $i % 3 === 0 ? 'woman' : 'man',
|
||||
'medical_system_code' => (string) (100000 + $i),
|
||||
'mobile_number' => sprintf('09126%06d', $i),
|
||||
|
||||
Reference in New Issue
Block a user