feat(settlement): 优化结算中心待处理视图与账单显示
Some checks failed
lotteryadmin CI / build (push) Has been cancelled
Some checks failed
lotteryadmin CI / build (push) Has been cancelled
- 新增接口参数 agent_scope 支持筛选直接代理 - 结算中心导航新增“待处理”视图,并设置默认视图为“待处理” - 结算中心页面新增待处理账单工作台,支持待确认、待收付和异常账单分类显示 - 调整账单状态文本,明确“已确认”改为“待收付”,“部分结清”改为“部分收付” - 优化账单明细中的代理结算说明和收益展示文案 - 账单详情页添加当前账单主要操作提示区,提示确认、登记付款或复核状态 - 结算中心流水模块及多语言资源关于“账务流水”统一改为“流水审计” - 优化结算主面板中待处理模式与全部模式的筛选和分页逻辑 - 结合管理员权限控制结算中心可见站点列表,增强集成站点加载逻辑
This commit is contained in:
@@ -18,6 +18,7 @@ export async function getAdminPlayers(params?: {
|
||||
status?: number;
|
||||
site_code?: string;
|
||||
agent_node_id?: number;
|
||||
agent_scope?: "subtree" | "direct";
|
||||
}): Promise<AdminPlayerListData> {
|
||||
return adminRequest.get<AdminPlayerListData>(`${A}/players`, { params });
|
||||
}
|
||||
|
||||
@@ -65,10 +65,13 @@
|
||||
},
|
||||
"nav": {
|
||||
"periods": "Periods",
|
||||
"pendingWorkbench": "To do",
|
||||
"allBills": "All bills",
|
||||
"bills": "Bills",
|
||||
"operations": "Payments & adjustments",
|
||||
"aria": "Period views",
|
||||
"ledger": "Account ledger",
|
||||
"ledgerAudit": "Ledger audit",
|
||||
"creditLedger": "Credit ledger",
|
||||
"playerBills": "Player bills",
|
||||
"agentBills": "Agent bills",
|
||||
@@ -194,9 +197,9 @@
|
||||
},
|
||||
"billStatus": {
|
||||
"pending_confirm": "Pending confirm",
|
||||
"confirmed": "Confirmed",
|
||||
"partial_paid": "Partial paid",
|
||||
"settled": "Settled",
|
||||
"confirmed": "Awaiting payment",
|
||||
"partial_paid": "Partial payment",
|
||||
"settled": "Fully settled",
|
||||
"overdue": "Overdue",
|
||||
"reversed": "Reversed"
|
||||
},
|
||||
@@ -229,26 +232,34 @@
|
||||
"payerLabel": "Payer",
|
||||
"payeeLabel": "Payee",
|
||||
"playerBreakdownIntro": "Players settle only with their direct agent: net = win/loss − rebate.",
|
||||
"agentBreakdownIntro": "Agents settle only with their upline: net = team net − downline share − own share.",
|
||||
"agentBreakdownIntro": "Start with this period's team win/loss, deduct player rebates and retained shares, then settle the remaining amount with the upline.",
|
||||
"playerGross": "Game win/loss",
|
||||
"playerLostHint": "Player lost; owes agent",
|
||||
"playerWonHint": "Player won; agent owes player",
|
||||
"playerNet": "Player net payable",
|
||||
"playerNetReceive": "Agent pays player",
|
||||
"teamGross": "Team game win/loss",
|
||||
"teamGrossHint": "Includes this agent and all downline players",
|
||||
"teamGross": "Team win/loss this period",
|
||||
"teamGrossHint": "Total for this agent and downline players in this period",
|
||||
"teamGrossShort": "Team",
|
||||
"playerGrossShort": "Player",
|
||||
"teamRebate": "Team rebate",
|
||||
"teamNet": "Team net",
|
||||
"teamRebate": "Deduct player rebates",
|
||||
"teamNet": "Profit available to split",
|
||||
"rebate": "Rebate",
|
||||
"agentShareKeep": "Share kept at this tier",
|
||||
"agentShareKeepHint": "Profit retained at this tier by share ratio",
|
||||
"agentDownlineShare": "Downline share",
|
||||
"agentDownlineShareHint": "Profit retained by downline agents (see breakdown below)",
|
||||
"agentShareKeep": "This tier keeps",
|
||||
"agentShareKeepHint": "Amount retained by this tier under the agreed share",
|
||||
"agentDownlineShare": "Downline agents keep",
|
||||
"agentDownlineShareHint": "Amount retained by downline agents first (see breakdown below)",
|
||||
"agentDownlineShareItem": "{{agent}} kept",
|
||||
"agentNet": "Pay {{counterparty}}",
|
||||
"agentNetReceive": "{{counterparty}} pays this tier",
|
||||
"agentNet": "Remit to {{counterparty}}",
|
||||
"agentNetReceive": "{{counterparty}} owes this tier",
|
||||
"actionPendingConfirmTitle": "Confirm this bill next",
|
||||
"actionPendingConfirmDesc": "Confirm the amount and parties, then the bill moves to awaiting payment.",
|
||||
"actionAwaitingPaymentTitle": "Record payment next",
|
||||
"actionAwaitingPaymentDesc": "Record offline payment according to the payer/payee direction. Partial payments are allowed.",
|
||||
"actionSettledTitle": "This bill is fully settled",
|
||||
"actionSettledDesc": "Review the breakdown and payment history below if needed.",
|
||||
"actionReviewTitle": "Review this bill status",
|
||||
"actionReviewDesc": "Use adjustment, reversal, or bad debt actions if the amount or direction is wrong.",
|
||||
"billOwner": "Bill owner",
|
||||
"billCounterparty": "Counterparty",
|
||||
"unpaidPendingConfirm": "Confirm the bill before recording payment",
|
||||
@@ -340,6 +351,15 @@
|
||||
"clientFilterHint": "Owner filter: showing {{shown}} / {{total}}",
|
||||
"emptyFiltered": "No bills match the filter. Try All statuses or reset.",
|
||||
"emptyClosed": "Period closed but no bills. Often no settled credit tickets in range.",
|
||||
"workbenchEmpty": "No bills need action in this period. Use All bills for settled or historical bills.",
|
||||
"workbench": {
|
||||
"pendingTitle": "Bills pending confirm",
|
||||
"pendingHint": "Confirm the amount and parties first; confirmed bills move to awaiting payment.",
|
||||
"awaitingTitle": "Bills awaiting payment",
|
||||
"awaitingHint": "Record offline payment by payer/payee direction. Bills close when fully paid.",
|
||||
"exceptionTitle": "Exceptions",
|
||||
"exceptionHint": "Review adjustment, reversal, and bad debt records here."
|
||||
},
|
||||
"rowHint": {
|
||||
"playerOwner": "Player owner",
|
||||
"agentOwner": "Agent owner",
|
||||
|
||||
@@ -64,9 +64,12 @@
|
||||
},
|
||||
"nav": {
|
||||
"periods": "अवधिहरू",
|
||||
"pendingWorkbench": "To do",
|
||||
"allBills": "All bills",
|
||||
"bills": "बिलहरू",
|
||||
"operations": "भुक्तानी र समायोजन",
|
||||
"ledger": "लेजर",
|
||||
"ledgerAudit": "Ledger audit",
|
||||
"creditLedger": "क्रेडिट लेजर",
|
||||
"playerBills": "Player bills",
|
||||
"agentBills": "Agent bills",
|
||||
@@ -192,9 +195,9 @@
|
||||
},
|
||||
"billStatus": {
|
||||
"pending_confirm": "Pending confirm",
|
||||
"confirmed": "Confirmed",
|
||||
"partial_paid": "Partial paid",
|
||||
"settled": "Settled",
|
||||
"confirmed": "Awaiting payment",
|
||||
"partial_paid": "Partial payment",
|
||||
"settled": "Fully settled",
|
||||
"overdue": "Overdue",
|
||||
"reversed": "Reversed"
|
||||
},
|
||||
@@ -226,26 +229,34 @@
|
||||
"payerLabel": "Payer",
|
||||
"payeeLabel": "Payee",
|
||||
"playerBreakdownIntro": "Players settle only with their direct agent: net = win/loss − rebate.",
|
||||
"agentBreakdownIntro": "Agents settle only with their upline: net = team net − downline share − own share.",
|
||||
"agentBreakdownIntro": "Start with this period's team win/loss, deduct player rebates and retained shares, then settle the remaining amount with the upline.",
|
||||
"playerGross": "Game win/loss",
|
||||
"playerLostHint": "Player lost; owes agent",
|
||||
"playerWonHint": "Player won; agent owes player",
|
||||
"playerNet": "Player net payable",
|
||||
"playerNetReceive": "Agent pays player",
|
||||
"teamGross": "Team game win/loss",
|
||||
"teamGrossHint": "Includes this agent and all downline players",
|
||||
"teamGross": "Team win/loss this period",
|
||||
"teamGrossHint": "Total for this agent and downline players in this period",
|
||||
"teamGrossShort": "Team",
|
||||
"playerGrossShort": "Player",
|
||||
"teamRebate": "Team rebate",
|
||||
"teamNet": "Team net",
|
||||
"teamRebate": "Deduct player rebates",
|
||||
"teamNet": "Profit available to split",
|
||||
"rebate": "Rebate",
|
||||
"agentShareKeep": "Share kept at this tier",
|
||||
"agentShareKeepHint": "Profit retained at this tier by share ratio",
|
||||
"agentDownlineShare": "Downline share",
|
||||
"agentDownlineShareHint": "Profit retained by downline agents (see breakdown below)",
|
||||
"agentShareKeep": "This tier keeps",
|
||||
"agentShareKeepHint": "Amount retained by this tier under the agreed share",
|
||||
"agentDownlineShare": "Downline agents keep",
|
||||
"agentDownlineShareHint": "Amount retained by downline agents first (see breakdown below)",
|
||||
"agentDownlineShareItem": "{{agent}} kept",
|
||||
"agentNet": "Pay {{counterparty}}",
|
||||
"agentNetReceive": "{{counterparty}} pays this tier",
|
||||
"agentNet": "Remit to {{counterparty}}",
|
||||
"agentNetReceive": "{{counterparty}} owes this tier",
|
||||
"actionPendingConfirmTitle": "Confirm this bill next",
|
||||
"actionPendingConfirmDesc": "Confirm the amount and parties, then the bill moves to awaiting payment.",
|
||||
"actionAwaitingPaymentTitle": "Record payment next",
|
||||
"actionAwaitingPaymentDesc": "Record offline payment according to the payer/payee direction. Partial payments are allowed.",
|
||||
"actionSettledTitle": "This bill is fully settled",
|
||||
"actionSettledDesc": "Review the breakdown and payment history below if needed.",
|
||||
"actionReviewTitle": "Review this bill status",
|
||||
"actionReviewDesc": "Use adjustment, reversal, or bad debt actions if the amount or direction is wrong.",
|
||||
"billOwner": "Bill owner",
|
||||
"billCounterparty": "Counterparty",
|
||||
"unpaidPendingConfirm": "Confirm the bill before recording payment",
|
||||
@@ -330,6 +341,15 @@
|
||||
"clientFilterHint": "Owner filter: showing {{shown}} / {{total}}",
|
||||
"emptyFiltered": "No bills match the filter. Try All statuses or reset.",
|
||||
"emptyClosed": "Period closed but no bills. Often no settled credit tickets in range.",
|
||||
"workbenchEmpty": "No bills need action in this period. Use All bills for settled or historical bills.",
|
||||
"workbench": {
|
||||
"pendingTitle": "Bills pending confirm",
|
||||
"pendingHint": "Confirm the amount and parties first; confirmed bills move to awaiting payment.",
|
||||
"awaitingTitle": "Bills awaiting payment",
|
||||
"awaitingHint": "Record offline payment by payer/payee direction. Bills close when fully paid.",
|
||||
"exceptionTitle": "Exceptions",
|
||||
"exceptionHint": "Review adjustment, reversal, and bad debt records here."
|
||||
},
|
||||
"rowHint": {
|
||||
"playerOwner": "Player owner",
|
||||
"agentOwner": "Agent owner",
|
||||
|
||||
@@ -50,10 +50,13 @@
|
||||
},
|
||||
"nav": {
|
||||
"periods": "账期",
|
||||
"pendingWorkbench": "待处理",
|
||||
"allBills": "全部账单",
|
||||
"bills": "账单",
|
||||
"operations": "收付与调账",
|
||||
"aria": "账期视图",
|
||||
"ledger": "账务流水",
|
||||
"ledger": "流水审计",
|
||||
"ledgerAudit": "流水审计",
|
||||
"creditLedger": "信用流水",
|
||||
"playerBills": "玩家账单",
|
||||
"agentBills": "代理账单",
|
||||
@@ -65,7 +68,7 @@
|
||||
"reports": "账期报表"
|
||||
},
|
||||
"operations": {
|
||||
"hint": "登记收付、坏账核销与补差/冲正的操作台账。玩家信用变动请查看「账务流水」。",
|
||||
"hint": "登记收付、坏账核销与补差/冲正的操作台账。玩家信用变动请查看「流水审计」。",
|
||||
"adjustmentsTitle": "调账 / 冲正 / 坏账",
|
||||
"loadFailed": "收付与调账记录加载失败",
|
||||
"operationType": "操作类型",
|
||||
@@ -179,8 +182,8 @@
|
||||
},
|
||||
"billStatus": {
|
||||
"pending_confirm": "待确认",
|
||||
"confirmed": "已确认",
|
||||
"partial_paid": "部分结清",
|
||||
"confirmed": "待收付",
|
||||
"partial_paid": "部分收付",
|
||||
"settled": "已结清",
|
||||
"overdue": "逾期",
|
||||
"reversed": "已冲正"
|
||||
@@ -214,26 +217,34 @@
|
||||
"payerLabel": "付款方",
|
||||
"payeeLabel": "收款方",
|
||||
"playerBreakdownIntro": "玩家只与直属代理结算,净额 = 输赢 − 回水。",
|
||||
"agentBreakdownIntro": "代理只与直属上级结算,净额 = 团队净额 − 下级占成 − 本级占成。",
|
||||
"agentBreakdownIntro": "先看本期团队赢亏,扣除玩家回水和各级保留后,就是本级需要上缴或应收的金额。",
|
||||
"playerGross": "游戏输赢",
|
||||
"playerLostHint": "玩家输了,应付代理",
|
||||
"playerWonHint": "玩家赢了,代理应付玩家",
|
||||
"playerNet": "玩家应付净额",
|
||||
"playerNetReceive": "代理应付玩家",
|
||||
"teamGross": "团队游戏输赢",
|
||||
"teamGrossHint": "含本级及下级玩家的合计",
|
||||
"teamGross": "本期团队赢亏",
|
||||
"teamGrossHint": "本级及下级玩家本期合计",
|
||||
"teamGrossShort": "团队",
|
||||
"playerGrossShort": "玩家",
|
||||
"teamRebate": "团队回水",
|
||||
"teamNet": "团队净额",
|
||||
"teamRebate": "扣玩家回水",
|
||||
"teamNet": "可分配利润",
|
||||
"rebate": "回水",
|
||||
"agentShareKeep": "本级占成",
|
||||
"agentShareKeepHint": "本级按占成比例留下的利润",
|
||||
"agentDownlineShare": "下级占成",
|
||||
"agentDownlineShareHint": "下级代理按占成比例保留的利润(明细见下行)",
|
||||
"agentShareKeep": "本级保留",
|
||||
"agentShareKeepHint": "本级按约定比例保留的金额",
|
||||
"agentDownlineShare": "下级代理保留",
|
||||
"agentDownlineShareHint": "下级代理按约定比例先保留的金额(明细见下行)",
|
||||
"agentDownlineShareItem": "{{agent}} 保留",
|
||||
"agentNet": "应付 {{counterparty}}",
|
||||
"agentNetReceive": "{{counterparty}} 应付本级",
|
||||
"agentNet": "应上缴 {{counterparty}}",
|
||||
"agentNetReceive": "{{counterparty}} 应补给本级",
|
||||
"actionPendingConfirmTitle": "当前应确认账单",
|
||||
"actionPendingConfirmDesc": "确认金额和双方无误后,账单会进入待收付。",
|
||||
"actionAwaitingPaymentTitle": "当前应登记收付",
|
||||
"actionAwaitingPaymentDesc": "按谁付谁登记线下收付;未结清可分多次登记。",
|
||||
"actionSettledTitle": "本账单已结清",
|
||||
"actionSettledDesc": "如需复核,可查看下方结算明细和收付记录。",
|
||||
"actionReviewTitle": "请复核账单状态",
|
||||
"actionReviewDesc": "如金额或方向有差异,可通过调账、冲正或坏账处理。",
|
||||
"billOwner": "账单主体",
|
||||
"billCounterparty": "结算对手",
|
||||
"unpaidPendingConfirm": "确认账单后可登记收付",
|
||||
@@ -327,6 +338,15 @@
|
||||
"hierarchyHint": "同一账期会生成多笔账单:玩家先与直属代理结,代理扣除本级占成后再向上级缴纳。因此「输赢」可能相同,但「结算金额」会逐级减少。",
|
||||
"emptyFiltered": "当前筛选下暂无账单,请改为「全部状态」或重置筛选。",
|
||||
"emptyClosed": "本期已关账但暂无账单。常见原因:账期内无信用盘玩家的已结算注单,或占成流水不在本账期时间范围内。",
|
||||
"workbenchEmpty": "本期暂无待处理账单。需要查历史或已结清账单,请进入「全部账单」。",
|
||||
"workbench": {
|
||||
"pendingTitle": "待确认账单",
|
||||
"pendingHint": "先确认金额与双方,确认后进入待收付。",
|
||||
"awaitingTitle": "待收付账单",
|
||||
"awaitingHint": "按谁付谁登记线下收付,全部收付后账单结清。",
|
||||
"exceptionTitle": "异常处理",
|
||||
"exceptionHint": "调账、冲正与坏账记录集中在这里复核。"
|
||||
},
|
||||
"intro": "关账后生成的占成账单。可按类型或状态筛选,详情内确认或登记收付。",
|
||||
"confirmOneTitle": "确认账单 #{{id}}?",
|
||||
"confirmOneDesc": "确认后进入待收付,可登记线下收付。",
|
||||
@@ -343,7 +363,7 @@
|
||||
"title": "结算概览"
|
||||
},
|
||||
"ledger": {
|
||||
"title": "账务流水"
|
||||
"title": "流水审计"
|
||||
},
|
||||
"bills": {
|
||||
"title": "全部账单"
|
||||
|
||||
@@ -582,6 +582,7 @@ export function AgentsConsole(): React.ReactElement {
|
||||
void getAdminPlayers({
|
||||
site_code: activeSiteCode.trim(),
|
||||
agent_node_id: selectedNodeId,
|
||||
agent_scope: "direct",
|
||||
page: 1,
|
||||
per_page: 1,
|
||||
})
|
||||
@@ -617,6 +618,7 @@ export function AgentsConsole(): React.ReactElement {
|
||||
void getAdminPlayers({
|
||||
site_code: activeSiteCode.trim(),
|
||||
agent_node_id: child.id,
|
||||
agent_scope: "direct",
|
||||
page: 1,
|
||||
per_page: 1,
|
||||
})
|
||||
|
||||
@@ -254,6 +254,7 @@ export function AgentsPlayersPanel({
|
||||
site_code: siteCode.trim(),
|
||||
...(appliedKeyword.trim() !== "" ? { keyword: appliedKeyword.trim() } : {}),
|
||||
...(effectiveAgentId !== null ? { agent_node_id: effectiveAgentId } : {}),
|
||||
...(effectiveAgentId !== null ? { agent_scope: "direct" } : {}),
|
||||
});
|
||||
setItems(data.items);
|
||||
setTotal(data.meta.total);
|
||||
|
||||
@@ -155,6 +155,50 @@ export function AgentBillDetail({
|
||||
bill.unpaid_amount > 0 &&
|
||||
["confirmed", "partial_paid", "overdue"].includes(bill.status) &&
|
||||
!["adjustment", "reversal", "bad_debt"].includes(bill.bill_type);
|
||||
const primaryAction =
|
||||
bill.status === "pending_confirm"
|
||||
? {
|
||||
title: t("settlementCenter:billDisplay.actionPendingConfirmTitle", {
|
||||
defaultValue: "当前应确认账单",
|
||||
}),
|
||||
description: t("settlementCenter:billDisplay.actionPendingConfirmDesc", {
|
||||
defaultValue: "确认金额和双方无误后,账单会进入待收付。",
|
||||
}),
|
||||
button: t("settlementBills.confirm", { defaultValue: "确认账单" }),
|
||||
onClick: () => confirmRef.current?.scrollIntoView({ behavior: "smooth", block: "nearest" }),
|
||||
}
|
||||
: ["confirmed", "partial_paid", "overdue"].includes(bill.status) && bill.unpaid_amount > 0
|
||||
? {
|
||||
title: t("settlementCenter:billDisplay.actionAwaitingPaymentTitle", {
|
||||
defaultValue: "当前应登记收付",
|
||||
}),
|
||||
description: t("settlementCenter:billDisplay.actionAwaitingPaymentDesc", {
|
||||
defaultValue: "按谁付谁登记线下收付;未结清可分多次登记。",
|
||||
}),
|
||||
button: paymentTitle,
|
||||
onClick: () => paymentRef.current?.scrollIntoView({ behavior: "smooth", block: "nearest" }),
|
||||
}
|
||||
: bill.status === "settled"
|
||||
? {
|
||||
title: t("settlementCenter:billDisplay.actionSettledTitle", {
|
||||
defaultValue: "本账单已结清",
|
||||
}),
|
||||
description: t("settlementCenter:billDisplay.actionSettledDesc", {
|
||||
defaultValue: "如需复核,可查看下方结算明细和收付记录。",
|
||||
}),
|
||||
button: null,
|
||||
onClick: null,
|
||||
}
|
||||
: {
|
||||
title: t("settlementCenter:billDisplay.actionReviewTitle", {
|
||||
defaultValue: "请复核账单状态",
|
||||
}),
|
||||
description: t("settlementCenter:billDisplay.actionReviewDesc", {
|
||||
defaultValue: "如金额或方向有差异,可通过调账、冲正或坏账处理。",
|
||||
}),
|
||||
button: null,
|
||||
onClick: null,
|
||||
};
|
||||
const rebateAllocationSummary = Object.values(
|
||||
rebateAllocations.reduce<Record<string, { key: string; label: string; amount: number; rows: number }>>(
|
||||
(acc, row) => {
|
||||
@@ -337,6 +381,22 @@ export function AgentBillDetail({
|
||||
<>
|
||||
<ConfirmDialog />
|
||||
<div className="flex flex-col gap-5 text-sm">
|
||||
<div className="flex flex-col gap-3 rounded-xl border border-primary/20 bg-primary/5 p-4 shadow-sm sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="min-w-0">
|
||||
<p className="font-semibold tracking-tight text-primary">{primaryAction.title}</p>
|
||||
<p className="mt-1 text-xs leading-relaxed text-muted-foreground">{primaryAction.description}</p>
|
||||
</div>
|
||||
{primaryAction.button && primaryAction.onClick ? (
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
className="w-full shrink-0 sm:w-auto"
|
||||
onClick={primaryAction.onClick}
|
||||
>
|
||||
{primaryAction.button}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
<SettlementBillSummaryHeader bill={bill} currencyCode={currencyCode} />
|
||||
<SettlementBillAmountBreakdown
|
||||
bill={bill}
|
||||
|
||||
@@ -216,7 +216,7 @@ export function SettlementBillAmountBreakdown({
|
||||
})
|
||||
: bill.bill_type === "agent"
|
||||
? t("settlementCenter:billDisplay.agentBreakdownIntro", {
|
||||
defaultValue: "代理只与直属上级结算,净额 = 团队净额 − 下级占成 − 本级占成。",
|
||||
defaultValue: "先看本期团队赢亏,扣除玩家回水和各级保留后,就是本级需要上缴或应收的金额。",
|
||||
})
|
||||
: null;
|
||||
|
||||
|
||||
@@ -271,19 +271,19 @@ export function buildBillAmountBreakdown(
|
||||
if (bill.gross_win_loss != null) {
|
||||
lines.push({
|
||||
key: "gross",
|
||||
label: t("settlementCenter:billDisplay.teamGross", { defaultValue: "团队游戏输赢" }),
|
||||
label: t("settlementCenter:billDisplay.teamGross", { defaultValue: "本期团队赢亏" }),
|
||||
amount: Math.abs(gross),
|
||||
signedAmount: gross,
|
||||
kind: "add",
|
||||
hint: t("settlementCenter:billDisplay.teamGrossHint", {
|
||||
defaultValue: "含本级及下级玩家的合计",
|
||||
defaultValue: "本级及下级玩家本期合计",
|
||||
}),
|
||||
});
|
||||
}
|
||||
if (bill.rebate_amount != null && rebate !== 0) {
|
||||
lines.push({
|
||||
key: "rebate",
|
||||
label: t("settlementCenter:billDisplay.teamRebate", { defaultValue: "团队回水" }),
|
||||
label: t("settlementCenter:billDisplay.teamRebate", { defaultValue: "扣玩家回水" }),
|
||||
amount: Math.abs(rebate),
|
||||
signedAmount: -Math.abs(rebate),
|
||||
kind: "subtract",
|
||||
@@ -292,7 +292,7 @@ export function buildBillAmountBreakdown(
|
||||
if (bill.gross_win_loss != null || bill.rebate_amount != null) {
|
||||
lines.push({
|
||||
key: "team-net",
|
||||
label: t("settlementCenter:billDisplay.teamNet", { defaultValue: "团队净额" }),
|
||||
label: t("settlementCenter:billDisplay.teamNet", { defaultValue: "可分配利润" }),
|
||||
amount: Math.abs(teamNet),
|
||||
signedAmount: teamNet,
|
||||
kind: "subtotal",
|
||||
@@ -301,14 +301,14 @@ export function buildBillAmountBreakdown(
|
||||
if (downlineShares && downlineShares.total > 0) {
|
||||
lines.push({
|
||||
key: "downline-share",
|
||||
label: billDisplayLabel(t, "agentDownlineShare", "下级占成"),
|
||||
label: billDisplayLabel(t, "agentDownlineShare", "下级代理保留"),
|
||||
amount: Math.abs(downlineShares.total),
|
||||
signedAmount: -Math.abs(downlineShares.total),
|
||||
kind: "subtract",
|
||||
hint: billDisplayLabel(
|
||||
t,
|
||||
"agentDownlineShareHint",
|
||||
"下级代理按占成比例保留的利润(明细见下行)",
|
||||
"下级代理按约定比例先保留的金额(明细见下行)",
|
||||
),
|
||||
children: downlineShares.items.map((item) => ({
|
||||
key: `downline-share-${item.owner_id}`,
|
||||
@@ -324,11 +324,11 @@ export function buildBillAmountBreakdown(
|
||||
if (meta.share_profit != null) {
|
||||
lines.push({
|
||||
key: "share",
|
||||
label: billDisplayLabel(t, "agentShareKeep", "本级占成"),
|
||||
label: billDisplayLabel(t, "agentShareKeep", "本级保留"),
|
||||
amount: Math.abs(shareProfit),
|
||||
signedAmount: -Math.abs(shareProfit),
|
||||
kind: "subtract",
|
||||
hint: billDisplayLabel(t, "agentShareKeepHint", "本级按占成比例留下的利润"),
|
||||
hint: billDisplayLabel(t, "agentShareKeepHint", "本级按约定比例保留的金额"),
|
||||
});
|
||||
}
|
||||
if (rounding !== 0) {
|
||||
@@ -344,8 +344,8 @@ export function buildBillAmountBreakdown(
|
||||
key: "net",
|
||||
label:
|
||||
bill.net_amount > 0
|
||||
? billDisplayLabel(t, "agentNet", "应付 {{counterparty}}", { counterparty })
|
||||
: billDisplayLabel(t, "agentNetReceive", "{{counterparty}} 应付本级", { counterparty }),
|
||||
? billDisplayLabel(t, "agentNet", "应上缴 {{counterparty}}", { counterparty })
|
||||
: billDisplayLabel(t, "agentNetReceive", "{{counterparty}} 应补给本级", { counterparty }),
|
||||
amount: Math.abs(bill.net_amount),
|
||||
signedAmount: bill.net_amount,
|
||||
kind: "total",
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { SettlementBillListScope } from "@/api/admin-agent-settlement";
|
||||
|
||||
export type SettlementPeriodView = "bills" | "ledger";
|
||||
export type SettlementPeriodView = "pending" | "bills" | "ledger";
|
||||
|
||||
export type SettlementBillScopeFilter = "all" | SettlementBillListScope;
|
||||
|
||||
const VALID_VIEWS: SettlementPeriodView[] = ["bills", "ledger"];
|
||||
const VALID_VIEWS: SettlementPeriodView[] = ["pending", "bills", "ledger"];
|
||||
|
||||
const VALID_BILL_SCOPES: SettlementBillScopeFilter[] = [
|
||||
"all",
|
||||
@@ -44,7 +44,7 @@ export function settlementCenterScopeHref(
|
||||
|
||||
export function settlementPeriodViewHref(
|
||||
periodId: number,
|
||||
view: SettlementPeriodView = "bills",
|
||||
view: SettlementPeriodView = "pending",
|
||||
adminSiteId?: number | null,
|
||||
billScope?: SettlementBillScopeFilter | null,
|
||||
): string {
|
||||
@@ -92,7 +92,7 @@ export function parseSettlementCenterView(
|
||||
const view =
|
||||
normalizedView !== null && VALID_VIEWS.includes(normalizedView as SettlementPeriodView)
|
||||
? (normalizedView as SettlementPeriodView)
|
||||
: "bills";
|
||||
: "pending";
|
||||
|
||||
const billScope =
|
||||
scopeRaw !== null &&
|
||||
|
||||
@@ -45,13 +45,11 @@ export function SettlementCenterPeriodDetail({
|
||||
const { t } = useTranslation("settlementCenter");
|
||||
|
||||
const subViews: { key: SettlementPeriodView; label: string }[] = [
|
||||
{ key: "bills", label: t("nav.bills", { defaultValue: "账单" }) },
|
||||
{ key: "ledger", label: t("nav.ledger", { defaultValue: "账务流水" }) },
|
||||
{ key: "pending", label: t("nav.pendingWorkbench", { defaultValue: "待处理" }) },
|
||||
{ key: "bills", label: t("nav.allBills", { defaultValue: "全部账单" }) },
|
||||
{ key: "ledger", label: t("nav.ledgerAudit", { defaultValue: "流水审计" }) },
|
||||
];
|
||||
|
||||
const pendingConfirm = period.summary?.pending_confirm ?? 0;
|
||||
const awaitingPayment = period.summary?.awaiting_payment ?? 0;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<SettlementPeriodHeader
|
||||
@@ -76,6 +74,21 @@ export function SettlementCenterPeriodDetail({
|
||||
))}
|
||||
</AdminSubnav>
|
||||
|
||||
{view === "pending" ? (
|
||||
<SettlementMainPanel
|
||||
key={`${adminSiteId}-${period.id}-${refreshKey}-pending`}
|
||||
adminSiteId={adminSiteId}
|
||||
currencyCode={currencyCode}
|
||||
periodFilter={period.id}
|
||||
mode="workbench"
|
||||
onOpenBillDetail={onOpenBillDetail}
|
||||
refreshKey={refreshKey}
|
||||
canOperateBills={canOperateBills}
|
||||
selectedPeriodStatus={period.status}
|
||||
boundAgentId={boundAgentId}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{view === "bills" ? (
|
||||
<SettlementMainPanel
|
||||
key={`${adminSiteId}-${period.id}-${refreshKey}-${billScope}`}
|
||||
@@ -86,8 +99,6 @@ export function SettlementCenterPeriodDetail({
|
||||
onOpenBillDetail={onOpenBillDetail}
|
||||
refreshKey={refreshKey}
|
||||
canOperateBills={canOperateBills}
|
||||
pendingConfirm={pendingConfirm}
|
||||
awaitingPayment={awaitingPayment}
|
||||
selectedPeriodStatus={period.status}
|
||||
boundAgentId={boundAgentId}
|
||||
/>
|
||||
@@ -104,4 +115,4 @@ export function SettlementCenterPeriodDetail({
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,11 @@ import {
|
||||
import { SettlementPeriodWorkbench } from "@/modules/settlement/settlement-period-workbench";
|
||||
import { formatAdminSiteLabel } from "@/lib/admin-site-display";
|
||||
import { adminHasAnyPermission } from "@/lib/admin-permissions";
|
||||
import { PRD_SETTLEMENT_AGENT_MANAGE } from "@/lib/admin-prd";
|
||||
import {
|
||||
PRD_INTEGRATION_MANAGE,
|
||||
PRD_INTEGRATION_VIEW,
|
||||
PRD_SETTLEMENT_AGENT_MANAGE,
|
||||
} from "@/lib/admin-prd";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -39,6 +43,30 @@ import { useAdminProfile } from "@/stores/admin-session";
|
||||
|
||||
type SiteOption = { id: number; label: string; code: string; currency_code: string };
|
||||
|
||||
function siteOptionsFromProfile(profile: ReturnType<typeof useAdminProfile>): SiteOption[] {
|
||||
const byId = new Map<number, SiteOption>();
|
||||
|
||||
if (profile?.site != null) {
|
||||
byId.set(profile.site.id, {
|
||||
id: profile.site.id,
|
||||
label: formatAdminSiteLabel(profile.site.name, profile.site.code),
|
||||
code: profile.site.code,
|
||||
currency_code: "NPR",
|
||||
});
|
||||
}
|
||||
|
||||
for (const site of profile?.accessible_sites ?? []) {
|
||||
byId.set(site.id, {
|
||||
id: site.id,
|
||||
label: formatAdminSiteLabel(site.name, site.code),
|
||||
code: site.code,
|
||||
currency_code: "NPR",
|
||||
});
|
||||
}
|
||||
|
||||
return Array.from(byId.values());
|
||||
}
|
||||
|
||||
export function SettlementCenterShell(): React.ReactElement {
|
||||
const { t } = useTranslation(["settlementCenter", "common"]);
|
||||
const router = useRouter();
|
||||
@@ -70,6 +98,9 @@ export function SettlementCenterShell(): React.ReactElement {
|
||||
adminHasAnyPermission(profile?.permissions, [PRD_SETTLEMENT_AGENT_MANAGE]);
|
||||
const canManagePeriods = canOperateBills && boundAgent === null;
|
||||
const canFinanceAdjustments = canOperateBills && boundAgent === null;
|
||||
const canLoadIntegrationSites =
|
||||
profile?.is_super_admin === true ||
|
||||
adminHasAnyPermission(profile?.permissions, [PRD_INTEGRATION_VIEW, PRD_INTEGRATION_MANAGE]);
|
||||
|
||||
const [siteOptions, setSiteOptions] = useState<SiteOption[]>([]);
|
||||
const [sitesReady, setSitesReady] = useState(() => boundAgent?.admin_site_id != null);
|
||||
@@ -120,6 +151,23 @@ export function SettlementCenterShell(): React.ReactElement {
|
||||
return;
|
||||
}
|
||||
|
||||
const profileSites = siteOptionsFromProfile(profile);
|
||||
if (!canLoadIntegrationSites && profileSites.length > 0) {
|
||||
setSiteOptions(profileSites);
|
||||
setAdminSiteId((current) => {
|
||||
if (siteFromUrl !== null && profileSites.some((site) => site.id === siteFromUrl)) {
|
||||
return siteFromUrl;
|
||||
}
|
||||
if (current !== null && profileSites.some((site) => site.id === current)) {
|
||||
return current;
|
||||
}
|
||||
return profileSites[0]?.id ?? null;
|
||||
});
|
||||
setSitesLoadErr(null);
|
||||
setSitesReady(true);
|
||||
return;
|
||||
}
|
||||
|
||||
setSitesReady(false);
|
||||
setSitesLoadErr(null);
|
||||
try {
|
||||
@@ -148,7 +196,7 @@ export function SettlementCenterShell(): React.ReactElement {
|
||||
} finally {
|
||||
setSitesReady(true);
|
||||
}
|
||||
}, [boundAgent, siteFromUrl, t]);
|
||||
}, [boundAgent, canLoadIntegrationSites, profile, siteFromUrl, t]);
|
||||
|
||||
useEffect(() => {
|
||||
void loadIntegrationSites();
|
||||
@@ -277,7 +325,7 @@ export function SettlementCenterShell(): React.ReactElement {
|
||||
const activePeriod =
|
||||
activePeriodId !== null ? (periods.find((row) => row.id === activePeriodId) ?? null) : null;
|
||||
|
||||
const openPeriodView = (periodId: number, view: SettlementPeriodView = "bills"): void => {
|
||||
const openPeriodView = (periodId: number, view: SettlementPeriodView = "pending"): void => {
|
||||
const period = periods.find((row) => row.id === periodId);
|
||||
const scope = preferredBillScopeForPeriod(period?.summary);
|
||||
router.push(settlementPeriodViewHref(periodId, view, siteId, scope));
|
||||
@@ -384,7 +432,7 @@ export function SettlementCenterShell(): React.ReactElement {
|
||||
canManage={canManagePeriods}
|
||||
periods={periods}
|
||||
headerActions={headerActions}
|
||||
onViewDetail={(id) => openPeriodView(id, "bills")}
|
||||
onViewDetail={(id) => openPeriodView(id, "pending")}
|
||||
onReloadPeriods={loadPeriods}
|
||||
onPeriodOpened={() => {
|
||||
setRefreshKey((n) => n + 1);
|
||||
|
||||
@@ -359,7 +359,7 @@ export function SettlementCreditLedgerPanel({
|
||||
<AdminTableNoResourceRow
|
||||
colSpan={COL_SPAN}
|
||||
message={t("creditLedger.emptyPeriod", {
|
||||
defaultValue: "本账期暂无账务流水。",
|
||||
defaultValue: "本账期暂无流水审计记录。",
|
||||
})}
|
||||
/>
|
||||
) : (
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
@@ -28,15 +27,11 @@ import { useConfirmAction } from "@/hooks/use-confirm-action";
|
||||
import type { AgentSettlementPeriodFilter } from "@/modules/settlement/agent-settlement-period-select";
|
||||
import { SettlementBillsTable } from "@/modules/settlement/settlement-bills-table";
|
||||
import { settlementBillOperableByBoundAgent } from "@/modules/settlement/settlement-bill-operable";
|
||||
import {
|
||||
settlementPeriodViewHref,
|
||||
type SettlementBillScopeFilter,
|
||||
} from "@/modules/settlement/settlement-center-nav";
|
||||
import { settlementBillStatusLabel } from "@/modules/settlement/settlement-status-label";
|
||||
import type { SettlementBillScopeFilter } from "@/modules/settlement/settlement-center-nav";
|
||||
import { LotteryApiBizError } from "@/types/api/errors";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type BillTypeFilter = "all" | "player" | "agent";
|
||||
type SettlementMainPanelMode = "workbench" | "all";
|
||||
|
||||
type BillFilters = {
|
||||
ownerKeyword: string;
|
||||
@@ -85,11 +80,10 @@ export type SettlementMainPanelProps = {
|
||||
currencyCode: string;
|
||||
periodFilter: AgentSettlementPeriodFilter;
|
||||
billScope?: SettlementBillScopeFilter;
|
||||
mode?: SettlementMainPanelMode;
|
||||
onOpenBillDetail: (billId: number, focus?: "confirm" | "payment") => void;
|
||||
refreshKey?: number;
|
||||
canOperateBills?: boolean;
|
||||
pendingConfirm: number;
|
||||
awaitingPayment: number;
|
||||
selectedPeriodStatus?: string | null;
|
||||
boundAgentId?: number | null;
|
||||
};
|
||||
@@ -99,11 +93,10 @@ export function SettlementMainPanel({
|
||||
currencyCode,
|
||||
periodFilter,
|
||||
billScope = "all",
|
||||
mode = "all",
|
||||
onOpenBillDetail,
|
||||
refreshKey = 0,
|
||||
canOperateBills = false,
|
||||
pendingConfirm,
|
||||
awaitingPayment,
|
||||
selectedPeriodStatus,
|
||||
boundAgentId = null,
|
||||
}: SettlementMainPanelProps): React.ReactElement {
|
||||
@@ -112,17 +105,17 @@ export function SettlementMainPanel({
|
||||
const periodOpen = selectedPeriodStatus === "open";
|
||||
const { request: requestConfirm, ConfirmDialog } = useConfirmAction();
|
||||
|
||||
const initialFilters = useMemo(
|
||||
() => filtersForPeriod(boundAgentId, billScope),
|
||||
[boundAgentId, billScope],
|
||||
);
|
||||
const initialFilters = useMemo(() => {
|
||||
const base = filtersForPeriod(boundAgentId, billScope);
|
||||
return mode === "workbench" ? { ...base, statusScope: "all" as const } : base;
|
||||
}, [boundAgentId, billScope, mode]);
|
||||
|
||||
const [draft, setDraft] = useState<BillFilters>(initialFilters);
|
||||
const [applied, setApplied] = useState<BillFilters>(initialFilters);
|
||||
const [rows, setRows] = useState<SettlementBillRow[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [page, setPage] = useState(1);
|
||||
const [perPage, setPerPage] = useState(20);
|
||||
const [perPage, setPerPage] = useState(mode === "workbench" ? 100 : 20);
|
||||
const [total, setTotal] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -141,8 +134,8 @@ export function SettlementMainPanel({
|
||||
bill_type: q.bill_type,
|
||||
scope: q.scope,
|
||||
keyword: q.keyword,
|
||||
page,
|
||||
per_page: perPage,
|
||||
page: mode === "workbench" ? 1 : page,
|
||||
per_page: mode === "workbench" ? 100 : perPage,
|
||||
});
|
||||
setRows(data.items ?? []);
|
||||
setTotal(data.total ?? data.items?.length ?? 0);
|
||||
@@ -157,7 +150,7 @@ export function SettlementMainPanel({
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [adminSiteId, applied, page, perPage, periodId, t]);
|
||||
}, [adminSiteId, applied, mode, page, perPage, periodId, t]);
|
||||
|
||||
useAsyncEffect(() => {
|
||||
void load();
|
||||
@@ -174,24 +167,6 @@ export function SettlementMainPanel({
|
||||
setPage(1);
|
||||
};
|
||||
|
||||
const statusOptionLabel = (value: SettlementBillScopeFilter): string => {
|
||||
if (value === "all") {
|
||||
return t("billsPanel.filterAll", { defaultValue: "全部状态" });
|
||||
}
|
||||
if (value === "pending_confirm") {
|
||||
const label = t("billsPanel.category.pendingConfirm", { defaultValue: "待确认" });
|
||||
return pendingConfirm > 0 ? `${label} (${pendingConfirm})` : label;
|
||||
}
|
||||
if (value === "awaiting_payment") {
|
||||
const label = t("billsPanel.category.awaitingPayment", { defaultValue: "待收付" });
|
||||
return awaitingPayment > 0 ? `${label} (${awaitingPayment})` : label;
|
||||
}
|
||||
if (value === "settled") {
|
||||
return settlementBillStatusLabel("settled", t);
|
||||
}
|
||||
return t("billsPanel.filterAdjustment", { defaultValue: "调账 / 冲正" });
|
||||
};
|
||||
|
||||
const emptyBillMessage = useMemo((): string | undefined => {
|
||||
if (periodOpen) {
|
||||
return t("empty.billsNeedClose", {
|
||||
@@ -223,21 +198,6 @@ export function SettlementMainPanel({
|
||||
}
|
||||
};
|
||||
|
||||
const scopeChipClass = (active: boolean): string =>
|
||||
cn(
|
||||
"inline-flex items-center rounded-full border px-2.5 py-1 text-xs font-medium transition-colors",
|
||||
active
|
||||
? "border-primary/40 bg-primary/10 text-primary"
|
||||
: "border-border/70 bg-muted/40 text-muted-foreground hover:bg-muted/70 hover:text-foreground",
|
||||
);
|
||||
|
||||
const scopeChips: SettlementBillScopeFilter[] = [
|
||||
"all",
|
||||
"pending_confirm",
|
||||
"awaiting_payment",
|
||||
"settled",
|
||||
];
|
||||
|
||||
const requestConfirmBill = (row: SettlementBillRow): void => {
|
||||
requestConfirm({
|
||||
title: t("billsPanel.confirmOneTitle", { defaultValue: "确认账单 #{{id}}?", id: row.id }),
|
||||
@@ -264,32 +224,115 @@ export function SettlementMainPanel({
|
||||
const canOperateRow = (row: SettlementBillRow): boolean =>
|
||||
canOperateBills && settlementBillOperableByBoundAgent(row, boundAgentId ? { id: boundAgentId } : null);
|
||||
|
||||
const pendingConfirmRows = rows.filter((row) => row.status === "pending_confirm");
|
||||
const awaitingPaymentRows = rows.filter((row) =>
|
||||
["confirmed", "partial_paid", "overdue"].includes(row.status) && row.unpaid_amount > 0,
|
||||
);
|
||||
const exceptionRows = rows.filter((row) =>
|
||||
["adjustment", "reversal", "bad_debt"].includes(row.bill_type),
|
||||
);
|
||||
const hasWorkbenchRows =
|
||||
pendingConfirmRows.length > 0 || awaitingPaymentRows.length > 0 || exceptionRows.length > 0;
|
||||
|
||||
const renderBillsTable = (
|
||||
tableRows: SettlementBillRow[],
|
||||
emptyMessage?: string,
|
||||
): React.ReactElement => (
|
||||
<SettlementBillsTable
|
||||
rows={tableRows}
|
||||
loading={loading}
|
||||
currencyCode={currencyCode}
|
||||
billTypeFilter={applied.billType}
|
||||
emptyMessage={emptyMessage}
|
||||
canOperate={canOperateBills}
|
||||
boundAgentId={boundAgentId}
|
||||
onOpenDetail={(billId) => onOpenBillDetail(billId)}
|
||||
onConfirmBill={(row) => {
|
||||
if (canOperateRow(row) && row.status === "pending_confirm") {
|
||||
requestConfirmBill(row);
|
||||
}
|
||||
}}
|
||||
onPayBill={(row) => {
|
||||
if (
|
||||
canOperateRow(row) &&
|
||||
["confirmed", "partial_paid", "overdue"].includes(row.status) &&
|
||||
row.unpaid_amount > 0
|
||||
) {
|
||||
onOpenBillDetail(row.id, "payment");
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
if (mode === "workbench") {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<ConfirmDialog />
|
||||
{loading && rows.length === 0 ? (
|
||||
<AdminLoadingState />
|
||||
) : periodOpen ? (
|
||||
renderBillsTable([], emptyBillMessage)
|
||||
) : !hasWorkbenchRows ? (
|
||||
renderBillsTable([], t("billsPanel.workbenchEmpty", {
|
||||
defaultValue: "本期暂无待处理账单。需要查历史或已结清账单,请进入「全部账单」。",
|
||||
}))
|
||||
) : (
|
||||
<>
|
||||
{pendingConfirmRows.length > 0 ? (
|
||||
<section className="space-y-2">
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold">
|
||||
{t("billsPanel.workbench.pendingTitle", { defaultValue: "待确认账单" })}
|
||||
</h3>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t("billsPanel.workbench.pendingHint", {
|
||||
defaultValue: "先确认金额与双方,确认后进入待收付。",
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
{renderBillsTable(pendingConfirmRows)}
|
||||
</section>
|
||||
) : null}
|
||||
{awaitingPaymentRows.length > 0 ? (
|
||||
<section className="space-y-2">
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold">
|
||||
{t("billsPanel.workbench.awaitingTitle", { defaultValue: "待收付账单" })}
|
||||
</h3>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t("billsPanel.workbench.awaitingHint", {
|
||||
defaultValue: "按谁付谁登记线下收付,全部收付后账单结清。",
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
{renderBillsTable(awaitingPaymentRows)}
|
||||
</section>
|
||||
) : null}
|
||||
{exceptionRows.length > 0 ? (
|
||||
<section className="space-y-2">
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold">
|
||||
{t("billsPanel.workbench.exceptionTitle", { defaultValue: "异常处理" })}
|
||||
</h3>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t("billsPanel.workbench.exceptionHint", {
|
||||
defaultValue: "逾期、调账、冲正与坏账记录集中在这里复核。",
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
{renderBillsTable(exceptionRows)}
|
||||
</section>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<ConfirmDialog />
|
||||
|
||||
{!periodOpen && periodId != null ? (
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{scopeChips.map((scope) => {
|
||||
const active = applied.statusScope === scope;
|
||||
const href = settlementPeriodViewHref(periodId, "bills", adminSiteId, scope);
|
||||
const count =
|
||||
scope === "pending_confirm"
|
||||
? pendingConfirm
|
||||
: scope === "awaiting_payment"
|
||||
? awaitingPayment
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<Link key={scope} href={href} className={scopeChipClass(active)}>
|
||||
{statusOptionLabel(scope)}
|
||||
{count != null && count > 0 ? ` ${count}` : null}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="admin-list-toolbar">
|
||||
<div className="admin-list-field min-w-[12rem] flex-1">
|
||||
<Label htmlFor="sb-owner" className="sm:shrink-0">
|
||||
@@ -352,30 +395,7 @@ export function SettlementMainPanel({
|
||||
<AdminLoadingState />
|
||||
) : (
|
||||
<>
|
||||
<SettlementBillsTable
|
||||
rows={rows}
|
||||
loading={loading}
|
||||
currencyCode={currencyCode}
|
||||
billTypeFilter={applied.billType}
|
||||
emptyMessage={emptyBillMessage}
|
||||
canOperate={canOperateBills}
|
||||
boundAgentId={boundAgentId}
|
||||
onOpenDetail={(billId) => onOpenBillDetail(billId)}
|
||||
onConfirmBill={(row) => {
|
||||
if (canOperateRow(row) && row.status === "pending_confirm") {
|
||||
requestConfirmBill(row);
|
||||
}
|
||||
}}
|
||||
onPayBill={(row) => {
|
||||
if (
|
||||
canOperateRow(row) &&
|
||||
["confirmed", "partial_paid", "overdue"].includes(row.status) &&
|
||||
row.unpaid_amount > 0
|
||||
) {
|
||||
onOpenBillDetail(row.id, "payment");
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{renderBillsTable(rows, emptyBillMessage)}
|
||||
<AdminListPaginationFooter
|
||||
selectId="settlement-bills-per-page"
|
||||
page={page}
|
||||
@@ -393,4 +413,4 @@ export function SettlementMainPanel({
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user