feat(secretary): enhance functionality for secretary role in appointments management
This commit is contained in:
@@ -86,3 +86,57 @@ describe('AppointmentsPage — پروفایل کلینیک چندپزشکه', ()
|
|||||||
expect(calledSlotsForD1).toBe(true);
|
expect(calledSlotsForD1).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('AppointmentsPage — منشی', () => {
|
||||||
|
/**
|
||||||
|
* منشی در هر دو ساختار باید تایملاین ببیند. لیست پزشکانِ مجاز از اندپوینت
|
||||||
|
* احرازشدهٔ /my/clinic-doctors میآید (نه لیست عمومی کلینیک).
|
||||||
|
*/
|
||||||
|
function mockSecretary(doctors: { uuid: string; name: string }[], scope: string | null) {
|
||||||
|
useAuthStore.setState({
|
||||||
|
primaryRole: 'secretary',
|
||||||
|
dbUuid: scope === 'clinic' ? 'clinic1' : 'doc1',
|
||||||
|
context: scope ? { type: scope, scope } : null,
|
||||||
|
} as any);
|
||||||
|
get.mockImplementation((url?: string) => {
|
||||||
|
if (typeof url !== 'string') return Promise.resolve({ success: true, data: [] });
|
||||||
|
if (url.includes('today-stats')) return Promise.resolve({ success: true, data: { total: 11, completed: 2, waiting: 7, cancelled: 2 } });
|
||||||
|
if (url.includes('/my/clinic-doctors')) return Promise.resolve({ success: true, data: { data: doctors } });
|
||||||
|
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: [] });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => get.mockReset());
|
||||||
|
|
||||||
|
it('منشیِ پزشک مستقل: همان پزشک خودکار انتخاب و تایملاین بارگذاری میشود', async () => {
|
||||||
|
mockSecretary([{ uuid: 'doc1', name: 'دکتر موسوی' }], null);
|
||||||
|
renderWithProviders(<AppointmentsPage />);
|
||||||
|
|
||||||
|
expect(await screen.findByText('این روز تعطیل است')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('برای نمایش زمانبندی، ابتدا یک پزشک انتخاب کنید')).toBeNull();
|
||||||
|
const calledSlots = get.mock.calls.some((c: any[]) => typeof c[0] === 'string' && c[0].includes('appointment-slots') && c[0].includes('doctor_uuid=doc1'));
|
||||||
|
expect(calledSlots).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('منشیِ کلینیک: تب پزشکانِ تخصیصیافته و انتخاب خودکار اولی', async () => {
|
||||||
|
mockSecretary([{ uuid: 'd1', name: 'دکتر محمدی' }, { uuid: 'd2', name: 'دکتر رضایی' }], 'clinic');
|
||||||
|
renderWithProviders(<AppointmentsPage />);
|
||||||
|
|
||||||
|
expect(await screen.findByText('دکتر محمدی')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('دکتر رضایی')).toBeInTheDocument();
|
||||||
|
await screen.findByText('این روز تعطیل است');
|
||||||
|
const calledSlotsForD1 = get.mock.calls.some((c: any[]) => typeof c[0] === 'string' && c[0].includes('appointment-slots') && c[0].includes('doctor_uuid=d1'));
|
||||||
|
expect(calledSlotsForD1).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('منشی هرگز از لیست عمومی کلینیک نمیخواند', async () => {
|
||||||
|
mockSecretary([{ uuid: 'doc1', name: 'دکتر موسوی' }], null);
|
||||||
|
renderWithProviders(<AppointmentsPage />);
|
||||||
|
await screen.findByText('این روز تعطیل است');
|
||||||
|
|
||||||
|
const usedPublicList = get.mock.calls.some((c: any[]) => typeof c[0] === 'string' && c[0].includes('/clinic/doctor-list/'));
|
||||||
|
expect(usedPublicList).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -495,9 +495,11 @@ export default function AppointmentsPage() {
|
|||||||
const isClinic = primaryRole === 'clinic';
|
const isClinic = primaryRole === 'clinic';
|
||||||
const isDoctor = primaryRole === 'doctor';
|
const isDoctor = primaryRole === 'doctor';
|
||||||
const isRepresentation = primaryRole === 'representation';
|
const isRepresentation = primaryRole === 'representation';
|
||||||
// منشیِ محیطِ کلینیک باید مثل کلینیک چندپزشکه رفتار کند: تب پزشکان + تایملاین.
|
// منشی در هر دو ساختار باید تایملاین ببیند: کلینیک چندپزشکه (تب پزشکانِ
|
||||||
// dbUuid در این محیط uuid کلینیک است (نه پزشک) — همان مبنای clinic/doctor-list.
|
// تخصیصیافته) و پزشک مستقل (همان یک پزشک). لیست در هر دو حالت از اندپوینتِ
|
||||||
const isClinicScopedSecretary = primaryRole === 'secretary' && scope === 'clinic';
|
// احرازشدهٔ /my/clinic-doctors میآید که خودش هر دو سناریو را resolve میکند.
|
||||||
|
const isSecretary = primaryRole === 'secretary';
|
||||||
|
const isClinicScopedSecretary = isSecretary && scope === 'clinic';
|
||||||
// مجوزهای منشی روی نوبتها؛ برای owner/پزشک همیشه true.
|
// مجوزهای منشی روی نوبتها؛ برای owner/پزشک همیشه true.
|
||||||
const { can } = usePermissions();
|
const { can } = usePermissions();
|
||||||
const canCreateAppt = can('appointments', 'create');
|
const canCreateAppt = can('appointments', 'create');
|
||||||
@@ -552,15 +554,15 @@ export default function AppointmentsPage() {
|
|||||||
|
|
||||||
// ── Clinic doctors (authoritative list for tabs)
|
// ── Clinic doctors (authoritative list for tabs)
|
||||||
const clinicDoctorsQuery = useQuery<ApiResponse<{ data: { uuid: string; name: string }[] }>>({
|
const clinicDoctorsQuery = useQuery<ApiResponse<{ data: { uuid: string; name: string }[] }>>({
|
||||||
queryKey: ['clinic-doctors', dbUuid, isClinicScopedSecretary],
|
queryKey: ['clinic-doctors', dbUuid, isSecretary],
|
||||||
// منشی از اندپوینتِ احرازشده میگیرد تا فقط پزشکانِ تخصیصیافتهاش بیایند؛
|
// منشی از اندپوینتِ احرازشده میگیرد تا فقط پزشکانِ تخصیصیافتهاش بیایند —
|
||||||
// کلینیک/ادمین از لیستِ کاملِ عمومیِ کلینیک.
|
// چه کلینیک چندپزشکه و چه پزشک مستقل؛ کلینیک/ادمین از لیستِ عمومیِ کلینیک.
|
||||||
queryFn: () => api.get(
|
queryFn: () => api.get(
|
||||||
isClinicScopedSecretary
|
isSecretary
|
||||||
? '/api/v1/my/clinic-doctors'
|
? '/api/v1/my/clinic-doctors'
|
||||||
: `/api/v1/clinic/doctor-list/${dbUuid}`,
|
: `/api/v1/clinic/doctor-list/${dbUuid}`,
|
||||||
),
|
),
|
||||||
enabled: (isClinic || isClinicScopedSecretary) && !!dbUuid,
|
enabled: isSecretary || (isClinic && !!dbUuid),
|
||||||
});
|
});
|
||||||
const clinicDoctorsList = clinicDoctorsQuery.data?.data?.data ?? [];
|
const clinicDoctorsList = clinicDoctorsQuery.data?.data?.data ?? [];
|
||||||
|
|
||||||
@@ -586,16 +588,17 @@ export default function AppointmentsPage() {
|
|||||||
|
|
||||||
// نوع پروفایل: کلینیک چندپزشکه (تب دکترها + مدیریت چند پزشک) در برابر پزشک مستقل.
|
// نوع پروفایل: کلینیک چندپزشکه (تب دکترها + مدیریت چند پزشک) در برابر پزشک مستقل.
|
||||||
// نقش clinic/admin = چندپزشکه؛ نقش doctor (حتی مهمانِ کلینیک) = مستقل، فقط برنامهٔ خودش.
|
// نقش clinic/admin = چندپزشکه؛ نقش doctor (حتی مهمانِ کلینیک) = مستقل، فقط برنامهٔ خودش.
|
||||||
const isMultiDoctorClinic = isClinic || isAdmin || isClinicScopedSecretary;
|
const isMultiDoctorClinic = isClinic || isAdmin || isSecretary;
|
||||||
const showDoctorTabs = isMultiDoctorClinic && doctors.length >= 1;
|
const showDoctorTabs = isMultiDoctorClinic && doctors.length >= 1;
|
||||||
const showDoctorCol = isAdmin && !selectedDoctorUuid;
|
const showDoctorCol = isAdmin && !selectedDoctorUuid;
|
||||||
|
|
||||||
// در کلینیک، اولین دکتر بهصورت پیشفرض انتخاب میشود تا زمانبندی مثل طرح پر باشد.
|
// کلینیک و منشی: اولین پزشکِ در دسترس پیشفرض انتخاب میشود تا تایملاین خالی نماند.
|
||||||
|
// برای منشیِ پزشک مستقل این تنها راه انتخاب است (تبِ پزشک هم یک گزینه بیشتر ندارد).
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if ((isClinic || isClinicScopedSecretary) && !selectedDoctorUuid && doctors.length > 0) {
|
if ((isClinic || isSecretary) && !selectedDoctorUuid && doctors.length > 0) {
|
||||||
setSelectedDoctorUuid(doctors[0].uuid);
|
setSelectedDoctorUuid(doctors[0].uuid);
|
||||||
}
|
}
|
||||||
}, [isClinic, isClinicScopedSecretary, selectedDoctorUuid, doctors]);
|
}, [isClinic, isSecretary, selectedDoctorUuid, doctors]);
|
||||||
|
|
||||||
// ── محل نوبتدهی برای ادمین
|
// ── محل نوبتدهی برای ادمین
|
||||||
// ادمین context کلینیکی ندارد (useClinicContext → null)؛ بدون clinic_uuid فقط برنامهٔ
|
// ادمین context کلینیکی ندارد (useClinicContext → null)؛ بدون clinic_uuid فقط برنامهٔ
|
||||||
|
|||||||
Reference in New Issue
Block a user