feat: add mobile-based patient lookup for appointment booking

- Implemented a new endpoint `/api/v1/my/appointment/patient-lookup` to search for patients by mobile number before booking an appointment.
- Updated the `NewAppointmentModal` component to utilize the new patient lookup feature, allowing for direct booking if the patient is found with a national code.
- Enhanced the appointment booking form to handle mobile input normalization and display relevant fields based on the search results.
- Added tests for the new patient lookup functionality, ensuring proper behavior for found and not found cases, as well as validation for mobile input.
- Updated sidebar tests to reflect changes in the sidebar component structure and functionality.
This commit is contained in:
hamed
2026-07-15 18:27:29 +03:30
parent f378bf58e6
commit 5d5089244b
8 changed files with 1416 additions and 467 deletions
@@ -40,6 +40,7 @@ class MyAppointmentsController extends BaseController
private readonly \App\ClinicService\Repository\ServiceItemRepository $itemRepo,
private readonly \App\Staff\Repository\ClinicStaffRepository $staffRepo,
private readonly PatientResolver $patientResolver,
private readonly \App\Auth\Repository\UserRepository $userRepo,
) {}
#[Route('/api/v1/my/appointment', methods: ['POST'])]
@@ -144,6 +145,39 @@ class MyAppointmentsController extends BaseController
], 201);
}
/**
* Booking-scoped patient lookup by mobile. Lets the booking form search an
* existing patient before asking for national code / name. Unlike
* /patient/search-user this is not gated by the patient_records feature and
* allows ROLE_ADMIN, because booking must work regardless of subscription.
*/
#[Route('/api/v1/my/appointment/patient-lookup', methods: ['GET'])]
#[IsGranted('IS_AUTHENTICATED_FULLY')]
public function patientLookup(Request $request, #[CurrentUser] User $user): JsonResponse
{
$allowed = ['ROLE_DOCTOR', 'ROLE_CLINIC', 'ROLE_SECRETARY', 'ROLE_ADMIN'];
if (!array_intersect($allowed, $user->getRoles())) {
return $this->error(ErrorCodes::FORBIDDEN, 'دسترسی ندارید', 403);
}
$mobile = InputValidator::toEnglishDigits(trim((string) $request->query->get('mobile', '')));
if (!InputValidator::isValidIranMobile($mobile)) {
return $this->error(ErrorCodes::VALIDATION, 'شماره موبایل نامعتبر است', 422, 'mobile');
}
$patient = $this->userRepo->findByMobile($mobile);
if ($patient === null) {
return $this->success(['found' => false]);
}
return $this->success([
'found' => true,
'name' => $patient->getRealName(),
'mobile' => $patient->getMobileNumber(),
'national_code' => $patient->getNationalCode(),
]);
}
#[Route('/api/v1/my/appointments', methods: ['GET'])]
#[IsGranted('IS_AUTHENTICATED_FULLY')]
public function myAppointments(Request $request, #[CurrentUser] User $user): JsonResponse