Refactor booking system: Remove unused policies, packages, and related entities
- Removed package consumption flags and related properties from PriceQuote. - Eliminated unused domain event publishing for policies and waitlist in Schedule. - Cleaned up BookingEngineSeeder by removing package and policy related logic. - Updated SeedScenariosCommand to reflect removal of policies from output. - Dropped policy, package, treatment course, cancellation, waitlist, and domain event tables in migration. - Removed domain event assertions from tests related to resource blocking.
This commit is contained in:
@@ -1,138 +1,27 @@
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { toast } from 'sonner';
|
||||
import { api, ApiError, type ApiResponse } from '../lib/api';
|
||||
import type { CancellationPolicy, CancellationPreview, CancellationResult, WaitlistEntry } from '../types';
|
||||
|
||||
/**
|
||||
* سیاست لغو و لیست انتظار.
|
||||
* لغو نوبت — فقط انتقال وضعیت.
|
||||
*
|
||||
* پیشنمایش لغو همیشه از سرور میآید — همان محاسبهای که خودِ لغو انجام میدهد، تا عددی
|
||||
* که کاربر میبیند با آنچه کسر میشود یکی باشد.
|
||||
* سیاست لغو، جریمه و لیست انتظار از محصول حذف شدهاند، پس پیشنمایش مالی هم وجود ندارد:
|
||||
* چیزی برای پیشنمایش نمانده. لغو همان `PATCH /api/v1/appointment/{uuid}/status` است که
|
||||
* تایملاین نوبت و دلیل را هم ثبت میکند.
|
||||
*/
|
||||
const POLICY_KEY = ['cancellation-policy'];
|
||||
const WAITLIST_KEY = ['waitlist'];
|
||||
|
||||
function fail(e: unknown, fallback: string) {
|
||||
toast.error(e instanceof ApiError ? e.message : fallback);
|
||||
}
|
||||
|
||||
interface PolicyResponse {
|
||||
default: CancellationPolicy | null;
|
||||
overrides: CancellationPolicy[];
|
||||
}
|
||||
|
||||
export function useCancellationPolicy() {
|
||||
const qc = useQueryClient();
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: POLICY_KEY,
|
||||
queryFn: () => api.get<ApiResponse<PolicyResponse>>('/api/v1/cancellation-policy'),
|
||||
});
|
||||
|
||||
const save = useMutation({
|
||||
mutationFn: (body: Record<string, unknown>) =>
|
||||
api.put<ApiResponse<CancellationPolicy>>('/api/v1/cancellation-policy', body),
|
||||
onSuccess: () => {
|
||||
toast.success('سیاست لغو ذخیره شد');
|
||||
qc.invalidateQueries({ queryKey: POLICY_KEY });
|
||||
},
|
||||
onError: (e) => fail(e, 'ذخیرهٔ سیاست ناموفق بود'),
|
||||
});
|
||||
|
||||
return {
|
||||
policy: query.data?.data?.default ?? null,
|
||||
overrides: query.data?.data?.overrides ?? [],
|
||||
loading: query.isLoading,
|
||||
save,
|
||||
};
|
||||
}
|
||||
|
||||
export function useCancellationPreview(appointmentUuid: string | undefined, by: 'user' | 'doctor') {
|
||||
const query = useQuery({
|
||||
queryKey: ['cancellation-preview', appointmentUuid, by],
|
||||
queryFn: () =>
|
||||
api.get<ApiResponse<CancellationPreview>>(
|
||||
`/api/v1/appointment/${appointmentUuid}/cancellation-preview?by=${by}`,
|
||||
),
|
||||
enabled: !!appointmentUuid,
|
||||
});
|
||||
|
||||
return { preview: query.data?.data, loading: query.isLoading };
|
||||
}
|
||||
|
||||
export function useCancelAppointment() {
|
||||
const qc = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({ uuid, by, reason }: { uuid: string; by: 'user' | 'doctor'; reason?: string }) =>
|
||||
api.post<ApiResponse<CancellationResult>>(`/api/v1/appointment/${uuid}/cancel`, {
|
||||
by,
|
||||
api.patch<ApiResponse<unknown>>(`/api/v1/appointment/${uuid}/status`, {
|
||||
status: by === 'user' ? 'cancelled_by_user' : 'cancelled_by_doctor',
|
||||
...(reason?.trim() ? { reason: reason.trim() } : {}),
|
||||
}),
|
||||
onSuccess: (res) => {
|
||||
const notified = res.data.waitlist_notified;
|
||||
toast.success(
|
||||
notified > 0 ? `نوبت لغو شد و ${notified} نفر از لیست انتظار خبر شدند` : 'نوبت لغو شد',
|
||||
);
|
||||
qc.invalidateQueries({ queryKey: ['appointments'] });
|
||||
qc.invalidateQueries({ queryKey: WAITLIST_KEY });
|
||||
},
|
||||
onError: (e) => fail(e, 'لغو نوبت ناموفق بود'),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* درخواستهایی که با یک زمان مشخص میخوانند — ابزار لحظهٔ آزاد شدن ظرفیت.
|
||||
*
|
||||
* جدا از فهرست است چون سؤال متفاوتی میپرسد: نه «چه کسی منتظر است» بلکه «چه کسی
|
||||
* برای **این** وقت منتظر است».
|
||||
*/
|
||||
export function useWaitlistMatches(params: { serviceUuid: string; start: number; branchUuid?: string }) {
|
||||
const query = useQuery({
|
||||
queryKey: ['waitlist-matches', params],
|
||||
queryFn: () =>
|
||||
api.get<ApiResponse<WaitlistEntry[]>>(
|
||||
`/api/v1/waitlist/matches?service_uuid=${params.serviceUuid}&start=${params.start}` +
|
||||
(params.branchUuid ? `&branch_uuid=${params.branchUuid}` : ''),
|
||||
),
|
||||
enabled: !!params.serviceUuid && params.start > 0,
|
||||
});
|
||||
|
||||
return { matches: Array.isArray(query.data?.data) ? query.data.data : [], loading: query.isFetching };
|
||||
}
|
||||
|
||||
export function useWaitlist(status?: string) {
|
||||
const qc = useQueryClient();
|
||||
const key = [...WAITLIST_KEY, status ?? ''];
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: key,
|
||||
queryFn: () =>
|
||||
api.get<ApiResponse<WaitlistEntry[]>>(`/api/v1/waitlist${status ? `?status=${status}` : ''}`),
|
||||
});
|
||||
|
||||
const remove = useMutation({
|
||||
mutationFn: (uuid: string) => api.delete<ApiResponse<null>>(`/api/v1/waitlist/${uuid}`),
|
||||
onSuccess: () => {
|
||||
toast.success('از لیست انتظار حذف شد');
|
||||
qc.invalidateQueries({ queryKey: WAITLIST_KEY });
|
||||
toast.success('نوبت لغو شد');
|
||||
qc.invalidateQueries({ queryKey: ['appointments'] });
|
||||
},
|
||||
onError: (e) => fail(e, 'حذف ناموفق بود'),
|
||||
onError: (e: unknown) => toast.error(e instanceof ApiError ? e.message : 'لغو نوبت ناموفق بود'),
|
||||
});
|
||||
|
||||
return { entries: query.data?.data ?? [], loading: query.isLoading, remove };
|
||||
}
|
||||
|
||||
/** خلاصهٔ عدم حضور یک بیمار — نشان است نه مانع؛ مسدودسازی کارِ قانون `eligibility` است. */
|
||||
export function usePatientNoShows(patientUuid: string | undefined) {
|
||||
const query = useQuery({
|
||||
queryKey: ['patient-no-shows', patientUuid],
|
||||
queryFn: () =>
|
||||
api.get<ApiResponse<{ count: number; threshold: number; window_days: number; at_risk: boolean }>>(
|
||||
`/api/v1/patient/${patientUuid}/no-shows`,
|
||||
),
|
||||
enabled: !!patientUuid,
|
||||
});
|
||||
|
||||
return { noShows: query.data?.data ?? null };
|
||||
}
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { toast } from 'sonner';
|
||||
import { api, ApiError, type ApiResponse } from '../lib/api';
|
||||
import type {
|
||||
Policy,
|
||||
PolicyCategory,
|
||||
PolicySchema,
|
||||
PolicySimulationRun,
|
||||
PolicyTemplate,
|
||||
} from '../types';
|
||||
|
||||
/**
|
||||
* قوانین کلینیک.
|
||||
*
|
||||
* دو نکتهٔ قراردادی که فرم به آنها وابسته است:
|
||||
* ۱. schema و الگوها از سرور میآیند، نه از فهرستی در فرانت — یک حقیقت، نه دو تا.
|
||||
* ۲. فعالسازی بدون آزمایشِ همان نسخه ۴۲۲ میگیرد؛ UI باید کاربر را اول به آزمایش ببرد.
|
||||
*/
|
||||
const POLICIES_KEY = ['policies'];
|
||||
|
||||
function fail(e: unknown, fallback: string) {
|
||||
toast.error(e instanceof ApiError ? e.message : fallback);
|
||||
}
|
||||
|
||||
/** schema تقریباً هرگز عوض نمیشود؛ نگهداشتن طولانیاش هزینهای ندارد. */
|
||||
export function usePolicySchema() {
|
||||
const query = useQuery({
|
||||
queryKey: ['policy-schema'],
|
||||
queryFn: () => api.get<ApiResponse<PolicySchema>>('/api/v1/policy-schema'),
|
||||
staleTime: 300_000,
|
||||
});
|
||||
|
||||
return { schema: query.data?.data, loading: query.isLoading };
|
||||
}
|
||||
|
||||
export function usePolicyTemplates() {
|
||||
const query = useQuery({
|
||||
queryKey: ['policy-templates'],
|
||||
queryFn: () => api.get<ApiResponse<PolicyTemplate[]>>('/api/v1/policy-templates'),
|
||||
staleTime: 300_000,
|
||||
});
|
||||
|
||||
return { templates: query.data?.data ?? [], loading: query.isLoading };
|
||||
}
|
||||
|
||||
export function usePolicies(category?: PolicyCategory | '') {
|
||||
const qc = useQueryClient();
|
||||
const key = [...POLICIES_KEY, category ?? ''];
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: key,
|
||||
queryFn: () =>
|
||||
api.get<ApiResponse<Policy[]>>(
|
||||
`/api/v1/policies${category ? `?category=${category}` : ''}`,
|
||||
),
|
||||
});
|
||||
|
||||
const invalidate = () => qc.invalidateQueries({ queryKey: POLICIES_KEY });
|
||||
|
||||
const activate = useMutation({
|
||||
mutationFn: (uuid: string) => api.post<ApiResponse<Policy>>(`/api/v1/policy/${uuid}/activate`, {}),
|
||||
onSuccess: () => {
|
||||
toast.success('قانون فعال شد');
|
||||
invalidate();
|
||||
},
|
||||
onError: (e) => fail(e, 'فعالسازی ناموفق بود'),
|
||||
});
|
||||
|
||||
const deactivate = useMutation({
|
||||
mutationFn: (uuid: string) => api.post<ApiResponse<Policy>>(`/api/v1/policy/${uuid}/deactivate`, {}),
|
||||
onSuccess: () => {
|
||||
toast.success('قانون غیرفعال شد');
|
||||
invalidate();
|
||||
},
|
||||
onError: (e) => fail(e, 'غیرفعالسازی ناموفق بود'),
|
||||
});
|
||||
|
||||
return {
|
||||
policies: query.data?.data ?? [],
|
||||
loading: query.isLoading,
|
||||
activate,
|
||||
deactivate,
|
||||
};
|
||||
}
|
||||
|
||||
export function usePolicy(uuid: string | undefined) {
|
||||
const query = useQuery({
|
||||
queryKey: ['policy', uuid],
|
||||
queryFn: () => api.get<ApiResponse<Policy>>(`/api/v1/policy/${uuid}`),
|
||||
enabled: !!uuid,
|
||||
});
|
||||
|
||||
return { policy: query.data?.data, loading: query.isLoading };
|
||||
}
|
||||
|
||||
export function usePolicyMutations() {
|
||||
const qc = useQueryClient();
|
||||
|
||||
const create = useMutation({
|
||||
mutationFn: (body: Record<string, unknown>) =>
|
||||
api.post<ApiResponse<Policy>>('/api/v1/policy', body),
|
||||
onSuccess: () => {
|
||||
toast.success('قانون ساخته شد — حالا آزمایشش کنید');
|
||||
qc.invalidateQueries({ queryKey: POLICIES_KEY });
|
||||
},
|
||||
onError: (e) => fail(e, 'ساخت قانون ناموفق بود'),
|
||||
});
|
||||
|
||||
const newVersion = useMutation({
|
||||
mutationFn: ({ uuid, body }: { uuid: string; body: Record<string, unknown> }) =>
|
||||
api.post<ApiResponse<Policy>>(`/api/v1/policy/${uuid}/version`, body),
|
||||
onSuccess: (_d, v) => {
|
||||
toast.success('نسخهٔ تازه ثبت شد');
|
||||
qc.invalidateQueries({ queryKey: POLICIES_KEY });
|
||||
qc.invalidateQueries({ queryKey: ['policy', v.uuid] });
|
||||
},
|
||||
onError: (e) => fail(e, 'ثبت نسخه ناموفق بود'),
|
||||
});
|
||||
|
||||
return { create, newVersion };
|
||||
}
|
||||
|
||||
export function usePolicySimulation(uuid: string | undefined) {
|
||||
const qc = useQueryClient();
|
||||
const key = ['policy-simulations', uuid];
|
||||
|
||||
const history = useQuery({
|
||||
queryKey: key,
|
||||
queryFn: () => api.get<ApiResponse<PolicySimulationRun[]>>(`/api/v1/policy/${uuid}/simulations`),
|
||||
enabled: !!uuid,
|
||||
});
|
||||
|
||||
const run = useMutation({
|
||||
mutationFn: (sampleSize?: number) =>
|
||||
api.post<ApiResponse<PolicySimulationRun>>(`/api/v1/policy/${uuid}/simulate`, {
|
||||
sample_size: sampleSize ?? 50,
|
||||
}),
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: key });
|
||||
qc.invalidateQueries({ queryKey: POLICIES_KEY });
|
||||
},
|
||||
onError: (e) => fail(e, 'اجرای آزمایشی ناموفق بود'),
|
||||
});
|
||||
|
||||
return { runs: history.data?.data ?? [], loading: history.isLoading, run };
|
||||
}
|
||||
Reference in New Issue
Block a user