feat(admin): service-aware time picking on the appointment edit page
In service mode the page now mounts the existing ServiceSlotPicker and hides the three free-form time inputs plus the single-service select: a 45-minute service could previously be shortened to 20 and the next patient would sit on top of it. Hidden rather than disabled — a disabled field reads as "you must do something here". Saving splits in two: the service-aware endpoint takes the time and services (the client sends no duration), then the usual PATCH carries deposit, insurance, status and note without slot_start/slot_end/version, since the reschedule already advanced the optimistic-lock version. Booking mode is read from the appointment's own schedule via an explicit clinic_uuid, not from the panel's current environment: a doctor can be slot-based in their office and service-based in a clinic. That required exposing clinic_uuid in Appointment::toArray(), which was missing. appointment-service-slots accepts exclude_appointment_uuid, gated on canManage of that appointment — an ungated parameter would let anyone fabricate availability. ServiceSlotPicker gained two optional props; its existing callers pass neither and are unaffected. Its reset-on-doctor-change effect now skips the first run, which would otherwise wipe the initial selection. Task: docs/new_feture/taskes/task-00-service-mode-completion/ Slot-mode contract: unchanged (--group=slot-mode-frozen green) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -219,6 +219,24 @@ class AppointmentController extends BaseController
|
||||
(array) $request->query->all('durations'),
|
||||
);
|
||||
|
||||
// جابهجایی: بازهٔ خودِ نوبتِ در حال ویرایش نباید اشغال حساب شود، وگرنه زمان
|
||||
// فعلیاش هرگز در فهرست نمیآید. فقط برای کسی که همان نوبت را مدیریت میکند —
|
||||
// این پارامتر آزاد نیست، چون در غیر اینصورت هر کسی میتوانست با uuid دلخواه
|
||||
// ظرفیتِ ساختگی ببیند.
|
||||
$excludeId = null;
|
||||
$excludeUuid = trim((string) $request->query->get('exclude_appointment_uuid', ''));
|
||||
if ($excludeUuid !== '') {
|
||||
$excluded = $this->appointmentRepo->findByUuid($excludeUuid);
|
||||
$actor = $this->getUser();
|
||||
if ($excluded === null || !$actor instanceof User || !$this->canManage($excluded, $actor)) {
|
||||
return $this->error(ErrorCodes::ERR_ACCESS_DENIED, 'دسترسی ممنوع', 403, 'exclude_appointment_uuid');
|
||||
}
|
||||
if ($excluded->getDoctor()->getId() !== $doctor->getId()) {
|
||||
return $this->error(ErrorCodes::ERR_VALIDATION_001, 'نوبت انتخابی به این پزشک تعلق ندارد', 422, 'exclude_appointment_uuid');
|
||||
}
|
||||
$excludeId = $excluded->getId();
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'doctor_uuid' => $doctorUuid,
|
||||
'date' => $date,
|
||||
@@ -231,6 +249,7 @@ class AppointmentController extends BaseController
|
||||
$duration->totalMinutes,
|
||||
$clinic,
|
||||
$this->isManagementContext($request, $doctor, $clinic),
|
||||
$excludeId,
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -467,6 +467,10 @@ class Appointment
|
||||
],
|
||||
'address' => $firstAddress?->toArray(),
|
||||
'address_id' => $this->addressId,
|
||||
// محلِ نوبتدهی این نوبت. null = مطب شخصی. کلاینت بدون این نمیداند روش
|
||||
// نوبتدهی را از کدام برنامه بپرسد: یک پزشک میتواند در مطب اسلاتی و در
|
||||
// کلینیک سرویسی باشد و محیط جاریِ پنل لزوماً محیط این نوبت نیست.
|
||||
'clinic_uuid' => $this->clinic?->getUuid(),
|
||||
'user' => [
|
||||
'uuid' => $this->user->getUuid(),
|
||||
'mobile' => $this->user->getMobileNumber(),
|
||||
|
||||
Reference in New Issue
Block a user