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>
35 lines
2.6 KiB
PHP
35 lines
2.6 KiB
PHP
<?php
|
|
namespace App\Tests\Course;
|
|
use App\Clinic\Entity\Clinic; use App\ClinicService\Entity\ServiceItem; use App\ClinicService\Entity\ServiceSection;
|
|
use App\Doctor\Entity\DoctorAddress; use App\Patient\Entity\PatientRecord; use App\Tests\ApiTestCase;
|
|
use PHPUnit\Framework\Attributes\Group;
|
|
#[Group('docs')]
|
|
class CourseDocsCaptureTest extends ApiTestCase {
|
|
public function testCapture(): void {
|
|
if (getenv('COURSE_DOCS') !== '1') { self::markTestSkipped('برای تولید خروجی مستندات: COURSE_DOCS=1'); }
|
|
$user = $this->createUser(['ROLE_USER','ROLE_CLINIC']);
|
|
$clinic = new Clinic($user); $clinic->setName('کلینیک نمونه'); $this->em->persist($clinic); $this->em->flush();
|
|
$section = new ServiceSection('clinic', $clinic->getId(), 'لیزر'); $this->em->persist($section);
|
|
$address = DoctorAddress::forClinic($clinic->getId()); $address->setName('شعبهٔ مرکزی'); $this->em->persist($address);
|
|
$pu = $this->createUser(['ROLE_USER']);
|
|
$patient = new PatientRecord('clinic', (int)$clinic->getId(), $pu, 'clinic', (int)$clinic->getId());
|
|
$this->em->persist($patient); $this->em->flush();
|
|
$item = new ServiceItem($section, 'لیزر فولبادی'); $item->setSoloDurationMinutes(30); $item->setPriceRials(5000000);
|
|
$this->em->persist($item); $this->em->flush();
|
|
$d = function(string $l, mixed $b): void { fwrite(STDERR, sprintf("\n===%s %d===\n%s\n", $l, $this->responseCode(), json_encode($b, JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT))); };
|
|
$p = $this->authJson('POST','/api/v1/course-protocols',$user,[
|
|
'service_uuid'=>$item->getUuid(),'session_count'=>8,'min_days'=>21,'ideal_days'=>28,'max_days'=>45,
|
|
'steps'=>[['session_number'=>1,'params'=>['energy'=>12]],['session_number'=>2,'params'=>['energy'=>14]]],
|
|
]);
|
|
$d('PROTOCOL_CREATE', $p);
|
|
$c = $this->authJson('POST','/api/v1/treatment-course',$user,['patient_uuid'=>$patient->getUuid(),'protocol_uuid'=>$p['data']['uuid']]);
|
|
$d('COURSE_CREATE', $c);
|
|
$d('COURSE_SHOW', $this->authJson('GET',"/api/v1/treatment-course/{$c['data']['uuid']}",$user));
|
|
$d('NEXT_SLOT', $this->authJson('GET',"/api/v1/treatment-course/{$c['data']['uuid']}/next-slot-suggestion?branch_uuid={$address->getUuid()}",$user));
|
|
$d('PATIENT_COURSES', $this->authJson('GET',"/api/v1/patient/{$patient->getUuid()}/courses",$user));
|
|
$d('DUPLICATE', $this->authJson('POST','/api/v1/treatment-course',$user,['patient_uuid'=>$patient->getUuid(),'protocol_uuid'=>$p['data']['uuid']]));
|
|
$d('ABANDON', $this->authJson('POST',"/api/v1/treatment-course/{$c['data']['uuid']}/abandon",$user,['reason'=>'انصراف بیمار']));
|
|
self::assertTrue(true);
|
|
}
|
|
}
|