diff --git a/contracts/admin-openapi.json b/contracts/admin-openapi.json index 267e8a1..7b46e15 100644 --- a/contracts/admin-openapi.json +++ b/contracts/admin-openapi.json @@ -1650,6 +1650,71 @@ "x-permissions": ["coin-seller:exchange-rate"] } }, + "/admin/host-withdrawals": { + "get": { + "operationId": "listHostWithdrawals", + "parameters": [ + { + "$ref": "#/components/parameters/Page" + }, + { + "$ref": "#/components/parameters/PageSize" + }, + { + "in": "query", + "name": "app_code", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "record_type", + "schema": { + "type": "string", + "enum": ["salary_transfer", "withdrawal"] + } + }, + { + "in": "query", + "name": "identity", + "schema": { + "type": "string", + "enum": ["host", "agency", "bd", "bd_leader_admin"] + } + }, + { + "$ref": "#/components/parameters/Status" + }, + { + "$ref": "#/components/parameters/Keyword" + }, + { + "in": "query", + "name": "start_at_ms", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "in": "query", + "name": "end_at_ms", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/HostWithdrawalPageResponse" + } + }, + "x-permission": "host-withdrawal:view", + "x-permissions": ["host-withdrawal:view"] + } + }, "/admin/countries": { "get": { "operationId": "listCountries", @@ -5819,6 +5884,16 @@ } } }, + "HostWithdrawalPageResponse": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseHostWithdrawalPage" + } + } + } + }, "HostProfilePageResponse": { "description": "OK", "content": { @@ -6159,6 +6234,27 @@ } } }, + "ApiPageHostWithdrawal": { + "type": "object", + "required": ["items", "page", "pageSize", "total"], + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/HostWithdrawal" + } + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + }, "ApiPageRechargeBill": { "type": "object", "required": ["items", "page", "pageSize", "total"], @@ -6349,6 +6445,21 @@ } ] }, + "ApiResponseHostWithdrawalPage": { + "allOf": [ + { + "$ref": "#/components/schemas/Envelope" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/ApiPageHostWithdrawal" + } + } + } + ] + }, "ApiResponseRechargeBillPage": { "allOf": [ { @@ -6852,6 +6963,114 @@ } } }, + "HostWithdrawalUser": { + "type": "object", + "properties": { + "userId": { + "type": "string" + }, + "displayUserId": { + "type": "string" + }, + "username": { + "type": "string" + }, + "avatar": { + "type": "string" + } + } + }, + "HostWithdrawal": { + "type": "object", + "required": [ + "id", + "recordType", + "identity", + "salaryAssetType", + "usdMinorAmount", + "coinAmount", + "status", + "createdAtMs", + "updatedAtMs" + ], + "properties": { + "id": { + "type": "string" + }, + "recordType": { + "type": "string", + "enum": ["salary_transfer", "withdrawal"] + }, + "identity": { + "type": "string" + }, + "salaryAssetType": { + "type": "string" + }, + "sourceUserId": { + "type": "string" + }, + "sourceUser": { + "$ref": "#/components/schemas/HostWithdrawalUser" + }, + "sellerUserId": { + "type": "string" + }, + "sellerUser": { + "$ref": "#/components/schemas/HostWithdrawalUser" + }, + "usdMinorAmount": { + "type": "integer", + "format": "int64" + }, + "coinAmount": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "transactionId": { + "type": "string" + }, + "commandId": { + "type": "string" + }, + "applicationId": { + "type": "string" + }, + "freezeTransactionId": { + "type": "string" + }, + "auditTransactionId": { + "type": "string" + }, + "withdrawMethod": { + "type": "string" + }, + "withdrawAddress": { + "type": "string" + }, + "auditRemark": { + "type": "string" + }, + "auditImageUrl": { + "type": "string" + }, + "metadata": { + "type": "object", + "additionalProperties": true + }, + "createdAtMs": { + "type": "integer", + "format": "int64" + }, + "updatedAtMs": { + "type": "integer", + "format": "int64" + } + } + }, "HumanRoomRobotCountryRule": { "type": "object", "required": ["countryCode", "maxRoomCount"], diff --git a/databi/src/api.js b/databi/src/api.js index 34a479b..67328ce 100644 --- a/databi/src/api.js +++ b/databi/src/api.js @@ -1,7 +1,7 @@ import { API_OPERATIONS, apiEndpointPath } from "../../src/shared/api/generated/endpoints.ts"; +import { API_BASE_URL } from "../../src/shared/config/env.js"; import { countryFlag, stripCountryFlagPrefix } from "./data/countryMeta.js"; -const API_BASE_URL = import.meta.env?.VITE_API_BASE_URL || "/api"; const TOKEN_KEY = "hyapp-admin.access-token"; const APP_CODE_KEY = "hyapp-admin.app-code"; const APP_CODE_HEADER = "X-App-Code"; diff --git a/src/app/navigation/menu.js b/src/app/navigation/menu.js index bb60f1d..709de0a 100644 --- a/src/app/navigation/menu.js +++ b/src/app/navigation/menu.js @@ -134,6 +134,7 @@ export const fallbackNavigation = [ routeNavItem("host-agency-policy", { icon: WalletOutlined }), routeNavItem("host-salary-settlement", { icon: ReceiptLongOutlined }), routeNavItem("host-org-coin-sellers", { icon: WalletOutlined }), + routeNavItem("host-withdrawals", { icon: ReceiptLongOutlined }), ], }, { diff --git a/src/app/permissions.ts b/src/app/permissions.ts index 5696c6d..4f32208 100644 --- a/src/app/permissions.ts +++ b/src/app/permissions.ts @@ -41,6 +41,7 @@ export const PERMISSIONS = { coinSellerUpdate: "coin-seller:update", coinSellerStockCredit: "coin-seller:stock-credit", coinSellerExchangeRate: "coin-seller:exchange-rate", + hostWithdrawalView: "host-withdrawal:view", financeView: "finance:view", financeApplicationCreate: "finance-application:create", financeApplicationAudit: "finance-application:audit", @@ -272,6 +273,7 @@ export const MENU_CODES = { hostAgencyPolicy: "host-agency-policy", hostSalarySettlement: "host-salary-settlement", hostOrgCoinSellers: "host-org-coin-sellers", + hostWithdrawals: "host-withdrawals", payment: "payment", paymentBillList: "payment-bill-list", paymentRechargeProducts: "payment-recharge-products", diff --git a/src/features/host-org/api.ts b/src/features/host-org/api.ts index 3d0d4e8..9bc5305 100644 --- a/src/features/host-org/api.ts +++ b/src/features/host-org/api.ts @@ -30,6 +30,7 @@ import type { CreateManagerPayload, EntityId, HostCommandPayload, + HostWithdrawalDto, HostProfileDto, ManagerDto, PageQuery, @@ -212,6 +213,14 @@ export function listCoinSellers(query: PageQuery = {}): Promise> { + const endpoint = API_ENDPOINTS.listHostWithdrawals; + return apiRequest>(apiEndpointPath(API_OPERATIONS.listHostWithdrawals), { + method: endpoint.method, + query, + }); +} + export function listSalaryWalletHistory(query: PageQuery = {}): Promise> { return apiRequest>("/v1/admin/host-org/salary-wallets/history", { method: "GET", diff --git a/src/features/host-org/hooks/useHostWithdrawalsPage.js b/src/features/host-org/hooks/useHostWithdrawalsPage.js new file mode 100644 index 0000000..40734c6 --- /dev/null +++ b/src/features/host-org/hooks/useHostWithdrawalsPage.js @@ -0,0 +1,90 @@ +import { useMemo, useState } from "react"; +import { listHostWithdrawals } from "@/features/host-org/api"; +import { useHostWithdrawalAbilities } from "@/features/host-org/permissions.js"; +import { usePaginatedQuery } from "@/shared/hooks/usePaginatedQuery.js"; + +const pageSize = 50; +const emptyData = { items: [], page: 1, pageSize, total: 0 }; +const emptyTimeRange = { endMs: "", startMs: "" }; + +export function useHostWithdrawalsPage() { + const abilities = useHostWithdrawalAbilities(); + const [page, setPage] = useState(1); + const [recordType, setRecordType] = useState(""); + const [identity, setIdentity] = useState(""); + const [status, setStatus] = useState(""); + const [keyword, setKeyword] = useState(""); + const [timeRange, setTimeRange] = useState(emptyTimeRange); + const filters = useMemo( + () => ({ + end_at_ms: timeRange.endMs || "", + identity, + keyword, + record_type: recordType, + start_at_ms: timeRange.startMs || "", + status, + }), + [identity, keyword, recordType, status, timeRange.endMs, timeRange.startMs], + ); + const query = usePaginatedQuery({ + errorMessage: "加载主播提现列表失败", + fetcher: listHostWithdrawals, + filters, + page, + pageSize, + queryKey: ["host-org", "host-withdrawals", filters, page], + }); + const data = query.data || emptyData; + const hasActiveFilters = Boolean(recordType || identity || status || keyword || timeRange.startMs || timeRange.endMs); + + const changeRecordType = (value) => { + setRecordType(value); + setPage(1); + }; + const changeIdentity = (value) => { + setIdentity(value); + setPage(1); + }; + const changeStatus = (value) => { + setStatus(value); + setPage(1); + }; + const changeKeyword = (value) => { + setKeyword(value); + setPage(1); + }; + const changeTimeRange = (nextRange) => { + setTimeRange(nextRange); + setPage(1); + }; + const resetFilters = () => { + setRecordType(""); + setIdentity(""); + setStatus(""); + setKeyword(""); + setTimeRange(emptyTimeRange); + setPage(1); + }; + + return { + abilities, + changeIdentity, + changeKeyword, + changeRecordType, + changeStatus, + changeTimeRange, + data, + error: query.error, + hasActiveFilters, + identity, + keyword, + loading: query.loading, + page, + recordType, + reload: query.reload, + resetFilters, + setPage, + status, + timeRange, + }; +} diff --git a/src/features/host-org/pages/HostWithdrawalsPage.jsx b/src/features/host-org/pages/HostWithdrawalsPage.jsx new file mode 100644 index 0000000..f178969 --- /dev/null +++ b/src/features/host-org/pages/HostWithdrawalsPage.jsx @@ -0,0 +1,276 @@ +import { + AdminFilterResetButton, + AdminListBody, + AdminListPage, + AdminListToolbar, +} from "@/shared/ui/AdminListLayout.jsx"; +import { AdminUserIdentity } from "@/shared/ui/AdminUserIdentity.jsx"; +import { DataState } from "@/shared/ui/DataState.jsx"; +import { DataTable } from "@/shared/ui/DataTable.jsx"; +import { TimeRangeFilter } from "@/shared/ui/TimeRangeFilter.jsx"; +import { createOptionsColumnFilter, createTextColumnFilter } from "@/shared/ui/tableFilters.js"; +import { formatMillis } from "@/shared/utils/time.js"; +import { useHostWithdrawalsPage } from "@/features/host-org/hooks/useHostWithdrawalsPage.js"; +import styles from "@/features/operations/operations.module.css"; + +const recordTypeOptions = [ + ["", "全部"], + ["salary_transfer", "工资转币商"], + ["withdrawal", "提现申请"], +]; + +const identityOptions = [ + ["", "全部"], + ["host", "Host"], + ["agency", "Agency"], + ["bd", "BD"], + ["bd_leader_admin", "BD Leader/Admin"], +]; + +const statusOptions = [ + ["", "全部"], + ["completed", "已完成"], + ["pending", "待审核"], + ["approved", "已通过"], + ["rejected", "已拒绝"], +]; + +const recordTypeLabels = Object.fromEntries(recordTypeOptions.filter(([value]) => value)); +const identityLabels = Object.fromEntries(identityOptions.filter(([value]) => value)); +const statusLabels = Object.fromEntries(statusOptions.filter(([value]) => value)); + +const baseColumns = [ + { + key: "createdAtMs", + label: "时间", + render: (item) => formatMillis(item.createdAtMs), + width: "minmax(170px, 0.8fr)", + }, + { + key: "recordType", + label: "类型", + render: (item) => recordTypeLabel(item.recordType), + width: "minmax(130px, 0.6fr)", + }, + { + key: "identity", + label: "身份", + render: (item) => identityLabel(item.identity, item.salaryAssetType), + width: "minmax(150px, 0.65fr)", + }, + { + key: "sourceUser", + label: "发起用户", + render: (item) => , + width: "minmax(230px, 1fr)", + }, + { + key: "receiver", + label: "币商/收款", + render: (item) => , + width: "minmax(240px, 1.05fr)", + }, + { + key: "usdMinorAmount", + label: "USD金额", + render: (item) => , + width: "minmax(120px, 0.55fr)", + }, + { + key: "coinAmount", + label: "币商金币", + render: (item) => formatCoinAmount(item), + width: "minmax(130px, 0.6fr)", + }, + { + key: "status", + label: "状态", + render: (item) => , + width: "minmax(110px, 0.55fr)", + }, + { + key: "recordId", + label: "交易/申请ID", + render: (item) => , + width: "minmax(260px, 1.1fr)", + }, +]; + +export function HostWithdrawalsPage() { + const page = useHostWithdrawalsPage(); + const data = page.data || { items: [], page: 1, pageSize: 50, total: 0 }; + const items = data.items || []; + const total = Number(data.total || 0); + const tableColumns = baseColumns.map((column) => { + if (column.key === "recordType") { + return { + ...column, + filter: createOptionsColumnFilter({ + emptyLabel: "全部", + options: recordTypeOptions, + placeholder: "类型", + value: page.recordType, + onChange: page.changeRecordType, + }), + }; + } + if (column.key === "identity") { + return { + ...column, + filter: createOptionsColumnFilter({ + emptyLabel: "全部", + options: identityOptions, + placeholder: "身份", + value: page.identity, + onChange: page.changeIdentity, + }), + }; + } + if (column.key === "sourceUser") { + return { + ...column, + filter: createTextColumnFilter({ + activeLabel: page.keyword ? `用户 ${page.keyword}` : "", + placeholder: "用户/交易/地址", + value: page.keyword, + onChange: page.changeKeyword, + }), + }; + } + if (column.key === "status") { + return { + ...column, + filter: createOptionsColumnFilter({ + emptyLabel: "全部", + options: statusOptions, + placeholder: "状态", + value: page.status, + onChange: page.changeStatus, + }), + }; + } + return column; + }); + + return ( + + + + + + } + /> + + + 0 + ? { + page: page.page, + pageSize: data.pageSize || 50, + total, + onPageChange: page.setPage, + } + : undefined + } + rowKey={(item) => item.id} + /> + + + + ); +} + +function ReceiverCell({ item }) { + if (item.recordType === "salary_transfer") { + return ; + } + const method = String(item.withdrawMethod || "").trim(); + const address = String(item.withdrawAddress || "").trim(); + if (!method && !address) { + return "-"; + } + return ( + + {method || "-"} + {address ? {address} : null} + + ); +} + +function RecordIdCell({ item }) { + const primary = item.transactionId || item.applicationId || item.id || "-"; + const secondary = item.commandId || item.freezeTransactionId || item.auditTransactionId || ""; + return ( + + {primary} + {secondary ? {secondary} : null} + + ); +} + +function StatusBadge({ status }) { + const normalized = String(status || "").trim(); + return ( + + + {statusLabel(normalized)} + + ); +} + +function AmountText({ value }) { + return {value}; +} + +function recordTypeLabel(value) { + return recordTypeLabels[value] || value || "-"; +} + +function identityLabel(identity, salaryAssetType) { + return identityLabels[identity] || salaryAssetLabel(salaryAssetType) || identity || "-"; +} + +function salaryAssetLabel(assetType) { + const labels = { + AGENCY_SALARY_USD: "Agency", + ADMIN_SALARY_USD: "BD Leader/Admin", + BD_SALARY_USD: "BD", + HOST_SALARY_USD: "Host", + }; + return labels[assetType] || ""; +} + +function statusLabel(status) { + return statusLabels[status] || status || "-"; +} + +function statusTone(status) { + if (status === "completed" || status === "approved") { + return "succeeded"; + } + if (status === "pending") { + return "warning"; + } + if (status === "rejected") { + return "danger"; + } + return "stopped"; +} + +function formatCoinAmount(item) { + if (item.recordType !== "salary_transfer") { + return "-"; + } + return Number(item.coinAmount || 0).toLocaleString("zh-CN"); +} + +function formatUSDMinor(value) { + return (Number(value || 0) / 100).toFixed(2); +} diff --git a/src/features/host-org/pages/HostWithdrawalsPage.test.jsx b/src/features/host-org/pages/HostWithdrawalsPage.test.jsx new file mode 100644 index 0000000..96b3e12 --- /dev/null +++ b/src/features/host-org/pages/HostWithdrawalsPage.test.jsx @@ -0,0 +1,175 @@ +import { fireEvent, render, screen } from "@testing-library/react"; +import { afterEach, expect, test, vi } from "vitest"; +import { HostWithdrawalsPage } from "./HostWithdrawalsPage.jsx"; +import { useHostWithdrawalsPage } from "@/features/host-org/hooks/useHostWithdrawalsPage.js"; + +const mocks = vi.hoisted(() => ({ + changeIdentity: vi.fn(), + changeKeyword: vi.fn(), + changeRecordType: vi.fn(), + changeStatus: vi.fn(), + changeTimeRange: vi.fn(), + dataTable: vi.fn(), + resetFilters: vi.fn(), +})); + +vi.mock("@/features/host-org/hooks/useHostWithdrawalsPage.js", () => ({ + useHostWithdrawalsPage: vi.fn(), +})); + +vi.mock("@/shared/ui/DataTable.jsx", () => ({ + DataTable: (props) => { + mocks.dataTable(props); + return ( +
+ {props.columns.map((column) => ( +
+ {column.label} +
+ ))} + {props.items.map((item) => ( +
+ {props.columns.map((column) => ( +
{column.render ? column.render(item) : item[column.key]}
+ ))} +
+ ))} +
+ ); + }, +})); + +vi.mock("@/shared/ui/TimeRangeFilter.jsx", () => ({ + TimeRangeFilter: ({ onChange }) => ( + + ), +})); + +afterEach(() => { + vi.clearAllMocks(); +}); + +test("host withdrawals page renders salary transfer and withdrawal records", () => { + vi.mocked(useHostWithdrawalsPage).mockReturnValue(pageFixture()); + + render(); + + expect(screen.getByText("工资转币商")).toBeInTheDocument(); + expect(screen.getByText("提现申请")).toBeInTheDocument(); + expect(screen.getByText("Host")).toBeInTheDocument(); + expect(screen.getByText("BD Leader/Admin")).toBeInTheDocument(); + expect(screen.getByText("$123.45")).toBeInTheDocument(); + expect(screen.getByText("1,112")).toBeInTheDocument(); + expect(screen.getByText("已完成")).toBeInTheDocument(); + expect(screen.getByText("待审核")).toBeInTheDocument(); + expect(screen.getByText("USDT-TRC20")).toBeInTheDocument(); + expect(screen.getByText("TRON9ADDRESS")).toBeInTheDocument(); + expect(screen.getByText("tx-salary-1")).toBeInTheDocument(); + expect(screen.getByText("9")).toBeInTheDocument(); +}); + +test("host withdrawals filters are wired to the page hook", () => { + vi.mocked(useHostWithdrawalsPage).mockReturnValue(pageFixture({ hasActiveFilters: true })); + + render(); + + const props = mocks.dataTable.mock.calls.at(-1)?.[0]; + props.columns.find((column) => column.key === "recordType").filter.onChange("withdrawal"); + props.columns.find((column) => column.key === "identity").filter.onChange("bd_leader_admin"); + props.columns.find((column) => column.key === "sourceUser").filter.onChange("168557"); + props.columns.find((column) => column.key === "status").filter.onChange("approved"); + fireEvent.click(screen.getByRole("button", { name: "时间" })); + fireEvent.click(screen.getByRole("button", { name: "重置" })); + + expect(mocks.changeRecordType).toHaveBeenCalledWith("withdrawal"); + expect(mocks.changeIdentity).toHaveBeenCalledWith("bd_leader_admin"); + expect(mocks.changeKeyword).toHaveBeenCalledWith("168557"); + expect(mocks.changeStatus).toHaveBeenCalledWith("approved"); + expect(mocks.changeTimeRange).toHaveBeenCalledWith({ endMs: "2000", startMs: "1000" }); + expect(mocks.resetFilters).toHaveBeenCalled(); +}); + +function pageFixture(patch = {}) { + return { + abilities: { canView: true }, + changeIdentity: mocks.changeIdentity, + changeKeyword: mocks.changeKeyword, + changeRecordType: mocks.changeRecordType, + changeStatus: mocks.changeStatus, + changeTimeRange: mocks.changeTimeRange, + data: { + items: [salaryTransferFixture(), withdrawalFixture()], + page: 1, + pageSize: 50, + total: 2, + }, + error: null, + hasActiveFilters: false, + identity: "", + keyword: "", + loading: false, + page: 1, + recordType: "", + reload: vi.fn(), + resetFilters: mocks.resetFilters, + setPage: vi.fn(), + status: "", + timeRange: { endMs: "", startMs: "" }, + ...patch, + }; +} + +function salaryTransferFixture() { + return { + coinAmount: 1112, + commandId: "cmd-salary-1", + createdAtMs: 1780000000000, + id: "salary_transfer:101", + identity: "host", + recordType: "salary_transfer", + salaryAssetType: "HOST_SALARY_USD", + sellerUser: { + displayUserId: "168556", + userId: "3001", + username: "Coin Seller", + }, + sellerUserId: "3001", + sourceUser: { + displayUserId: "168577", + userId: "1001", + username: "Raj", + }, + sourceUserId: "1001", + status: "completed", + transactionId: "tx-salary-1", + updatedAtMs: 1780000000000, + usdMinorAmount: 12345, + }; +} + +function withdrawalFixture() { + return { + applicationId: "9", + coinAmount: 0, + commandId: "freeze-cmd-9", + createdAtMs: 1780000001000, + freezeTransactionId: "freeze-tx-9", + id: "withdrawal:9", + identity: "bd_leader_admin", + recordType: "withdrawal", + salaryAssetType: "ADMIN_SALARY_USD", + sourceUser: { + displayUserId: "481479", + userId: "2002", + username: "Mao", + }, + sourceUserId: "2002", + status: "pending", + updatedAtMs: 1780000001000, + usdMinorAmount: 5000, + withdrawAddress: "TRON9ADDRESS", + withdrawMethod: "USDT-TRC20", + }; +} diff --git a/src/features/host-org/permissions.js b/src/features/host-org/permissions.js index 93c5f0e..c3efd71 100644 --- a/src/features/host-org/permissions.js +++ b/src/features/host-org/permissions.js @@ -67,3 +67,11 @@ export function useCoinSellerAbilities() { canView: can(PERMISSIONS.coinSellerView), }; } + +export function useHostWithdrawalAbilities() { + const { can } = useAuth(); + + return { + canView: can(PERMISSIONS.hostWithdrawalView), + }; +} diff --git a/src/features/host-org/routes.js b/src/features/host-org/routes.js index 5c2da67..30e4a1c 100644 --- a/src/features/host-org/routes.js +++ b/src/features/host-org/routes.js @@ -65,4 +65,12 @@ export const hostOrgRoutes = [ path: "/host/coin-sellers", permission: PERMISSIONS.coinSellerView, }, + { + label: "主播提现", + loader: () => import("./pages/HostWithdrawalsPage.jsx").then((module) => module.HostWithdrawalsPage), + menuCode: MENU_CODES.hostWithdrawals, + pageKey: "host-withdrawals", + path: "/host/withdrawals", + permission: PERMISSIONS.hostWithdrawalView, + }, ]; diff --git a/src/shared/api/generated/endpoints.ts b/src/shared/api/generated/endpoints.ts index d0db9f6..be4cb62 100644 --- a/src/shared/api/generated/endpoints.ts +++ b/src/shared/api/generated/endpoints.ts @@ -175,6 +175,7 @@ export const API_OPERATIONS = { listHostAgencyPolicies: "listHostAgencyPolicies", listHosts: "listHosts", listHostSalarySettlements: "listHostSalarySettlements", + listHostWithdrawals: "listHostWithdrawals", listInviteActivityRewardClaims: "listInviteActivityRewardClaims", listLevelConfig: "listLevelConfig", listLoginLogs: "listLoginLogs", @@ -1460,6 +1461,13 @@ export const API_ENDPOINTS: Record = { permission: "host-salary-settlement:view", permissions: ["host-salary-settlement:view"] }, + listHostWithdrawals: { + method: "GET", + operationId: API_OPERATIONS.listHostWithdrawals, + path: "/v1/admin/host-withdrawals", + permission: "host-withdrawal:view", + permissions: ["host-withdrawal:view"] + }, listInviteActivityRewardClaims: { method: "GET", operationId: API_OPERATIONS.listInviteActivityRewardClaims, diff --git a/src/shared/api/generated/schema.d.ts b/src/shared/api/generated/schema.d.ts index 3ea6d2c..93320ec 100644 --- a/src/shared/api/generated/schema.d.ts +++ b/src/shared/api/generated/schema.d.ts @@ -1140,6 +1140,22 @@ export interface paths { patch?: never; trace?: never; }; + "/admin/host-withdrawals": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listHostWithdrawals"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/admin/countries": { parameters: { query?: never; @@ -3672,6 +3688,12 @@ export interface components { pageSize: number; total: number; }; + ApiPageHostWithdrawal: { + items: components["schemas"]["HostWithdrawal"][]; + page: number; + pageSize: number; + total: number; + }; ApiPageRechargeBill: { items: components["schemas"]["RechargeBill"][]; page: number; @@ -3714,6 +3736,9 @@ export interface components { ApiResponseFinanceWithdrawalApplicationPage: components["schemas"]["Envelope"] & { data?: components["schemas"]["ApiPageFinanceWithdrawalApplication"]; }; + ApiResponseHostWithdrawalPage: components["schemas"]["Envelope"] & { + data?: components["schemas"]["ApiPageHostWithdrawal"]; + }; ApiResponseRechargeBillPage: components["schemas"]["Envelope"] & { data?: components["schemas"]["ApiPageRechargeBill"]; }; @@ -3845,6 +3870,44 @@ export interface components { updatedAtMs?: number; userId: string; }; + HostWithdrawalUser: { + userId?: string; + displayUserId?: string; + username?: string; + avatar?: string; + }; + HostWithdrawal: { + id: string; + /** @enum {string} */ + recordType: "salary_transfer" | "withdrawal"; + identity: string; + salaryAssetType: string; + sourceUserId?: string; + sourceUser?: components["schemas"]["HostWithdrawalUser"]; + sellerUserId?: string; + sellerUser?: components["schemas"]["HostWithdrawalUser"]; + /** Format: int64 */ + usdMinorAmount: number; + /** Format: int64 */ + coinAmount: number; + status: string; + transactionId?: string; + commandId?: string; + applicationId?: string; + freezeTransactionId?: string; + auditTransactionId?: string; + withdrawMethod?: string; + withdrawAddress?: string; + auditRemark?: string; + auditImageUrl?: string; + metadata?: { + [key: string]: unknown; + }; + /** Format: int64 */ + createdAtMs: number; + /** Format: int64 */ + updatedAtMs: number; + }; HumanRoomRobotCountryRule: { allowedOwnerIds?: string[]; countryCode: string; @@ -4137,6 +4200,15 @@ export interface components { }; }; /** @description OK */ + HostWithdrawalPageResponse: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponseHostWithdrawalPage"]; + }; + }; + /** @description OK */ HostProfilePageResponse: { headers: { [name: string]: unknown; @@ -5802,6 +5874,28 @@ export interface operations { 200: components["responses"]["EmptyResponse"]; }; }; + listHostWithdrawals: { + parameters: { + query?: { + page?: components["parameters"]["Page"]; + page_size?: components["parameters"]["PageSize"]; + app_code?: string; + record_type?: "salary_transfer" | "withdrawal"; + identity?: "host" | "agency" | "bd" | "bd_leader_admin"; + status?: components["parameters"]["Status"]; + keyword?: components["parameters"]["Keyword"]; + start_at_ms?: number; + end_at_ms?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["HostWithdrawalPageResponse"]; + }; + }; listCountries: { parameters: { query?: never; diff --git a/src/shared/api/types.ts b/src/shared/api/types.ts index ab2b926..03e92a2 100644 --- a/src/shared/api/types.ts +++ b/src/shared/api/types.ts @@ -273,6 +273,32 @@ export interface CoinSellerLedgerDto { transferUsdMinor?: number; } +export interface HostWithdrawalDto { + applicationId?: string; + auditImageUrl?: string; + auditRemark?: string; + auditTransactionId?: string; + coinAmount: number; + commandId?: string; + createdAtMs?: number; + freezeTransactionId?: string; + id: string; + identity: string; + metadata?: Record; + recordType: string; + salaryAssetType: string; + sellerUser?: CoinLedgerUserDto; + sellerUserId?: string; + sourceUser?: CoinLedgerUserDto; + sourceUserId?: string; + status: string; + transactionId?: string; + updatedAtMs?: number; + usdMinorAmount: number; + withdrawAddress?: string; + withdrawMethod?: string; +} + export interface CoinAdjustmentOperatorDto { adminId?: string; name?: string;