fix: issue invoice on demand so view-invoice buttons actually work

The admin SPA never called POST /billing/invoices, so every session had
invoice_uuid = null and the view-invoice buttons on the patient services
tab (and MyPatients visit modal) were permanently disabled. Add a shared
useIssueInvoice hook (idempotent create + finalize when draft) and wire
it into:
- DetailsStep: the wizard's final step now really issues the invoice
- SessionServiceCard / PatientDetailPage: clicking view-invoice on a
  session without an invoice issues it first, then opens the summary
- MyPatientsPage visit modal: same, replacing the dead disabled button

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
hamed
2026-07-16 15:39:02 +03:30
co-authored by Claude Opus 4.8
parent 0ba4bf383b
commit 76bfa5d36f
10 changed files with 204 additions and 18 deletions
@@ -100,6 +100,33 @@ describe('PatientDetailPage (پرونده تب‌دار)', () => {
expect(await screen.findByText('اطلاعات فاکتور')).toBeInTheDocument();
});
it('issues the invoice first (create + finalize) when the paid session has none', async () => {
const base = get.getMockImplementation()!;
get.mockImplementation((url: string) => {
if (url === '/api/v1/patient/r1/sessions') return Promise.resolve({ success: true, data: [
{ uuid: 's3', services: [{ service_name: 'لیزر' }], doctor_name: 'دکتر فتحی', final_price_rials: 1000000, is_paid: true, patient_debt_rials: 0, created_at: 1700000000, visit_price_rials: 0 },
], meta: { totalRecords: 1 } });
if (url === '/api/v1/billing/invoices/iv9') return Promise.resolve({ success: true, data: { data: {
uuid: 'iv9', status: 'finalized', issued_at: 1700000000, total_rials: 1000000,
base_insurance_rials: 0, supplementary_rials: 0, patient_rials: 1000000, items: [],
} } });
return base(url);
});
const post = api.post as ReturnType<typeof vi.fn>;
post.mockReset();
post.mockImplementation((url: string) => {
if (url === '/api/v1/billing/invoices') return Promise.resolve({ success: true, data: { data: { uuid: 'iv9', status: 'draft' } } });
return Promise.resolve({ success: true, data: {} });
});
renderDetail();
fireEvent.click(await screen.findByText('مشاهده فاکتور'));
await waitFor(() => expect(post).toHaveBeenCalledWith('/api/v1/billing/invoices', { session_uuid: 's3' }));
expect(post).toHaveBeenCalledWith('/api/v1/billing/invoices/iv9/finalize', {});
expect(await screen.findByText('خلاصه فاکتور')).toBeInTheDocument();
});
it('renders the editable «اطلاعات پرونده» form prefilled from the profile', async () => {
renderDetail();
await loaded();