Files
clinicpro/migrations/Version20260718132000.php
hamedandClaude Opus 4.8 e6422014d1 fix(appointments): file the case file on every confirmation path
Confirming an appointment was supposed to create the patient's record and its
session, and PatientService already knew how. Only two of the five paths that
confirm an appointment ever called it, and the one that mattered most did not:
a booking paid for online was confirmed inside the payment callback, which
never ran the side-effects. Every Nobat724 booking therefore went unfiled — 7
confirmed appointments in dev had no session at all.

The side-effects now run through AppointmentConfirmationService, which every
path calls: the payment callback, both PATCH endpoints, and panel/admin
bookings. Creating the record can no longer roll back a confirmation or a
payment; a failure is logged and can be repaired with the new
app:appointment:backfill-sessions command.

Two related defects fixed along the way:

- A doctor working at a clinic got two records for one appointment, one under
  the doctor and one under the clinic, so a single visit's revenue was counted
  twice. The booking context now decides, and it decides once.
- That context was inferred from address_id, falling back to "the doctor's only
  clinic" — a guess that files an appointment under the wrong practice now that
  schedules are per-context. It is stored as appointments.clinic_id instead.

Panel and admin bookings were left pending forever: nothing confirmed them and
no payment was expected. They are created confirmed.

Repeat confirmations no longer duplicate the session; an archived one still
counts as filed, so archiving a mistaken visit does not resurrect it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 16:55:39 +03:30

50 lines
1.9 KiB
PHP

<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Makes an appointment's booking context explicit.
*
* The clinic used to be inferred at read time from address_id, falling back to
* "the doctor's only clinic" when the address was missing. With per-context
* weekly schedules that guess silently files an appointment under the wrong
* practice, so the context is now stored on the row.
*/
final class Version20260718132000 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add appointments.clinic_id (booking context; NULL = doctor personal practice)';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE appointments ADD clinic_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE appointments ADD CONSTRAINT FK_6A41727ACC22AD4 FOREIGN KEY (clinic_id) REFERENCES clinics (id) ON DELETE SET NULL');
$this->addSql('CREATE INDEX IDX_6A41727ACC22AD4 ON appointments (clinic_id)');
// Backfill only where the booked address genuinely belongs to a clinic.
// The old "doctor's only clinic" fallback is deliberately not reproduced:
// a row without a clinic address is a personal-practice appointment, and
// guessing here would bake the very bug this column removes into history.
$this->addSql(<<<'SQL'
UPDATE appointments a
JOIN doctor_addresses da ON da.id = a.address_id
SET a.clinic_id = da.clinic_id
WHERE da.clinic_id IS NOT NULL
SQL);
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE appointments DROP FOREIGN KEY FK_6A41727ACC22AD4');
$this->addSql('DROP INDEX IDX_6A41727ACC22AD4 ON appointments');
$this->addSql('ALTER TABLE appointments DROP clinic_id');
}
}