Files
clinicpro/migrations/Version20260803093000.php
hamedandClaude Opus 5 ab4974d174 feat(resource): every resource is supervised by a doctor
Supervision now lives on the resource itself instead of being asked for again at
booking time, so one relation answers it everywhere.

The column is deliberately separate from the existing doctor_id bridge. That
bridge means "this resource IS this doctor" and isPerson() uses it to pin
capacity at 1; a supervised three-seat device must not become a person resource.
The FK is SET NULL rather than CASCADE because deleting a doctor should not take
the clinic's laser with it.

Required on create and non-clearable on update, enforced in the API where it can
give a Persian message. Ownership is checked through Clinic::hasDoctor so a
secretary cannot put their device under a doctor of another clinic; that returns
404, not 403, keeping foreign data invisible.

The 13 existing resources are backfilled deterministically: a practice resource
gets its own doctor, a clinic resource gets that clinic's first doctor. Both are
editable from the resource form.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 12:15:42 +03:30

60 lines
2.5 KiB
PHP

<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* پزشکِ ناظر روی منبع.
*
* هر منبع باید زیر نظر یک پزشک باشد، ولی ۱۳ منبعِ موجود ناظری ندارند. backfill
* قطعی و قابل‌توضیح است، نه دلبخواه:
* • منبعِ محیطِ پزشک → همان پزشکِ محیط
* • منبعِ محیطِ کلینیک → پزشکِ اولِ همان کلینیک (کمترین `doctor_id`)
*
* ستون تهی‌پذیر می‌ماند چون کلید خارجی `SET NULL` است: حذف یک پزشک نباید دستگاه
* کلینیک را با خودش ببرد. «الزامی بودن» ناظر در لایهٔ API اجرا می‌شود، جایی که
* می‌شود خطای فارسی و قابل‌فهم داد.
*/
final class Version20260803093000 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add supervisor doctor to clinic resources and backfill existing rows';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE clinic_resources ADD supervisor_id INT DEFAULT NULL');
$this->addSql(
'ALTER TABLE clinic_resources '
. 'ADD CONSTRAINT FK_resource_supervisor FOREIGN KEY (supervisor_id) '
. 'REFERENCES doctors (id) ON DELETE SET NULL'
);
$this->addSql('CREATE INDEX idx_resource_supervisor ON clinic_resources (supervisor_id)');
// منبعِ یک مطب شخصی: ناظرش خودِ همان پزشک است.
$this->addSql(
'UPDATE clinic_resources SET supervisor_id = entity_id '
. "WHERE supervisor_id IS NULL AND entity_type = 'doctor'"
);
// منبعِ کلینیک: پزشکِ اولِ همان کلینیک. اپراتور می‌تواند بعداً عوضش کند.
$this->addSql(
'UPDATE clinic_resources r SET r.supervisor_id = ('
. ' SELECT MIN(cd.doctor_id) FROM clinic_doctors cd WHERE cd.clinic_id = r.entity_id'
. ") WHERE r.supervisor_id IS NULL AND r.entity_type = 'clinic'"
);
}
public function down(Schema $schema): void
{
$this->addSql('DROP INDEX idx_resource_supervisor ON clinic_resources');
$this->addSql('ALTER TABLE clinic_resources DROP FOREIGN KEY FK_resource_supervisor');
$this->addSql('ALTER TABLE clinic_resources DROP supervisor_id');
}
}