修复无法扣除币商余额
This commit is contained in:
parent
81fa3a25bb
commit
6ab21a8baa
@ -1488,6 +1488,28 @@
|
||||
"x-permissions": ["coin-seller:stock-credit"]
|
||||
}
|
||||
},
|
||||
"/admin/coin-sellers/{user_id}/stock-debits": {
|
||||
"post": {
|
||||
"operationId": "debitCoinSellerStock",
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/components/responses/EmptyResponse"
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "user_id",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"x-permission": "coin-seller:stock-credit",
|
||||
"x-permissions": ["coin-seller:stock-credit"]
|
||||
}
|
||||
},
|
||||
"/admin/coin-seller-salary-rates/{region_id}": {
|
||||
"get": {
|
||||
"operationId": "getCoinSellerSalaryRates",
|
||||
|
||||
@ -92,6 +92,7 @@ test("host org list APIs use generated admin paths and filters", async () => {
|
||||
await debitCoinSellerStock("1001", {
|
||||
coinAmount: 1000,
|
||||
commandId: "coin-seller-stock-debit-test",
|
||||
rechargeAmount: "1.25",
|
||||
reason: "deduct bad stock",
|
||||
});
|
||||
await deleteAgency(7001, { commandId: "agency-delete-test", reason: "cleanup" });
|
||||
|
||||
@ -318,11 +318,12 @@ export function debitCoinSellerStock(
|
||||
userId: EntityId,
|
||||
payload: CoinSellerStockDebitPayload,
|
||||
): Promise<CoinSellerStockDebitDto> {
|
||||
const endpoint = API_ENDPOINTS.debitCoinSellerStock;
|
||||
return apiRequest<CoinSellerStockDebitDto, CoinSellerStockDebitPayload>(
|
||||
`/v1/admin/coin-sellers/${encodeURIComponent(String(userId))}/stock-debits`,
|
||||
apiEndpointPath(API_OPERATIONS.debitCoinSellerStock, { user_id: userId }),
|
||||
{
|
||||
body: payload,
|
||||
method: "POST",
|
||||
method: endpoint.method,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@ -41,6 +41,7 @@ const emptyStockDebitForm = () => ({
|
||||
coinAmount: "",
|
||||
commandId: makeCommandId("coin-seller-stock-debit"),
|
||||
reason: "",
|
||||
rechargeAmount: "",
|
||||
});
|
||||
const emptyRateTier = (index = 0) => ({
|
||||
coinPerUsd: "",
|
||||
|
||||
@ -326,17 +326,30 @@ export function HostCoinSellersPage() {
|
||||
</AdminFormFieldGrid>
|
||||
</AdminFormSection>
|
||||
<AdminFormSection title="扣除信息">
|
||||
<AdminFormAmountField
|
||||
disabled={!page.abilities.canStockCredit}
|
||||
inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }}
|
||||
label="扣除金币"
|
||||
required
|
||||
unit="金币"
|
||||
value={stockDebitForm.coinAmount}
|
||||
onChange={(event) =>
|
||||
page.setStockDebitForm?.({ ...stockDebitForm, coinAmount: event.target.value })
|
||||
}
|
||||
/>
|
||||
<AdminFormFieldGrid columns="repeat(2, minmax(0, 1fr))">
|
||||
<AdminFormAmountField
|
||||
disabled={!page.abilities.canStockCredit}
|
||||
inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }}
|
||||
label="扣除金币"
|
||||
required
|
||||
unit="金币"
|
||||
value={stockDebitForm.coinAmount}
|
||||
onChange={(event) =>
|
||||
page.setStockDebitForm?.({ ...stockDebitForm, coinAmount: event.target.value })
|
||||
}
|
||||
/>
|
||||
<AdminFormAmountField
|
||||
disabled={!page.abilities.canStockCredit}
|
||||
inputProps={{ inputMode: "decimal" }}
|
||||
label="扣除USDT数量"
|
||||
required
|
||||
unit="USDT"
|
||||
value={stockDebitForm.rechargeAmount}
|
||||
onChange={(event) =>
|
||||
page.setStockDebitForm?.({ ...stockDebitForm, rechargeAmount: event.target.value })
|
||||
}
|
||||
/>
|
||||
</AdminFormFieldGrid>
|
||||
</AdminFormSection>
|
||||
<AdminFormSection title="备注信息">
|
||||
<TextField
|
||||
@ -504,7 +517,7 @@ function SellerActions({ item, page }) {
|
||||
) : null}
|
||||
{page.abilities.canStockCredit ? (
|
||||
<AdminActionIconButton
|
||||
disabled={item.status !== "active" || page.loadingAction === `coin-seller-stock-${item.userId}`}
|
||||
disabled={page.loadingAction === `coin-seller-stock-${item.userId}`}
|
||||
label="加金币"
|
||||
sx={coinCreditActionSx}
|
||||
onClick={() => page.openStockCredit(item)}
|
||||
@ -514,7 +527,7 @@ function SellerActions({ item, page }) {
|
||||
) : null}
|
||||
{page.abilities.canStockCredit ? (
|
||||
<AdminActionIconButton
|
||||
disabled={item.status !== "active" || page.loadingAction === `coin-seller-stock-debit-${item.userId}`}
|
||||
disabled={page.loadingAction === `coin-seller-stock-debit-${item.userId}`}
|
||||
label="扣金币"
|
||||
sx={coinDebitActionSx}
|
||||
onClick={() => page.openStockDebit(item)}
|
||||
|
||||
@ -134,6 +134,7 @@ function pageFixture(patch = {}) {
|
||||
setPage: vi.fn(),
|
||||
setStockForm: vi.fn(),
|
||||
status: "",
|
||||
stockDebitForm: { coinAmount: "", reason: "", rechargeAmount: "" },
|
||||
stockForm: { coinAmount: "", reason: "", rechargeAmount: "", type: "usdt_purchase" },
|
||||
sortBy: "",
|
||||
sortDirection: "",
|
||||
|
||||
@ -148,6 +148,11 @@ export const coinSellerStockCreditSchema = z
|
||||
export const coinSellerStockDebitSchema = z.object({
|
||||
coinAmount: z.coerce.number().int().positive("请输入扣除金币"),
|
||||
commandId: z.string().trim().min(1, "请输入 Command ID").max(120, "Command ID 不能超过 120 个字符"),
|
||||
rechargeAmount: z
|
||||
.string()
|
||||
.trim()
|
||||
.regex(/^(0|[1-9]\d*)(\.\d{1,6})?$/, "请输入最多 6 位小数的 USDT 数量")
|
||||
.refine((value) => Number(value) > 0, "请输入扣除USDT数量"),
|
||||
reason: z.string().trim().min(1, "请输入扣除原因").max(512, "原因不能超过 512 个字符"),
|
||||
});
|
||||
|
||||
|
||||
@ -30,6 +30,7 @@ const ledgerTypeLabels = {
|
||||
|
||||
const bizTypeLabels = {
|
||||
coin_seller_coin_compensation: "金币补偿",
|
||||
coin_seller_stock_deduction: "USDT扣除",
|
||||
coin_seller_stock_purchase: "USDT进货",
|
||||
coin_seller_transfer: "币商转用户",
|
||||
manual_credit: "金币增加",
|
||||
@ -280,11 +281,16 @@ function SalaryTransferAmount({ entry }) {
|
||||
|
||||
function PaidUSDTAmount({ entry }) {
|
||||
const stockType = entry.stockType || entry.metadata?.stock_type;
|
||||
if (entry.bizType !== "coin_seller_stock_purchase" && stockType !== "usdt_purchase") {
|
||||
const isUSDTStock =
|
||||
entry.bizType === "coin_seller_stock_purchase" ||
|
||||
entry.bizType === "coin_seller_stock_deduction" ||
|
||||
stockType === "usdt_purchase" ||
|
||||
stockType === "usdt_deduction";
|
||||
if (!isUSDTStock) {
|
||||
return "-";
|
||||
}
|
||||
const amountMicro = Number(entry.paidAmountMicro || entry.metadata?.paid_amount_micro || 0);
|
||||
return amountMicro > 0 ? formatUSDTMicro(amountMicro) : "-";
|
||||
return amountMicro !== 0 ? formatSignedUSDTMicro(amountMicro) : "-";
|
||||
}
|
||||
|
||||
function formatUSDMinor(value) {
|
||||
@ -301,3 +307,12 @@ function formatUSDTMicro(value) {
|
||||
const trimmedFraction = String(fraction).padStart(6, "0").replace(/0+$/, "");
|
||||
return trimmedFraction ? `${whole}.${trimmedFraction}` : String(whole);
|
||||
}
|
||||
|
||||
function formatSignedUSDTMicro(value) {
|
||||
const amount = Number(value || 0);
|
||||
if (!Number.isFinite(amount) || amount === 0) {
|
||||
return "0";
|
||||
}
|
||||
const sign = amount < 0 ? "-" : "";
|
||||
return `${sign}${formatUSDTMicro(Math.abs(amount))}`;
|
||||
}
|
||||
|
||||
@ -64,6 +64,7 @@ export const API_OPERATIONS = {
|
||||
createWeeklyStarCycle: "createWeeklyStarCycle",
|
||||
creditCoinSellerStock: "creditCoinSellerStock",
|
||||
dashboardOverview: "dashboardOverview",
|
||||
debitCoinSellerStock: "debitCoinSellerStock",
|
||||
deleteAgency: "deleteAgency",
|
||||
deleteAppVersion: "deleteAppVersion",
|
||||
deleteBanner: "deleteBanner",
|
||||
@ -649,6 +650,13 @@ export const API_ENDPOINTS: Record<ApiOperationId, ApiEndpoint> = {
|
||||
permission: "overview:view",
|
||||
permissions: ["overview:view"]
|
||||
},
|
||||
debitCoinSellerStock: {
|
||||
method: "POST",
|
||||
operationId: API_OPERATIONS.debitCoinSellerStock,
|
||||
path: "/v1/admin/coin-sellers/{user_id}/stock-debits",
|
||||
permission: "coin-seller:stock-credit",
|
||||
permissions: ["coin-seller:stock-credit"]
|
||||
},
|
||||
deleteAgency: {
|
||||
method: "POST",
|
||||
operationId: API_OPERATIONS.deleteAgency,
|
||||
|
||||
30
src/shared/api/generated/schema.d.ts
vendored
30
src/shared/api/generated/schema.d.ts
vendored
@ -1044,6 +1044,22 @@ export interface paths {
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/admin/coin-sellers/{user_id}/stock-debits": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
post: operations["debitCoinSellerStock"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/admin/coin-seller-salary-rates/{region_id}": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
@ -4959,6 +4975,20 @@ export interface operations {
|
||||
200: components["responses"]["EmptyResponse"];
|
||||
};
|
||||
};
|
||||
debitCoinSellerStock: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
user_id: number;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
200: components["responses"]["EmptyResponse"];
|
||||
};
|
||||
};
|
||||
getCoinSellerSalaryRates: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
|
||||
@ -671,6 +671,7 @@ export interface CoinSellerStockDebitPayload {
|
||||
coinAmount: number;
|
||||
commandId: string;
|
||||
evidenceRef?: string;
|
||||
rechargeAmount: string;
|
||||
reason: string;
|
||||
}
|
||||
|
||||
@ -678,7 +679,12 @@ export interface CoinSellerStockDebitDto {
|
||||
availableDelta: number;
|
||||
balanceAfter: number;
|
||||
coinAmount: number;
|
||||
countsAsSellerRecharge?: boolean;
|
||||
createdAtMs?: number;
|
||||
paidAmountMicro?: number;
|
||||
paidCurrencyCode?: string;
|
||||
sellerUserId: string;
|
||||
stockType?: string;
|
||||
transactionId: string;
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user