feat(doctor): update schedule fields in doctor API responses to reflect actual availability

This commit is contained in:
hamed
2026-06-15 15:11:29 +03:30
parent ec36eefeb8
commit c662eb9b4f
5 changed files with 398 additions and 28 deletions
@@ -19,6 +19,19 @@ class WeeklyScheduleRepository extends ServiceEntityRepository
return $this->findOneBy(['doctor' => $doctor]);
}
/** @param Doctor[] $doctors @return WeeklySchedule[] */
public function findByDoctors(array $doctors): array
{
if (empty($doctors)) {
return [];
}
return $this->createQueryBuilder('ws')
->where('ws.doctor IN (:doctors)')
->setParameter('doctors', $doctors)
->getQuery()
->getResult();
}
public function findByUuid(string $uuid): ?WeeklySchedule
{
return $this->findOneBy(['uuid' => $uuid]);
+16 -5
View File
@@ -2,6 +2,7 @@
namespace App\Doctor\Controller;
use App\Appointment\Repository\WeeklyScheduleRepository;
use App\Auth\Entity\User;
use App\Auth\Repository\UserRepository;
use App\Clinic\Entity\Clinic;
@@ -37,6 +38,7 @@ class DoctorController extends BaseController
private readonly CityRepository $cityRepo,
private readonly UserRepository $userRepo,
private readonly FileValidatorService $fileValidator,
private readonly WeeklyScheduleRepository $scheduleRepo,
private readonly string $projectDir,
) {}
@@ -114,7 +116,8 @@ class DoctorController extends BaseController
$this->userRepo->save($user);
}
return $this->success(['data' => $doctor->toDetailArray()], 201);
$schedule = $this->scheduleRepo->findByDoctor($doctor);
return $this->success(['data' => $doctor->toDetailArray($schedule)], 201);
}
#[OA\Get(
@@ -160,7 +163,8 @@ class DoctorController extends BaseController
],
], $clinics);
return $this->success(['data' => array_merge($doctor->toDetailArray(), ['clinics' => $clinicData])]);
$schedule = $this->scheduleRepo->findByDoctor($doctor);
return $this->success(['data' => array_merge($doctor->toDetailArray($schedule), ['clinics' => $clinicData])]);
}
#[Route('/api/v1/clinic/my-doctor/{doctorUuid}', methods: ['GET'])]
@@ -181,7 +185,8 @@ class DoctorController extends BaseController
return $this->error(ErrorCodes::ERR_AUTH_006, 'این پزشک عضو کلینیک شما نیست', 403);
}
return $this->success(['data' => array_merge($doctor->toDetailArray(), ['clinics' => [[
$schedule = $this->scheduleRepo->findByDoctor($doctor);
return $this->success(['data' => array_merge($doctor->toDetailArray($schedule), ['clinics' => [[
'id' => (string) $clinic->getId(),
'uuid' => $clinic->getUuid(),
'name' => $clinic->getName(),
@@ -235,8 +240,13 @@ class DoctorController extends BaseController
$filters = $request->query->all();
$result = $this->doctorRepo->findWithFilters($filters);
$scheduleMap = [];
foreach ($this->scheduleRepo->findByDoctors($result['items']) as $schedule) {
$scheduleMap[$schedule->getDoctor()->getId()] = $schedule;
}
return $this->paginated(
array_map(fn(Doctor $d) => $d->toListArray(), $result['items']),
array_map(fn(Doctor $d) => $d->toListArray($scheduleMap[$d->getId()] ?? null), $result['items']),
$result['total'],
$result['page'],
$result['limit']
@@ -309,7 +319,8 @@ class DoctorController extends BaseController
$this->hydrateDoctor($doctor, $data);
$this->doctorRepo->save($doctor);
return $this->success(['data' => $doctor->toDetailArray()]);
$schedule = $this->scheduleRepo->findByDoctor($doctor);
return $this->success(['data' => $doctor->toDetailArray($schedule)]);
}
#[OA\Delete(
+83 -8
View File
@@ -2,6 +2,7 @@
namespace App\Doctor\Entity;
use App\Appointment\Entity\WeeklySchedule;
use App\Auth\Entity\User;
use App\DoctorService\Entity\DoctorService;
use App\Location\Entity\City;
@@ -167,6 +168,78 @@ class Doctor
private function touch(): void { $this->updatedAt = time(); }
private const DAY_NAMES = ['شنبه', 'یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چهارشنبه', 'پنجشنبه', 'جمعه'];
private function computeScheduleFields(?WeeklySchedule $schedule): array
{
if ($schedule === null) {
return ['free_turn' => 'نوبت آزادی موجود نیست', 'hours_of_work' => 'برنامه کاری تنظیم نشده', 'has_schedule' => false];
}
$setting = $schedule->getSetting();
// استخراج ساعت‌های هر روز — key: dayIdx، value: رشته ساعت‌ها یا null
$dayTimes = [];
for ($i = 0; $i < 7; $i++) {
$activeSessions = array_values(array_filter(
$setting[$i]['sessions'] ?? [],
fn($s) => ($s['active'] ?? false) && !empty($s['start_time'])
));
$dayTimes[$i] = empty($activeSessions)
? null
: implode(' و ', array_map(fn($s) => $s['start_time'] . '' . $s['end_time'], $activeSessions));
}
$hasAnyDay = array_filter($dayTimes) !== [];
if (!$hasAnyDay) {
return ['free_turn' => 'نوبت آزادی موجود نیست', 'hours_of_work' => 'برنامه کاری تنظیم نشده', 'has_schedule' => false];
}
// گروه‌بندی روزهای متوالی با ساعت یکسان
// مثال: شنبه–پنجشنبه ۹–۱۳ و ۱۴–۱۸ | جمعه تعطیل
$groups = [];
$current = ['start' => 0, 'times' => $dayTimes[0]];
for ($i = 1; $i < 7; $i++) {
if ($dayTimes[$i] === $current['times']) {
continue; // ادامه همان گروه
}
if ($current['times'] !== null) {
$groups[] = ['start' => $current['start'], 'end' => $i - 1, 'times' => $current['times']];
}
$current = ['start' => $i, 'times' => $dayTimes[$i]];
}
if ($current['times'] !== null) {
$groups[] = ['start' => $current['start'], 'end' => 6, 'times' => $current['times']];
}
$parts = [];
foreach ($groups as $g) {
$parts[] = $g['start'] === $g['end']
? self::DAY_NAMES[$g['start']]
: self::DAY_NAMES[$g['start']] . ' تا ' . self::DAY_NAMES[$g['end']];
}
// نزدیک‌ترین روز کاری — PHP date('w'): 0=Sun,6=Sat → ایندکس ایرانی: 0=Sat,...,6=Fri
$phpDay = (int) date('w');
$iranDay = $phpDay === 0 ? 1 : ($phpDay === 6 ? 0 : $phpDay + 1);
$freeTurn = null;
for ($i = 0; $i < 7; $i++) {
$idx = ($iranDay + $i) % 7;
if ($dayTimes[$idx] !== null) {
$firstTime = explode(' و ', $dayTimes[$idx])[0];
$freeTurn = self::DAY_NAMES[$idx] . ' ' . $firstTime;
break;
}
}
return [
'free_turn' => $freeTurn ?? 'نوبت آزادی موجود نیست',
'hours_of_work' => implode(' | ', $parts),
'has_schedule' => true,
];
}
public function getExperience(): int
{
if ($this->activityTime === null) {
@@ -175,8 +248,9 @@ class Doctor
return max(0, (int)((time() - $this->activityTime) / (365.25 * 24 * 3600)));
}
public function toListArray(): array
public function toListArray(?WeeklySchedule $schedule = null): array
{
$sf = $this->computeScheduleFields($schedule);
return [
'id' => (string) $this->id,
'uuid' => $this->uuid,
@@ -189,14 +263,15 @@ class Doctor
], $this->specialties->toArray()),
'satisfaction' => (string) $this->doctorRatePercentage,
'point' => (string) $this->doctorRate,
'free_turn' => 'نوبت آزادی موجود نیست',
'hours_of_work' => 'برنامه کاری تنظیم نشده',
'active' => $this->activeDoctorAppointment,
'free_turn' => $sf['free_turn'],
'hours_of_work' => $sf['hours_of_work'],
'active' => $this->activeDoctorAppointment && $sf['has_schedule'],
];
}
public function toDetailArray(): array
public function toDetailArray(?WeeklySchedule $schedule = null): array
{
$sf = $this->computeScheduleFields($schedule);
return [
'id' => (string) $this->id,
'uuid' => $this->uuid,
@@ -211,15 +286,15 @@ class Doctor
'uuid' => $s->getUuid(), 'id' => (string) $s->getId(), 'name' => $s->getName(),
'parent_id' => $s->getParent()?->getId() !== null ? (string) $s->getParent()->getId() : null,
], $this->specialties->toArray()),
'active' => $this->activeDoctorAppointment,
'active' => $this->activeDoctorAppointment && $sf['has_schedule'],
'img' => $this->images ?? [],
'expertise' => array_map(fn(DoctorService $ds) => [
'uuid' => $ds->getUuid(), 'id' => (string) $ds->getId(), 'name' => $ds->getName(),
], $this->services->toArray()),
'satisfaction' => (string) $this->doctorRatePercentage,
'point' => (string) $this->doctorRate,
'free_turn' => 'نوبت آزادی موجود نیست',
'hours_of_work' => 'برنامه کاری تنظیم نشده',
'free_turn' => $sf['free_turn'],
'hours_of_work' => $sf['hours_of_work'],
'address' => array_map(fn(DoctorAddress $a) => $a->toArray(), $this->addresses->toArray()),
'average_rate' => ['total_rates' => null],
'state' => array_map(fn(Province $p) => [