feat(invoice): implement recorder identity resolution for payments and update related tests
This commit is contained in:
@@ -1,12 +1,25 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { api } from '../lib/api';
|
||||
import type { ApiResponse } from '../lib/api';
|
||||
import { useAuthStore } from '../stores/authStore';
|
||||
import Modal from './ui/Modal';
|
||||
import { formatDate, formatDateTime, formatRial } from '../lib/utils';
|
||||
import { METHOD_LABELS } from './session/PaymentStep';
|
||||
|
||||
interface InvoiceItem { uuid: string; title: string; quantity: number; total_rials: number; patient_rials: number }
|
||||
interface SessionPayment { uuid: string; method: string; amount_rials: number; paid_at: number; created_by_name: string | null }
|
||||
/** ثبتکنندهٔ پرداخت — نامش زنده از خودِ کاربر حل میشود، نه از عکسِ لحظهٔ ثبت. */
|
||||
interface PaymentRecorder {
|
||||
user_uuid: string;
|
||||
name: string | null;
|
||||
role: 'admin' | 'clinic' | 'doctor' | 'secretary' | 'staff' | 'representation' | 'user';
|
||||
doctor_uuid: string | null;
|
||||
}
|
||||
interface SessionPayment {
|
||||
uuid: string; method: string; amount_rials: number; paid_at: number;
|
||||
created_by_name: string | null;
|
||||
created_by?: PaymentRecorder | null;
|
||||
}
|
||||
interface SessionConsumable { uuid: string; item_name: string; quantity: number; line_total_rials: number }
|
||||
interface SessionData {
|
||||
session_at: number | null; paid_at: number | null;
|
||||
@@ -60,8 +73,47 @@ function SectionTable({ title, cols, rows }: { title: string; cols: string[]; ro
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* پروفایلِ ثبتکننده در پنلِ همین بیننده — یا `null` وقتی صفحهای برایش وجود ندارد.
|
||||
*
|
||||
* فقط پزشک صفحهٔ پروفایلِ مستقل دارد؛ منشی و پرسنل صفحهٔ فهرستِ مدیریتشان را دارند
|
||||
* و ادمین فهرستِ خودش را. مسیری که نقشِ بیننده اجازهاش را ندارد لینک نمیشود، وگرنه
|
||||
* کلیک به داشبورد پرت میکرد.
|
||||
*/
|
||||
function recorderProfilePath(recorder: PaymentRecorder, viewerRole: string | null): string | null {
|
||||
if (recorder.role === 'doctor' && recorder.doctor_uuid) {
|
||||
return ['admin', 'doctor', 'clinic', 'representation'].includes(viewerRole ?? '')
|
||||
? `/admin/doctors/${recorder.doctor_uuid}`
|
||||
: null;
|
||||
}
|
||||
if (recorder.role === 'secretary') {
|
||||
if (viewerRole === 'admin') return '/admin/secretaries';
|
||||
return viewerRole === 'clinic' || viewerRole === 'doctor' ? '/admin/my-secretaries' : null;
|
||||
}
|
||||
if (recorder.role === 'staff') {
|
||||
return ['clinic', 'doctor', 'secretary'].includes(viewerRole ?? '') ? '/admin/staff' : null;
|
||||
}
|
||||
if (viewerRole === 'admin') return `/admin/users/${recorder.user_uuid}`;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/** سلولِ «ثبتکننده»: نام، و اگر پروفایلی در دسترسِ بیننده باشد، لینکش. */
|
||||
function RecorderCell({ payment, viewerRole }: { payment: SessionPayment; viewerRole: string | null }) {
|
||||
const recorder = payment.created_by ?? null;
|
||||
const name = recorder?.name ?? payment.created_by_name;
|
||||
if (!name) return <>-</>;
|
||||
|
||||
const path = recorder ? recorderProfilePath(recorder, viewerRole) : null;
|
||||
|
||||
return path
|
||||
? <Link to={path} style={{ color: 'var(--primary)', textDecoration: 'underline' }}>{name}</Link>
|
||||
: <>{name}</>;
|
||||
}
|
||||
|
||||
/** خلاصه فاکتور — invoice summary, ported pixel-for-pixel from tauri InvoiceSummary. */
|
||||
export default function InvoiceSummaryModal({ invoiceUuid, onClose }: { invoiceUuid: string | null; onClose: () => void }) {
|
||||
const viewerRole = useAuthStore(s => s.primaryRole);
|
||||
const { data, isLoading } = useQuery<ApiResponse<any>>({
|
||||
queryKey: ['invoice', invoiceUuid],
|
||||
queryFn: () => api.get(`/api/v1/billing/invoices/${invoiceUuid}`),
|
||||
@@ -156,7 +208,7 @@ export default function InvoiceSummaryModal({ invoiceUuid, onClose }: { invoiceU
|
||||
METHOD_LABELS[p.method] ?? p.method,
|
||||
formatRial(p.amount_rials),
|
||||
p.paid_at ? formatDateTime(p.paid_at) : '-',
|
||||
p.created_by_name ?? '-',
|
||||
<RecorderCell payment={p} viewerRole={viewerRole} />,
|
||||
]),
|
||||
['', <span style={{ fontWeight: 700 }}>مجموع پرداختیها</span>, <span style={{ fontWeight: 700 }}>{formatRial(session.paid_total_rials)}</span>, '', ''],
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user