fix(booking): aggregate public booking state across all schedules
The public doctor payload built `active`/`free_turn`/`hours_of_work` from the personal schedule alone, so a doctor bookable only at a clinic was reported as "نوبتدهی غیرفعال". Aggregate over every schedule instead: any schedule with online booking on and an active day makes the doctor bookable, and the disabled label only appears when all of them are off. Three admin-panel fixes for the same class of bug: - AppointmentsPage took the selected doctor from `dbUuid`, which is the clinic's uuid inside a clinic context — the slots request 404'd. Use `doctorUuid`. - TurnsTimeline rendered any error or unknown empty_reason as "این روز شیفت کاری ندارد". Errors now surface as errors and unknown reasons get a neutral message; the day-off wording is reserved for an explicit day_off from the backend. - Admins have no clinic context, so slots fell back to the personal schedule. They now pick a location from `appointment-booking-locations` and that choice drives the slot, service and create-appointment requests. Adds `app:schedule:normalize-format` for legacy rows stored as a bare JSON list covering only Saturday, which read as day-off for the rest of the week. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -414,29 +414,52 @@ class Doctor
|
||||
|
||||
private const APPOINTMENT_DISABLED_LABEL = 'نوبتدهی آنلاین غیرفعال است';
|
||||
|
||||
private function computeScheduleFields(?WeeklySchedule $schedule): array
|
||||
/**
|
||||
* وضعیت نوبتدهی از دید سایت عمومی، تجمیعشده روی همهٔ برنامههای پزشک
|
||||
* (شخصی + هر کلینیک). برنامهٔ شخصیِ خاموش نباید برنامهٔ کلینیکیِ روشن را بپوشاند.
|
||||
*
|
||||
* @param WeeklySchedule[] $schedules
|
||||
*/
|
||||
private function computeScheduleFields(array $schedules): array
|
||||
{
|
||||
$parts = $this->computeScheduleParts($schedule);
|
||||
|
||||
// Online booking enabled flag lives in the weekly schedule meta.
|
||||
// When disabled, free_turn reflects that while hours_of_work is kept.
|
||||
if ($schedule !== null && !$schedule->getMeta()['online_booking_enabled']) {
|
||||
return [
|
||||
'free_turn' => self::APPOINTMENT_DISABLED_LABEL,
|
||||
'hours_of_work' => $parts['hours_of_work'],
|
||||
'has_schedule' => false,
|
||||
];
|
||||
}
|
||||
|
||||
return $parts;
|
||||
}
|
||||
|
||||
private function computeScheduleParts(?WeeklySchedule $schedule): array
|
||||
{
|
||||
if ($schedule === null) {
|
||||
if ($schedules === []) {
|
||||
return ['free_turn' => 'نوبت آزادی موجود نیست', 'hours_of_work' => 'برنامه کاری تنظیم نشده', 'has_schedule' => false];
|
||||
}
|
||||
|
||||
$candidates = [];
|
||||
foreach ($schedules as $schedule) {
|
||||
if (!$schedule->getMeta()['online_booking_enabled']) {
|
||||
continue;
|
||||
}
|
||||
$parts = $this->computeScheduleParts($schedule);
|
||||
if ($parts['has_schedule']) {
|
||||
$candidates[] = $parts;
|
||||
}
|
||||
}
|
||||
|
||||
if ($candidates === []) {
|
||||
$allDisabled = array_filter($schedules, fn(WeeklySchedule $s) => $s->getMeta()['online_booking_enabled']) === [];
|
||||
if ($allDisabled) {
|
||||
return [
|
||||
'free_turn' => self::APPOINTMENT_DISABLED_LABEL,
|
||||
'hours_of_work' => $this->computeScheduleParts($schedules[array_key_first($schedules)])['hours_of_work'],
|
||||
'has_schedule' => false,
|
||||
];
|
||||
}
|
||||
return ['free_turn' => 'نوبت آزادی موجود نیست', 'hours_of_work' => 'برنامه کاری تنظیم نشده', 'has_schedule' => false];
|
||||
}
|
||||
|
||||
// نزدیکترین نوبت بین همهٔ محلها؛ ساعت کاری همان محل نمایش داده میشود
|
||||
// تا ترکیب ساعتهای دو محل در یک رشته گمراهکننده نشود.
|
||||
usort($candidates, fn(array $a, array $b) => $a['rank'] <=> $b['rank']);
|
||||
$best = $candidates[0];
|
||||
unset($best['rank']);
|
||||
|
||||
return $best;
|
||||
}
|
||||
|
||||
private function computeScheduleParts(WeeklySchedule $schedule): array
|
||||
{
|
||||
$setting = $schedule->getSetting();
|
||||
|
||||
// استخراج ساعتهای هر روز — key: dayIdx، value: رشته ساعتها یا null
|
||||
@@ -453,7 +476,7 @@ class Doctor
|
||||
|
||||
$hasAnyDay = array_filter($dayTimes) !== [];
|
||||
if (!$hasAnyDay) {
|
||||
return ['free_turn' => 'نوبت آزادی موجود نیست', 'hours_of_work' => 'برنامه کاری تنظیم نشده', 'has_schedule' => false];
|
||||
return ['free_turn' => 'نوبت آزادی موجود نیست', 'hours_of_work' => 'برنامه کاری تنظیم نشده', 'has_schedule' => false, 'rank' => [7, '99:99']];
|
||||
}
|
||||
|
||||
// گروهبندی روزهای متوالی با ساعت یکسان
|
||||
@@ -485,11 +508,13 @@ class Doctor
|
||||
$iranDay = $phpDay === 0 ? 1 : ($phpDay === 6 ? 0 : $phpDay + 1);
|
||||
|
||||
$freeTurn = null;
|
||||
$rank = [7, '99:99'];
|
||||
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;
|
||||
$rank = [$i, explode('–', $firstTime)[0]];
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -498,6 +523,8 @@ class Doctor
|
||||
'free_turn' => $freeTurn ?? 'نوبت آزادی موجود نیست',
|
||||
'hours_of_work' => implode(' | ', $parts),
|
||||
'has_schedule' => true,
|
||||
// فاصله تا نزدیکترین روز کاری + ساعت شروع — برای مقایسهٔ بین برنامهها
|
||||
'rank' => $rank,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -509,9 +536,10 @@ class Doctor
|
||||
return max(0, (int)((time() - $this->activityTime) / (365.25 * 24 * 3600)));
|
||||
}
|
||||
|
||||
public function toListArray(?WeeklySchedule $schedule = null): array
|
||||
/** @param WeeklySchedule[] $schedules همهٔ برنامههای پزشک (شخصی + کلینیکها) */
|
||||
public function toListArray(array $schedules = []): array
|
||||
{
|
||||
$sf = $this->computeScheduleFields($schedule);
|
||||
$sf = $this->computeScheduleFields($schedules);
|
||||
return [
|
||||
'id' => (string) $this->id,
|
||||
'uuid' => $this->uuid,
|
||||
@@ -533,9 +561,10 @@ class Doctor
|
||||
];
|
||||
}
|
||||
|
||||
public function toDetailArray(?WeeklySchedule $schedule = null): array
|
||||
/** @param WeeklySchedule[] $schedules همهٔ برنامههای پزشک (شخصی + کلینیکها) */
|
||||
public function toDetailArray(array $schedules = []): array
|
||||
{
|
||||
$sf = $this->computeScheduleFields($schedule);
|
||||
$sf = $this->computeScheduleFields($schedules);
|
||||
return [
|
||||
'id' => (string) $this->id,
|
||||
'uuid' => $this->uuid,
|
||||
|
||||
Reference in New Issue
Block a user