Files
clinicpro/migrations/Version20260731074710.php
T
hamedandClaude Opus 5 fc504f4415 feat(course): treatment courses with protocol-driven session planning
Laser is six to eight sessions; the previous design only knew single
appointments, which is the exception rather than the rule.

- CourseProtocol per service: session count and three distinct spacings —
  min is the earliest that is clinically allowed, ideal is best, max is where
  the course starts losing its effect
- Starting a course creates every session up front as `planned` and copies the
  protocol's numbers and per-session params, so changing the protocol tomorrow
  leaves a running course alone
- Suggestions anchor on the last *completed* session, not the course start:
  when session 2 slips, session 3 moves with it
- Slots are ranked by distance from ideal, not by earliest available — day 21
  is worse than day 27 when 28 is the target
- book-all is all-or-nothing inside one transaction, with a moving anchor and a
  90-day horizon; sessions past the horizon stay planned and are reported, not
  treated as failures
- The effective minimum is the stricter of the protocol and the task-09 spacing
  policy, so a clinic rule never fights the protocol
- Cancelling one session returns only that session to planned; abandoning a
  course does not cancel its appointments, which stays an explicit decision

One active course per (patient, service) via active_course_key, the same
partial-uniqueness trick as Appointment::activeSlotKey.

Admin: CourseProtocolsPage, TreatmentCoursePage and a courses tab on the
patient record.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 11:33:07 +03:30

56 lines
6.1 KiB
PHP

<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20260731074710 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE course_protocol_steps (id INT AUTO_INCREMENT NOT NULL, session_number SMALLINT NOT NULL, params JSON DEFAULT NULL, override_duration_minutes SMALLINT DEFAULT NULL, protocol_id INT NOT NULL, INDEX IDX_58A0726CCD59258 (protocol_id), UNIQUE INDEX uniq_step (protocol_id, session_number), PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4');
$this->addSql('CREATE TABLE course_protocols (id INT AUTO_INCREMENT NOT NULL, uuid VARCHAR(36) NOT NULL, session_count SMALLINT NOT NULL, min_days SMALLINT NOT NULL, ideal_days SMALLINT NOT NULL, max_days SMALLINT NOT NULL, prefer_same_resource TINYINT DEFAULT 1 NOT NULL, active TINYINT DEFAULT 1 NOT NULL, created_at INT NOT NULL, updated_at INT NOT NULL, entity_type VARCHAR(10) NOT NULL, entity_id INT NOT NULL, service_item_id INT NOT NULL, UNIQUE INDEX UNIQ_E60D42A0D17F50A6 (uuid), INDEX idx_protocols_tenant (entity_type, entity_id, active), UNIQUE INDEX uniq_protocol_service (service_item_id), PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4');
$this->addSql('CREATE TABLE course_sessions (id INT AUTO_INCREMENT NOT NULL, uuid VARCHAR(36) NOT NULL, session_number SMALLINT NOT NULL, params JSON DEFAULT NULL, status VARCHAR(12) DEFAULT \'planned\' NOT NULL, completed_at INT DEFAULT NULL, created_at INT NOT NULL, updated_at INT NOT NULL, entity_type VARCHAR(10) NOT NULL, entity_id INT NOT NULL, course_id INT NOT NULL, appointment_id INT DEFAULT NULL, UNIQUE INDEX UNIQ_33D4F045D17F50A6 (uuid), INDEX IDX_33D4F045591CC992 (course_id), INDEX idx_sessions_tenant (entity_type, entity_id, status), UNIQUE INDEX uniq_course_session (course_id, session_number), UNIQUE INDEX uniq_session_appointment (appointment_id), PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4');
$this->addSql('CREATE TABLE treatment_courses (id INT AUTO_INCREMENT NOT NULL, uuid VARCHAR(36) NOT NULL, session_count SMALLINT NOT NULL, min_days SMALLINT NOT NULL, ideal_days SMALLINT NOT NULL, max_days SMALLINT NOT NULL, status VARCHAR(12) DEFAULT \'active\' NOT NULL, active_course_key VARCHAR(64) DEFAULT NULL, abandon_reason VARCHAR(255) DEFAULT NULL, started_at INT NOT NULL, completed_at INT DEFAULT NULL, created_at INT NOT NULL, updated_at INT NOT NULL, entity_type VARCHAR(10) NOT NULL, entity_id INT NOT NULL, patient_record_id INT NOT NULL, service_item_id INT NOT NULL, protocol_id INT NOT NULL, patient_package_id INT DEFAULT NULL, preferred_resource_id INT DEFAULT NULL, UNIQUE INDEX UNIQ_D1639880D17F50A6 (uuid), UNIQUE INDEX UNIQ_D1639880DEBE4F6D (active_course_key), INDEX IDX_D1639880EB76A733 (patient_record_id), INDEX IDX_D1639880DDEB00C2 (service_item_id), INDEX IDX_D1639880CCD59258 (protocol_id), INDEX IDX_D1639880428C0D10 (patient_package_id), INDEX IDX_D1639880CB4D0F54 (preferred_resource_id), INDEX idx_courses_tenant (entity_type, entity_id, status, started_at), INDEX idx_courses_patient (patient_record_id, status), PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4');
$this->addSql('ALTER TABLE course_protocol_steps ADD CONSTRAINT FK_58A0726CCD59258 FOREIGN KEY (protocol_id) REFERENCES course_protocols (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE course_protocols ADD CONSTRAINT FK_E60D42A0DDEB00C2 FOREIGN KEY (service_item_id) REFERENCES service_items (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE course_sessions ADD CONSTRAINT FK_33D4F045591CC992 FOREIGN KEY (course_id) REFERENCES treatment_courses (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE course_sessions ADD CONSTRAINT FK_33D4F045E5B533F9 FOREIGN KEY (appointment_id) REFERENCES appointments (id) ON DELETE SET NULL');
$this->addSql('ALTER TABLE treatment_courses ADD CONSTRAINT FK_D1639880EB76A733 FOREIGN KEY (patient_record_id) REFERENCES patient_records (id) ON DELETE RESTRICT');
$this->addSql('ALTER TABLE treatment_courses ADD CONSTRAINT FK_D1639880DDEB00C2 FOREIGN KEY (service_item_id) REFERENCES service_items (id) ON DELETE RESTRICT');
$this->addSql('ALTER TABLE treatment_courses ADD CONSTRAINT FK_D1639880CCD59258 FOREIGN KEY (protocol_id) REFERENCES course_protocols (id) ON DELETE RESTRICT');
$this->addSql('ALTER TABLE treatment_courses ADD CONSTRAINT FK_D1639880428C0D10 FOREIGN KEY (patient_package_id) REFERENCES patient_packages (id) ON DELETE SET NULL');
$this->addSql('ALTER TABLE treatment_courses ADD CONSTRAINT FK_D1639880CB4D0F54 FOREIGN KEY (preferred_resource_id) REFERENCES clinic_resources (id) ON DELETE SET NULL');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE course_protocol_steps DROP FOREIGN KEY FK_58A0726CCD59258');
$this->addSql('ALTER TABLE course_protocols DROP FOREIGN KEY FK_E60D42A0DDEB00C2');
$this->addSql('ALTER TABLE course_sessions DROP FOREIGN KEY FK_33D4F045591CC992');
$this->addSql('ALTER TABLE course_sessions DROP FOREIGN KEY FK_33D4F045E5B533F9');
$this->addSql('ALTER TABLE treatment_courses DROP FOREIGN KEY FK_D1639880EB76A733');
$this->addSql('ALTER TABLE treatment_courses DROP FOREIGN KEY FK_D1639880DDEB00C2');
$this->addSql('ALTER TABLE treatment_courses DROP FOREIGN KEY FK_D1639880CCD59258');
$this->addSql('ALTER TABLE treatment_courses DROP FOREIGN KEY FK_D1639880428C0D10');
$this->addSql('ALTER TABLE treatment_courses DROP FOREIGN KEY FK_D1639880CB4D0F54');
$this->addSql('DROP TABLE course_protocol_steps');
$this->addSql('DROP TABLE course_protocols');
$this->addSql('DROP TABLE course_sessions');
$this->addSql('DROP TABLE treatment_courses');
}
}