Some checks failed
lotteryadmin CI / build (push) Has been cancelled
Updated the player management components to improve error handling and display for various operations, including ticket loading and transaction retrieval. Introduced new status badges for better visual representation of player statuses. Enhanced localization support for error messages and improved the overall user experience in the admin console. Refactored components for better maintainability and clarity.
545 lines
22 KiB
TypeScript
545 lines
22 KiB
TypeScript
"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<SettlementBillRow | null>(null);
|
||
const [payments, setPayments] = useState<SettlementBillPaymentRow[]>([]);
|
||
const [rebateAllocations, setRebateAllocations] = useState<RebateAllocationRow[]>([]);
|
||
const [downlineShares, setDownlineShares] = useState<DownlineShareBreakdown | null>(null);
|
||
const [loading, setLoading] = useState(true);
|
||
const [loadErr, setLoadErr] = useState<string | null>(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 <AdminLoadingState />;
|
||
}
|
||
|
||
if (loadErr || !bill) {
|
||
return (
|
||
<div className="space-y-3 py-4">
|
||
<p className="text-sm text-destructive">{loadErr ?? t("loadFailed", { ns: "common" })}</p>
|
||
<Button type="button" size="sm" variant="outline" onClick={() => void load()}>
|
||
{t("retry", { ns: "common", defaultValue: "重试" })}
|
||
</Button>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
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<Record<string, { key: string; label: string; amount: number; rows: number }>>(
|
||
(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 (
|
||
<>
|
||
<ConfirmDialog />
|
||
<div className="flex flex-col gap-5 text-sm">
|
||
<SettlementBillSummaryHeader bill={bill} currencyCode={currencyCode} />
|
||
<SettlementBillAmountBreakdown
|
||
bill={bill}
|
||
currencyCode={currencyCode}
|
||
downlineShares={downlineShares}
|
||
/>
|
||
|
||
{payments.length > 0 ? (
|
||
<div className="space-y-2 rounded-xl border border-border/70 p-4">
|
||
<p className="font-medium">
|
||
{t("settlementBills.paymentsHistory", { defaultValue: "收付记录" })}
|
||
</p>
|
||
<ul className="space-y-1.5 text-muted-foreground">
|
||
{payments.map((p) => (
|
||
<li key={p.id} className="flex justify-between gap-2">
|
||
<span>
|
||
{p.method
|
||
? `${p.method}`
|
||
: t("settlementCenter:billDisplay.payment", { defaultValue: "收付" })}
|
||
{p.remark ? ` · ${p.remark}` : ""}
|
||
</span>
|
||
<span className="shrink-0 tabular-nums">
|
||
{formatDashboardMoneyMinor(p.amount, currencyCode)}
|
||
</span>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</div>
|
||
) : null}
|
||
|
||
{rebateAllocations.length > 0 ? (
|
||
<div className="space-y-2 rounded-xl border border-border/70 bg-card p-5 shadow-sm">
|
||
<p className="font-semibold tracking-tight">
|
||
{t("settlementBills.rebateAllocations", { defaultValue: "回水分摊" })}
|
||
</p>
|
||
<p className="text-xs text-muted-foreground/80">
|
||
{t("settlementCenter:billDisplay.rebateAllocationsHint", {
|
||
defaultValue: "各层级代理对回水的承担明细。",
|
||
})}
|
||
</p>
|
||
<div className="mt-3 space-y-3">
|
||
<ul className="space-y-1.5 text-muted-foreground">
|
||
{rebateAllocationSummary.map((row) => (
|
||
<li key={row.key} className="flex justify-between gap-2">
|
||
<span className="min-w-0">
|
||
{row.label}
|
||
<span className="ml-2 text-xs text-muted-foreground/60">
|
||
{t("common:count", { defaultValue: "{{count}} 条", count: row.rows })}
|
||
</span>
|
||
</span>
|
||
<span className="shrink-0 tabular-nums">
|
||
{formatDashboardMoneyMinor(row.amount, currencyCode)}
|
||
</span>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
|
||
<button
|
||
type="button"
|
||
className="text-xs font-medium text-primary underline-offset-4 hover:underline"
|
||
onClick={() => setRebateDetailsOpen((open) => !open)}
|
||
>
|
||
{rebateDetailsOpen
|
||
? t("settlementCenter:billDisplay.hideRawRebateAllocations", {
|
||
defaultValue: "收起原始明细",
|
||
})
|
||
: t("settlementCenter:billDisplay.showRawRebateAllocations", {
|
||
defaultValue: "展开原始明细",
|
||
})}
|
||
</button>
|
||
|
||
{rebateDetailsOpen ? (
|
||
<ul className="max-h-[280px] space-y-1.5 overflow-y-auto rounded-lg border border-dashed border-border/70 p-3 pr-2 text-muted-foreground">
|
||
{rebateAllocations.map((row) => (
|
||
<li key={row.id} className="flex justify-between gap-2">
|
||
<span>
|
||
{row.participant_label ?? `${row.participant_type}#${row.participant_id}`} ·{" "}
|
||
{row.allocation_rule}
|
||
</span>
|
||
<span className="shrink-0 tabular-nums">
|
||
{formatDashboardMoneyMinor(row.allocated_amount, currencyCode)}
|
||
</span>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
) : null}
|
||
</div>
|
||
</div>
|
||
) : null}
|
||
|
||
{canOperateBill && bill.status === "pending_confirm" ? (
|
||
<div className="space-y-3 rounded-xl border border-primary/20 bg-primary/5 p-5 shadow-sm">
|
||
<div className="space-y-1">
|
||
<p className="font-semibold tracking-tight text-primary">
|
||
{t("settlementBills.confirm", { defaultValue: "确认账单" })}
|
||
</p>
|
||
<p className="text-xs text-muted-foreground/80">
|
||
{t("settlementCenter:billDisplay.confirmHint", {
|
||
defaultValue: "确认后才可以登记收款或付款。",
|
||
})}
|
||
</p>
|
||
</div>
|
||
<Button
|
||
type="button"
|
||
className="w-full sm:w-auto sm:min-w-[10rem]"
|
||
disabled={confirmBusy}
|
||
onClick={requestConfirmBill}
|
||
>
|
||
{t("settlementBills.confirm", { defaultValue: "确认账单" })}
|
||
</Button>
|
||
</div>
|
||
) : null}
|
||
|
||
{canOperateBill && ["confirmed", "partial_paid", "overdue"].includes(bill.status) && bill.unpaid_amount > 0 ? (
|
||
<div className="space-y-4 rounded-xl border border-border/70 bg-card p-5 shadow-sm">
|
||
<div className="space-y-2">
|
||
<div className="flex flex-wrap items-center justify-between gap-2">
|
||
<p className="font-semibold tracking-tight">{paymentTitle}</p>
|
||
<span className="rounded-full bg-amber-500/10 px-2.5 py-0.5 text-xs font-medium text-amber-600 dark:bg-amber-500/20 dark:text-amber-400">
|
||
{t("settlementCenter:columns.unpaid", { defaultValue: "未结" })}{" "}
|
||
{formatDashboardMoneyMinor(bill.unpaid_amount, currencyCode)}
|
||
</span>
|
||
</div>
|
||
<div className="flex flex-wrap items-center gap-1 text-[13px] text-muted-foreground">
|
||
<span className="font-medium text-foreground/80">{direction.payer}</span>
|
||
<ArrowRight className="size-3.5 shrink-0" aria-hidden />
|
||
<span className="font-medium text-foreground/80">{direction.payee}</span>
|
||
</div>
|
||
</div>
|
||
<div className="grid gap-4 mt-2">
|
||
<div className="space-y-1.5">
|
||
<Label className="text-muted-foreground">{t("settlementBills.paymentAmount", { defaultValue: "收付金额" })}</Label>
|
||
<Input
|
||
value={payAmount}
|
||
onChange={(e) => setPayAmount(e.target.value)}
|
||
inputMode="decimal"
|
||
placeholder={formatAdminMinorDecimal(bill.unpaid_amount, currencyCode)}
|
||
className="bg-background/50 transition-colors focus:bg-background"
|
||
/>
|
||
</div>
|
||
<div className="space-y-1.5">
|
||
<Label className="text-muted-foreground">{t("settlementBills.paymentMethod", { defaultValue: "收付方式" })}</Label>
|
||
<Input
|
||
value={payMethod}
|
||
onChange={(e) => setPayMethod(e.target.value)}
|
||
placeholder={t("settlementBills.paymentMethodPlaceholder", {
|
||
defaultValue: "例如:现金 / 银行转账",
|
||
})}
|
||
className="bg-background/50 transition-colors focus:bg-background"
|
||
/>
|
||
</div>
|
||
<div className="space-y-1.5">
|
||
<Label className="text-muted-foreground">{t("settlementBills.paymentProof", { defaultValue: "凭证/备注" })}</Label>
|
||
<Input
|
||
value={payProof}
|
||
onChange={(e) => setPayProof(e.target.value)}
|
||
placeholder={t("settlementBills.paymentProofPlaceholder", {
|
||
defaultValue: "可填写流水号、截图说明或备注",
|
||
})}
|
||
className="bg-background/50 transition-colors focus:bg-background"
|
||
/>
|
||
</div>
|
||
</div>
|
||
<Button
|
||
type="button"
|
||
className="w-full mt-2"
|
||
disabled={confirmBusy}
|
||
onClick={requestPayment}
|
||
>
|
||
{paymentSubmit}
|
||
</Button>
|
||
</div>
|
||
) : null}
|
||
|
||
{canWriteOff ? (
|
||
<div className="space-y-4 rounded-xl border border-destructive/20 bg-destructive/5 p-5 shadow-sm">
|
||
<div className="space-y-1">
|
||
<p className="font-semibold tracking-tight text-destructive">
|
||
{t("settlementBills.badDebtWriteOff", { defaultValue: "坏账核销" })}
|
||
</p>
|
||
<p className="text-xs text-destructive/80">
|
||
{t("settlementBills.badDebtHint", {
|
||
defaultValue: "仅在确认无法收回时使用,核销后会生成坏账记录。",
|
||
})}
|
||
</p>
|
||
</div>
|
||
<div className="space-y-1.5 mt-2">
|
||
<Label className="text-destructive/90">{t("settlementBills.badDebtReason", { defaultValue: "核销原因" })}</Label>
|
||
<Input
|
||
value={badDebtReason}
|
||
onChange={(e) => setBadDebtReason(e.target.value)}
|
||
placeholder={t("settlementBills.badDebtReasonPlaceholder", {
|
||
defaultValue: "例如:客户失联、确认坏账",
|
||
})}
|
||
className="bg-background/50 transition-colors focus:bg-background border-destructive/30"
|
||
/>
|
||
</div>
|
||
<Button
|
||
type="button"
|
||
variant="destructive"
|
||
className="w-full mt-2"
|
||
disabled={confirmBusy}
|
||
onClick={requestBadDebtWriteOff}
|
||
>
|
||
{t("settlementBills.confirmBadDebt", { defaultValue: "确认核销" })}
|
||
</Button>
|
||
</div>
|
||
) : null}
|
||
|
||
{canFinanceAdjustments && locked ? (
|
||
<div className="space-y-4 rounded-xl border border-dashed border-border/70 bg-card p-5 shadow-sm">
|
||
<div className="space-y-1">
|
||
<p className="font-semibold tracking-tight">
|
||
{t("settlementBills.adjustment", { defaultValue: "补差/冲正单" })}
|
||
</p>
|
||
<p className="text-xs text-muted-foreground/80">
|
||
{t("settlementBills.adjustmentHint", {
|
||
defaultValue: "正数表示补收,负数表示冲减;提交后会生成一张独立调账单。",
|
||
})}
|
||
</p>
|
||
</div>
|
||
<div className="space-y-1.5 mt-2">
|
||
<Label className="text-muted-foreground">{t("settlementBills.adjustmentAmount", { defaultValue: "调整金额(可负)" })}</Label>
|
||
<Input
|
||
value={adjustAmount}
|
||
onChange={(e) => setAdjustAmount(e.target.value)}
|
||
inputMode="decimal"
|
||
placeholder={t("settlementBills.adjustmentAmountPlaceholder", {
|
||
defaultValue: "例如:35.20 或 -10.00",
|
||
})}
|
||
className="bg-background/50 transition-colors focus:bg-background"
|
||
/>
|
||
</div>
|
||
<div className="space-y-1.5">
|
||
<Label className="text-muted-foreground">{t("settlementBills.adjustmentReason", { defaultValue: "调整原因" })}</Label>
|
||
<Input
|
||
value={adjustReason}
|
||
onChange={(e) => setAdjustReason(e.target.value)}
|
||
placeholder={t("settlementBills.adjustmentReasonPlaceholder", {
|
||
defaultValue: "例如:人工复核补差、冲正错账",
|
||
})}
|
||
className="bg-background/50 transition-colors focus:bg-background"
|
||
/>
|
||
</div>
|
||
<Button
|
||
type="button"
|
||
variant="outline"
|
||
className="w-full mt-2"
|
||
disabled={confirmBusy}
|
||
onClick={requestAdjustment}
|
||
>
|
||
{t("settlementBills.createAdjustment", { defaultValue: "创建补差单" })}
|
||
</Button>
|
||
</div>
|
||
) : null}
|
||
</div>
|
||
</>
|
||
);
|
||
}
|