fix(treatment): let the operator actually record an area
Starting a session created area records with no device, and the panel only ever read the device it never set — so every "اتمام این ناحیه" came back 422 with "دستگاه این ناحیه مشخص نیست". The backend tests passed because they sent resource_uuid explicitly; from the UI the flow was unusable end to end. The device now inherits from the appointment's resource, which the secretary already chose at booking; asking the operator again is taking one decision twice. The session screen offers a picker per area on top of that, because one session really does run bikini on an alexandrite and underarms on a diode. Treating without a device is allowed: botox is an injection, and requiring a device would make clinics invent a fake resource per injection. Sending readings with no device is still rejected — there would be no schema to validate against. A protocol whose service has no ResourceServiceOffering rows now says so in the tab where the manager is standing. It does not block booking: "no offering means any resource" is a deliberate, tested rule. But silence meant the gap surfaced only when the operator was already in front of a patient. Also adds the live timer the spec asked for, and wires slot-suggestions into the unbooked queue — the endpoint existed and tested green but no screen called it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -41,7 +41,7 @@ class SessionExecutionTest extends ApiTestCase
|
||||
* @return array{staffUser: User, staff: ClinicStaff, session: TreatmentSession,
|
||||
* resource: ClinicResource, case: TreatmentCase, appointment: Appointment}
|
||||
*/
|
||||
private function scenario(int $areaCount = 2): array
|
||||
private function scenario(int $areaCount = 2, bool $withResource = true): array
|
||||
{
|
||||
$owner = $this->createUser(['ROLE_USER', 'ROLE_CLINIC']);
|
||||
$clinic = new Clinic($owner);
|
||||
@@ -108,7 +108,9 @@ class SessionExecutionTest extends ApiTestCase
|
||||
}
|
||||
|
||||
$appointment = $this->newAppointment($doctor, $record->getUser(), time() + 3600, time() + 5400, $clinic);
|
||||
$appointment->setResource($resource);
|
||||
if ($withResource) {
|
||||
$appointment->setResource($resource);
|
||||
}
|
||||
$appointment->setStaff($staff);
|
||||
$appointment->transitionTo(Appointment::STATUS_CONFIRMED);
|
||||
$this->em->persist($appointment);
|
||||
@@ -125,6 +127,9 @@ class SessionExecutionTest extends ApiTestCase
|
||||
->upsert($staffUser, $clinic->getUuid(), EntityContext::TYPE_CLINIC);
|
||||
|
||||
return [
|
||||
'clinic' => $clinic,
|
||||
'address' => $address,
|
||||
'doctor' => $doctor,
|
||||
'staffUser' => $staffUser,
|
||||
'staff' => $staff,
|
||||
'session' => $session,
|
||||
@@ -134,6 +139,26 @@ class SessionExecutionTest extends ApiTestCase
|
||||
];
|
||||
}
|
||||
|
||||
/** دستگاه دومِ همان محیط — برای سنجیدن تعویض دستگاه در سطح ناحیه. */
|
||||
private function otherDevice(array $s): ClinicResource
|
||||
{
|
||||
$type = new ResourceType('clinic', (int) $s['clinic']->getId(), 'laser_' . bin2hex(random_bytes(3)), 'لیزر دوم');
|
||||
$type->setFieldSchema(self::LASER_SCHEMA);
|
||||
$this->em->persist($type);
|
||||
$this->em->flush();
|
||||
|
||||
// درخواستهای کرنل نمونههای محلی را جدا میکنند؛ دوباره از همین EM خوانده میشوند.
|
||||
$address = $this->em->getRepository(DoctorAddress::class)->find($s['address']->getId());
|
||||
$doctor = $this->em->getRepository(Doctor::class)->find($s['doctor']->getId());
|
||||
|
||||
$resource = new ClinicResource($address, $type, 'Alexandrite Laser');
|
||||
$resource->setSupervisor($doctor);
|
||||
$this->em->persist($resource);
|
||||
$this->em->flush();
|
||||
|
||||
return $resource;
|
||||
}
|
||||
|
||||
private function areaRecords(TreatmentSession $session): array
|
||||
{
|
||||
$records = $this->em->getRepository(SessionAreaRecord::class)->findBy(['session' => $session]);
|
||||
@@ -181,6 +206,91 @@ class SessionExecutionTest extends ApiTestCase
|
||||
self::assertSame(Appointment::STATUS_SALON, $appointment->getStatus());
|
||||
}
|
||||
|
||||
/** دستگاه از نوبت به ارث میرسد؛ اپراتور نباید همان تصمیم را دوباره بگیرد. */
|
||||
public function testAreaRecordsInheritTheAppointmentDevice(): void
|
||||
{
|
||||
$s = $this->scenario();
|
||||
|
||||
$body = $this->authJson('POST', '/api/v1/dashboard/staff/treatment-session/' . $s['session']->getUuid() . '/start', $s['staffUser']);
|
||||
|
||||
self::assertSame(200, $this->responseCode(), json_encode($body, JSON_UNESCAPED_UNICODE));
|
||||
foreach ($body['data']['areas'] as $area) {
|
||||
self::assertSame($s['resource']->getUuid(), $area['resource']['uuid']);
|
||||
}
|
||||
}
|
||||
|
||||
/** با دستگاه ارثرسیده، اپراتور بدون فرستادن resource_uuid هم میتواند ببندد. */
|
||||
public function testAnAreaCanBeCompletedWithoutResendingTheDevice(): void
|
||||
{
|
||||
$s = $this->scenario();
|
||||
$this->authJson('POST', '/api/v1/dashboard/staff/treatment-session/' . $s['session']->getUuid() . '/start', $s['staffUser']);
|
||||
$record = $this->areaRecords($s['session'])[0];
|
||||
|
||||
$body = $this->authJson('POST', '/api/v1/dashboard/staff/session-area/' . $record->getUuid() . '/complete', $s['staffUser'], [
|
||||
'parameters' => ['energy' => 18, 'pulse' => 3, 'shots' => 100],
|
||||
]);
|
||||
|
||||
self::assertSame(200, $this->responseCode(), json_encode($body, JSON_UNESCAPED_UNICODE));
|
||||
self::assertSame($s['resource']->getUuid(), $body['data']['resource']['uuid']);
|
||||
}
|
||||
|
||||
/** اپراتور میتواند دستگاه یک ناحیه را عوض کند — بیکینی با یک دستگاه، زیر بغل با دیگری. */
|
||||
public function testTheOperatorCanOverrideTheDevicePerArea(): void
|
||||
{
|
||||
$s = $this->scenario();
|
||||
$this->authJson('POST', '/api/v1/dashboard/staff/treatment-session/' . $s['session']->getUuid() . '/start', $s['staffUser']);
|
||||
|
||||
// دستگاه پیش از خواندن صفحه ساخته میشود، وگرنه در فهرست همان درخواست نیست.
|
||||
$second = $this->otherDevice($s);
|
||||
$body = $this->authJson('GET', '/api/v1/dashboard/staff/treatment-session/' . $s['session']->getUuid(), $s['staffUser']);
|
||||
|
||||
self::assertContains($second->getUuid(), array_column($body['data']['devices'], 'uuid'));
|
||||
|
||||
$record = $this->areaRecords($s['session'])[1];
|
||||
$body = $this->authJson('POST', '/api/v1/dashboard/staff/session-area/' . $record->getUuid() . '/complete', $s['staffUser'], [
|
||||
'resource_uuid' => $second->getUuid(),
|
||||
'parameters' => ['energy' => 8, 'pulse' => 5, 'shots' => 50],
|
||||
]);
|
||||
|
||||
self::assertSame(200, $this->responseCode(), json_encode($body, JSON_UNESCAPED_UNICODE));
|
||||
self::assertSame($second->getUuid(), $body['data']['resource']['uuid']);
|
||||
}
|
||||
|
||||
/**
|
||||
* بوتاکس تزریق است، نه دستگاه — نوبتِ بیمنبع باید بتواند ناحیهاش را ببندد.
|
||||
*
|
||||
* اجبارِ دستگاه یعنی کلینیک برای هر تزریق یک منبع ساختگی بسازد.
|
||||
*/
|
||||
public function testAnAreaWithoutAnyDeviceCanStillBeCompleted(): void
|
||||
{
|
||||
$s = $this->scenario(withResource: false);
|
||||
$this->authJson('POST', '/api/v1/dashboard/staff/treatment-session/' . $s['session']->getUuid() . '/start', $s['staffUser']);
|
||||
$record = $this->areaRecords($s['session'])[0];
|
||||
|
||||
$body = $this->authJson('POST', '/api/v1/dashboard/staff/session-area/' . $record->getUuid() . '/complete', $s['staffUser'], [
|
||||
'note' => 'تزریق انجام شد',
|
||||
]);
|
||||
|
||||
self::assertSame(200, $this->responseCode(), json_encode($body, JSON_UNESCAPED_UNICODE));
|
||||
self::assertNull($body['data']['resource']);
|
||||
self::assertNull($body['data']['parameters']);
|
||||
self::assertSame('تزریق انجام شد', $body['data']['note']);
|
||||
}
|
||||
|
||||
/** ولی مقدار بدون دستگاه پذیرفته نمیشود — با چه چیزی سنجیده شود؟ */
|
||||
public function testReadingsWithoutADeviceAreRejected(): void
|
||||
{
|
||||
$s = $this->scenario(withResource: false);
|
||||
$this->authJson('POST', '/api/v1/dashboard/staff/treatment-session/' . $s['session']->getUuid() . '/start', $s['staffUser']);
|
||||
$record = $this->areaRecords($s['session'])[0];
|
||||
|
||||
$this->authJson('POST', '/api/v1/dashboard/staff/session-area/' . $record->getUuid() . '/complete', $s['staffUser'], [
|
||||
'parameters' => ['energy' => 18],
|
||||
]);
|
||||
|
||||
self::assertSame(422, $this->responseCode());
|
||||
}
|
||||
|
||||
public function testCompletingAnAreaStoresTheDeviceReadings(): void
|
||||
{
|
||||
$s = $this->scenario();
|
||||
|
||||
Reference in New Issue
Block a user