refactor(booking): extract ServiceBookingCalculator from the controller
"Allowed duration of a service combination" lived inside AppointmentController::serviceSlots(). Three upcoming callers need the same computation (PATCH duration validation, service-aware reschedule, reserve conversion); copying it would mean four variants with four different edge-case behaviours. The extraction is behaviour-preserving: BaseController::error() and ExceptionSubscriber emit an identical envelope, so returning $this->error() was replaced by throwing AppException with the same code/message/field. Tenant ownership now goes through TenantOwnershipChecker::belongsToPair() (the documented single point) instead of an inline section pair comparison. The repo property is named itemRepo on purpose: TenantLookupInventoryTest only counts recognised property names, so any other name would slip past the safety net. The naive duration sum is kept deliberately — switching to solo/additional minutes is task 04 and changes one line here. 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:
@@ -43,6 +43,7 @@ class AppointmentController extends BaseController
|
||||
private readonly \App\Appointment\Repository\AppointmentEventRepository $eventRepo,
|
||||
private readonly \App\Appointment\Security\AppointmentAccessChecker $accessChecker,
|
||||
private readonly \App\Appointment\Service\AppointmentInsuranceService $appointmentInsurance,
|
||||
private readonly \App\Appointment\Service\ServiceBookingCalculator $serviceCalculator,
|
||||
private readonly \Psr\Log\LoggerInterface $logger,
|
||||
) {}
|
||||
|
||||
@@ -207,46 +208,26 @@ class AppointmentController extends BaseController
|
||||
return $this->error(ErrorCodes::ERR_VALIDATION_001, 'انتخاب حداقل یک سرویس الزامی است', 422, 'service_item_uuids');
|
||||
}
|
||||
|
||||
// پیش از هر بررسی دیگری: سرویس باید مالِ همین محیط باشد. مسیر ثبت نوبت همین
|
||||
// گارد را دارد و این مسیر نداشت، پس با uuid سرویسِ محیط دیگر میشد وجود،
|
||||
// فعالبودن و مدتش را از پیامهای خطا و اسلاتهای برگشتی استنتاج کرد.
|
||||
if (($err = $this->assertServicesMatchContext($uuids, $doctor, $clinic)) !== null) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
// مدتِ override منشی (فقط برای همین محاسبه؛ پیشفرض سرویس تغییر نمیکند). durations[uuid]=minutes
|
||||
$overrides = (array) $request->query->all('durations');
|
||||
|
||||
$totalMinutes = 0;
|
||||
foreach ($uuids as $u) {
|
||||
$item = $this->itemRepo->findByUuid($u);
|
||||
if ($item === null) {
|
||||
return $this->error(ErrorCodes::ERR_VALIDATION_002, 'سرویس یافت نشد', 422, 'service_item_uuids');
|
||||
}
|
||||
if (!$item->isBookable()) {
|
||||
return $this->error(ErrorCodes::ERR_VALIDATION_001, 'این سرویس برای نوبتدهی فعال نیست', 422, 'service_item_uuids');
|
||||
}
|
||||
$duration = isset($overrides[$u]) && (int) $overrides[$u] > 0
|
||||
? (int) $overrides[$u]
|
||||
: (int) ($item->getDurationMinutes() ?? 0);
|
||||
if ($duration <= 0) {
|
||||
return $this->error(ErrorCodes::ERR_VALIDATION_001, 'مدت سرویس تعریف نشده است', 422, 'service_item_uuids');
|
||||
}
|
||||
$totalMinutes += $duration;
|
||||
}
|
||||
|
||||
$meta = $schedule ? $schedule->getMeta() : WeeklySchedule::DEFAULT_META;
|
||||
// مالکیت محیط، وجود، فعالبودن و مدت — همه داخل calculate() و با همان ترتیب و
|
||||
// همان کد/پیام/فیلدِ قبلی (AppException و $this->error() یک envelope میسازند).
|
||||
$duration = $this->serviceCalculator->calculate(
|
||||
$doctor,
|
||||
$clinic,
|
||||
$uuids,
|
||||
(array) $request->query->all('durations'),
|
||||
);
|
||||
|
||||
return $this->success([
|
||||
'doctor_uuid' => $doctorUuid,
|
||||
'date' => $date,
|
||||
'total_duration_minutes' => $totalMinutes,
|
||||
'buffer_minutes' => (int) $meta['buffer_minutes'],
|
||||
'total_duration_minutes' => $duration->totalMinutes,
|
||||
'buffer_minutes' => $duration->bufferMinutes,
|
||||
'clinic_uuid' => $clinic?->getUuid(),
|
||||
'start_times' => $this->slotCalculator->getServiceStartTimes(
|
||||
$doctor,
|
||||
$date,
|
||||
$totalMinutes,
|
||||
$duration->totalMinutes,
|
||||
$clinic,
|
||||
$this->isManagementContext($request, $doctor, $clinic),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user