"use client"; import { useCallback, useEffect, useState } from "react"; import { ArrowRight } from "lucide-react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; import { getSettlementBill, postSettlementBillAdjustment, postSettlementBillBadDebtWriteOff, postSettlementBillConfirm, postSettlementBillPayment, type RebateAllocationRow, type SettlementBillRow, type SettlementBillPaymentRow, type DownlineShareBreakdown, } from "@/api/admin-agent-settlement"; import { AdminLoadingState } from "@/components/admin/admin-loading-state"; import { formatAdminMinorDecimal, parseAdminMajorToMinor, parseSignedAdminMajorToMinor } from "@/lib/money"; import { formatDashboardMoneyMinor } from "@/modules/dashboard/use-dashboard-analytics"; import { SettlementBillAmountBreakdown, SettlementBillSummaryHeader, } from "@/modules/settlement/settlement-bill-breakdown"; import { describeBillPaymentDirection } from "@/modules/settlement/settlement-bill-display"; import { settlementBillOperableByBoundAgent } from "@/modules/settlement/settlement-bill-operable"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { useConfirmAction } from "@/hooks/use-confirm-action"; import { LotteryApiBizError } from "@/types/api/errors"; type AgentBillDetailProps = { billId: number; currencyCode: string; canManage?: boolean; canFinanceAdjustments?: boolean; boundAgent?: { id: number } | null; onUpdated?: () => void; }; export function AgentBillDetail({ billId, currencyCode, canManage = true, canFinanceAdjustments = false, boundAgent = null, onUpdated, }: AgentBillDetailProps): React.ReactElement { const { t } = useTranslation(["agents", "settlementCenter", "common"]); const [bill, setBill] = useState(null); const [payments, setPayments] = useState([]); const [rebateAllocations, setRebateAllocations] = useState([]); const [downlineShares, setDownlineShares] = useState(null); const [loading, setLoading] = useState(true); const [loadErr, setLoadErr] = useState(null); const [payAmount, setPayAmount] = useState(""); const [payMethod, setPayMethod] = useState(""); const [payProof, setPayProof] = useState(""); const [adjustAmount, setAdjustAmount] = useState(""); const [adjustReason, setAdjustReason] = useState(""); const [badDebtReason, setBadDebtReason] = useState(""); const [rebateDetailsOpen, setRebateDetailsOpen] = useState(false); const { request: requestConfirm, ConfirmDialog, busy: confirmBusy } = useConfirmAction(); const load = useCallback(async () => { setLoading(true); setLoadErr(null); try { const data = await getSettlementBill(billId); setBill(data.bill); setPayments(data.payments ?? []); setRebateAllocations(data.rebate_allocations ?? []); setDownlineShares(data.downline_shares ?? null); setPayAmount(formatAdminMinorDecimal(data.bill.unpaid_amount ?? 0, currencyCode)); } catch (e) { setBill(null); setPayments([]); setRebateAllocations([]); setDownlineShares(null); setLoadErr(e instanceof LotteryApiBizError ? e.message : t("loadFailed", { ns: "common" })); } finally { setLoading(false); } }, [billId, currencyCode, t]); useEffect(() => { void load(); }, [load]); if (loading && !bill) { return ; } if (loadErr || !bill) { return (

{loadErr ?? t("loadFailed", { ns: "common" })}

); } const direction = describeBillPaymentDirection(bill, t); const canOperateBill = canManage && settlementBillOperableByBoundAgent(bill, boundAgent); const locked = ["confirmed", "partial_paid", "settled", "overdue"].includes(bill.status); const paymentTitle = direction.ownerOwes ? t("settlementBills.submitReceipt", { defaultValue: "登记收款" }) : t("settlementBills.submitPayout", { defaultValue: "登记付款" }); const paymentSubmit = direction.ownerOwes ? t("settlementBills.submitReceipt", { defaultValue: "确认收款" }) : t("settlementBills.submitPayout", { defaultValue: "确认付款" }); const canWriteOff = canFinanceAdjustments && bill.unpaid_amount > 0 && ["confirmed", "partial_paid", "overdue"].includes(bill.status) && !["adjustment", "reversal", "bad_debt"].includes(bill.bill_type); const rebateAllocationSummary = Object.values( rebateAllocations.reduce>( (acc, row) => { const label = row.participant_label ?? `${row.participant_type}#${row.participant_id}`; const key = `${row.participant_type}:${row.participant_id}:${row.allocation_rule}`; const current = acc[key]; if (current) { current.amount += row.allocated_amount; current.rows += 1; return acc; } acc[key] = { key, label: `${label} · ${row.allocation_rule}`, amount: row.allocated_amount, rows: 1, }; return acc; }, {}, ), ).sort((a, b) => b.amount - a.amount || a.label.localeCompare(b.label, "zh-CN")); const showActionError = (err: unknown, fallback: string): void => { toast.error(err instanceof LotteryApiBizError ? err.message : fallback); }; const requestConfirmBill = (): void => { requestConfirm({ title: t("settlementBills.confirmBillTitle", { defaultValue: "确认账单?" }), description: t("settlementBills.confirmBillDescription", { defaultValue: "确认后账单会进入待收付状态,请确认金额与双方无误。", }), confirmLabel: t("settlementBills.confirm", { defaultValue: "确认账单" }), confirmVariant: "default", onConfirm: async () => { try { await postSettlementBillConfirm(billId); await load(); await onUpdated?.(); toast.success(t("settlementBills.confirmed", { defaultValue: "已确认" })); } catch (err: unknown) { showActionError( err, t("settlementBills.confirmFailed", { defaultValue: "确认账单失败" }), ); } }, }); }; const requestPayment = (): void => { const amount = parseAdminMajorToMinor(payAmount, currencyCode); if (amount === null || amount <= 0) { toast.error(t("settlementBills.paymentAmountInvalid", { defaultValue: "请输入大于 0 的有效金额" })); return; } if (amount > bill.unpaid_amount) { toast.error(t("settlementBills.paymentAmountTooLarge", { defaultValue: "收付金额不能超过未结金额" })); return; } requestConfirm({ title: t("settlementBills.paymentConfirmTitle", { defaultValue: "确认登记收付?" }), description: t("settlementBills.paymentConfirmDescription", { defaultValue: "这会写入收付记录并更新账单已付/未结金额,请确认金额与方向无误。", }), confirmLabel: paymentSubmit, confirmVariant: "default", onConfirm: async () => { try { await postSettlementBillPayment(billId, { amount, method: payMethod.trim() || undefined, proof: payProof.trim() || undefined, }); await load(); await onUpdated?.(); toast.success(t("settlementBills.paid", { defaultValue: "已登记收付" })); } catch (err: unknown) { showActionError( err, t("settlementBills.paymentFailed", { defaultValue: "登记收付失败" }), ); } }, }); }; const requestBadDebtWriteOff = (): void => { const reason = badDebtReason.trim(); if (!reason) { toast.error(t("settlementBills.badDebtReasonRequired", { defaultValue: "请填写核销原因" })); return; } requestConfirm({ title: t("settlementBills.badDebtConfirmTitle", { defaultValue: "确认核销坏账?" }), description: t("settlementBills.badDebtConfirmDescription", { defaultValue: "核销会把未结金额归档为坏账记录,这类资金动作需要确认原因无误。", }), confirmLabel: t("settlementBills.confirmBadDebt", { defaultValue: "确认核销" }), confirmVariant: "destructive", onConfirm: async () => { try { await postSettlementBillBadDebtWriteOff(billId, { reason }); await load(); await onUpdated?.(); toast.success(t("settlementBills.badDebtDone", { defaultValue: "已核销坏账" })); } catch (err: unknown) { showActionError( err, t("settlementBills.badDebtFailed", { defaultValue: "核销坏账失败" }), ); } }, }); }; const requestAdjustment = (): void => { const amount = parseSignedAdminMajorToMinor(adjustAmount, currencyCode); const reason = adjustReason.trim(); if (amount === null || amount === 0) { toast.error(t("settlementBills.adjustmentAmountInvalid", { defaultValue: "请输入非 0 的有效金额" })); return; } if (!reason) { toast.error(t("settlementBills.adjustmentReasonRequired", { defaultValue: "请填写补差/冲正原因" })); return; } requestConfirm({ title: t("settlementBills.adjustmentConfirmTitle", { defaultValue: "确认创建补差/冲正单?" }), description: t("settlementBills.adjustmentConfirmDescription", { defaultValue: "提交后会生成独立调账单,需要后续确认与收付;请确认正负方向和原因无误。", }), confirmLabel: t("settlementBills.createAdjustment", { defaultValue: "创建补差单" }), confirmVariant: amount < 0 ? "destructive" : "default", onConfirm: async () => { try { await postSettlementBillAdjustment(billId, { amount, adjustment_type: amount < 0 ? "reversal" : "adjustment", reason, }); setAdjustAmount(""); setAdjustReason(""); toast.success(t("settlementBills.adjustmentCreated", { defaultValue: "已创建补差单" })); await onUpdated?.(); } catch (err: unknown) { showActionError( err, t("settlementBills.adjustmentFailed", { defaultValue: "创建补差单失败" }), ); } }, }); }; return ( <>
{payments.length > 0 ? (

{t("settlementBills.paymentsHistory", { defaultValue: "收付记录" })}

    {payments.map((p) => (
  • {p.method ? `${p.method}` : t("settlementCenter:billDisplay.payment", { defaultValue: "收付" })} {p.remark ? ` · ${p.remark}` : ""} {formatDashboardMoneyMinor(p.amount, currencyCode)}
  • ))}
) : null} {rebateAllocations.length > 0 ? (

{t("settlementBills.rebateAllocations", { defaultValue: "回水分摊" })}

{t("settlementCenter:billDisplay.rebateAllocationsHint", { defaultValue: "各层级代理对回水的承担明细。", })}

    {rebateAllocationSummary.map((row) => (
  • {row.label} {t("common:count", { defaultValue: "{{count}} 条", count: row.rows })} {formatDashboardMoneyMinor(row.amount, currencyCode)}
  • ))}
{rebateDetailsOpen ? (
    {rebateAllocations.map((row) => (
  • {row.participant_label ?? `${row.participant_type}#${row.participant_id}`} ·{" "} {row.allocation_rule} {formatDashboardMoneyMinor(row.allocated_amount, currencyCode)}
  • ))}
) : null}
) : null} {canOperateBill && bill.status === "pending_confirm" ? (

{t("settlementBills.confirm", { defaultValue: "确认账单" })}

{t("settlementCenter:billDisplay.confirmHint", { defaultValue: "确认后才可以登记收款或付款。", })}

) : null} {canOperateBill && ["confirmed", "partial_paid", "overdue"].includes(bill.status) && bill.unpaid_amount > 0 ? (

{paymentTitle}

{t("settlementCenter:columns.unpaid", { defaultValue: "未结" })}{" "} {formatDashboardMoneyMinor(bill.unpaid_amount, currencyCode)}
{direction.payer} {direction.payee}
setPayAmount(e.target.value)} inputMode="decimal" placeholder={formatAdminMinorDecimal(bill.unpaid_amount, currencyCode)} className="bg-background/50 transition-colors focus:bg-background" />
setPayMethod(e.target.value)} placeholder={t("settlementBills.paymentMethodPlaceholder", { defaultValue: "例如:现金 / 银行转账", })} className="bg-background/50 transition-colors focus:bg-background" />
setPayProof(e.target.value)} placeholder={t("settlementBills.paymentProofPlaceholder", { defaultValue: "可填写流水号、截图说明或备注", })} className="bg-background/50 transition-colors focus:bg-background" />
) : null} {canWriteOff ? (

{t("settlementBills.badDebtWriteOff", { defaultValue: "坏账核销" })}

{t("settlementBills.badDebtHint", { defaultValue: "仅在确认无法收回时使用,核销后会生成坏账记录。", })}

setBadDebtReason(e.target.value)} placeholder={t("settlementBills.badDebtReasonPlaceholder", { defaultValue: "例如:客户失联、确认坏账", })} className="bg-background/50 transition-colors focus:bg-background border-destructive/30" />
) : null} {canFinanceAdjustments && locked ? (

{t("settlementBills.adjustment", { defaultValue: "补差/冲正单" })}

{t("settlementBills.adjustmentHint", { defaultValue: "正数表示补收,负数表示冲减;提交后会生成一张独立调账单。", })}

setAdjustAmount(e.target.value)} inputMode="decimal" placeholder={t("settlementBills.adjustmentAmountPlaceholder", { defaultValue: "例如:35.20 或 -10.00", })} className="bg-background/50 transition-colors focus:bg-background" />
setAdjustReason(e.target.value)} placeholder={t("settlementBills.adjustmentReasonPlaceholder", { defaultValue: "例如:人工复核补差、冲正错账", })} className="bg-background/50 transition-colors focus:bg-background" />
) : null}
); }