From d6826d5a1c2268009e24af26f2be8b8b3fa379cc Mon Sep 17 00:00:00 2001 From: kang Date: Fri, 10 Jul 2026 10:23:46 +0800 Subject: [PATCH] =?UTF-8?q?feat(reports,=20risk,=20deployment):=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=BC=80=E6=B3=A8=E5=95=86=E7=BB=8F=E8=90=A5?= =?UTF-8?q?=E6=8A=A5=E8=A1=A8=E3=80=81=E9=A3=8E=E9=99=A9=E6=B1=A0=E6=8C=89?= =?UTF-8?q?=E5=BC=80=E6=B3=A8=E5=95=86=E6=93=8D=E4=BD=9C=E5=B9=B6=E8=B0=83?= =?UTF-8?q?=E6=95=B4=20PM2=20=E9=83=A8=E7=BD=B2=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/deploy.sh | 12 ++++---- src/api/admin-reports.ts | 7 +++++ src/api/admin-risk.ts | 11 +++++++- .../[drawId]/risk/pools/[number]/page.tsx | 5 +++- .../draws/[drawId]/pools/[number]/page.tsx | 5 +++- src/i18n/locales/en/reports.json | 4 +++ src/i18n/locales/en/risk.json | 5 ++-- src/i18n/locales/ne/reports.json | 4 +++ src/i18n/locales/ne/risk.json | 5 ++-- src/i18n/locales/zh/reports.json | 5 ++++ src/i18n/locales/zh/risk.json | 5 ++-- src/lib/report-export-map.ts | 2 ++ .../config/doc/risk-cap-doc-screen.tsx | 10 ++++++- src/modules/reports/report-preview-tables.tsx | 7 +++++ src/modules/reports/reports-console.tsx | 20 ++++++++++++- src/modules/reports/reports-definitions.ts | 13 ++++++++- src/modules/reports/reports-utils.ts | 4 ++- src/modules/risk/risk-pool-detail-console.tsx | 16 ++++++++--- src/modules/risk/risk-pools-console.tsx | 28 ++++++++++++------- src/types/api/admin-config.ts | 1 + src/types/api/admin-reports.ts | 9 ++++++ src/types/api/admin-risk.ts | 2 ++ 22 files changed, 147 insertions(+), 33 deletions(-) diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 7bbf354..be3cb98 100644 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -10,12 +10,12 @@ REMOTE_DIR="/www/wwwroot/lotteryAdmin" echo "🔨 构建中..." npm run build -echo "📦 上传到服务器..." -ssh ${SERVER} "mkdir -p ${REMOTE_DIR}/.next/standalone ${REMOTE_DIR}/logs" -rsync -avz --delete .next/standalone/ ${SERVER}:${REMOTE_DIR}/.next/standalone/ -rsync -avz ecosystem.config.cjs ${SERVER}:${REMOTE_DIR}/ecosystem.config.cjs +echo "📦 上传到服务器 app/ ..." +ssh ${SERVER} "mkdir -p ${REMOTE_DIR}/app ${REMOTE_DIR}/logs" +rsync -az --delete .next/standalone/ ${SERVER}:${REMOTE_DIR}/app/ +rsync -az ecosystem.config.cjs ${SERVER}:${REMOTE_DIR}/ecosystem.config.cjs -echo "🚀 服务器重启 PM2..." -ssh ${SERVER} "source /etc/profile && source ~/.bashrc && mkdir -p ${REMOTE_DIR}/logs && cd ${REMOTE_DIR} && (pm2 restart lotteryadmin 2>/dev/null || pm2 start ecosystem.config.cjs) && pm2 save" +echo "🚀 重启 PM2..." +ssh ${SERVER} "source /etc/profile && source ~/.bashrc && cd ${REMOTE_DIR} && (pm2 delete lotteryadmin 2>/dev/null || true) && pm2 start ecosystem.config.cjs --only lotteryadmin --update-env && pm2 save" echo "✅ lotteryAdmin 部署完成!" diff --git a/src/api/admin-reports.ts b/src/api/admin-reports.ts index 1593370..3ab330f 100644 --- a/src/api/admin-reports.ts +++ b/src/api/admin-reports.ts @@ -5,6 +5,7 @@ import type { AdminReportDailyProfitRow, AdminReportListData, AdminReportPlayDimensionRow, + AdminReportProviderProfitRow, AdminReportPlayerWinLossRow, AdminReportQueryParams, } from "@/types/api/admin-reports"; @@ -19,6 +20,12 @@ export async function getAdminReportDailyProfit( }); } +export async function getAdminReportProviderProfit( + params: AdminReportQueryParams, +): Promise> { + return adminRequest.get>(`${A}/reports/provider-profit`, { params }); +} + export async function getAdminReportPlayerWinLoss( params: AdminReportQueryParams, ): Promise> { diff --git a/src/api/admin-risk.ts b/src/api/admin-risk.ts index d4e1d70..1397d16 100644 --- a/src/api/admin-risk.ts +++ b/src/api/admin-risk.ts @@ -12,6 +12,7 @@ const A = `/admin`; export type AdminRiskPoolListQuery = { page?: number; per_page?: number; + provider_code?: string; sold_out_only?: boolean; high_risk_only?: boolean; active_only?: boolean; @@ -27,6 +28,7 @@ export async function getAdminRiskPools( params: { page: q.page, per_page: q.per_page, + provider_code: q.provider_code || undefined, sold_out_only: q.sold_out_only === true ? 1 : undefined, high_risk_only: q.high_risk_only === true ? 1 : undefined, active_only: q.active_only === true ? 1 : undefined, @@ -39,26 +41,33 @@ export async function getAdminRiskPools( export async function postAdminRiskPoolManualClose( drawId: number, number4d: string, + providerCode: string, ): Promise { const encoded = encodeURIComponent(number4d); return adminRequest.post( `${A}/draws/${drawId}/risk-pools/${encoded}/manual-close`, + undefined, + { params: { provider_code: providerCode || undefined } }, ); } export async function postAdminRiskPoolRecover( drawId: number, number4d: string, + providerCode: string, ): Promise { const encoded = encodeURIComponent(number4d); return adminRequest.post( `${A}/draws/${drawId}/risk-pools/${encoded}/recover`, + undefined, + { params: { provider_code: providerCode || undefined } }, ); } export type AdminRiskPoolShowQuery = { page?: number; per_page?: number; + provider_code?: string; }; export async function getAdminRiskPoolDetail( @@ -69,6 +78,6 @@ export async function getAdminRiskPoolDetail( const encoded = encodeURIComponent(number4d); return adminRequest.get( `${A}/draws/${drawId}/risk-pools/${encoded}`, - { params: { page: q.page, per_page: q.per_page } }, + { params: { page: q.page, per_page: q.per_page, provider_code: q.provider_code || undefined } }, ); } diff --git a/src/app/admin/(shell)/draws/[drawId]/risk/pools/[number]/page.tsx b/src/app/admin/(shell)/draws/[drawId]/risk/pools/[number]/page.tsx index 5b908f1..cf46d60 100644 --- a/src/app/admin/(shell)/draws/[drawId]/risk/pools/[number]/page.tsx +++ b/src/app/admin/(shell)/draws/[drawId]/risk/pools/[number]/page.tsx @@ -2,9 +2,12 @@ import { RiskPoolDetailConsole } from "@/modules/risk/risk-pool-detail-console"; export default async function AdminDrawRiskPoolDetailPage(props: { params: Promise<{ drawId: string; number: string }>; + searchParams: Promise<{ provider_code?: string }>; }) { const { drawId, number } = await props.params; + const { provider_code } = await props.searchParams; const id = Number(drawId); + const providerCode = (provider_code ?? "SG").trim().toUpperCase() || "SG"; - return ; + return ; } diff --git a/src/app/admin/(shell)/risk/draws/[drawId]/pools/[number]/page.tsx b/src/app/admin/(shell)/risk/draws/[drawId]/pools/[number]/page.tsx index da79436..428c6fd 100644 --- a/src/app/admin/(shell)/risk/draws/[drawId]/pools/[number]/page.tsx +++ b/src/app/admin/(shell)/risk/draws/[drawId]/pools/[number]/page.tsx @@ -2,7 +2,10 @@ import { redirect } from "next/navigation"; export default async function AdminRiskPoolDetailPage(props: { params: Promise<{ drawId: string; number: string }>; + searchParams: Promise<{ provider_code?: string }>; }) { const { drawId, number } = await props.params; - redirect(`/admin/draws/${drawId}/risk/pools/${number}`); + const { provider_code } = await props.searchParams; + const suffix = provider_code ? `?provider_code=${encodeURIComponent(provider_code)}` : ""; + redirect(`/admin/draws/${drawId}/risk/pools/${number}${suffix}`); } diff --git a/src/i18n/locales/en/reports.json b/src/i18n/locales/en/reports.json index 92de401..ae4736a 100644 --- a/src/i18n/locales/en/reports.json +++ b/src/i18n/locales/en/reports.json @@ -316,6 +316,10 @@ "title": "Play dimension report", "summary": "P&L by play and business date; defaults to the last 30 days. Ticket amounts — not credit-line settlement." }, + "provider_profit": { + "title": "Bet provider performance", + "summary": "Ticket items, bets, payouts, and platform gross by provider and business date; defaults to the last 30 days." + }, "sold_out_number": { "title": "Sold-out number report", "summary": "Review sold-out numbers, sold-out time, and risk lock state by draw." diff --git a/src/i18n/locales/en/risk.json b/src/i18n/locales/en/risk.json index 7dc0ca8..47ff770 100644 --- a/src/i18n/locales/en/risk.json +++ b/src/i18n/locales/en/risk.json @@ -25,6 +25,7 @@ "searchNumber": "Search number", "searchNumberPlaceholder": "For example 8888", "riskFilter": "Risk filter", + "provider": "Provider", "sort": "Sort", "filterAll": "All", "filterActive": "Active / high risk", @@ -51,9 +52,9 @@ "actionFailed": "Action failed", "confirm": { "closeTitle": "Confirm close number?", - "closeDescription": "Number {{number}} will be blocked for this draw.", + "closeDescription": "Number {{number}} ({{provider}}) will be blocked for this draw.", "recoverTitle": "Confirm recover number?", - "recoverDescription": "Number {{number}} will be open for betting again." + "recoverDescription": "Number {{number}} ({{provider}}) will be open for betting again." }, "detailTitle": "Risk pool details", "loadDetailFailed": "Failed to load risk pool details", diff --git a/src/i18n/locales/ne/reports.json b/src/i18n/locales/ne/reports.json index 61b2ada..ca66483 100644 --- a/src/i18n/locales/ne/reports.json +++ b/src/i18n/locales/ne/reports.json @@ -68,6 +68,10 @@ "summary": "खेल र व्यावसायिक मितिअनुसार P&L; मिति नचयेमा पछिल्लो ३० दिन।", "title": "खेल आयाम रिपोर्ट" }, + "provider_profit": { + "summary": "खुलाउने कम्पनी र व्यावसायिक मितिअनुसार टिकट, बेट, पेआउट र प्लेटफर्म नाफा; मिति नचयेमा पछिल्लो ३० दिन।", + "title": "खुलाउने कम्पनी सञ्चालन रिपोर्ट" + }, "player_transfer": { "summary": "वालेट-मोड मुख्य साइट ट्रान्सफर, रेकर्ड समय अनुसार; मिति नचयेमा पछिल्लो ३० दिन।", "title": "खेलाडी ट्रान्सफर रिपोर्ट" diff --git a/src/i18n/locales/ne/risk.json b/src/i18n/locales/ne/risk.json index 538c02f..cc04a45 100644 --- a/src/i18n/locales/ne/risk.json +++ b/src/i18n/locales/ne/risk.json @@ -16,9 +16,9 @@ "closeTime": "बन्द समय", "combinationCount": "संयोजन", "confirm": { - "closeDescription": "नम्बर {{number}} यो ड्रका लागि ब्लक हुनेछ।", + "closeDescription": "नम्बर {{number}} ({{provider}}) यो ड्रका लागि ब्लक हुनेछ।", "closeTitle": "नम्बर बन्द गर्ने?", - "recoverDescription": "नम्बर {{number}} फेरि बाजीका लागि खुल्नेछ।", + "recoverDescription": "नम्बर {{number}} ({{provider}}) फेरि बाजीका लागि खुल्नेछ।", "recoverTitle": "नम्बर पुन: खोल्ने?" }, "databaseStatus": "डेटाबेस स्थिति", @@ -69,6 +69,7 @@ "remainingAmount": "बाँकी", "remainingSellable": "बाँकी बिक्रीयोग्य", "riskFilter": "जोखिम फिल्टर", + "provider": "प्रोभाइडर", "search": "खोज", "searchNumber": "नम्बर खोज्नुहोस्", "searchNumberPlaceholder": "जस्तै 8888", diff --git a/src/i18n/locales/zh/reports.json b/src/i18n/locales/zh/reports.json index 081cf2d..aa0e87f 100644 --- a/src/i18n/locales/zh/reports.json +++ b/src/i18n/locales/zh/reports.json @@ -67,6 +67,7 @@ "transfer_orders_daily": "转账订单(日)", "hot_number_risk_report": "热门号码风险", "play_dimension_report": "玩法维度", + "provider_profit_report": "开注商经营", "sold_out_number_report": "售罄号码", "audit_operation_report": "后台操作审计" }, @@ -316,6 +317,10 @@ "title": "玩法维度报表", "summary": "按玩法与业务日拆分盈亏;未选日期默认近 30 天。注单口径,非信用账期结算。" }, + "provider_profit": { + "title": "开注商经营报表", + "summary": "按开注商与业务日汇总注项、投注、派彩和平台毛利;未选日期默认近 30 天。" + }, "sold_out_number": { "title": "售罄号码报表", "summary": "查看单期已售罄号码、售罄时间和风险封锁情况。" diff --git a/src/i18n/locales/zh/risk.json b/src/i18n/locales/zh/risk.json index a51f527..f392143 100644 --- a/src/i18n/locales/zh/risk.json +++ b/src/i18n/locales/zh/risk.json @@ -25,6 +25,7 @@ "searchNumber": "搜索号码", "searchNumberPlaceholder": "如 8888", "riskFilter": "风险筛选", + "provider": "开注商", "sort": "排序", "filterAll": "全部", "filterActive": "有占用/高风险", @@ -51,9 +52,9 @@ "actionFailed": "操作失败", "confirm": { "closeTitle": "确认关闭该号码下注?", - "closeDescription": "号码 {{number}} 在本期将被禁止下注。", + "closeDescription": "号码 {{number}}({{provider}})在本期将被禁止下注。", "recoverTitle": "确认恢复该号码下注?", - "recoverDescription": "号码 {{number}} 将恢复为可下注状态。" + "recoverDescription": "号码 {{number}}({{provider}})将恢复为可下注状态。" }, "detailTitle": "风险池详情", "loadDetailFailed": "加载风险池详情失败", diff --git a/src/lib/report-export-map.ts b/src/lib/report-export-map.ts index 57e25ae..d0dba9e 100644 --- a/src/lib/report-export-map.ts +++ b/src/lib/report-export-map.ts @@ -17,6 +17,7 @@ export type ReportUiKey = | "player_transfer" | "hot_number_risk" | "play_dimension" + | "provider_profit" | "sold_out_number" | "admin_audit"; @@ -28,6 +29,7 @@ export const REPORT_UI_TO_JOB_TYPE: Record = { player_transfer: "wallet_transfer_report", hot_number_risk: "hot_number_risk_report", play_dimension: "play_dimension_report", + provider_profit: "provider_profit_report", sold_out_number: "sold_out_number_report", admin_audit: "audit_operation_report", }; diff --git a/src/modules/config/doc/risk-cap-doc-screen.tsx b/src/modules/config/doc/risk-cap-doc-screen.tsx index 66bc34f..560a6b8 100644 --- a/src/modules/config/doc/risk-cap-doc-screen.tsx +++ b/src/modules/config/doc/risk-cap-doc-screen.tsx @@ -65,6 +65,7 @@ type DraftRiskRow = Omit & { clientKey: string }; function newRow(): DraftRiskRow { return { clientKey: `new-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, + provider_code: "GLOBAL", draw_id: null, normalized_number: "0000", cap_amount: 0, @@ -79,6 +80,7 @@ function isDefaultRiskRow(row: DraftRiskRow): boolean { function defaultRiskRowFromAmount(amount: number): DraftRiskRow { return { clientKey: `default-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, + provider_code: "GLOBAL", draw_id: null, normalized_number: "0000", cap_amount: amount, @@ -175,6 +177,7 @@ export function RiskCapDocScreen() { setDetail(d); const mapped = d.items.map((it) => ({ clientKey: `srv-${it.id}`, + provider_code: it.provider_code ?? "GLOBAL", draw_id: it.draw_id, normalized_number: it.normalized_number, cap_amount: it.cap_amount, @@ -285,6 +288,7 @@ export function RiskCapDocScreen() { setSaving(true); try { const payload = rowsToSave.map((r) => ({ + provider_code: r.provider_code ?? "GLOBAL", draw_id: r.draw_id && r.draw_id > 0 ? r.draw_id : null, normalized_number: r.normalized_number, cap_amount: r.cap_amount, @@ -294,6 +298,7 @@ export function RiskCapDocScreen() { setDetail(d); const saved = d.items.map((it) => ({ clientKey: `srv-${it.id}`, + provider_code: it.provider_code ?? "GLOBAL", draw_id: it.draw_id, normalized_number: it.normalized_number, cap_amount: it.cap_amount, @@ -320,6 +325,7 @@ export function RiskCapDocScreen() { setDetail(d); const pub = d.items.map((it) => ({ clientKey: `srv-${it.id}`, + provider_code: it.provider_code ?? "GLOBAL", draw_id: it.draw_id, normalized_number: it.normalized_number, cap_amount: it.cap_amount, @@ -351,6 +357,7 @@ export function RiskCapDocScreen() { setDetail(d); const nd = d.items.map((it) => ({ clientKey: `srv-${it.id}`, + provider_code: it.provider_code ?? "GLOBAL", draw_id: it.draw_id, normalized_number: it.normalized_number, cap_amount: it.cap_amount, @@ -424,6 +431,7 @@ export function RiskCapDocScreen() { setDetail(d); const mapped = d.items.map((it) => ({ clientKey: `srv-${it.id}`, + provider_code: it.provider_code ?? "GLOBAL", draw_id: it.draw_id, normalized_number: it.normalized_number, cap_amount: it.cap_amount, @@ -691,4 +699,4 @@ export function RiskCapDocScreen() { ); -} \ No newline at end of file +} diff --git a/src/modules/reports/report-preview-tables.tsx b/src/modules/reports/report-preview-tables.tsx index 93934cc..2fea2fd 100644 --- a/src/modules/reports/report-preview-tables.tsx +++ b/src/modules/reports/report-preview-tables.tsx @@ -100,6 +100,13 @@ export function ReportPreviewTable({ } switch (result.key) { + case "provider_profit": + return ( + + 开注商注项数下注派彩平台毛利 + {result.raw.map((item) => {item.provider_name}{item.provider_code}{item.ticket_item_count}{formatPlainMoney(item.total_bet_minor, displayCurrency)}{formatPlainMoney(item.total_payout_minor, displayCurrency)}{formatPlainMoney(item.approx_house_gross_minor, displayCurrency)})} +
+ ); case "draw_profit": return ( diff --git a/src/modules/reports/reports-console.tsx b/src/modules/reports/reports-console.tsx index b02241c..b21831f 100644 --- a/src/modules/reports/reports-console.tsx +++ b/src/modules/reports/reports-console.tsx @@ -19,6 +19,7 @@ import { downloadAdminReportJob, postAdminReportJob } from "@/api/admin-report-j import { getAdminReportDailyProfit, getAdminReportPlayDimension, + getAdminReportProviderProfit, getAdminReportPlayerWinLoss, } from "@/api/admin-reports"; import { @@ -375,6 +376,23 @@ export function ReportsConsole({ initialCategory = "profit" }: ReportsConsolePro }); break; } + case "provider_profit": { + const payload = await getAdminReportProviderProfit(reportListParams(filters, page, perPage)); + const currencyCode = resolveDisplayCurrency(payload.currency_code); + setDisplayCurrency(currencyCode); + const gross = payload.items.reduce((sum, item) => sum + item.approx_house_gross_minor, 0); + setResult({ + key: "provider_profit", + raw: payload.items, + rows: payload.items, + meta: metaFromList(payload.meta), + summary: [ + { label: t("preview.stats.records"), value: String(payload.meta.total) }, + { label: pageScopedLabel("houseGross"), value: formatPlainMoney(gross, currencyCode), tone: gross >= 0 ? "good" : "bad" }, + ], + }); + break; + } case "admin_audit": { const operatorId = filters.operatorId ?? parsePositiveInteger(filters.operator); const payload = await getAdminAuditLogs({ @@ -841,4 +859,4 @@ export function ReportsConsole({ initialCategory = "profit" }: ReportsConsolePro ); -} \ No newline at end of file +} diff --git a/src/modules/reports/reports-definitions.ts b/src/modules/reports/reports-definitions.ts index 5015072..1fe7722 100644 --- a/src/modules/reports/reports-definitions.ts +++ b/src/modules/reports/reports-definitions.ts @@ -1,6 +1,7 @@ import type { LucideIcon } from "lucide-react"; import { CalendarDays, + Building2, FileSpreadsheet, ListFilter, ShieldAlert, @@ -44,6 +45,7 @@ export type ReportKey = | "player_transfer" | "hot_number_risk" | "play_dimension" + | "provider_profit" | "sold_out_number" | "admin_audit"; @@ -60,6 +62,15 @@ export type ReportDefinition = { const PRD_REPORTS_VIEW_ACCESS_ANY = [PRD_REPORT_VIEW] as const; export const REPORT_DEFINITIONS: ReportDefinition[] = [ + { + key: "provider_profit", + category: "profit", + icon: Building2, + filterKind: "date", + fields: ["period"], + connected: true, + requiredAny: PRD_REPORTS_VIEW_ACCESS_ANY, + }, { key: "draw_profit", category: "profit", @@ -138,4 +149,4 @@ export const CATEGORY_SHORTCUT_HREF: Partial> = { wallet: "/admin/wallet/transfer-orders", risk: "/admin/risk", audit: "/admin/audit-logs", -}; \ No newline at end of file +}; diff --git a/src/modules/reports/reports-utils.ts b/src/modules/reports/reports-utils.ts index a71fd1a..2adc7c4 100644 --- a/src/modules/reports/reports-utils.ts +++ b/src/modules/reports/reports-utils.ts @@ -16,6 +16,7 @@ import type { AdminTransferOrderItem } from "@/types/api/admin-wallet"; import type { AdminReportDailyProfitRow, AdminReportPlayDimensionRow, + AdminReportProviderProfitRow, AdminReportPlayerWinLossRow, } from "@/types/api/admin-reports"; @@ -59,6 +60,7 @@ export type ReportResult = | { key: "player_transfer"; rows: ExportRow[]; summary: StatCard[]; meta: ReportMeta; raw: AdminTransferOrderItem[] } | { key: "hot_number_risk"; rows: ExportRow[]; summary: StatCard[]; meta: ReportMeta | null; raw: AdminRiskPoolShowData } | { key: "play_dimension"; rows: ExportRow[]; summary: StatCard[]; meta: ReportMeta; raw: AdminReportPlayDimensionRow[] } + | { key: "provider_profit"; rows: ExportRow[]; summary: StatCard[]; meta: ReportMeta; raw: AdminReportProviderProfitRow[] } | { key: "sold_out_number"; rows: ExportRow[]; summary: StatCard[]; meta: ReportMeta; raw: AdminRiskPoolRow[] } | { key: "admin_audit"; rows: ExportRow[]; summary: StatCard[]; meta: ReportMeta; raw: AdminAuditLogRow[] }; @@ -350,4 +352,4 @@ export function headlineStats(result: ReportResult | null, reportKey: ReportKey) return []; } return result.summary.slice(0, 2); -} \ No newline at end of file +} diff --git a/src/modules/risk/risk-pool-detail-console.tsx b/src/modules/risk/risk-pool-detail-console.tsx index 2684b09..f1e5d9c 100644 --- a/src/modules/risk/risk-pool-detail-console.tsx +++ b/src/modules/risk/risk-pool-detail-console.tsx @@ -33,9 +33,11 @@ import type { AdminRiskPoolDetailLogRow, AdminRiskPoolShowData } from "@/types/a export function RiskPoolDetailConsole({ drawId, number4d, + providerCode, }: { drawId: number; number4d: string; + providerCode: string; }) { const { t } = useTranslation(["risk", "common"]); const tRef = useTranslationRef(["risk", "common"]); @@ -53,7 +55,11 @@ export function RiskPoolDetailConsole({ setLoading(true); setError(null); try { - const d = await getAdminRiskPoolDetail(drawId, number4d, { page, per_page: perPage }); + const d = await getAdminRiskPoolDetail(drawId, number4d, { + page, + per_page: perPage, + provider_code: providerCode, + }); setData(d); } catch (e) { const msg = @@ -63,11 +69,11 @@ export function RiskPoolDetailConsole({ } finally { setLoading(false); } - }, [drawId, number4d, page, perPage, tRef]); + }, [drawId, number4d, page, perPage, providerCode, tRef]); useAsyncEffect(() => { void load(); - }, [drawId, number4d, page, perPage]); + }, [drawId, number4d, page, perPage, providerCode]); if (error && !data) { return ( @@ -115,7 +121,9 @@ export function RiskPoolDetailConsole({ {t("numberTitle", { number: pool.normalized_number })} -

{t("drawMeta", { drawNo: data.draw_no })}

+

+ {t("drawMeta", { drawNo: data.draw_no })} · {t("provider")} {pool.provider_code} +

diff --git a/src/modules/risk/risk-pools-console.tsx b/src/modules/risk/risk-pools-console.tsx index 5084e04..ceebc0c 100644 --- a/src/modules/risk/risk-pools-console.tsx +++ b/src/modules/risk/risk-pools-console.tsx @@ -119,7 +119,7 @@ export function RiskPoolsConsole({ const [perPage, setPerPage] = useState(10); const [data, setData] = useState(null); const [loading, setLoading] = useState(true); - const [actingNumber, setActingNumber] = useState(null); + const [actingKey, setActingKey] = useState(null); const [error, setError] = useState(null); const load = useCallback(async () => { @@ -152,11 +152,12 @@ export function RiskPoolsConsole({ const handleManualStatus = useCallback( async (row: AdminRiskPoolRow) => { - setActingNumber(row.normalized_number); + const rowKey = `${row.provider_code}:${row.normalized_number}`; + setActingKey(rowKey); try { const updated = row.is_sold_out - ? await postAdminRiskPoolRecover(drawId, row.normalized_number) - : await postAdminRiskPoolManualClose(drawId, row.normalized_number); + ? await postAdminRiskPoolRecover(drawId, row.normalized_number, row.provider_code) + : await postAdminRiskPoolManualClose(drawId, row.normalized_number, row.provider_code); setData((current) => { if (!current) return current; @@ -164,7 +165,10 @@ export function RiskPoolsConsole({ return { ...current, items: current.items.map((item) => - item.normalized_number === updated.normalized_number ? updated : item, + item.normalized_number === updated.normalized_number && + item.provider_code === updated.provider_code + ? updated + : item, ), }; }); @@ -172,7 +176,7 @@ export function RiskPoolsConsole({ } catch (e) { toast.error(e instanceof LotteryApiBizError ? e.message : t("actionFailed")); } finally { - setActingNumber(null); + setActingKey(null); } }, [drawId, t], @@ -275,6 +279,7 @@ export function RiskPoolsConsole({
+ {t("provider")} {t("searchNumber")} {t("capAmount")} {t("lockedAmount")} @@ -285,15 +290,15 @@ export function RiskPoolsConsole({ - {loading && !data ? : null} + {loading && !data ? : null} {(data?.items ?? []).map((row: AdminRiskPoolRow) => { const highRisk = (row.usage_ratio ?? 0) >= 0.8; - const acting = actingNumber === row.normalized_number; + const acting = actingKey === `${row.provider_code}:${row.normalized_number}`; const currencyCode = data?.currency_code ?? "NPR"; return ( + {row.provider_code} {row.normalized_number} {formatAdminMinorUnits(row.total_cap_amount, currencyCode)} @@ -331,7 +337,7 @@ export function RiskPoolsConsole({ key: "view", label: t("view"), icon: Eye, - href: `/admin/draws/${drawId}/risk/pools/${row.normalized_number}`, + href: `/admin/draws/${drawId}/risk/pools/${row.normalized_number}?provider_code=${encodeURIComponent(row.provider_code)}`, }, { key: "toggle", @@ -347,9 +353,11 @@ export function RiskPoolsConsole({ description: row.is_sold_out ? t("confirm.recoverDescription", { number: row.normalized_number, + provider: row.provider_code, }) : t("confirm.closeDescription", { number: row.normalized_number, + provider: row.provider_code, }), confirmVariant: row.is_sold_out ? "default" : "destructive", onConfirm: () => handleManualStatus(row), diff --git a/src/types/api/admin-config.ts b/src/types/api/admin-config.ts index 43b1cb3..cbce645 100644 --- a/src/types/api/admin-config.ts +++ b/src/types/api/admin-config.ts @@ -77,6 +77,7 @@ export type OddsVersionDetail = ConfigVersionSummary & { export type RiskCapItemRow = { id: number; + provider_code?: string; draw_id: number | null; normalized_number: string; cap_amount: number; diff --git a/src/types/api/admin-reports.ts b/src/types/api/admin-reports.ts index 1789d11..a34ed4e 100644 --- a/src/types/api/admin-reports.ts +++ b/src/types/api/admin-reports.ts @@ -24,6 +24,15 @@ export type AdminReportPlayDimensionRow = { approx_house_gross_minor: number; }; +export type AdminReportProviderProfitRow = { + provider_code: string; + provider_name: string; + ticket_item_count: number; + total_bet_minor: number; + total_payout_minor: number; + approx_house_gross_minor: number; +}; + export type AdminReportListData = { items: T[]; meta: { diff --git a/src/types/api/admin-risk.ts b/src/types/api/admin-risk.ts index 3164bf8..974866d 100644 --- a/src/types/api/admin-risk.ts +++ b/src/types/api/admin-risk.ts @@ -6,6 +6,7 @@ export type AdminRiskPoolListMeta = { }; export type AdminRiskPoolRow = { + provider_code: string; normalized_number: string; total_cap_amount: number; locked_amount: number; @@ -26,6 +27,7 @@ export type AdminRiskPoolListData = { export type AdminRiskPoolDetailLogRow = { id: number; + provider_code: string; action_type: string; amount: number; source_reason: string | null;