Files
clinicpro/tests/Appointment/ServiceBasedSlotsTest.php
T
hamed 5937f7e176 feat: add service-based booking mode to appointment scheduling
- Introduced a new booking mode in WeeklySchedule to support service-based appointments.
- Updated SlotCalculatorService to calculate available start times based on selected service durations and buffer times.
- Enhanced AppointmentController to handle service items during booking, calculating slot_end on the server side.
- Implemented validation to ensure at least one bookable service exists for doctors in service mode.
- Added new API endpoint to retrieve available appointment slots based on selected services.
- Updated MyAppointmentsController to accept service items during appointment creation.
- Modified ServiceItem entity to include a bookable flag, allowing services to be marked for scheduling.
- Created migration to add bookable column to service_items table.
- Added tests for service-based slot calculations and validation logic.
2026-07-15 23:15:45 +03:30

106 lines
4.4 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Tests\Appointment;
use App\Appointment\Entity\Appointment;
use App\Appointment\Entity\WeeklySchedule;
use App\Appointment\Service\SlotCalculatorService;
use App\Doctor\Entity\Doctor;
use App\Tests\ApiTestCase;
/**
* حالت نوبت‌دهی سرویسی: getServiceStartTimes باید فضای خالیِ داخل شیفت را با توجه
* به مدت سرویس (+ بافر) بچیند و بازه‌های اشغال‌شده را رد کند. همچنین متای mode/buffer.
*/
class ServiceBasedSlotsTest extends ApiTestCase
{
private function makeDoctorWithServiceSchedule(int $buffer = 5): array
{
$owner = $this->createUser(['ROLE_DOCTOR']);
$doctor = new Doctor($owner, 'دکتر سرویس');
$this->em->persist($doctor);
// فردا در بازهٔ booking-window قرار دارد و روز گذشته نیست.
$date = date('Y-m-d', strtotime('tomorrow'));
$dayKey = (string)(((int) date('w', strtotime($date)) + 1) % 7);
$schedule = new WeeklySchedule($doctor, [
$dayKey => ['sessions' => [[
'active' => true,
'start_time' => '15:00',
'end_time' => '17:00',
'duration_per_patient' => 20,
'location_id' => 1,
]]],
]);
$schedule->setMeta(['booking_mode' => WeeklySchedule::MODE_SERVICE, 'buffer_minutes' => $buffer]);
$this->em->persist($schedule);
$this->em->flush();
return [$doctor, $date];
}
public function testGapPackingWithBuffer(): void
{
[$doctor, $date] = $this->makeDoctorWithServiceSchedule(5);
$calc = static::getContainer()->get(SlotCalculatorService::class);
$slots = $calc->getServiceStartTimes($doctor, $date, 30);
// پنجره 15:0017:00، سرویس 30 + بافر 5 → گام 35 دقیقه: 15:00, 15:35, 16:10
$times = array_column($slots, 'start_time');
$this->assertSame(['15:00', '15:35', '16:10'], $times);
// زمان پایانِ ذخیره‌شده بدون بافر است.
$this->assertSame(strtotime($date . ' 15:00') + 30 * 60, $slots[0]['end']);
}
public function testBookedIntervalIsSkipped(): void
{
[$doctor, $date] = $this->makeDoctorWithServiceSchedule(5);
$patient = $this->createUser(['ROLE_USER']);
$start = strtotime($date . ' 15:00');
$appt = new Appointment($doctor, $patient, $start, $start + 30 * 60);
$appt->transitionTo(Appointment::STATUS_CONFIRMED);
$this->em->persist($appt);
$this->em->flush();
$calc = static::getContainer()->get(SlotCalculatorService::class);
$times = array_column($calc->getServiceStartTimes($doctor, $date, 30), 'start_time');
// 15:00 اشغال است → از 15:30 شروع می‌شود.
$this->assertNotContains('15:00', $times);
$this->assertContains('15:30', $times);
}
public function testNoRoomReturnsEmpty(): void
{
[$doctor, $date] = $this->makeDoctorWithServiceSchedule(0);
// سرویس 200 دقیقه در پنجرهٔ 120 دقیقه‌ای جا نمی‌شود.
$calc = static::getContainer()->get(SlotCalculatorService::class);
$this->assertSame([], $calc->getServiceStartTimes($doctor, $date, 200));
}
public function testMetaDefaultsAndWhitelist(): void
{
$owner = $this->createUser(['ROLE_DOCTOR']);
$doctor = new Doctor($owner, 'دکتر متا');
$schedule = new WeeklySchedule($doctor, []);
// پیش‌فرض = اسلاتی
$this->assertSame(WeeklySchedule::MODE_SLOT, $schedule->getMeta()['booking_mode']);
$this->assertSame(0, $schedule->getMeta()['buffer_minutes']);
$schedule->setMeta(['booking_mode' => 'service', 'buffer_minutes' => 7]);
$this->assertSame('service', $schedule->getMeta()['booking_mode']);
$this->assertSame(7, $schedule->getMeta()['buffer_minutes']);
// مقدار نامعتبر mode نادیده گرفته می‌شود (whitelist)، بافر منفی → صفر.
$schedule->setMeta(['booking_mode' => 'bogus', 'buffer_minutes' => -3]);
$this->assertSame('service', $schedule->getMeta()['booking_mode']);
$this->assertSame(0, $schedule->getMeta()['buffer_minutes']);
}
}