feat(appointment): let a platform admin change a doctor's locked booking mode

The booking mode locks after the first save because existing appointments were
computed under that mode's rules. A lock with no key, though, traps a practice
that picked the wrong mode on day one, so ROLE_ADMIN can now open it.

The first attempt is still refused when the doctor has active appointments in
the next year, and says how many; the admin repeats the request with
force_mode_change to confirm they know what happens to those. The flag does
nothing for anyone else. GET now returns booking_mode_changeable so the panel
enables the toggle from the server's answer rather than guessing from the role.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
hamed
2026-08-20 17:32:52 +03:30
co-authored by Claude Opus 5
parent 7096b8980d
commit a3fc7f5c8b
5 changed files with 274 additions and 19 deletions
@@ -21,7 +21,7 @@ const ADDRESSES: AddressData[] = [{
}];
/** پاسخ برنامهٔ هفتگی؛ فقط `meta` بین تست‌ها فرق می‌کند. */
function mockSchedule(meta: Record<string, unknown>, locked = false) {
function mockSchedule(meta: Record<string, unknown>, locked = false, changeable = false) {
get.mockImplementation((url?: string) => {
if (typeof url === 'string' && url.includes('weekly-schedule')) {
return Promise.resolve({
@@ -32,6 +32,7 @@ function mockSchedule(meta: Record<string, unknown>, locked = false) {
schedule: {},
meta: { online_booking_enabled: true, booking_window_value: 3, booking_window_unit: 'month', buffer_minutes: 0, ...meta },
booking_mode_locked: locked,
booking_mode_changeable: changeable,
},
});
}
@@ -77,3 +78,21 @@ describe('WeeklyScheduleTab — روش نوبت‌دهی', () => {
expect(screen.queryByRole('button', { name: /نوبت‌دهی منبع‌محور/ })).toBeNull();
});
});
describe('WeeklyScheduleTab — قفل نوع نوبت‌دهی', () => {
it('برای کاربر عادی، نوعِ ثبت‌شده قفل است', async () => {
mockSchedule({ booking_mode: 'slot' }, true);
renderWithProviders(<WeeklyScheduleTab doctorUuid="d1" addresses={ADDRESSES} />);
expect(await screen.findByText('نوع نوبت‌دهی ثبت شده و دیگر قابل تغییر نیست.')).toBeInTheDocument();
expect(screen.getByText('نوبت‌دهی سرویسی').closest('button')).toBeDisabled();
});
it('برای ادمین، همان نوع قابل تغییر است و هشدارش را می‌گوید', async () => {
mockSchedule({ booking_mode: 'slot' }, true, true);
renderWithProviders(<WeeklyScheduleTab doctorUuid="d1" addresses={ADDRESSES} />);
expect(await screen.findByText(/فقط ادمین می‌تواند عوضش کند/)).toBeInTheDocument();
expect(screen.getByText('نوبت‌دهی سرویسی').closest('button')).not.toBeDisabled();
});
});