feat(appointment): implement immutable booking mode after first save and update API documentation

This commit is contained in:
hamed
2026-07-15 23:33:00 +03:30
parent ad94c165e8
commit 2df16c6d29
5 changed files with 176 additions and 32 deletions
@@ -41,6 +41,18 @@ class AppointmentSettingsController extends BaseController
* در حالت نوبت‌دهی سرویسی، پزشک باید حداقل یک سرویسِ «نمایش در نوبت‌دهی»
* (bookable) داشته باشد؛ وگرنه هیچ نوبتی قابل‌محاسبه نیست.
*/
/**
* نوع نوبت‌دهی پس از اولین ثبت غیرقابل‌تغییر است. اگر قبلاً mode ذخیره شده بود
* ($prevMode !== null) و meta جدید آن را تغییر دهد، خطای 422 برمی‌گرداند.
*/
private function assertModeImmutable(?string $prevMode, array $newMeta): ?JsonResponse
{
if ($prevMode !== null && ($newMeta['booking_mode'] ?? null) !== $prevMode) {
return $this->error(ErrorCodes::ERR_VALIDATION_001, 'نوع نوبت‌دهی پس از ثبت قابل تغییر نیست', 422, 'booking_mode');
}
return null;
}
private function serviceModeHasNoBookable(array $meta, \App\Doctor\Entity\Doctor $doctor): bool
{
return ($meta['booking_mode'] ?? WeeklySchedule::MODE_SLOT) === WeeklySchedule::MODE_SERVICE
@@ -70,6 +82,7 @@ class AppointmentSettingsController extends BaseController
// Only one schedule per doctor — upsert
$schedule = $this->scheduleRepo->findByDoctor($doctor);
$prevMode = $schedule?->getStoredBookingMode();
if ($schedule !== null) {
$schedule->setSetting($data['schedule'] ?? []);
} else {
@@ -80,6 +93,10 @@ class AppointmentSettingsController extends BaseController
$schedule->setMeta($data['meta']);
}
if (($err = $this->assertModeImmutable($prevMode, $schedule->getMeta())) !== null) {
return $err;
}
if ($this->serviceModeHasNoBookable($schedule->getMeta(), $doctor)) {
return $this->error(ErrorCodes::ERR_VALIDATION_001, 'برای نوبت‌دهی سرویسی حداقل یک سرویس با «نمایش در نوبت‌دهی» لازم است', 422, 'booking_mode');
}
@@ -107,6 +124,7 @@ class AppointmentSettingsController extends BaseController
return $this->error(ErrorCodes::ERR_AUTH_006, 'دسترسی ممنوع', 403);
}
$prevMode = $schedule->getStoredBookingMode();
$data = json_decode($request->getContent(), true) ?? [];
if (isset($data['schedule'])) {
if (($err = $this->validateSessionsHaveLocation($data['schedule'])) !== null) {
@@ -118,6 +136,10 @@ class AppointmentSettingsController extends BaseController
$schedule->setMeta($data['meta']);
}
if (($err = $this->assertModeImmutable($prevMode, $schedule->getMeta())) !== null) {
return $err;
}
if ($this->serviceModeHasNoBookable($schedule->getMeta(), $schedule->getDoctor())) {
return $this->error(ErrorCodes::ERR_VALIDATION_001, 'برای نوبت‌دهی سرویسی حداقل یک سرویس با «نمایش در نوبت‌دهی» لازم است', 422, 'booking_mode');
}
+12
View File
@@ -79,6 +79,16 @@ class WeeklySchedule
return array_merge(self::DEFAULT_META, $this->setting[self::META_KEY] ?? []);
}
/**
* booking_mode ذخیره‌شده به‌صورت خام (بدون merge پیش‌فرض). null یعنی هنوز
* صریحاً ثبت نشده — تا وقتی null است، انتخاب روش قابل‌تغییر است؛ پس از اولین
* ثبت، قفل می‌شود.
*/
public function getStoredBookingMode(): ?string
{
return $this->setting[self::META_KEY]['booking_mode'] ?? null;
}
public function setMeta(array $meta): self
{
$current = $this->getMeta();
@@ -111,6 +121,8 @@ class WeeklySchedule
'doctor_uuid' => $this->doctor->getUuid(),
'schedule' => $this->getDaySchedule(),
'meta' => $this->getMeta(),
// نوع نوبت‌دهی پس از اولین ثبت قفل می‌شود (پنل توگل را غیرفعال می‌کند).
'booking_mode_locked' => $this->getStoredBookingMode() !== null,
'created_at' => $this->createdAt,
'updated_at' => $this->updatedAt,
];