feat: add mobile number change functionality for doctors and clinics

- Implemented PATCH endpoints for changing the login mobile number of doctors and clinics.
- Added ChangeLoginMobileModal component for handling mobile number updates in the UI.
- Updated ClinicsPage and DoctorsPage to include buttons for changing mobile numbers.
- Enhanced AdminApiController to manage mobile number changes with validation.
- Created tests to ensure proper functionality and validation for mobile number changes.
- Updated API documentation to reflect new endpoints and their usage.
This commit is contained in:
hamed
2026-07-25 21:40:38 +03:30
parent 3cc4a59459
commit 50ba7e44ff
13 changed files with 520 additions and 38 deletions
@@ -306,6 +306,86 @@ class AdminApiController extends BaseController
return $this->success(['is_active' => $doctor->isActiveDoctorAppointment()]);
}
/**
* تغییر شمارهٔ **ورود** پزشک/کلینیک توسط مدیر کل.
*
* شماره، هویتِ ورود همان کاربر است؛ پس یکتا بودنش کنترل می‌شود و شمارهٔ نمایشیِ
* پزشک هم با آن هم‌گام می‌ماند تا دو مقدار واگرا نشوند.
*/
private function changeLoginMobile(User $owner, mixed $raw, ?Doctor $doctor = null): JsonResponse
{
$mobile = InputValidator::toEnglishDigits(trim((string) $raw));
if (!InputValidator::isValidIranMobile($mobile)) {
return $this->error(ErrorCodes::ERR_VALIDATION_001, 'شماره موبایل نامعتبر است (۰۹ و ۱۱ رقم)', 422, 'mobile_number');
}
$existing = $this->em->getRepository(User::class)->findOneBy(['mobileNumber' => $mobile]);
if ($existing !== null && $existing->getId() !== $owner->getId()) {
return $this->error(ErrorCodes::ERR_CONFLICT_001, 'این شماره موبایل قبلاً ثبت شده است', 409, 'mobile_number');
}
$previous = $owner->getMobileNumber();
$owner->setMobileNumber($mobile);
// شمارهٔ نمایشیِ پزشک اگر با شمارهٔ ورود یکی بوده (یا خالی است) همراهش به‌روز شود.
if ($doctor !== null && ($doctor->getMobileNumber() === null || $doctor->getMobileNumber() === $previous)) {
$doctor->setMobileNumber($mobile);
}
$this->em->flush();
return $this->success(['data' => ['mobile_number' => $mobile, 'previous_mobile_number' => $previous]]);
}
#[OA\Patch(
path: '/api/v1/admin/doctors/{uuid}/mobile',
summary: 'Change the login mobile number of a doctor account',
security: [['bearerAuth' => []]],
responses: [
new OA\Response(response: 200, description: 'Mobile changed'),
new OA\Response(response: 404, description: 'Doctor not found'),
new OA\Response(response: 409, description: 'Mobile already taken'),
new OA\Response(response: 422, description: 'Invalid mobile'),
]
)]
#[Route('/api/v1/admin/doctors/{uuid}/mobile', methods: ['PATCH'])]
public function changeDoctorMobile(string $uuid, Request $request): JsonResponse
{
$doctor = $this->em->getRepository(Doctor::class)->findOneBy(['uuid' => $uuid]);
if ($doctor === null) {
return $this->error(ErrorCodes::DOCTOR_NOT_FOUND, 'پزشک یافت نشد', 404);
}
$data = json_decode($request->getContent(), true) ?? [];
return $this->changeLoginMobile($doctor->getUser(), $data['mobile_number'] ?? '', $doctor);
}
#[OA\Patch(
path: '/api/v1/admin/clinic/{uuid}/mobile',
summary: 'Change the login mobile number of a clinic account',
security: [['bearerAuth' => []]],
responses: [
new OA\Response(response: 200, description: 'Mobile changed'),
new OA\Response(response: 404, description: 'Clinic not found'),
new OA\Response(response: 409, description: 'Mobile already taken'),
new OA\Response(response: 422, description: 'Invalid mobile'),
]
)]
#[Route('/api/v1/admin/clinic/{uuid}/mobile', methods: ['PATCH'])]
public function changeClinicMobile(string $uuid, Request $request): JsonResponse
{
$clinic = $this->em->getRepository(\App\Clinic\Entity\Clinic::class)->findOneBy(['uuid' => $uuid]);
if ($clinic === null) {
return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'کلینیک یافت نشد', 404);
}
$data = json_decode($request->getContent(), true) ?? [];
return $this->changeLoginMobile($clinic->getUser(), $data['mobile_number'] ?? '');
}
#[Route('/api/v1/admin/doctors/{uuid}/representation', methods: ['PUT'])]
public function setDoctorRepresentation(string $uuid, Request $request, RepresentationRepository $repRepo): JsonResponse
{