fix(appointment): one weekly schedule per doctor, place chosen per shift

A doctor working both at their own practice and at a clinic had to write two
independent schedules and neither panel could see the other, so the clinic
showed an empty form even though the doctor had configured their practice.

The schedule is now a single record owned by the doctor. What varies between
days is the place: the context of a shift is read from its location_id, not
from the record it lives in. Booking in a context therefore sees only that
context's days, so a personal-practice secretary still cannot book a clinic
day. The caller's own context decides which addresses they may assign: the
doctor gets every place of theirs, a clinic manager only its own, and shifts
outside their reach are returned for display but preserved verbatim on save.

Existing per-clinic rows are merged by migration; location_id was already
stored on every shift, so no context information is lost.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
hamed
2026-08-20 17:20:53 +03:30
co-authored by Claude Opus 5
parent 2f030edef1
commit a0b4a969fc
15 changed files with 789 additions and 84 deletions
+20 -2
View File
@@ -102,7 +102,14 @@ class ServiceModeContextTest extends ApiTestCase
self::assertSame(201, $this->responseCode());
}
public function testBookingModeLocksPerContextNotGlobally(): void
/**
* برنامه یکی است، پس نوع نوبت‌دهی هم یکی است.
*
* پیش‌تر هر محیط رکورد خودش را داشت و می‌شد مطب را اسلاتی و کلینیک را سرویسی کرد.
* حالا که یک برنامه بیشتر نیست، تغییر نوع بعد از اولین ثبت رد می‌شود — همان قفلی
* که از قبل داخل یک محیط وجود داشت، فقط دامنه‌اش پزشک شده.
*/
public function testBookingModeIsLockedOnceForTheWholeDoctor(): void
{
[$doctor, $clinic, $owner] = $this->makeDoctorInClinic();
$this->bookableServiceFor('clinic', $clinic->getId());
@@ -123,8 +130,19 @@ class ServiceModeContextTest extends ApiTestCase
]);
self::assertSame(201, $this->responseCode());
// همان پزشک در کلینیک: سرویسی — قفلِ محیط دیگر نباید مانع شود
// همان پزشک در کلینیک، این بار سرویسی — نوع قفل است و عوض نمی‌شود.
$this->authJson('POST', '/api/v1/appointment-settings/weekly-schedule', $owner, $this->servicePayload($doctor, $inClinic->getId(), $clinic));
self::assertSame(422, $this->responseCode());
// ولی افزودن شیفت کلینیک با همان نوع اسلاتی مشکلی ندارد.
$this->authJson('POST', '/api/v1/appointment-settings/weekly-schedule', $owner, [
'doctor_uuid' => $doctor->getUuid(),
'clinic_uuid' => $clinic->getUuid(),
'schedule' => [['day' => 'monday', 'sessions' => [
['active' => true, 'location_id' => $inClinic->getId(), 'start' => '16:00', 'end' => '20:00'],
]]],
'meta' => ['booking_mode' => 'slot'],
]);
self::assertSame(201, $this->responseCode());
}
}