feat(settlement): add idempotency keys to bill operations and enhance credit ledger filtering
Some checks failed
lotteryadmin CI / build (push) Has been cancelled

Added idempotency_key parameter to settlement bill payment and adjustment API calls to prevent duplicate transactions. Enhanced credit ledger panel with expanded reason filters including bet_hold_release, game_settlement_win/loss/reversal, and settlement_confirm/payout. Updated localization files to include new game_settlement_reversal labels across English, Nepali, and Chinese. Improved credit ledger reason
This commit is contained in:
2026-06-23 16:50:14 +08:00
parent c6119da9f8
commit ce38995546
8 changed files with 34 additions and 4 deletions

View File

@@ -300,7 +300,7 @@ export async function postSettlementBillConfirm(billId: number): Promise<{ bill_
export async function postSettlementBillPayment( export async function postSettlementBillPayment(
billId: number, billId: number,
body: { amount: number; method?: string; proof?: string; remark?: string }, body: { amount: number; method?: string; proof?: string; remark?: string; idempotency_key?: string },
): Promise<{ bill: SettlementBillRow }> { ): Promise<{ bill: SettlementBillRow }> {
return adminRequest.post(`${A}/settlement-bills/${billId}/payments`, body); return adminRequest.post(`${A}/settlement-bills/${billId}/payments`, body);
} }
@@ -324,7 +324,7 @@ export async function getAgentSettlementReport(params: {
export async function postSettlementBillAdjustment( export async function postSettlementBillAdjustment(
billId: number, billId: number,
body: { amount: number; adjustment_type?: "adjustment" | "reversal"; reason?: string }, body: { amount: number; adjustment_type?: "adjustment" | "reversal"; reason?: string; idempotency_key?: string },
): Promise<{ adjustment_bill_id: number; bill: SettlementBillRow }> { ): Promise<{ adjustment_bill_id: number; bill: SettlementBillRow }> {
return adminRequest.post(`${A}/settlement-bills/${billId}/adjustments`, body); return adminRequest.post(`${A}/settlement-bills/${billId}/adjustments`, body);
} }

View File

@@ -146,6 +146,7 @@
"payment_record": "Bill payment", "payment_record": "Bill payment",
"bet_hold": "Bet hold", "bet_hold": "Bet hold",
"game_settlement": "Draw settlement", "game_settlement": "Draw settlement",
"game_settlement_reversal": "Draw reversal",
"game_settlement_win": "Draw settlement credit", "game_settlement_win": "Draw settlement credit",
"bet_hold_release": "Hold release", "bet_hold_release": "Hold release",
"game_settlement_loss": "Draw settlement debit", "game_settlement_loss": "Draw settlement debit",

View File

@@ -146,6 +146,7 @@
"payment_record": "Bill payment", "payment_record": "Bill payment",
"bet_hold": "Bet hold", "bet_hold": "Bet hold",
"game_settlement": "Draw settlement", "game_settlement": "Draw settlement",
"game_settlement_reversal": "Draw reversal",
"game_settlement_win": "Draw settlement credit", "game_settlement_win": "Draw settlement credit",
"bet_hold_release": "Hold release", "bet_hold_release": "Hold release",
"game_settlement_loss": "Draw settlement debit", "game_settlement_loss": "Draw settlement debit",

View File

@@ -132,6 +132,7 @@
"bet_hold": "下注冻结", "bet_hold": "下注冻结",
"freezeAmount": "冻结 {{amount}}", "freezeAmount": "冻结 {{amount}}",
"game_settlement": "开奖结算", "game_settlement": "开奖结算",
"game_settlement_reversal": "开奖冲正",
"game_settlement_win": "开奖结算入账", "game_settlement_win": "开奖结算入账",
"bet_hold_release": "占用释放", "bet_hold_release": "占用释放",
"game_settlement_loss": "开奖结算扣款", "game_settlement_loss": "开奖结算扣款",

View File

@@ -1011,7 +1011,7 @@ export function AgentsConsole(): React.ReactElement {
{canViewAgents && err ? <p className="px-1 text-sm text-destructive">{err}</p> : null} {canViewAgents && err ? <p className="px-1 text-sm text-destructive">{err}</p> : null}
{canViewAgents ? ( {canViewAgents ? (
<div className="flex max-h-[calc(100dvh-14rem)] min-h-[28rem] flex-1 flex-col overflow-hidden rounded-2xl border border-border/70 bg-card shadow-sm lg:flex-row"> <div className="flex max-h-[calc(100dvh-14rem)] min-h-0 flex-1 flex-col overflow-hidden rounded-2xl border border-border/70 bg-card shadow-sm lg:flex-row">
{showAgentSidebar ? ( {showAgentSidebar ? (
<AgentLineSidebar <AgentLineSidebar
siteLabel={selectedSiteLabel} siteLabel={selectedSiteLabel}

View File

@@ -182,6 +182,7 @@ export function AgentBillDetail({
return; return;
} }
const idempotencyKey = `pay-${billId}-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
requestConfirm({ requestConfirm({
title: t("settlementBills.paymentConfirmTitle", { defaultValue: "确认登记收付?" }), title: t("settlementBills.paymentConfirmTitle", { defaultValue: "确认登记收付?" }),
description: t("settlementBills.paymentConfirmDescription", { description: t("settlementBills.paymentConfirmDescription", {
@@ -195,6 +196,7 @@ export function AgentBillDetail({
amount, amount,
method: payMethod.trim() || undefined, method: payMethod.trim() || undefined,
proof: payProof.trim() || undefined, proof: payProof.trim() || undefined,
idempotency_key: idempotencyKey,
}); });
await load(); await load();
await onUpdated?.(); await onUpdated?.();
@@ -251,6 +253,7 @@ export function AgentBillDetail({
return; return;
} }
const idempotencyKey = `adj-${billId}-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
requestConfirm({ requestConfirm({
title: t("settlementBills.adjustmentConfirmTitle", { defaultValue: "确认创建补差/冲正单?" }), title: t("settlementBills.adjustmentConfirmTitle", { defaultValue: "确认创建补差/冲正单?" }),
description: t("settlementBills.adjustmentConfirmDescription", { description: t("settlementBills.adjustmentConfirmDescription", {
@@ -264,6 +267,7 @@ export function AgentBillDetail({
amount, amount,
adjustment_type: amount < 0 ? "reversal" : "adjustment", adjustment_type: amount < 0 ? "reversal" : "adjustment",
reason, reason,
idempotency_key: idempotencyKey,
}); });
setAdjustAmount(""); setAdjustAmount("");
setAdjustReason(""); setAdjustReason("");

View File

@@ -42,12 +42,17 @@ import { creditLedgerReasonLabel, settlementBillStatusLabel } from "@/modules/se
const REASON_FILTERS = [ const REASON_FILTERS = [
"all", "all",
"bet_hold", "bet_hold",
"bet_hold_release",
"game_settlement", "game_settlement",
"game_settlement_win",
"game_settlement_loss",
"game_settlement_reversal",
"settlement_confirm",
"settlement_payout",
"payment_record", "payment_record",
"adjustment", "adjustment",
"reversal", "reversal",
"bad_debt", "bad_debt",
"settlement_payout",
"share_ledger", "share_ledger",
] as const; ] as const;

View File

@@ -55,9 +55,27 @@ export function creditLedgerReasonLabel(
if (reason === "game_settlement") { if (reason === "game_settlement") {
return t("creditLedger.reason.game_settlement", { defaultValue: "开奖结算" }); return t("creditLedger.reason.game_settlement", { defaultValue: "开奖结算" });
} }
if (reason === "game_settlement_reversal") {
return t("creditLedger.reason.game_settlement_reversal", { defaultValue: "开奖冲正" });
}
if (reason === "bet_hold") { if (reason === "bet_hold") {
return t("creditLedger.reason.bet_hold", { defaultValue: "下注冻结" }); return t("creditLedger.reason.bet_hold", { defaultValue: "下注冻结" });
} }
if (reason === "bet_hold_release") {
return t("creditLedger.reason.bet_hold_release", { defaultValue: "占用释放" });
}
if (reason === "game_settlement_win") {
return t("creditLedger.reason.game_settlement_win", { defaultValue: "开奖结算入账" });
}
if (reason === "game_settlement_loss") {
return t("creditLedger.reason.game_settlement_loss", { defaultValue: "开奖结算扣款" });
}
if (reason === "settlement_confirm") {
return t("creditLedger.reason.settlement_confirm", { defaultValue: "账期结算确认" });
}
if (reason === "settlement_payout") {
return t("creditLedger.reason.settlement_payout", { defaultValue: "结算收付入账" });
}
const key = `creditLedger.reason.${reason}` as const; const key = `creditLedger.reason.${reason}` as const;
return t(key, { defaultValue: reason }); return t(key, { defaultValue: reason });