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>> $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 */ private function decode(mixed $raw): array { $decoded = json_decode((string) $raw, true); return is_array($decoded) ? $decoded : []; } /** * @param array $base * @param array $extra * * @return array */ 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; } }