fix(booking): aggregate public booking state across all schedules

The public doctor payload built `active`/`free_turn`/`hours_of_work` from the
personal schedule alone, so a doctor bookable only at a clinic was reported as
"نوبت‌دهی غیرفعال". Aggregate over every schedule instead: any schedule with
online booking on and an active day makes the doctor bookable, and the disabled
label only appears when all of them are off.

Three admin-panel fixes for the same class of bug:

- AppointmentsPage took the selected doctor from `dbUuid`, which is the clinic's
  uuid inside a clinic context — the slots request 404'd. Use `doctorUuid`.
- TurnsTimeline rendered any error or unknown empty_reason as "این روز شیفت کاری
  ندارد". Errors now surface as errors and unknown reasons get a neutral message;
  the day-off wording is reserved for an explicit day_off from the backend.
- Admins have no clinic context, so slots fell back to the personal schedule.
  They now pick a location from `appointment-booking-locations` and that choice
  drives the slot, service and create-appointment requests.

Adds `app:schedule:normalize-format` for legacy rows stored as a bare JSON list
covering only Saturday, which read as day-off for the rest of the week.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
hamed
2026-07-18 16:25:24 +03:30
co-authored by Claude Fable 5
parent 63bf2cdd12
commit 7baa4df3d4
15 changed files with 631 additions and 69 deletions
+3 -3
View File
@@ -15,10 +15,10 @@ const get = api.get as ReturnType<typeof vi.fn>;
beforeEach(() => {
get.mockReset();
useAuthStore.setState({ primaryRole: 'doctor', dbUuid: 'doc1' } as any);
useAuthStore.setState({ primaryRole: 'doctor', dbUuid: 'doc1', doctorUuid: 'doc1' } as any);
get.mockImplementation((url: string) => {
if (url.includes('today-stats')) return Promise.resolve({ success: true, data: { total: 7, completed: 3, waiting: 2, cancelled: 1 } });
if (url.includes('appointment-slots')) return Promise.resolve({ success: true, data: { sessions: [] } });
if (url.includes('appointment-slots')) return Promise.resolve({ success: true, data: { sessions: [], empty_reason: 'holiday' } });
if (url.includes('/my/appointments')) return Promise.resolve({ success: true, data: [], meta: { totalRecords: 0, totalPages: 0, currentPage: 1 } });
return Promise.resolve({ success: true, data: [] });
});
@@ -64,7 +64,7 @@ describe('AppointmentsPage — پروفایل کلینیک چندپزشکه', ()
if (url.includes('/clinic/doctor-list/')) return Promise.resolve({ success: true, data: { data: [
{ uuid: 'd1', name: 'دکتر محمدی' }, { uuid: 'd2', name: 'دکتر رضایی' },
] } });
if (url.includes('appointment-slots')) return Promise.resolve({ success: true, data: { sessions: [] } });
if (url.includes('appointment-slots')) return Promise.resolve({ success: true, data: { sessions: [], empty_reason: 'holiday' } });
if (url.includes('/my/appointments')) return Promise.resolve({ success: true, data: [], meta: { totalRecords: 0, totalPages: 0, currentPage: 1 } });
return Promise.resolve({ success: true, data: [] });
});