feat(tenant): mark the booking tables with their owning environment
Phase 2 of the tenant-marking series. appointments, weekly_schedules and date_overrides kept their environment implicit in a nullable clinic_id, so every query that wanted "this environment's rows" had to rebuild clinic_id IS NULL ? doctor : clinic itself. The two calendar tables also depended on a MariaDB-only generated column, clinic_key = IFNULL(clinic_id, 0), purely to make a unique key work across NULLs. All three now carry the (entity_type, entity_id) pair that service_sections, patient_records and clinic_staff already use, via a shared TenantOwnedTrait. The pair is a deliberate denormalisation of clinic_id/doctor_id: the automatic tenant filter and the tenant-leading indexes both need a real column, and neither can be built on an IF() expression. - Unique keys keep doctor_id alongside the pair. A clinic has several doctors and each has their own schedule, so (entity_type, entity_id) alone would reject the second doctor. - clinic_key is gone from both calendar tables. - appointments gained tenant-leading indexes; EXPLAIN on the panel's list query now picks idx_appointments_tenant_slot. Deliberately unchanged, both with the reason already recorded in the code: active_slot_key stays keyed on doctor + slot, since adding the environment would let one doctor be booked in their own practice and a clinic at the same moment. holidays keeps its nullable clinic_id, where NULL means "every environment" rather than "personal practice" — a meaning the pair cannot carry. The migration adds the columns nullable, backfills, aborts if any row is left without an owner, and only then tightens to NOT NULL. It creates each replacement unique index before dropping the old one, so the tables are never left unprotected — MariaDB commits implicitly on DDL, so ordering is the only safety net. It runs its statements through the connection rather than addSql() because the guard has to sit between the backfill and the NOT NULL change. Columns are NOT NULL with no default on purpose: a construction site that forgets assignTenant() fails at flush instead of silently writing entity_id 0, which the phase 4 filter would then hide from everyone. Verified on the dev database: 0 rows without a tenant, 0 personal bookings mismatched against their doctor, 0 clinic bookings mismatched against their clinic. Tests: 819 passing (813 + 6 new in BookingTenantTest). PHPStan clean on every changed file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,13 @@
|
||||
|
||||
namespace App\Tests;
|
||||
|
||||
use App\Appointment\Entity\Appointment;
|
||||
use App\Appointment\Entity\DateOverride;
|
||||
use App\Appointment\Entity\WeeklySchedule;
|
||||
use App\Auth\Entity\User;
|
||||
use App\Clinic\Entity\Clinic;
|
||||
use App\Doctor\Entity\Doctor;
|
||||
use App\Shared\Context\EntityContext;
|
||||
use App\Subscription\Entity\SubscriptionPlan;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;
|
||||
@@ -70,6 +76,74 @@ abstract class ApiTestCase extends WebTestCase
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* A booking with its owning tenant already assigned — the test-side mirror of
|
||||
* what the booking controllers do after resolving the environment. Constructing
|
||||
* an Appointment without it fails at flush, since entity_type/entity_id are NOT
|
||||
* NULL and have no default.
|
||||
*/
|
||||
protected function newAppointment(
|
||||
Doctor $doctor,
|
||||
User $patient,
|
||||
int $slotStart,
|
||||
int $slotEnd,
|
||||
?Clinic $clinic = null,
|
||||
): Appointment {
|
||||
$appointment = new Appointment($doctor, $patient, $slotStart, $slotEnd);
|
||||
$appointment->setClinic($clinic);
|
||||
$appointment->assignTenant(EntityContext::forBooking($doctor, $clinic));
|
||||
|
||||
return $appointment;
|
||||
}
|
||||
|
||||
/**
|
||||
* A weekly schedule with its owning tenant assigned. The doctor is flushed
|
||||
* first when it has no id yet: the tenant pair stores scalars, so the owner
|
||||
* must already exist in the database.
|
||||
*/
|
||||
protected function newWeeklySchedule(Doctor $doctor, array $setting, ?Clinic $clinic = null): WeeklySchedule
|
||||
{
|
||||
$this->flushIfNew($doctor, $clinic);
|
||||
|
||||
$schedule = new WeeklySchedule($doctor, $setting, $clinic);
|
||||
$schedule->assignTenant(EntityContext::forBooking($doctor, $clinic));
|
||||
|
||||
return $schedule;
|
||||
}
|
||||
|
||||
protected function newDateOverride(Doctor $doctor, int $date, bool $active = false, ?Clinic $clinic = null): DateOverride
|
||||
{
|
||||
$this->flushIfNew($doctor, $clinic);
|
||||
|
||||
$override = new DateOverride($doctor, $date, $active, $clinic);
|
||||
$override->assignTenant(EntityContext::forBooking($doctor, $clinic));
|
||||
|
||||
return $override;
|
||||
}
|
||||
|
||||
/**
|
||||
* The tenant pair stores scalars, so the owner needs a database id before it
|
||||
* can be assigned. persist() on an already-managed entity is a no-op, which
|
||||
* makes this safe for owners the test never persisted itself.
|
||||
*/
|
||||
private function flushIfNew(Doctor $doctor, ?Clinic $clinic): void
|
||||
{
|
||||
$pending = array_filter(
|
||||
[$doctor, $clinic],
|
||||
static fn(?object $owner) => $owner !== null && $owner->getId() === null,
|
||||
);
|
||||
|
||||
if ($pending === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($pending as $owner) {
|
||||
$this->em->persist($owner);
|
||||
}
|
||||
|
||||
$this->em->flush();
|
||||
}
|
||||
|
||||
protected function jwtFor(User $user): string
|
||||
{
|
||||
return static::getContainer()
|
||||
|
||||
@@ -54,8 +54,7 @@ class AppointmentConfirmFlowTest extends ApiTestCase
|
||||
// اسلات یکتا بهازای هر نوبت: db_test بین اجراها پاک نمیشود.
|
||||
$start = strtotime('+30 days') + random_int(0, 500_000) * 7;
|
||||
|
||||
$appointment = new Appointment($doctor, $patient, $start, $start + 900);
|
||||
$appointment->setClinic($clinic);
|
||||
$appointment = $this->newAppointment($doctor, $patient, $start, $start + 900, $clinic);
|
||||
$appointment->setVisitPriceRials($visitPriceRials);
|
||||
$this->em->persist($appointment);
|
||||
$this->em->flush();
|
||||
|
||||
@@ -35,7 +35,7 @@ class AppointmentConfirmInsuranceSharesTest extends ApiTestCase
|
||||
private function makeAppointment(Doctor $doctor): Appointment
|
||||
{
|
||||
$start = strtotime('+30 days') + random_int(0, 500_000) * 7;
|
||||
$appointment = new Appointment($doctor, $this->createUser(), $start, $start + 900);
|
||||
$appointment = $this->newAppointment($doctor, $this->createUser(), $start, $start + 900);
|
||||
$appointment->setVisitPriceRials(self::VISIT_RIALS);
|
||||
$this->em->persist($appointment);
|
||||
$this->em->flush();
|
||||
|
||||
@@ -27,7 +27,7 @@ class AppointmentExpiryServiceTest extends ApiTestCase
|
||||
$patient = $this->createUser(['ROLE_USER']);
|
||||
// distinct past slots — one live booking per (doctor, slot)
|
||||
$slotStart = $past - $i * 1000;
|
||||
$appt = new Appointment($doctor, $patient, $slotStart, $slotStart + 900);
|
||||
$appt = $this->newAppointment($doctor, $patient, $slotStart, $slotStart + 900);
|
||||
// مثل مسیر واقعیِ رزرو آنلاین: نگهداشتِ موقت تا پرداخت درگاه.
|
||||
$appt->markPendingWithTtl(-1);
|
||||
$this->em->persist($appt);
|
||||
@@ -67,7 +67,7 @@ class AppointmentExpiryServiceTest extends ApiTestCase
|
||||
$this->em->persist($doctor);
|
||||
|
||||
$slotStart = time() - 7200;
|
||||
$appt = new Appointment($doctor, $this->createUser(['ROLE_USER']), $slotStart, $slotStart + 900);
|
||||
$appt = $this->newAppointment($doctor, $this->createUser(['ROLE_USER']), $slotStart, $slotStart + 900);
|
||||
$this->em->persist($appt);
|
||||
$this->em->flush();
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ class AppointmentInsuranceSelectionTest extends ApiTestCase
|
||||
{
|
||||
// اسلات یکتا بهازای هر نوبت: db_test بین اجراها پاک نمیشود.
|
||||
$start = strtotime('+30 days') + random_int(0, 500_000) * 7;
|
||||
$appointment = new Appointment($doctor, $this->createUser(), $start, $start + 900);
|
||||
$appointment = $this->newAppointment($doctor, $this->createUser(), $start, $start + 900);
|
||||
$appointment->setVisitPriceRials(5_952_000);
|
||||
$this->em->persist($appointment);
|
||||
$this->em->flush();
|
||||
|
||||
@@ -23,7 +23,7 @@ class AppointmentUpdateTest extends ApiTestCase
|
||||
$this->em->persist($doctor);
|
||||
$this->em->flush();
|
||||
|
||||
$appointment = new Appointment($doctor, $this->createUser(), time() + 86_400, time() + 86_400 + 1_800);
|
||||
$appointment = $this->newAppointment($doctor, $this->createUser(), time() + 86_400, time() + 86_400 + 1_800);
|
||||
$this->em->persist($appointment);
|
||||
$this->em->flush();
|
||||
|
||||
@@ -77,7 +77,7 @@ class AppointmentUpdateTest extends ApiTestCase
|
||||
[$owner, $doctor, $appointment] = $this->booking();
|
||||
|
||||
$otherStart = time() + 3 * 86_400;
|
||||
$this->em->persist(new Appointment($doctor, $this->createUser(), $otherStart, $otherStart + 1_800));
|
||||
$this->em->persist($this->newAppointment($doctor, $this->createUser(), $otherStart, $otherStart + 1_800));
|
||||
$this->em->flush();
|
||||
|
||||
$this->authJson('PATCH', '/api/v1/appointment/' . $appointment->getUuid(), $owner, [
|
||||
|
||||
@@ -24,7 +24,7 @@ class AppointmentWorkflowFieldsTest extends ApiTestCase
|
||||
|
||||
private function newBooking(Doctor $doctor, int $start): Appointment
|
||||
{
|
||||
$a = new Appointment($doctor, $this->createUser(), $start, $start + 1_800);
|
||||
$a = $this->newAppointment($doctor, $this->createUser(), $start, $start + 1_800);
|
||||
$this->em->persist($a);
|
||||
$this->em->flush();
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ class BookingLocationValidityTest extends ApiTestCase
|
||||
'duration_per_patient' => 20,
|
||||
], fn($v) => $v !== null)]];
|
||||
|
||||
$schedule = new WeeklySchedule(
|
||||
$schedule = $this->newWeeklySchedule(
|
||||
$doctor,
|
||||
array_fill_keys(array_map('strval', range(0, 6)), $day),
|
||||
$clinic
|
||||
@@ -128,7 +128,7 @@ class BookingLocationValidityTest extends ApiTestCase
|
||||
]]];
|
||||
$setting = array_fill_keys(array_map('strval', range(0, 6)), ['sessions' => []]);
|
||||
$setting['0'] = $active;
|
||||
$this->em->persist(new WeeklySchedule($doctor, $setting));
|
||||
$this->em->persist($this->newWeeklySchedule($doctor, $setting));
|
||||
$this->em->flush();
|
||||
|
||||
$saturday = $this->nextWeekday(6); // date('w'): 6 = Saturday
|
||||
@@ -165,7 +165,7 @@ class BookingLocationValidityTest extends ApiTestCase
|
||||
$this->em->flush();
|
||||
|
||||
$setting = array_fill_keys(array_map('strval', range(0, 6)), ['sessions' => []]);
|
||||
$this->em->persist(new WeeklySchedule($doctor, $setting));
|
||||
$this->em->persist($this->newWeeklySchedule($doctor, $setting));
|
||||
$this->em->flush();
|
||||
|
||||
$body = $this->authJson('GET', "/api/v1/appointment-slots?doctor_uuid={$doctor->getUuid()}&date={$today}", $doctor->getUser());
|
||||
|
||||
@@ -43,7 +43,7 @@ class BookingLocationsScanTest extends ApiTestCase
|
||||
$this->em->persist($personalAddress);
|
||||
$this->em->flush();
|
||||
|
||||
$this->em->persist(new WeeklySchedule(
|
||||
$this->em->persist($this->newWeeklySchedule(
|
||||
$doctor,
|
||||
$this->weekOfSessions($personalAddress->getId(), '09:00', '13:00')
|
||||
));
|
||||
@@ -59,7 +59,7 @@ class BookingLocationsScanTest extends ApiTestCase
|
||||
$this->em->persist($address);
|
||||
$this->em->flush();
|
||||
|
||||
$this->em->persist(new WeeklySchedule(
|
||||
$this->em->persist($this->newWeeklySchedule(
|
||||
$doctor,
|
||||
$this->weekOfSessions($address->getId(), '16:00', '20:00'),
|
||||
$clinic
|
||||
|
||||
@@ -29,7 +29,7 @@ class BookingServicesPublicTest extends ApiTestCase
|
||||
$this->em->persist($bookable);
|
||||
$this->em->persist($hidden);
|
||||
|
||||
$schedule = new WeeklySchedule($doctor, []);
|
||||
$schedule = $this->newWeeklySchedule($doctor, []);
|
||||
$schedule->setMeta(['booking_mode' => 'service', 'buffer_minutes' => 5]);
|
||||
$this->em->persist($schedule);
|
||||
$this->em->flush();
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Appointment;
|
||||
|
||||
use App\Appointment\Entity\Appointment;
|
||||
use App\Appointment\Entity\WeeklySchedule;
|
||||
use App\Clinic\Entity\Clinic;
|
||||
use App\Doctor\Entity\Doctor;
|
||||
use App\Shared\Context\EntityContext;
|
||||
use App\Tests\ApiTestCase;
|
||||
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
|
||||
|
||||
/**
|
||||
* Phase 2 of tenant marking: appointments, weekly schedules and date overrides
|
||||
* carry their owning environment as the (entity_type, entity_id) pair rather
|
||||
* than leaving it implicit in a nullable clinic_id.
|
||||
*/
|
||||
class BookingTenantTest extends ApiTestCase
|
||||
{
|
||||
private function makeDoctor(): Doctor
|
||||
{
|
||||
$doctor = new Doctor($this->createUser(['ROLE_DOCTOR']), 'دکتر تست محیط');
|
||||
$this->em->persist($doctor);
|
||||
$this->em->flush();
|
||||
|
||||
return $doctor;
|
||||
}
|
||||
|
||||
private function makeClinic(): Clinic
|
||||
{
|
||||
$clinic = new Clinic($this->createUser(['ROLE_CLINIC']));
|
||||
$clinic->setName('کلینیک تست محیط');
|
||||
$this->em->persist($clinic);
|
||||
$this->em->flush();
|
||||
|
||||
return $clinic;
|
||||
}
|
||||
|
||||
private function uniqueSlot(): int
|
||||
{
|
||||
return strtotime('+90 days') + random_int(0, 500_000) * 7;
|
||||
}
|
||||
|
||||
public function testPersonalBookingIsOwnedByTheDoctor(): void
|
||||
{
|
||||
$doctor = $this->makeDoctor();
|
||||
$start = $this->uniqueSlot();
|
||||
|
||||
$appointment = $this->newAppointment($doctor, $this->createUser(), $start, $start + 900);
|
||||
$this->em->persist($appointment);
|
||||
$this->em->flush();
|
||||
|
||||
self::assertSame('doctor', $appointment->getEntityType());
|
||||
self::assertSame($doctor->getId(), $appointment->getEntityId());
|
||||
}
|
||||
|
||||
public function testClinicBookingIsOwnedByTheClinic(): void
|
||||
{
|
||||
$doctor = $this->makeDoctor();
|
||||
$clinic = $this->makeClinic();
|
||||
$clinic->getDoctors()->add($doctor);
|
||||
$this->em->flush();
|
||||
|
||||
$start = $this->uniqueSlot();
|
||||
$appointment = $this->newAppointment($doctor, $this->createUser(), $start, $start + 900, $clinic);
|
||||
$this->em->persist($appointment);
|
||||
$this->em->flush();
|
||||
|
||||
self::assertSame('clinic', $appointment->getEntityType());
|
||||
self::assertSame($clinic->getId(), $appointment->getEntityId());
|
||||
}
|
||||
|
||||
/** یک پزشک در دو محیط = دو مالک متفاوت برای دو نوبت. */
|
||||
public function testSameDoctorGetsDifferentOwnersInDifferentEnvironments(): void
|
||||
{
|
||||
$doctor = $this->makeDoctor();
|
||||
$clinic = $this->makeClinic();
|
||||
$clinic->getDoctors()->add($doctor);
|
||||
$this->em->flush();
|
||||
|
||||
$personal = $this->newAppointment($doctor, $this->createUser(), $this->uniqueSlot(), $this->uniqueSlot() + 900);
|
||||
$inClinic = $this->newAppointment($doctor, $this->createUser(), $this->uniqueSlot(), $this->uniqueSlot() + 900, $clinic);
|
||||
$this->em->persist($personal);
|
||||
$this->em->persist($inClinic);
|
||||
$this->em->flush();
|
||||
|
||||
self::assertSame(['doctor', $doctor->getId()], [$personal->getEntityType(), $personal->getEntityId()]);
|
||||
self::assertSame(['clinic', $clinic->getId()], [$inClinic->getEntityType(), $inClinic->getEntityId()]);
|
||||
}
|
||||
|
||||
/** ❌ محیط حلنشده هرگز ذخیره نمیشود — نه با شناسهٔ صفر، نه بیصدا. */
|
||||
public function testAssigningAnUnresolvedContextIsRejected(): void
|
||||
{
|
||||
$doctor = $this->makeDoctor();
|
||||
$start = $this->uniqueSlot();
|
||||
$appointment = new Appointment($doctor, $this->createUser(), $start, $start + 900);
|
||||
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$appointment->assignTenant(EntityContext::unknown());
|
||||
}
|
||||
|
||||
/**
|
||||
* ⚠️ حالت مرزی: دو پزشکِ متفاوت در یک کلینیک، هر کدام برنامهٔ هفتگی خودشان —
|
||||
* کلید یکتا باید doctor_id را هم داشته باشد وگرنه پزشک دوم رد میشود.
|
||||
*/
|
||||
public function testTwoDoctorsInOneClinicEachKeepTheirOwnSchedule(): void
|
||||
{
|
||||
$clinic = $this->makeClinic();
|
||||
$doctorA = $this->makeDoctor();
|
||||
$doctorB = $this->makeDoctor();
|
||||
$clinic->getDoctors()->add($doctorA);
|
||||
$clinic->getDoctors()->add($doctorB);
|
||||
$this->em->flush();
|
||||
|
||||
$this->em->persist($this->newWeeklySchedule($doctorA, [], $clinic));
|
||||
$this->em->persist($this->newWeeklySchedule($doctorB, [], $clinic));
|
||||
$this->em->flush();
|
||||
|
||||
$rows = $this->em->getRepository(WeeklySchedule::class)
|
||||
->findBy(['entityType' => 'clinic', 'entityId' => $clinic->getId()]);
|
||||
|
||||
self::assertCount(2, $rows);
|
||||
}
|
||||
|
||||
/** همان پزشک، همان محیط، دو برنامه — هنوز غیرممکن است. */
|
||||
public function testOneSchedulePerEnvironmentPerDoctorIsStillEnforced(): void
|
||||
{
|
||||
$doctor = $this->makeDoctor();
|
||||
|
||||
$this->em->persist($this->newWeeklySchedule($doctor, []));
|
||||
$this->em->flush();
|
||||
|
||||
$this->em->persist($this->newWeeklySchedule($doctor, []));
|
||||
$this->expectException(UniqueConstraintViolationException::class);
|
||||
$this->em->flush();
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ class BookingWindowUnitTest extends ApiTestCase
|
||||
]]];
|
||||
}
|
||||
|
||||
$schedule = new WeeklySchedule($doctor, $days);
|
||||
$schedule = $this->newWeeklySchedule($doctor, $days);
|
||||
if ($meta !== []) {
|
||||
$schedule->setMeta($meta);
|
||||
}
|
||||
@@ -94,7 +94,7 @@ class BookingWindowUnitTest extends ApiTestCase
|
||||
{
|
||||
$owner = $this->createUser(['ROLE_DOCTOR']);
|
||||
$doctor = new Doctor($owner, 'دکتر واحد نامعتبر');
|
||||
$schedule = new WeeklySchedule($doctor, []);
|
||||
$schedule = $this->newWeeklySchedule($doctor, []);
|
||||
|
||||
$schedule->setMeta(['booking_window_unit' => 'day', 'booking_window_value' => 10]);
|
||||
$schedule->setMeta(['booking_window_unit' => 'year']);
|
||||
|
||||
@@ -49,8 +49,7 @@ class ClinicAppointmentAccessTest extends ApiTestCase
|
||||
$patient ??= $this->createUser();
|
||||
$start = strtotime('+3 days 10:00');
|
||||
|
||||
$appointment = new Appointment($doctor, $patient, $start, $start + 900);
|
||||
$appointment->setClinic($clinic);
|
||||
$appointment = $this->newAppointment($doctor, $patient, $start, $start + 900, $clinic);
|
||||
$this->em->persist($appointment);
|
||||
$this->em->flush();
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ class DateOverrideOwnershipTest extends ApiTestCase
|
||||
$owner = $this->createUser(['ROLE_DOCTOR']);
|
||||
$doctor = new Doctor($owner, 'دکتر تست');
|
||||
$this->em->persist($doctor);
|
||||
$override = new DateOverride($doctor, time(), true);
|
||||
$override = $this->newDateOverride($doctor, time(), true);
|
||||
$this->em->persist($override);
|
||||
$this->em->flush();
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ class DoctorAppointmentFilterTest extends ApiTestCase
|
||||
|
||||
private function booking(Doctor $doctor, int $start, ?string $status = null, ?string $name = null): Appointment
|
||||
{
|
||||
$a = new Appointment($doctor, $this->createUser(), $start, $start + 1_800);
|
||||
$a = $this->newAppointment($doctor, $this->createUser(), $start, $start + 1_800);
|
||||
if ($name !== null) {
|
||||
$a->setPatientName($name);
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ class OnlineBookingManagementTest extends ApiTestCase
|
||||
$date = date('Y-m-d', strtotime('tomorrow'));
|
||||
$dayKey = (string)(((int) date('w', strtotime($date)) + 1) % 7);
|
||||
|
||||
$schedule = new WeeklySchedule($doctor, [
|
||||
$schedule = $this->newWeeklySchedule($doctor, [
|
||||
$dayKey => ['sessions' => [[
|
||||
'active' => true,
|
||||
'start_time' => '10:00',
|
||||
@@ -143,7 +143,7 @@ class OnlineBookingManagementTest extends ApiTestCase
|
||||
$dayKey = (string)(((int) date('w', strtotime($date)) + 1) % 7);
|
||||
|
||||
// برنامهٔ نوبتدهی در محیط کلینیک با نوبتدهی آنلاینِ خاموش.
|
||||
$schedule = new WeeklySchedule($doctor, [
|
||||
$schedule = $this->newWeeklySchedule($doctor, [
|
||||
$dayKey => ['sessions' => [[
|
||||
'active' => true, 'start_time' => '10:00', 'end_time' => '12:00',
|
||||
'duration_per_patient' => 30, 'location_id' => 1,
|
||||
|
||||
@@ -18,7 +18,7 @@ class ScheduleOwnershipTest extends ApiTestCase
|
||||
$owner = $this->createUser(['ROLE_DOCTOR']);
|
||||
$doctor = new Doctor($owner, 'دکتر تست');
|
||||
$this->em->persist($doctor);
|
||||
$schedule = new WeeklySchedule($doctor, ['sat' => []]);
|
||||
$schedule = $this->newWeeklySchedule($doctor, ['sat' => []]);
|
||||
$this->em->persist($schedule);
|
||||
$this->em->flush();
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ class ServiceBasedSlotsTest extends ApiTestCase
|
||||
$date = date('Y-m-d', strtotime('tomorrow'));
|
||||
$dayKey = (string)(((int) date('w', strtotime($date)) + 1) % 7);
|
||||
|
||||
$schedule = new WeeklySchedule($doctor, [
|
||||
$schedule = $this->newWeeklySchedule($doctor, [
|
||||
$dayKey => ['sessions' => [[
|
||||
'active' => true,
|
||||
'start_time' => '15:00',
|
||||
@@ -61,7 +61,7 @@ class ServiceBasedSlotsTest extends ApiTestCase
|
||||
|
||||
$patient = $this->createUser(['ROLE_USER']);
|
||||
$start = strtotime($date . ' 15:00');
|
||||
$appt = new Appointment($doctor, $patient, $start, $start + 30 * 60);
|
||||
$appt = $this->newAppointment($doctor, $patient, $start, $start + 30 * 60);
|
||||
$appt->transitionTo(Appointment::STATUS_CONFIRMED);
|
||||
$this->em->persist($appt);
|
||||
$this->em->flush();
|
||||
@@ -87,7 +87,7 @@ class ServiceBasedSlotsTest extends ApiTestCase
|
||||
{
|
||||
$owner = $this->createUser(['ROLE_DOCTOR']);
|
||||
$doctor = new Doctor($owner, 'دکتر متا');
|
||||
$schedule = new WeeklySchedule($doctor, []);
|
||||
$schedule = $this->newWeeklySchedule($doctor, []);
|
||||
|
||||
// پیشفرض = اسلاتی
|
||||
$this->assertSame(WeeklySchedule::MODE_SLOT, $schedule->getMeta()['booking_mode']);
|
||||
|
||||
@@ -24,7 +24,7 @@ class ServiceModeSectionDurationTest extends ApiTestCase
|
||||
|
||||
$date = date('Y-m-d', strtotime('tomorrow'));
|
||||
$dayKey = (string) (((int) date('w', strtotime($date)) + 1) % 7);
|
||||
$schedule = new WeeklySchedule($doctor, [
|
||||
$schedule = $this->newWeeklySchedule($doctor, [
|
||||
$dayKey => ['sessions' => [[
|
||||
'active' => true, 'start_time' => '15:00', 'end_time' => '19:00',
|
||||
'duration_per_patient' => 20, 'location_id' => 1,
|
||||
|
||||
@@ -27,7 +27,7 @@ class SlotUniquenessTest extends ApiTestCase
|
||||
|
||||
private function newBooking(Doctor $doctor, int $start): Appointment
|
||||
{
|
||||
return new Appointment($doctor, $this->createUser(), $start, $start + 1_800);
|
||||
return $this->newAppointment($doctor, $this->createUser(), $start, $start + 1_800);
|
||||
}
|
||||
|
||||
public function testTwoLiveBookingsOnSameSlotViolateUniqueKey(): void
|
||||
|
||||
@@ -88,7 +88,7 @@ class DashboardChartPeriodTest extends ApiTestCase
|
||||
$patient = $this->createUser(['ROLE_USER']);
|
||||
|
||||
$start = time();
|
||||
$this->em->persist(new Appointment($doctor, $patient, $start, $start + 1_800));
|
||||
$this->em->persist($this->newAppointment($doctor, $patient, $start, $start + 1_800));
|
||||
$this->em->flush();
|
||||
|
||||
$res = $this->authJson('GET', '/api/v1/dashboard/clinic', $owner);
|
||||
|
||||
@@ -24,7 +24,7 @@ class DashboardTodayAppointmentsTest extends ApiTestCase
|
||||
{
|
||||
// now() is guaranteed within [today midnight, tomorrow midnight-1]
|
||||
$start = time();
|
||||
$appt = new Appointment($doctor, $patient, $start, $start + 1_800);
|
||||
$appt = $this->newAppointment($doctor, $patient, $start, $start + 1_800);
|
||||
$this->em->persist($appt);
|
||||
$this->em->flush();
|
||||
|
||||
|
||||
@@ -47,8 +47,8 @@ class UniqueConstraintsTest extends ApiTestCase
|
||||
$this->em->flush();
|
||||
|
||||
$date = time();
|
||||
$this->em->persist(new DateOverride($doctor, $date));
|
||||
$this->em->persist(new DateOverride($doctor, $date));
|
||||
$this->em->persist($this->newDateOverride($doctor, $date));
|
||||
$this->em->persist($this->newDateOverride($doctor, $date));
|
||||
|
||||
$this->expectException(UniqueConstraintViolationException::class);
|
||||
$this->em->flush();
|
||||
|
||||
@@ -60,8 +60,8 @@ class DoctorBookingStateAggregationTest extends ApiTestCase
|
||||
$this->em->persist($clinicAddress);
|
||||
$this->em->flush();
|
||||
|
||||
$personal = new WeeklySchedule($doctor, $this->week($this->session(false, $personalAddress->getId())));
|
||||
$clinicSchedule = new WeeklySchedule(
|
||||
$personal = $this->newWeeklySchedule($doctor, $this->week($this->session(false, $personalAddress->getId())));
|
||||
$clinicSchedule = $this->newWeeklySchedule(
|
||||
$doctor,
|
||||
$this->week($this->session(true, $clinicAddress->getId())),
|
||||
$clinic
|
||||
|
||||
@@ -39,10 +39,10 @@ class DoctorListBookableSortFilterTest extends ApiTestCase
|
||||
$this->flagOff->setActiveDoctorAppointment(false);
|
||||
|
||||
$days = [['sessions' => [['active' => true, 'start_time' => '09:00', 'end_time' => '13:00']]]];
|
||||
$this->em->persist(new WeeklySchedule($this->bookable, $days));
|
||||
$this->em->persist(new WeeklySchedule($this->flagOff, $days));
|
||||
$this->em->persist($this->newWeeklySchedule($this->bookable, $days));
|
||||
$this->em->persist($this->newWeeklySchedule($this->flagOff, $days));
|
||||
|
||||
$disabled = new WeeklySchedule($this->bookingDisabled, $days);
|
||||
$disabled = $this->newWeeklySchedule($this->bookingDisabled, $days);
|
||||
$disabled->setMeta(['online_booking_enabled' => false]);
|
||||
$this->em->persist($disabled);
|
||||
|
||||
|
||||
@@ -47,8 +47,7 @@ class AutoCreateSessionOnConfirmTest extends ApiTestCase
|
||||
|
||||
private function makeAppointment(Doctor $doctor, ?Clinic $clinic = null): Appointment
|
||||
{
|
||||
$appointment = new Appointment($doctor, $this->createUser(['ROLE_USER']), 1_790_000_000, 1_790_001_800);
|
||||
$appointment->setClinic($clinic);
|
||||
$appointment = $this->newAppointment($doctor, $this->createUser(['ROLE_USER']), 1_790_000_000, 1_790_001_800, $clinic);
|
||||
$appointment->transitionTo(Appointment::STATUS_CONFIRMED);
|
||||
$this->em->persist($appointment);
|
||||
$this->em->flush();
|
||||
@@ -110,7 +109,7 @@ class AutoCreateSessionOnConfirmTest extends ApiTestCase
|
||||
$this->confirmation()->onConfirmed($first);
|
||||
$this->em->flush();
|
||||
|
||||
$second = new Appointment($doctor, $patient, 1_790_100_000, 1_790_101_800);
|
||||
$second = $this->newAppointment($doctor, $patient, 1_790_100_000, 1_790_101_800);
|
||||
$second->transitionTo(Appointment::STATUS_CONFIRMED);
|
||||
$this->em->persist($second);
|
||||
$this->em->flush();
|
||||
|
||||
@@ -61,8 +61,7 @@ class ClinicRecordAccessTest extends ApiTestCase
|
||||
$this->em->persist($record);
|
||||
|
||||
$start = strtotime('+60 days') + random_int(0, 500_000) * 7;
|
||||
$appointment = new Appointment($doctor, $patient, $start, $start + 900);
|
||||
$appointment->setClinic($clinic);
|
||||
$appointment = $this->newAppointment($doctor, $patient, $start, $start + 900, $clinic);
|
||||
$this->em->persist($appointment);
|
||||
|
||||
$this->em->flush();
|
||||
@@ -221,8 +220,7 @@ class ClinicRecordAccessTest extends ApiTestCase
|
||||
$patient = $this->createUser();
|
||||
|
||||
$start = strtotime('+70 days') + random_int(0, 500_000) * 7;
|
||||
$appointment = new Appointment($doctor, $patient, $start, $start + 900);
|
||||
$appointment->setClinic($clinic);
|
||||
$appointment = $this->newAppointment($doctor, $patient, $start, $start + 900, $clinic);
|
||||
$appointment->setVisitPriceRials(3_000_000);
|
||||
$this->em->persist($appointment);
|
||||
$this->em->flush();
|
||||
|
||||
@@ -31,7 +31,7 @@ class PatientAppointmentsTest extends ApiTestCase
|
||||
|
||||
private function appointment(Doctor $doctor, PatientRecord $record, int $slotStart): Appointment
|
||||
{
|
||||
$appt = new Appointment($doctor, $record->getUser(), $slotStart, $slotStart + 1800);
|
||||
$appt = $this->newAppointment($doctor, $record->getUser(), $slotStart, $slotStart + 1800);
|
||||
$this->em->persist($appt);
|
||||
$this->em->flush();
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ class AppointmentPaidConfirmFilesSessionTest extends ApiTestCase
|
||||
$this->em->flush();
|
||||
|
||||
$slotStart = strtotime('+30 days') + random_int(0, 500_000) * 7;
|
||||
$appointment = new Appointment($doctor, $this->createUser(['ROLE_USER']), $slotStart, $slotStart + 1_800);
|
||||
$appointment = $this->newAppointment($doctor, $this->createUser(['ROLE_USER']), $slotStart, $slotStart + 1_800);
|
||||
// رزرو آنلاین با پنجرهٔ پرداخت ثبت میشود؛ پرداخت باید همین را پاک کند.
|
||||
$appointment->markPendingWithTtl(Appointment::PAYMENT_TTL);
|
||||
if ($withClinic !== null) {
|
||||
|
||||
@@ -39,8 +39,8 @@ class SecretaryAppointmentScopeTest extends ApiTestCase
|
||||
// یک نوبت برای هر پزشک
|
||||
$patient = $this->createUser(['ROLE_USER']);
|
||||
$start = time() + 3600;
|
||||
$this->em->persist(new Appointment($doctorA, $patient, $start, $start + 900));
|
||||
$this->em->persist(new Appointment($doctorB, $patient, $start + 1800, $start + 2700));
|
||||
$this->em->persist($this->newAppointment($doctorA, $patient, $start, $start + 900));
|
||||
$this->em->persist($this->newAppointment($doctorB, $patient, $start + 1800, $start + 2700));
|
||||
$this->em->flush();
|
||||
|
||||
$body = $this->authJson('GET', '/api/v1/my/appointments', $secretaryUser);
|
||||
@@ -66,7 +66,7 @@ class SecretaryAppointmentScopeTest extends ApiTestCase
|
||||
|
||||
$patient = $this->createUser(['ROLE_USER']);
|
||||
$start = time() + 3600;
|
||||
$this->em->persist(new Appointment($doctor, $patient, $start, $start + 900));
|
||||
$this->em->persist($this->newAppointment($doctor, $patient, $start, $start + 900));
|
||||
$this->em->flush();
|
||||
|
||||
$body = $this->authJson('GET', '/api/v1/my/appointments', $secretaryUser);
|
||||
|
||||
@@ -62,8 +62,7 @@ class SecretaryOnlineShareTest extends ApiTestCase
|
||||
private function makeOnlinePayment(Doctor $doctor, ?Clinic $clinic = null): Payment
|
||||
{
|
||||
$start = strtotime('+30 days') + random_int(0, 500_000) * 7;
|
||||
$appointment = new Appointment($doctor, $this->createUser(), $start, $start + 900);
|
||||
$appointment->setClinic($clinic);
|
||||
$appointment = $this->newAppointment($doctor, $this->createUser(), $start, $start + 900, $clinic);
|
||||
$this->em->persist($appointment);
|
||||
|
||||
$payment = new Payment($this->createUser(), self::GROSS, 'mock', Payment::TYPE_APPOINTMENT, '');
|
||||
|
||||
Reference in New Issue
Block a user