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:
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace App\Appointment\Command;
|
||||
|
||||
use App\Appointment\Repository\WeeklyScheduleRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
/**
|
||||
* Converts legacy weekly-schedule settings to the canonical shape.
|
||||
*
|
||||
* Canonical: keys 0..6 (0 = Saturday), each {"sessions": [...]}, plus a "meta"
|
||||
* key. Legacy rows are either a bare JSON list ([{"sessions": ...}]) or miss
|
||||
* some day keys entirely; readers treat the absent days as day-off, which does
|
||||
* not match what the owner configured and hides the row from per-day tooling.
|
||||
*/
|
||||
#[AsCommand(name: 'app:schedule:normalize-format', description: 'Report (and optionally rewrite) weekly schedules stored in a legacy setting format')]
|
||||
class NormalizeScheduleFormatCommand extends Command
|
||||
{
|
||||
private const DAY_COUNT = 7;
|
||||
|
||||
public function __construct(
|
||||
private readonly WeeklyScheduleRepository $scheduleRepo,
|
||||
private readonly EntityManagerInterface $em,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->addOption('fix', null, InputOption::VALUE_NONE, 'Rewrite offending rows to the canonical 7-day format');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$fix = (bool) $input->getOption('fix');
|
||||
|
||||
$rows = [];
|
||||
$touched = 0;
|
||||
|
||||
foreach ($this->scheduleRepo->findAll() as $schedule) {
|
||||
$setting = $schedule->getDaySchedule();
|
||||
// json_decode کلیدهای "0".."6" را به int تبدیل میکند؛ ردیف canonical کامل
|
||||
// هم list سرراست است. تنها نشانهٔ قابلاتکای فرمت legacy، غیبت روزهاست.
|
||||
$missing = [];
|
||||
for ($i = 0; $i < self::DAY_COUNT; $i++) {
|
||||
if (!isset($setting[$i]['sessions'])) {
|
||||
$missing[] = $i;
|
||||
}
|
||||
}
|
||||
|
||||
if ($missing === []) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$clinic = $schedule->getClinic();
|
||||
$rows[] = [
|
||||
$schedule->getDoctor()->getUuid(),
|
||||
$clinic === null ? 'personal' : ($clinic->getName() ?? 'clinic'),
|
||||
implode(',', $missing),
|
||||
];
|
||||
|
||||
if (!$fix) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$normalized = [];
|
||||
for ($i = 0; $i < self::DAY_COUNT; $i++) {
|
||||
$day = $setting[$i] ?? null;
|
||||
$normalized[$i] = is_array($day) && isset($day['sessions'])
|
||||
? $day
|
||||
: ['sessions' => []];
|
||||
}
|
||||
|
||||
// setSetting جای meta موجود را حفظ میکند؛ setMeta آن را (در نبودش با
|
||||
// DEFAULT_META) صریح در ردیف مینویسد تا فرمت canonical کامل شود.
|
||||
$schedule->setSetting($normalized);
|
||||
$schedule->setMeta($schedule->getMeta());
|
||||
$touched++;
|
||||
}
|
||||
|
||||
if ($rows === []) {
|
||||
$io->success('All weekly schedules already use the canonical 7-day format.');
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$io->table(['doctor', 'context', 'missing days'], $rows);
|
||||
|
||||
if (!$fix) {
|
||||
$io->warning(sprintf('%d legacy row(s). Re-run with --fix to rewrite them.', count($rows)));
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$this->em->flush();
|
||||
$io->success(sprintf('Rewrote %d schedule(s) to the canonical format.', $touched));
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -47,15 +47,6 @@ class WeeklyScheduleRepository extends ServiceEntityRepository
|
||||
->getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated برنامهٔ context شخصی را برمیگرداند. برای کد جدید از
|
||||
* findByDoctorAndClinic() استفاده کن تا context صریح باشد.
|
||||
*/
|
||||
public function findByDoctor(Doctor $doctor): ?WeeklySchedule
|
||||
{
|
||||
return $this->findByDoctorAndClinic($doctor, null);
|
||||
}
|
||||
|
||||
/** @param Doctor[] $doctors @return WeeklySchedule[] */
|
||||
public function findByDoctors(array $doctors): array
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user