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:
@@ -74,10 +74,23 @@ class SessionExecutionController extends BaseController
|
||||
{
|
||||
$session = $this->requireSession($user, $uuid);
|
||||
|
||||
[$entityType, $entityId] = $this->pair($user);
|
||||
$devices = $this->resources->findForPair($entityType, $entityId, ['active' => true]);
|
||||
|
||||
return $this->success($session->toArray(withAreas: true) + [
|
||||
'case' => $session->getTreatmentCase()->toArray(),
|
||||
// فرمِ هر ناحیه از نوع منبعش میآید؛ پنل نباید فیلدها را حدس بزند.
|
||||
'forms' => $this->formsFor($session),
|
||||
'case' => $session->getTreatmentCase()->toArray(),
|
||||
// دستگاههای قابل انتخاب — اپراتور باید بتواند دستگاهِ یک ناحیه را عوض کند،
|
||||
// مثل بیکینی با الکساندرایت و زیر بغل با دایود در همان جلسه.
|
||||
'devices' => array_map(
|
||||
static fn (ClinicResource $r): array => [
|
||||
'uuid' => $r->getUuid(),
|
||||
'name' => $r->getName(),
|
||||
'type' => $r->getType()->getCode(),
|
||||
],
|
||||
$devices,
|
||||
),
|
||||
// فرمِ هر دستگاه از نوعش میآید؛ پنل نباید فیلدها را حدس بزند.
|
||||
'forms' => $this->formsFor($devices),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -137,17 +150,16 @@ class SessionExecutionController extends BaseController
|
||||
return $this->success($this->executor->skipArea($this->requireAreaRecord($user, $uuid))->toArray());
|
||||
}
|
||||
|
||||
/** @return array<string, list<array<string, mixed>>> uuid منبع => تعریف فیلدها */
|
||||
private function formsFor(TreatmentSession $session): array
|
||||
/**
|
||||
* @param list<ClinicResource> $devices
|
||||
* @return array<string, list<array<string, mixed>>> uuid منبع => تعریف فیلدها
|
||||
*/
|
||||
private function formsFor(array $devices): array
|
||||
{
|
||||
$forms = [];
|
||||
|
||||
foreach ($session->getAreaRecords() as $record) {
|
||||
$resource = $record->getResource();
|
||||
|
||||
if ($resource !== null) {
|
||||
$forms[$resource->getUuid()] = $resource->getType()->getFieldSchema() ?? [];
|
||||
}
|
||||
foreach ($devices as $device) {
|
||||
$forms[$device->getUuid()] = $device->getType()->getFieldSchema() ?? [];
|
||||
}
|
||||
|
||||
return $forms;
|
||||
|
||||
@@ -9,6 +9,7 @@ use App\Doctor\Service\AddressResolver;
|
||||
use App\Shared\Constant\ErrorCodes;
|
||||
use App\Shared\Controller\BaseController;
|
||||
use App\Shared\Exception\AppException;
|
||||
use App\Resource\Repository\ResourceServiceOfferingRepository;
|
||||
use App\Treatment\Repository\TreatmentProtocolRepository;
|
||||
use App\Treatment\Service\TreatmentProtocolWriter;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
@@ -27,6 +28,7 @@ class TreatmentProtocolController extends BaseController
|
||||
private readonly TreatmentProtocolRepository $protocols,
|
||||
private readonly ServiceItemRepository $items,
|
||||
private readonly TreatmentProtocolWriter $writer,
|
||||
private readonly ResourceServiceOfferingRepository $offerings,
|
||||
private readonly AddressResolver $branches,
|
||||
private readonly EntityManagerInterface $em,
|
||||
) {}
|
||||
@@ -35,9 +37,21 @@ class TreatmentProtocolController extends BaseController
|
||||
#[Route('/api/v1/service-item/{uuid}/treatment-protocol', name: 'treatment_protocol_show', methods: ['GET'])]
|
||||
public function show(#[CurrentUser] User $user, string $uuid): JsonResponse
|
||||
{
|
||||
$protocol = $this->protocols->findForService($this->requireItem($user, $uuid));
|
||||
$service = $this->requireItem($user, $uuid);
|
||||
$protocol = $this->protocols->findForService($service);
|
||||
|
||||
return $this->success($protocol?->toArray());
|
||||
if ($protocol === null) {
|
||||
return $this->success(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* سرویسی که هیچ منبعی ارائهاش نمیدهد قفل نمیشود — قاعدهٔ «بدون offering
|
||||
* یعنی همه مجاز» عمدی و مستند است. ولی مدیر باید ببیند، وگرنه تازه وقتی
|
||||
* اپراتور جلوی بیمار میرسد معلوم میشود دستگاهی وصل نشده.
|
||||
*/
|
||||
return $this->success($protocol->toArray() + [
|
||||
'service_has_resources' => $this->offerings->hasAnyFor($service),
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/api/v1/service-item/{uuid}/treatment-protocol', name: 'treatment_protocol_replace', methods: ['PUT'])]
|
||||
|
||||
@@ -95,6 +95,15 @@ class SessionAreaRecord
|
||||
public function getFinishedAt(): ?int { return $this->finishedAt; }
|
||||
public function getNote(): ?string { return $this->note; }
|
||||
|
||||
/** دستگاهِ پیشفرض، پیش از آنکه اپراتور کاری کند — از منبعِ نوبت. */
|
||||
public function assignResource(?ClinicResource $resource): self
|
||||
{
|
||||
$this->resource = $resource;
|
||||
$this->touch();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function start(?ClinicResource $resource = null): self
|
||||
{
|
||||
$this->status = self::STATUS_IN_PROGRESS;
|
||||
|
||||
@@ -84,11 +84,18 @@ final class SessionExecutor
|
||||
|
||||
$resource ??= $record->getResource();
|
||||
|
||||
if ($resource === null) {
|
||||
throw new AppException(ErrorCodes::ERR_VALIDATION_002, 'دستگاه این ناحیه مشخص نیست', 422, 'resource_uuid');
|
||||
// درمانِ بیدستگاه واقعی است: بوتاکس تزریق است، نه دستگاه. اجبارِ دستگاه یعنی
|
||||
// کلینیک برای هر تزریق یک منبع ساختگی بسازد تا مدل راضی شود.
|
||||
if ($resource === null && $parameters !== null && $parameters !== []) {
|
||||
throw new AppException(
|
||||
ErrorCodes::ERR_VALIDATION_002,
|
||||
'برای ثبت این اطلاعات باید دستگاه مشخص شود',
|
||||
422,
|
||||
'resource_uuid',
|
||||
);
|
||||
}
|
||||
|
||||
$clean = $this->fieldSchema->validateValues($resource->getType()->getFieldSchema(), $parameters);
|
||||
$clean = $this->fieldSchema->validateValues($resource?->getType()->getFieldSchema(), $parameters);
|
||||
|
||||
$record->complete($resource, $clean, $note);
|
||||
$this->em->flush();
|
||||
@@ -141,7 +148,14 @@ final class SessionExecutor
|
||||
return ['session' => $session, 'unsettled_areas' => $unsettled];
|
||||
}
|
||||
|
||||
/** رکورد هر ناحیهٔ پرونده، یک بار per جلسه. */
|
||||
/**
|
||||
* رکورد هر ناحیهٔ پرونده، یک بار per جلسه.
|
||||
*
|
||||
* دستگاه از منبعِ خودِ نوبت به ارث میرسد: منشی همان لحظهٔ رزرو انتخابش کرده و
|
||||
* پرسیدن دوبارهاش از اپراتور یعنی یک تصمیم را دو بار گرفتن. اپراتور میتواند
|
||||
* per ناحیه عوضش کند — همان کاری که وقتی بیکینی با الکساندرایت و زیر بغل با
|
||||
* دایود انجام میشود لازم است.
|
||||
*/
|
||||
private function ensureAreaRecords(TreatmentSession $session): void
|
||||
{
|
||||
$existing = [];
|
||||
@@ -149,10 +163,20 @@ final class SessionExecutor
|
||||
$existing[(int) $record->getCaseArea()->getId()] = true;
|
||||
}
|
||||
|
||||
$inherited = $session->getAppointment()?->getResource();
|
||||
|
||||
foreach ($session->getTreatmentCase()->getAreas() as $area) {
|
||||
if (!isset($existing[(int) $area->getId()])) {
|
||||
$session->addAreaRecord(new SessionAreaRecord($session, $area));
|
||||
if (isset($existing[(int) $area->getId()])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$record = new SessionAreaRecord($session, $area);
|
||||
|
||||
if ($inherited !== null) {
|
||||
$record->assignResource($inherited);
|
||||
}
|
||||
|
||||
$session->addAreaRecord($record);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user