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