feat(api, agents, i18n): enhance settlement features and multi-language support
Added new types and API functions for settlement period summaries and credit ledgers, improving the management of agent settlements. Updated the admin console to reflect these changes, enhancing user experience with better navigation and data presentation. Additionally, expanded multi-language support by incorporating new translations in English, Nepali, and Chinese for settlement-related terms, ensuring consistency across the platform.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { ArrowRight } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
|
||||
@@ -16,30 +17,16 @@ import {
|
||||
} from "@/api/admin-agent-settlement";
|
||||
import { AdminLoadingState } from "@/components/admin/admin-loading-state";
|
||||
import { formatDashboardMoneyMinor } from "@/modules/dashboard/use-dashboard-analytics";
|
||||
import {
|
||||
SettlementBillAmountBreakdown,
|
||||
SettlementBillPartiesRow,
|
||||
SettlementBillSummaryHeader,
|
||||
} from "@/modules/settlement/settlement-bill-breakdown";
|
||||
import { describeBillPaymentDirection } from "@/modules/settlement/settlement-bill-display";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
||||
function parseBillMeta(metaJson: SettlementBillRow["meta_json"]): {
|
||||
share_profit?: number;
|
||||
platform_share_profit?: number;
|
||||
} {
|
||||
if (metaJson == null || metaJson === "") {
|
||||
return {};
|
||||
}
|
||||
try {
|
||||
const parsed =
|
||||
typeof metaJson === "string" ? (JSON.parse(metaJson) as Record<string, unknown>) : metaJson;
|
||||
return {
|
||||
share_profit: parsed.share_profit != null ? Number(parsed.share_profit) : undefined,
|
||||
platform_share_profit:
|
||||
parsed.platform_share_profit != null ? Number(parsed.platform_share_profit) : undefined,
|
||||
};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
type AgentBillDetailProps = {
|
||||
billId: number;
|
||||
currencyCode: string;
|
||||
@@ -53,17 +40,17 @@ export function AgentBillDetail({
|
||||
canManage = true,
|
||||
onUpdated,
|
||||
}: AgentBillDetailProps): React.ReactElement {
|
||||
const { t } = useTranslation(["agents", "common"]);
|
||||
const { t } = useTranslation(["agents", "settlementCenter", "common"]);
|
||||
const [bill, setBill] = useState<SettlementBillRow | null>(null);
|
||||
const [payments, setPayments] = useState<SettlementPaymentRow[]>([]);
|
||||
const [rebateAllocations, setRebateAllocations] = useState<RebateAllocationRow[]>([]);
|
||||
const [tierEdge, setTierEdge] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [payAmount, setPayAmount] = useState("");
|
||||
const [payMethod, setPayMethod] = useState("");
|
||||
const [payProof, setPayProof] = useState("");
|
||||
const [adjustAmount, setAdjustAmount] = useState("");
|
||||
const [badDebtReason, setBadDebtReason] = useState("");
|
||||
const [rebateDetailsOpen, setRebateDetailsOpen] = useState(false);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
setLoading(true);
|
||||
@@ -72,7 +59,6 @@ export function AgentBillDetail({
|
||||
setBill(data.bill);
|
||||
setPayments(data.payments ?? []);
|
||||
setRebateAllocations(data.rebate_allocations ?? []);
|
||||
setTierEdge(data.tier_edge ?? null);
|
||||
setPayAmount(String(data.bill.unpaid_amount ?? 0));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@@ -87,20 +73,12 @@ export function AgentBillDetail({
|
||||
return <AdminLoadingState />;
|
||||
}
|
||||
|
||||
const owner =
|
||||
bill.owner_label ??
|
||||
`${bill.owner_type}#${bill.owner_id}`;
|
||||
const counterparty =
|
||||
bill.counterparty_label === "platform"
|
||||
? t("settlementBills.platform", { defaultValue: "平台" })
|
||||
: bill.counterparty_label ?? `${bill.counterparty_type}#${bill.counterparty_id}`;
|
||||
|
||||
const direction = describeBillPaymentDirection(bill, t);
|
||||
const locked = ["confirmed", "partial_paid", "settled", "overdue"].includes(bill.status);
|
||||
const ownerOwes = bill.net_amount > 0;
|
||||
const paymentTitle = ownerOwes
|
||||
? t("settlementBills.recordReceipt", { defaultValue: "登记收款" })
|
||||
: t("settlementBills.recordPayout", { defaultValue: "登记付款" });
|
||||
const paymentSubmit = ownerOwes
|
||||
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 =
|
||||
@@ -108,206 +86,300 @@ export function AgentBillDetail({
|
||||
bill.unpaid_amount > 0 &&
|
||||
["confirmed", "partial_paid", "overdue"].includes(bill.status) &&
|
||||
!["adjustment", "reversal", "bad_debt"].includes(bill.bill_type);
|
||||
const meta = parseBillMeta(bill.meta_json);
|
||||
const hasSubtreeFields =
|
||||
bill.gross_win_loss != null ||
|
||||
bill.rebate_amount != null ||
|
||||
bill.platform_rounding_adjustment != null ||
|
||||
meta.share_profit != null;
|
||||
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"));
|
||||
|
||||
return (
|
||||
<div className="space-y-4 text-sm">
|
||||
<div>
|
||||
<span className="text-muted-foreground">{t("settlementBills.columns.party", { defaultValue: "本方" })}: </span>
|
||||
{owner}
|
||||
<div className="grid gap-6 xl:grid-cols-[minmax(0,1.35fr)_minmax(340px,0.95fr)]">
|
||||
<div className="space-y-5 text-sm">
|
||||
<SettlementBillSummaryHeader bill={bill} currencyCode={currencyCode} />
|
||||
<SettlementBillPartiesRow bill={bill} />
|
||||
<SettlementBillAmountBreakdown bill={bill} currencyCode={currencyCode} />
|
||||
|
||||
{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}
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted-foreground">
|
||||
{t("settlementBills.columns.counterparty", { defaultValue: "对方" })}:{" "}
|
||||
</span>
|
||||
{counterparty}
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted-foreground">{t("settlementBills.columns.type", { defaultValue: "类型" })}: </span>
|
||||
{bill.bill_type} / {bill.status}
|
||||
{tierEdge ? ` · ${tierEdge}` : ""}
|
||||
</div>
|
||||
{hasSubtreeFields ? (
|
||||
<div className="space-y-1 rounded-md border border-border/60 p-3">
|
||||
<p className="font-medium">
|
||||
{t("settlementBills.subtreeSummary", { defaultValue: "子树汇总" })}
|
||||
</p>
|
||||
{bill.gross_win_loss != null ? (
|
||||
<div>
|
||||
<span className="text-muted-foreground">
|
||||
{t("settlementBills.grossWinLoss", { defaultValue: "输赢 (gross_win_loss)" })}:{" "}
|
||||
</span>
|
||||
{formatDashboardMoneyMinor(bill.gross_win_loss, currencyCode)}
|
||||
|
||||
<div className="space-y-5 text-sm">
|
||||
{rebateAllocations.length > 0 ? (
|
||||
<div className="space-y-2 rounded-xl border border-border/70 p-4">
|
||||
<p className="font-medium">
|
||||
{t("settlementBills.rebateAllocations", { defaultValue: "回水分摊" })}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t("settlementCenter:billDisplay.rebateAllocationsHint", {
|
||||
defaultValue: "各层级代理对回水的承担明细。",
|
||||
})}
|
||||
</p>
|
||||
<div className="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/75">
|
||||
{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>
|
||||
) : null}
|
||||
{bill.rebate_amount != null ? (
|
||||
<div>
|
||||
<span className="text-muted-foreground">
|
||||
{t("settlementBills.rebateAmount", { defaultValue: "回水" })}:{" "}
|
||||
</span>
|
||||
{formatDashboardMoneyMinor(bill.rebate_amount, currencyCode)}
|
||||
</div>
|
||||
) : null}
|
||||
{meta.share_profit != null ? (
|
||||
<div>
|
||||
<span className="text-muted-foreground">
|
||||
{t("settlementBills.shareProfit", { defaultValue: "占成利润" })}:{" "}
|
||||
</span>
|
||||
{formatDashboardMoneyMinor(meta.share_profit, currencyCode)}
|
||||
</div>
|
||||
) : null}
|
||||
{bill.platform_rounding_adjustment != null && bill.platform_rounding_adjustment !== 0 ? (
|
||||
<div>
|
||||
<span className="text-muted-foreground">
|
||||
{t("settlementBills.platformRounding", { defaultValue: "平台尾差" })}:{" "}
|
||||
</span>
|
||||
{formatDashboardMoneyMinor(bill.platform_rounding_adjustment, currencyCode)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div>
|
||||
<span className="text-muted-foreground">{t("settlementBills.columns.net", { defaultValue: "净额" })}: </span>
|
||||
{formatDashboardMoneyMinor(bill.net_amount, currencyCode)}
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted-foreground">{t("settlementBills.columns.unpaid", { defaultValue: "未结" })}: </span>
|
||||
{formatDashboardMoneyMinor(bill.unpaid_amount, currencyCode)}
|
||||
</div>
|
||||
|
||||
{rebateAllocations.length > 0 ? (
|
||||
<div className="space-y-1 rounded-md border border-border/60 p-3">
|
||||
<p className="font-medium">{t("settlementBills.rebateAllocations", { defaultValue: "回水分摊" })}</p>
|
||||
<ul className="space-y-1 text-muted-foreground">
|
||||
{rebateAllocations.map((row) => (
|
||||
<li key={row.id}>
|
||||
{row.participant_type}#{row.participant_id} · {row.allocation_rule} ·{" "}
|
||||
{formatDashboardMoneyMinor(row.allocated_amount, currencyCode)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{payments.length > 0 ? (
|
||||
<div className="space-y-1 rounded-md border border-border/60 p-3">
|
||||
<p className="font-medium">{t("settlementBills.paymentsHistory", { defaultValue: "收付记录" })}</p>
|
||||
<ul className="space-y-1 text-muted-foreground">
|
||||
{payments.map((p) => (
|
||||
<li key={p.id}>
|
||||
{formatDashboardMoneyMinor(p.amount, currencyCode)}
|
||||
{p.method ? ` · ${p.method}` : ""}
|
||||
{p.remark ? ` · ${p.remark}` : ""}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{canManage && bill.status === "pending_confirm" ? (
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
void postSettlementBillConfirm(billId)
|
||||
.then(load)
|
||||
.then(onUpdated)
|
||||
.then(() => toast.success(t("settlementBills.confirmed", { defaultValue: "已确认" })))
|
||||
}
|
||||
>
|
||||
{t("settlementBills.confirm", { defaultValue: "确认账单" })}
|
||||
</Button>
|
||||
) : null}
|
||||
|
||||
{canManage && ["confirmed", "partial_paid", "overdue"].includes(bill.status) && bill.unpaid_amount > 0 ? (
|
||||
<div className="space-y-2 rounded-md border border-border/60 p-3">
|
||||
<p className="font-medium">{paymentTitle}</p>
|
||||
<div className="grid gap-2 sm:grid-cols-2">
|
||||
{canManage && bill.status === "pending_confirm" ? (
|
||||
<div className="space-y-3 rounded-xl border border-border/70 bg-muted/15 p-4">
|
||||
<div className="space-y-1">
|
||||
<Label>{t("settlementBills.paymentAmount", { defaultValue: "收付金额" })}</Label>
|
||||
<Input value={payAmount} onChange={(e) => setPayAmount(e.target.value)} />
|
||||
<p className="font-medium">
|
||||
{t("settlementBills.confirm", { defaultValue: "确认账单" })}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t("settlementCenter:billDisplay.confirmHint", {
|
||||
defaultValue: "确认后才可以登记收款或付款。",
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
className="w-full"
|
||||
onClick={() =>
|
||||
void postSettlementBillConfirm(billId)
|
||||
.then(load)
|
||||
.then(onUpdated)
|
||||
.then(() => toast.success(t("settlementBills.confirmed", { defaultValue: "已确认" })))
|
||||
}
|
||||
>
|
||||
{t("settlementBills.confirm", { defaultValue: "确认账单" })}
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{canManage && ["confirmed", "partial_paid", "overdue"].includes(bill.status) && bill.unpaid_amount > 0 ? (
|
||||
<div className="space-y-3 rounded-xl border border-border/70 p-4">
|
||||
<div className="space-y-1">
|
||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||
<p className="font-medium">{paymentTitle}</p>
|
||||
<span className="rounded-full bg-amber-50 px-2.5 py-1 text-xs font-medium text-amber-700 dark:bg-amber-950/30 dark:text-amber-300">
|
||||
{t("settlementCenter:columns.unpaid", { defaultValue: "未结" })}{" "}
|
||||
{formatDashboardMoneyMinor(bill.unpaid_amount, currencyCode)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-1 text-xs text-muted-foreground">
|
||||
<span>{direction.payer}</span>
|
||||
<ArrowRight className="size-3.5 shrink-0" aria-hidden />
|
||||
<span>{direction.payee}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<div className="space-y-1">
|
||||
<Label>{t("settlementBills.paymentAmount", { defaultValue: "收付金额" })}</Label>
|
||||
<Input
|
||||
value={payAmount}
|
||||
onChange={(e) => setPayAmount(e.target.value)}
|
||||
placeholder={String(bill.unpaid_amount)}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label>{t("settlementBills.paymentMethod", { defaultValue: "收付方式" })}</Label>
|
||||
<Input
|
||||
value={payMethod}
|
||||
onChange={(e) => setPayMethod(e.target.value)}
|
||||
placeholder={t("settlementBills.paymentMethodPlaceholder", {
|
||||
defaultValue: "例如:现金 / 银行转账",
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label>{t("settlementBills.paymentProof", { defaultValue: "凭证/备注" })}</Label>
|
||||
<Input
|
||||
value={payProof}
|
||||
onChange={(e) => setPayProof(e.target.value)}
|
||||
placeholder={t("settlementBills.paymentProofPlaceholder", {
|
||||
defaultValue: "可填写流水号、截图说明或备注",
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
className="w-full"
|
||||
onClick={() =>
|
||||
void postSettlementBillPayment(billId, {
|
||||
amount: Number(payAmount),
|
||||
method: payMethod.trim() || undefined,
|
||||
proof: payProof.trim() || undefined,
|
||||
})
|
||||
.then(load)
|
||||
.then(onUpdated)
|
||||
.then(() => toast.success(t("settlementBills.paid", { defaultValue: "已登记收付" })))
|
||||
}
|
||||
>
|
||||
{paymentSubmit}
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{canWriteOff ? (
|
||||
<div className="space-y-3 rounded-xl border border-border/70 p-4">
|
||||
<div className="space-y-1">
|
||||
<p className="font-medium">
|
||||
{t("settlementBills.badDebtWriteOff", { defaultValue: "坏账核销" })}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t("settlementBills.badDebtHint", {
|
||||
defaultValue: "仅在确认无法收回时使用,核销后会生成坏账记录。",
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label>{t("settlementBills.paymentMethod", { defaultValue: "方式" })}</Label>
|
||||
<Input value={payMethod} onChange={(e) => setPayMethod(e.target.value)} placeholder="cash" />
|
||||
<Label>{t("settlementBills.badDebtReason", { defaultValue: "核销原因" })}</Label>
|
||||
<Input
|
||||
value={badDebtReason}
|
||||
onChange={(e) => setBadDebtReason(e.target.value)}
|
||||
placeholder={t("settlementBills.badDebtReasonPlaceholder", {
|
||||
defaultValue: "例如:客户失联、确认坏账",
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1 sm:col-span-2">
|
||||
<Label>{t("settlementBills.paymentProof", { defaultValue: "凭证/备注" })}</Label>
|
||||
<Input value={payProof} onChange={(e) => setPayProof(e.target.value)} />
|
||||
<Button
|
||||
type="button"
|
||||
variant="destructive"
|
||||
className="w-full"
|
||||
onClick={() =>
|
||||
void postSettlementBillBadDebtWriteOff(billId, {
|
||||
reason: badDebtReason.trim() || undefined,
|
||||
})
|
||||
.then(load)
|
||||
.then(onUpdated)
|
||||
.then(() =>
|
||||
toast.success(t("settlementBills.badDebtDone", { defaultValue: "已核销坏账" })),
|
||||
)
|
||||
}
|
||||
>
|
||||
{t("settlementBills.confirmBadDebt", { defaultValue: "确认核销" })}
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{canManage && locked ? (
|
||||
<div className="space-y-3 rounded-xl border border-dashed border-border/70 p-4">
|
||||
<div className="space-y-1">
|
||||
<p className="font-medium">
|
||||
{t("settlementBills.adjustment", { defaultValue: "补差/冲正单" })}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t("settlementBills.adjustmentHint", {
|
||||
defaultValue: "正数表示补收,负数表示冲减;提交后会生成一张独立调账单。",
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label>{t("settlementBills.adjustmentAmount", { defaultValue: "调整金额(可负)" })}</Label>
|
||||
<Input
|
||||
value={adjustAmount}
|
||||
onChange={(e) => setAdjustAmount(e.target.value)}
|
||||
type="number"
|
||||
placeholder={t("settlementBills.adjustmentAmountPlaceholder", {
|
||||
defaultValue: "输入正数或负数",
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
onClick={() =>
|
||||
void postSettlementBillAdjustment(billId, {
|
||||
amount: Number(adjustAmount),
|
||||
reason: "manual_adjustment",
|
||||
})
|
||||
.then(() =>
|
||||
toast.success(t("settlementBills.adjustmentCreated", { defaultValue: "已创建补差单" })),
|
||||
)
|
||||
.then(onUpdated)
|
||||
}
|
||||
>
|
||||
{t("settlementBills.createAdjustment", { defaultValue: "创建补差单" })}
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
void postSettlementBillPayment(billId, {
|
||||
amount: Number(payAmount),
|
||||
method: payMethod.trim() || undefined,
|
||||
proof: payProof.trim() || undefined,
|
||||
})
|
||||
.then(load)
|
||||
.then(onUpdated)
|
||||
.then(() => toast.success(t("settlementBills.paid", { defaultValue: "已登记收付" })))
|
||||
}
|
||||
>
|
||||
{paymentSubmit}
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{canWriteOff ? (
|
||||
<div className="space-y-2 rounded-md border border-border/60 p-3">
|
||||
<p className="font-medium">{t("settlementBills.badDebtWriteOff", { defaultValue: "坏账核销" })}</p>
|
||||
<div className="space-y-1">
|
||||
<Label>{t("settlementBills.badDebtReason", { defaultValue: "核销原因" })}</Label>
|
||||
<Input value={badDebtReason} onChange={(e) => setBadDebtReason(e.target.value)} />
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="destructive"
|
||||
onClick={() =>
|
||||
void postSettlementBillBadDebtWriteOff(billId, {
|
||||
reason: badDebtReason.trim() || undefined,
|
||||
})
|
||||
.then(load)
|
||||
.then(onUpdated)
|
||||
.then(() =>
|
||||
toast.success(t("settlementBills.badDebtDone", { defaultValue: "已核销坏账" })),
|
||||
)
|
||||
}
|
||||
>
|
||||
{t("settlementBills.confirmBadDebt", { defaultValue: "确认核销" })}
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{canManage && locked ? (
|
||||
<div className="space-y-2 rounded-md border border-dashed border-border/60 p-3">
|
||||
<p className="font-medium">{t("settlementBills.adjustment", { defaultValue: "补差/冲正单" })}</p>
|
||||
<div className="space-y-1">
|
||||
<Label>{t("settlementBills.adjustmentAmount", { defaultValue: "调整金额(可负)" })}</Label>
|
||||
<Input value={adjustAmount} onChange={(e) => setAdjustAmount(e.target.value)} type="number" />
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() =>
|
||||
void postSettlementBillAdjustment(billId, {
|
||||
amount: Number(adjustAmount),
|
||||
reason: "manual_adjustment",
|
||||
})
|
||||
.then(() => toast.success(t("settlementBills.adjustmentCreated", { defaultValue: "已创建补差单" })))
|
||||
.then(onUpdated)
|
||||
}
|
||||
>
|
||||
{t("settlementBills.createAdjustment", { defaultValue: "创建补差单" })}
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user