feat(admin, settlement, dashboard): strengthen permission gating and billing workflows

This commit is contained in:
2026-06-09 13:44:19 +08:00
parent 7e65c53732
commit b7278e68a4
41 changed files with 900 additions and 199 deletions

View File

@@ -165,9 +165,6 @@ export function AgentsPlayersPanel({
const viewPlayerLabel = t("players:viewDetail", { defaultValue: "查看玩家详情" });
const editPlayerLabel = t("players:editPlayer", { defaultValue: "编辑玩家" });
const deletePlayerLabel = t("players:deletePlayer", { defaultValue: "删除玩家" });
const settlementCenterLabel = t("playersPanel.gotoSettlementCenter", {
defaultValue: "去结算中心",
});
const profile = useAdminProfile();
const boundAgent = profile?.agent ?? null;
const isSuperAdmin = profile?.is_super_admin === true;
@@ -521,10 +518,15 @@ export function AgentsPlayersPanel({
async function handlePayBill(): Promise<void> {
if (selectedBill === null) return;
const amount = parseBillingAmount(payAmount || String(selectedBill.unpaid_amount ?? 0));
if (amount === null || amount <= 0 || amount > Number(selectedBill.unpaid_amount ?? 0)) {
toast.error(t("playersPanel.paymentAmountInvalid", { defaultValue: "请输入有效的收付金额" }));
return;
}
setBillingBusy(true);
try {
await postSettlementBillPayment(selectedBill.id, {
amount: Number(payAmount || selectedBill.unpaid_amount || 0),
amount,
method: payMethod.trim() || undefined,
proof: payProof.trim() || undefined,
});
@@ -546,10 +548,15 @@ export function AgentsPlayersPanel({
async function handleWriteOffBill(): Promise<void> {
if (selectedBill === null) return;
const reason = badDebtReason.trim();
if (!reason) {
toast.error(t("playersPanel.badDebtReasonRequired", { defaultValue: "请填写核销原因" }));
return;
}
setBillingBusy(true);
try {
await postSettlementBillBadDebtWriteOff(selectedBill.id, {
reason: badDebtReason.trim() || undefined,
reason,
});
toast.success(t("playersPanel.billWrittenOff", { defaultValue: "已核销坏账" }));
await load();
@@ -567,6 +574,68 @@ export function AgentsPlayersPanel({
}
}
function parseBillingAmount(raw: string): number | null {
const value = Number(raw);
if (!Number.isFinite(value) || !Number.isInteger(value)) {
return null;
}
return value;
}
function requestConfirmBillAction(): void {
if (selectedBill === null) return;
requestConfirm({
title: t("playersPanel.confirmBillTitle", { defaultValue: "确认账单?" }),
description: t("playersPanel.confirmBillDescription", {
defaultValue: "确认后账单会进入待收付状态,请确认金额与玩家无误。",
}),
confirmLabel: t("agents:settlementBills.confirm", { defaultValue: "确认账单" }),
confirmVariant: "default",
onConfirm: handleConfirmBill,
});
}
function requestPayBillAction(): void {
if (selectedBill === null) return;
const amount = parseBillingAmount(payAmount || String(selectedBill.unpaid_amount ?? 0));
if (amount === null || amount <= 0) {
toast.error(t("playersPanel.paymentAmountInvalid", { defaultValue: "请输入大于 0 的整数金额" }));
return;
}
if (amount > Number(selectedBill.unpaid_amount ?? 0)) {
toast.error(t("playersPanel.paymentAmountTooLarge", { defaultValue: "收付金额不能超过未结金额" }));
return;
}
requestConfirm({
title: t("playersPanel.payBillConfirmTitle", { defaultValue: "确认登记收付?" }),
description: t("playersPanel.payBillConfirmDescription", {
defaultValue: "这会写入收付记录并更新玩家账单金额,请确认金额与凭证无误。",
}),
confirmLabel: t("agents:settlementBills.paid", { defaultValue: "登记收付" }),
confirmVariant: "default",
onConfirm: handlePayBill,
});
}
function requestWriteOffBillAction(): void {
if (selectedBill === null) return;
if (!badDebtReason.trim()) {
toast.error(t("playersPanel.badDebtReasonRequired", { defaultValue: "请填写核销原因" }));
return;
}
requestConfirm({
title: t("playersPanel.writeOffBillConfirmTitle", { defaultValue: "确认核销坏账?" }),
description: t("playersPanel.writeOffBillConfirmDescription", {
defaultValue: "核销会把该玩家账单未结金额归档为坏账记录,请确认已无法收回。",
}),
confirmLabel: t("agents:settlementBills.confirmBadDebt", { defaultValue: "确认核销" }),
confirmVariant: "destructive",
onConfirm: handleWriteOffBill,
});
}
return (
<div className="space-y-4">
<ConfirmDialog />
@@ -936,7 +1005,7 @@ export function AgentsPlayersPanel({
</div>
{selectedBill.status === "pending_confirm" ? (
<Button type="button" className="w-full" disabled={billingBusy} onClick={() => void handleConfirmBill()}>
<Button type="button" className="w-full" disabled={billingBusy || confirmBusy} onClick={requestConfirmBillAction}>
{t("agents:settlementBills.confirm", { defaultValue: "确认账单" })}
</Button>
) : null}
@@ -967,7 +1036,7 @@ export function AgentsPlayersPanel({
})}
/>
</div>
<Button type="button" className="w-full" disabled={billingBusy} onClick={() => void handlePayBill()}>
<Button type="button" className="w-full" disabled={billingBusy || confirmBusy} onClick={requestPayBillAction}>
{t("agents:settlementBills.paid", { defaultValue: "登记收付" })}
</Button>
@@ -981,7 +1050,7 @@ export function AgentsPlayersPanel({
})}
/>
</div>
<Button type="button" variant="destructive" className="w-full" disabled={billingBusy} onClick={() => void handleWriteOffBill()}>
<Button type="button" variant="destructive" className="w-full" disabled={billingBusy || confirmBusy} onClick={requestWriteOffBillAction}>
{t("agents:settlementBills.confirmBadDebt", { defaultValue: "确认核销" })}
</Button>
</div>