A doctor working both at their own practice and at a clinic had to write two independent schedules and neither panel could see the other, so the clinic showed an empty form even though the doctor had configured their practice. The schedule is now a single record owned by the doctor. What varies between days is the place: the context of a shift is read from its location_id, not from the record it lives in. Booking in a context therefore sees only that context's days, so a personal-practice secretary still cannot book a clinic day. The caller's own context decides which addresses they may assign: the doctor gets every place of theirs, a clinic manager only its own, and shifts outside their reach are returned for display but preserved verbatim on save. Existing per-clinic rows are merged by migration; location_id was already stored on every shift, so no context information is lost. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
114 lines
4.3 KiB
PHP
114 lines
4.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace DoctrineMigrations;
|
|
|
|
use Doctrine\DBAL\Schema\Schema;
|
|
use Doctrine\Migrations\AbstractMigration;
|
|
|
|
/**
|
|
* ادغام برنامههای هفتگیِ هر پزشک در یک برنامهٔ واحد.
|
|
*
|
|
* تا امروز هر پزشک به ازای مطب شخصی و هر کلینیک یک برنامهٔ جدا داشت، پس پزشکی که هر
|
|
* دو را دارد باید دو جا برنامه مینوشت و هیچکدام دیگری را نمیدید. برنامه یکی میشود
|
|
* و آنچه بین روزها فرق میکند مکان است؛ محیطِ هر شیفت از `location_id` همان شیفت
|
|
* خوانده میشود، که از قبل روی هر شیفت ذخیره شده بود.
|
|
*
|
|
* رکورد شخصی مبنا میماند (اگر نبود، قدیمیترین رکورد) و شیفتهای بقیه به همان روزها
|
|
* اضافه میشوند. `meta` رکورد مبنا برنده است: نوع نوبتدهی و بازهٔ رزرو ویژگی پزشکاند
|
|
* نه ویژگی روز، و ادغامشان معنا ندارد.
|
|
*/
|
|
final class Version20260820120000 extends AbstractMigration
|
|
{
|
|
private const META_KEY = 'meta';
|
|
|
|
public function getDescription(): string
|
|
{
|
|
return 'Merge per-context weekly schedules into one schedule per doctor';
|
|
}
|
|
|
|
public function up(Schema $schema): void
|
|
{
|
|
$rows = $this->connection->fetchAllAssociative(
|
|
'SELECT id, doctor_id, clinic_id, setting FROM weekly_schedules ORDER BY doctor_id ASC, clinic_id IS NULL DESC, id ASC'
|
|
);
|
|
|
|
/** @var array<int, list<array<string, mixed>>> $byDoctor */
|
|
$byDoctor = [];
|
|
foreach ($rows as $row) {
|
|
$byDoctor[(int) $row['doctor_id']][] = $row;
|
|
}
|
|
|
|
foreach ($byDoctor as $doctorId => $doctorRows) {
|
|
$base = array_shift($doctorRows);
|
|
$setting = $this->decode($base['setting']);
|
|
$deleteIds = [];
|
|
|
|
foreach ($doctorRows as $row) {
|
|
$setting = $this->mergeDays($setting, $this->decode($row['setting']));
|
|
$deleteIds[] = (int) $row['id'];
|
|
}
|
|
|
|
$this->connection->executeStatement(
|
|
'UPDATE weekly_schedules SET setting = :setting, clinic_id = NULL, entity_type = :type, entity_id = :entityId, updated_at = :now WHERE id = :id',
|
|
[
|
|
'setting' => json_encode($setting, JSON_UNESCAPED_UNICODE),
|
|
'type' => 'doctor',
|
|
'entityId' => $doctorId,
|
|
'now' => time(),
|
|
'id' => (int) $base['id'],
|
|
]
|
|
);
|
|
|
|
if ($deleteIds !== []) {
|
|
$this->connection->executeStatement(
|
|
'DELETE FROM weekly_schedules WHERE id IN (' . implode(',', $deleteIds) . ')'
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* برگشتناپذیر است: بعد از ادغام معلوم نیست کدام شیفت از کدام رکورد آمده بود.
|
|
* خودِ شیفتها از دست نمیروند و `location_id` هرکدام سر جایش است.
|
|
*/
|
|
public function down(Schema $schema): void
|
|
{
|
|
$this->throwIrreversibleMigration('Merged weekly schedules cannot be split back into per-clinic rows.');
|
|
}
|
|
|
|
/** @return array<string, mixed> */
|
|
private function decode(mixed $raw): array
|
|
{
|
|
$decoded = json_decode((string) $raw, true);
|
|
|
|
return is_array($decoded) ? $decoded : [];
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $base
|
|
* @param array<string, mixed> $extra
|
|
*
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function mergeDays(array $base, array $extra): array
|
|
{
|
|
foreach ($extra as $dayKey => $day) {
|
|
if ($dayKey === self::META_KEY || !is_array($day)) {
|
|
continue;
|
|
}
|
|
|
|
$sessions = $day['sessions'] ?? [];
|
|
if (!is_array($sessions) || $sessions === []) {
|
|
continue;
|
|
}
|
|
|
|
$existing = $base[$dayKey]['sessions'] ?? [];
|
|
$base[$dayKey] = ['sessions' => array_merge(is_array($existing) ? $existing : [], $sessions)];
|
|
}
|
|
|
|
return $base;
|
|
}
|
|
}
|