币商转账
This commit is contained in:
parent
aa7ce50e4f
commit
1447de8563
@ -6,6 +6,7 @@ import {
|
|||||||
createCoinSeller,
|
createCoinSeller,
|
||||||
createRegion,
|
createRegion,
|
||||||
creditCoinSellerStock,
|
creditCoinSellerStock,
|
||||||
|
debitCoinSellerStock,
|
||||||
deleteAgency,
|
deleteAgency,
|
||||||
disableRegion,
|
disableRegion,
|
||||||
listAgencies,
|
listAgencies,
|
||||||
@ -61,6 +62,11 @@ test("host org list APIs use generated admin paths and filters", async () => {
|
|||||||
rechargeAmount: "100.000000",
|
rechargeAmount: "100.000000",
|
||||||
type: "usdt_purchase",
|
type: "usdt_purchase",
|
||||||
});
|
});
|
||||||
|
await debitCoinSellerStock("1001", {
|
||||||
|
coinAmount: 1000,
|
||||||
|
commandId: "coin-seller-stock-debit-test",
|
||||||
|
reason: "deduct bad stock",
|
||||||
|
});
|
||||||
await deleteAgency(7001, { commandId: "agency-delete-test", reason: "cleanup" });
|
await deleteAgency(7001, { commandId: "agency-delete-test", reason: "cleanup" });
|
||||||
|
|
||||||
const [bdUrl, bdInit] = vi.mocked(fetch).mock.calls[0];
|
const [bdUrl, bdInit] = vi.mocked(fetch).mock.calls[0];
|
||||||
@ -72,7 +78,8 @@ test("host org list APIs use generated admin paths and filters", async () => {
|
|||||||
const [coinSellerCreateUrl, coinSellerCreateInit] = vi.mocked(fetch).mock.calls[6];
|
const [coinSellerCreateUrl, coinSellerCreateInit] = vi.mocked(fetch).mock.calls[6];
|
||||||
const [coinSellerStatusUrl, coinSellerStatusInit] = vi.mocked(fetch).mock.calls[7];
|
const [coinSellerStatusUrl, coinSellerStatusInit] = vi.mocked(fetch).mock.calls[7];
|
||||||
const [coinSellerStockUrl, coinSellerStockInit] = vi.mocked(fetch).mock.calls[8];
|
const [coinSellerStockUrl, coinSellerStockInit] = vi.mocked(fetch).mock.calls[8];
|
||||||
const [agencyDeleteUrl, agencyDeleteInit] = vi.mocked(fetch).mock.calls[9];
|
const [coinSellerStockDebitUrl, coinSellerStockDebitInit] = vi.mocked(fetch).mock.calls[9];
|
||||||
|
const [agencyDeleteUrl, agencyDeleteInit] = vi.mocked(fetch).mock.calls[10];
|
||||||
|
|
||||||
expect(String(bdUrl)).toContain("/api/v1/admin/bds?");
|
expect(String(bdUrl)).toContain("/api/v1/admin/bds?");
|
||||||
expect(String(bdUrl)).toContain("parent_leader_user_id=21");
|
expect(String(bdUrl)).toContain("parent_leader_user_id=21");
|
||||||
@ -104,6 +111,8 @@ test("host org list APIs use generated admin paths and filters", async () => {
|
|||||||
expect(coinSellerStatusInit?.method).toBe("PATCH");
|
expect(coinSellerStatusInit?.method).toBe("PATCH");
|
||||||
expect(String(coinSellerStockUrl)).toContain("/api/v1/admin/coin-sellers/1001/stock-credits");
|
expect(String(coinSellerStockUrl)).toContain("/api/v1/admin/coin-sellers/1001/stock-credits");
|
||||||
expect(coinSellerStockInit?.method).toBe("POST");
|
expect(coinSellerStockInit?.method).toBe("POST");
|
||||||
|
expect(String(coinSellerStockDebitUrl)).toContain("/api/v1/admin/coin-sellers/1001/stock-debits");
|
||||||
|
expect(coinSellerStockDebitInit?.method).toBe("POST");
|
||||||
expect(String(agencyDeleteUrl)).toContain("/api/v1/admin/agencies/7001/delete");
|
expect(String(agencyDeleteUrl)).toContain("/api/v1/admin/agencies/7001/delete");
|
||||||
expect(agencyDeleteInit?.method).toBe("POST");
|
expect(agencyDeleteInit?.method).toBe("POST");
|
||||||
expect(JSON.parse(String(agencyDeleteInit?.body))).toMatchObject({ commandId: "agency-delete-test" });
|
expect(JSON.parse(String(agencyDeleteInit?.body))).toMatchObject({ commandId: "agency-delete-test" });
|
||||||
|
|||||||
@ -14,6 +14,8 @@ import type {
|
|||||||
CoinSellerSalaryRatesPayload,
|
CoinSellerSalaryRatesPayload,
|
||||||
CoinSellerStockCreditDto,
|
CoinSellerStockCreditDto,
|
||||||
CoinSellerStockCreditPayload,
|
CoinSellerStockCreditPayload,
|
||||||
|
CoinSellerStockDebitDto,
|
||||||
|
CoinSellerStockDebitPayload,
|
||||||
CoinSellerStatusPayload,
|
CoinSellerStatusPayload,
|
||||||
CountryDto,
|
CountryDto,
|
||||||
CountryPayload,
|
CountryPayload,
|
||||||
@ -283,6 +285,19 @@ export function creditCoinSellerStock(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function debitCoinSellerStock(
|
||||||
|
userId: EntityId,
|
||||||
|
payload: CoinSellerStockDebitPayload,
|
||||||
|
): Promise<CoinSellerStockDebitDto> {
|
||||||
|
return apiRequest<CoinSellerStockDebitDto, CoinSellerStockDebitPayload>(
|
||||||
|
`/v1/admin/coin-sellers/${encodeURIComponent(String(userId))}/stock-debits`,
|
||||||
|
{
|
||||||
|
body: payload,
|
||||||
|
method: "POST",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function getCoinSellerSalaryRates(regionId: EntityId): Promise<CoinSellerSalaryRatesDto> {
|
export function getCoinSellerSalaryRates(regionId: EntityId): Promise<CoinSellerSalaryRatesDto> {
|
||||||
const endpoint = API_ENDPOINTS.getCoinSellerSalaryRates;
|
const endpoint = API_ENDPOINTS.getCoinSellerSalaryRates;
|
||||||
return apiRequest<CoinSellerSalaryRatesDto>(
|
return apiRequest<CoinSellerSalaryRatesDto>(
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { useToast } from "@/shared/ui/ToastProvider.jsx";
|
|||||||
import {
|
import {
|
||||||
createCoinSeller,
|
createCoinSeller,
|
||||||
creditCoinSellerStock,
|
creditCoinSellerStock,
|
||||||
|
debitCoinSellerStock,
|
||||||
getCoinSellerSalaryRates,
|
getCoinSellerSalaryRates,
|
||||||
listCoinSellers,
|
listCoinSellers,
|
||||||
replaceCoinSellerSalaryRates,
|
replaceCoinSellerSalaryRates,
|
||||||
@ -15,6 +16,7 @@ import { useCoinSellerAbilities } from "@/features/host-org/permissions.js";
|
|||||||
import {
|
import {
|
||||||
coinSellerStatusSchema,
|
coinSellerStatusSchema,
|
||||||
coinSellerStockCreditSchema,
|
coinSellerStockCreditSchema,
|
||||||
|
coinSellerStockDebitSchema,
|
||||||
createCoinSellerSchema,
|
createCoinSellerSchema,
|
||||||
} from "@/features/host-org/schema";
|
} from "@/features/host-org/schema";
|
||||||
|
|
||||||
@ -35,6 +37,11 @@ const emptyStockForm = () => ({
|
|||||||
rechargeAmount: "",
|
rechargeAmount: "",
|
||||||
type: "usdt_purchase",
|
type: "usdt_purchase",
|
||||||
});
|
});
|
||||||
|
const emptyStockDebitForm = () => ({
|
||||||
|
coinAmount: "",
|
||||||
|
commandId: makeCommandId("coin-seller-stock-debit"),
|
||||||
|
reason: "",
|
||||||
|
});
|
||||||
const emptyRateTier = (index = 0) => ({
|
const emptyRateTier = (index = 0) => ({
|
||||||
coinPerUsd: "",
|
coinPerUsd: "",
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@ -57,6 +64,7 @@ export function useHostCoinSellersPage() {
|
|||||||
const [createForm, setCreateForm] = useState(emptyCreateForm);
|
const [createForm, setCreateForm] = useState(emptyCreateForm);
|
||||||
const [editForm, setEditForm] = useState(emptyEditForm);
|
const [editForm, setEditForm] = useState(emptyEditForm);
|
||||||
const [stockForm, setStockForm] = useState(emptyStockForm);
|
const [stockForm, setStockForm] = useState(emptyStockForm);
|
||||||
|
const [stockDebitForm, setStockDebitForm] = useState(emptyStockDebitForm);
|
||||||
const [rateRegionId, setRateRegionId] = useState("");
|
const [rateRegionId, setRateRegionId] = useState("");
|
||||||
const [rateTiers, setRateTiers] = useState([]);
|
const [rateTiers, setRateTiers] = useState([]);
|
||||||
const [loadingRates, setLoadingRates] = useState(false);
|
const [loadingRates, setLoadingRates] = useState(false);
|
||||||
@ -158,6 +166,15 @@ export function useHostCoinSellersPage() {
|
|||||||
setActiveAction("stock");
|
setActiveAction("stock");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openStockDebit = (seller) => {
|
||||||
|
if (!seller?.userId || seller.status !== "active" || !abilities.canStockCredit) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSelectedSeller(seller);
|
||||||
|
setStockDebitForm(emptyStockDebitForm());
|
||||||
|
setActiveAction("stock-debit");
|
||||||
|
};
|
||||||
|
|
||||||
const openSellerLedger = (seller) => {
|
const openSellerLedger = (seller) => {
|
||||||
if (!seller?.userId || !abilities.canLedger) {
|
if (!seller?.userId || !abilities.canLedger) {
|
||||||
return;
|
return;
|
||||||
@ -308,6 +325,20 @@ export function useHostCoinSellersPage() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const submitStockDebit = async (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (!selectedSeller?.userId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await runAction(`coin-seller-stock-debit-${selectedSeller.userId}`, "币商金币已扣除", async () => {
|
||||||
|
const payload = parseForm(coinSellerStockDebitSchema, stockDebitForm);
|
||||||
|
await debitCoinSellerStock(selectedSeller.userId, payload);
|
||||||
|
setStockDebitForm(emptyStockDebitForm());
|
||||||
|
closeAction();
|
||||||
|
await reload();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const submitRateSettings = async (event) => {
|
const submitRateSettings = async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (!rateRegionId) {
|
if (!rateRegionId) {
|
||||||
@ -383,6 +414,7 @@ export function useHostCoinSellersPage() {
|
|||||||
openEditSeller,
|
openEditSeller,
|
||||||
openSellerLedger,
|
openSellerLedger,
|
||||||
openStockCredit,
|
openStockCredit,
|
||||||
|
openStockDebit,
|
||||||
page,
|
page,
|
||||||
query,
|
query,
|
||||||
regionId,
|
regionId,
|
||||||
@ -400,7 +432,9 @@ export function useHostCoinSellersPage() {
|
|||||||
setEditForm,
|
setEditForm,
|
||||||
setPage,
|
setPage,
|
||||||
setStockForm,
|
setStockForm,
|
||||||
|
setStockDebitForm,
|
||||||
status,
|
status,
|
||||||
|
stockDebitForm,
|
||||||
stockForm,
|
stockForm,
|
||||||
sortBy,
|
sortBy,
|
||||||
sortDirection,
|
sortDirection,
|
||||||
@ -408,6 +442,7 @@ export function useHostCoinSellersPage() {
|
|||||||
submitCreateSeller,
|
submitCreateSeller,
|
||||||
submitEditSeller,
|
submitEditSeller,
|
||||||
submitStockCredit,
|
submitStockCredit,
|
||||||
|
submitStockDebit,
|
||||||
toggleSeller,
|
toggleSeller,
|
||||||
updateRateTier,
|
updateRateTier,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import ArrowDownwardOutlined from "@mui/icons-material/ArrowDownwardOutlined";
|
|||||||
import ArrowUpwardOutlined from "@mui/icons-material/ArrowUpwardOutlined";
|
import ArrowUpwardOutlined from "@mui/icons-material/ArrowUpwardOutlined";
|
||||||
import DeleteOutlineOutlined from "@mui/icons-material/DeleteOutlineOutlined";
|
import DeleteOutlineOutlined from "@mui/icons-material/DeleteOutlineOutlined";
|
||||||
import MonetizationOnOutlined from "@mui/icons-material/MonetizationOnOutlined";
|
import MonetizationOnOutlined from "@mui/icons-material/MonetizationOnOutlined";
|
||||||
|
import RemoveCircleOutlineOutlined from "@mui/icons-material/RemoveCircleOutlineOutlined";
|
||||||
import ReceiptLongOutlined from "@mui/icons-material/ReceiptLongOutlined";
|
import ReceiptLongOutlined from "@mui/icons-material/ReceiptLongOutlined";
|
||||||
import SettingsOutlined from "@mui/icons-material/SettingsOutlined";
|
import SettingsOutlined from "@mui/icons-material/SettingsOutlined";
|
||||||
import SwapVertOutlined from "@mui/icons-material/SwapVertOutlined";
|
import SwapVertOutlined from "@mui/icons-material/SwapVertOutlined";
|
||||||
@ -79,6 +80,7 @@ export function HostCoinSellersPage() {
|
|||||||
const page = useHostCoinSellersPage();
|
const page = useHostCoinSellersPage();
|
||||||
const items = page.data.items || [];
|
const items = page.data.items || [];
|
||||||
const total = page.data.total || 0;
|
const total = page.data.total || 0;
|
||||||
|
const stockDebitForm = page.stockDebitForm || { coinAmount: "", reason: "" };
|
||||||
const createDisabled = !page.abilities.canCreate;
|
const createDisabled = !page.abilities.canCreate;
|
||||||
const updateDisabled = !page.abilities.canUpdate;
|
const updateDisabled = !page.abilities.canUpdate;
|
||||||
const columns = [
|
const columns = [
|
||||||
@ -95,7 +97,7 @@ export function HostCoinSellersPage() {
|
|||||||
key: "actions",
|
key: "actions",
|
||||||
label: "操作",
|
label: "操作",
|
||||||
render: (item) => <SellerActions item={item} page={page} />,
|
render: (item) => <SellerActions item={item} page={page} />,
|
||||||
width: "minmax(90px, 0.5fr)",
|
width: "minmax(130px, 0.65fr)",
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
]
|
]
|
||||||
@ -303,6 +305,52 @@ export function HostCoinSellersPage() {
|
|||||||
</AdminFormSection>
|
</AdminFormSection>
|
||||||
</HostOrgActionModal>
|
</HostOrgActionModal>
|
||||||
|
|
||||||
|
<HostOrgActionModal
|
||||||
|
disabled={!page.abilities.canStockCredit || page.selectedSeller?.status !== "active"}
|
||||||
|
loading={String(page.loadingAction).startsWith("coin-seller-stock-debit")}
|
||||||
|
open={page.activeAction === "stock-debit"}
|
||||||
|
sectioned={false}
|
||||||
|
size="standard"
|
||||||
|
submitLabel="确认扣除"
|
||||||
|
onClose={page.closeAction}
|
||||||
|
onSubmit={page.submitStockDebit}
|
||||||
|
title="币商金币扣除"
|
||||||
|
>
|
||||||
|
<AdminFormSection title="基础信息">
|
||||||
|
<AdminFormFieldGrid columns="repeat(2, minmax(0, 1fr))">
|
||||||
|
<AdminFormReadOnlyField label="币商用户 ID" value={page.selectedSeller?.userId || ""} />
|
||||||
|
<AdminFormReadOnlyField
|
||||||
|
label="当前库存余额"
|
||||||
|
value={formatNumber(page.selectedSeller?.merchantBalance)}
|
||||||
|
/>
|
||||||
|
</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 })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</AdminFormSection>
|
||||||
|
<AdminFormSection title="备注信息">
|
||||||
|
<TextField
|
||||||
|
disabled={!page.abilities.canStockCredit}
|
||||||
|
label="原因"
|
||||||
|
multiline
|
||||||
|
required
|
||||||
|
minRows={2}
|
||||||
|
value={stockDebitForm.reason}
|
||||||
|
onChange={(event) => page.setStockDebitForm?.({ ...stockDebitForm, reason: event.target.value })}
|
||||||
|
/>
|
||||||
|
</AdminFormSection>
|
||||||
|
</HostOrgActionModal>
|
||||||
|
|
||||||
<SideDrawer
|
<SideDrawer
|
||||||
className={styles.ledgerDrawer}
|
className={styles.ledgerDrawer}
|
||||||
contentClassName={styles.ledgerDrawerBody}
|
contentClassName={styles.ledgerDrawerBody}
|
||||||
@ -464,6 +512,16 @@ function SellerActions({ item, page }) {
|
|||||||
<MonetizationOnOutlined fontSize="small" />
|
<MonetizationOnOutlined fontSize="small" />
|
||||||
</AdminActionIconButton>
|
</AdminActionIconButton>
|
||||||
) : null}
|
) : null}
|
||||||
|
{page.abilities.canStockCredit ? (
|
||||||
|
<AdminActionIconButton
|
||||||
|
disabled={item.status !== "active" || page.loadingAction === `coin-seller-stock-debit-${item.userId}`}
|
||||||
|
label="扣金币"
|
||||||
|
sx={coinDebitActionSx}
|
||||||
|
onClick={() => page.openStockDebit(item)}
|
||||||
|
>
|
||||||
|
<RemoveCircleOutlineOutlined fontSize="small" />
|
||||||
|
</AdminActionIconButton>
|
||||||
|
) : null}
|
||||||
</AdminRowActions>
|
</AdminRowActions>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -555,3 +613,17 @@ const coinCreditActionSx = {
|
|||||||
color: "#f59e0b",
|
color: "#f59e0b",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const coinDebitActionSx = {
|
||||||
|
borderColor: "rgba(220, 38, 38, 0.34)",
|
||||||
|
backgroundColor: "rgba(254, 226, 226, 0.65)",
|
||||||
|
color: "#b91c1c",
|
||||||
|
"&:hover": {
|
||||||
|
borderColor: "rgba(220, 38, 38, 0.58)",
|
||||||
|
backgroundColor: "rgba(254, 202, 202, 0.75)",
|
||||||
|
color: "#991b1b",
|
||||||
|
},
|
||||||
|
"& .MuiSvgIcon-root": {
|
||||||
|
color: "#dc2626",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|||||||
@ -130,6 +130,12 @@ 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 个字符"),
|
||||||
|
reason: z.string().trim().min(1, "请输入扣除原因").max(512, "原因不能超过 512 个字符"),
|
||||||
|
});
|
||||||
|
|
||||||
export const createAgencySchema = commandBaseSchema.extend({
|
export const createAgencySchema = commandBaseSchema.extend({
|
||||||
joinEnabled: z.boolean().default(true),
|
joinEnabled: z.boolean().default(true),
|
||||||
name: z.string().trim().min(1, "请输入 Agency 名称").max(80, "Agency 名称不能超过 80 个字符"),
|
name: z.string().trim().min(1, "请输入 Agency 名称").max(80, "Agency 名称不能超过 80 个字符"),
|
||||||
@ -181,6 +187,7 @@ export type BDStatusForm = z.infer<typeof bdStatusSchema>;
|
|||||||
export type CreateCoinSellerForm = z.infer<typeof createCoinSellerSchema>;
|
export type CreateCoinSellerForm = z.infer<typeof createCoinSellerSchema>;
|
||||||
export type CoinSellerStatusForm = z.infer<typeof coinSellerStatusSchema>;
|
export type CoinSellerStatusForm = z.infer<typeof coinSellerStatusSchema>;
|
||||||
export type CoinSellerStockCreditForm = z.infer<typeof coinSellerStockCreditSchema>;
|
export type CoinSellerStockCreditForm = z.infer<typeof coinSellerStockCreditSchema>;
|
||||||
|
export type CoinSellerStockDebitForm = z.infer<typeof coinSellerStockDebitSchema>;
|
||||||
export type CreateAgencyForm = z.infer<typeof createAgencySchema>;
|
export type CreateAgencyForm = z.infer<typeof createAgencySchema>;
|
||||||
export type AgencyCloseForm = z.infer<typeof agencyCloseSchema>;
|
export type AgencyCloseForm = z.infer<typeof agencyCloseSchema>;
|
||||||
export type AgencyJoinEnabledForm = z.infer<typeof agencyJoinEnabledSchema>;
|
export type AgencyJoinEnabledForm = z.infer<typeof agencyJoinEnabledSchema>;
|
||||||
|
|||||||
@ -30,8 +30,9 @@ const ledgerTypeLabels = {
|
|||||||
|
|
||||||
const bizTypeLabels = {
|
const bizTypeLabels = {
|
||||||
coin_seller_coin_compensation: "金币补偿",
|
coin_seller_coin_compensation: "金币补偿",
|
||||||
coin_seller_stock_purchase: "币商进货",
|
coin_seller_stock_purchase: "USDT进货",
|
||||||
coin_seller_transfer: "币商转用户",
|
coin_seller_transfer: "币商转用户",
|
||||||
|
manual_credit: "金币增加",
|
||||||
salary_transfer_to_coin_seller: "工资转币商",
|
salary_transfer_to_coin_seller: "工资转币商",
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -50,10 +51,22 @@ const baseColumns = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "amount",
|
key: "amount",
|
||||||
label: "转账金额",
|
label: "金币变动",
|
||||||
render: (entry) => <AmountValue entry={entry} />,
|
render: (entry) => <AmountValue entry={entry} />,
|
||||||
width: "minmax(130px, 0.65fr)",
|
width: "minmax(130px, 0.65fr)",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "transferUsdMinor",
|
||||||
|
label: "转账金额",
|
||||||
|
render: (entry) => <SalaryTransferAmount entry={entry} />,
|
||||||
|
width: "minmax(130px, 0.65fr)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "paidAmountMicro",
|
||||||
|
label: "USDT数量",
|
||||||
|
render: (entry) => <PaidUSDTAmount entry={entry} />,
|
||||||
|
width: "minmax(130px, 0.65fr)",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: "receiver",
|
key: "receiver",
|
||||||
label: "收款人信息",
|
label: "收款人信息",
|
||||||
@ -197,7 +210,7 @@ export function CoinSellerLedgerTable({ lockedSeller = null, sellerUserId = "" }
|
|||||||
columns={tableColumns}
|
columns={tableColumns}
|
||||||
emptyLabel="当前无币商流水"
|
emptyLabel="当前无币商流水"
|
||||||
items={items}
|
items={items}
|
||||||
minWidth={fixedSellerUserId ? "890px" : "1160px"}
|
minWidth={fixedSellerUserId ? "1130px" : "1380px"}
|
||||||
pagination={
|
pagination={
|
||||||
total > 0
|
total > 0
|
||||||
? {
|
? {
|
||||||
@ -247,11 +260,44 @@ function AmountValue({ entry }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ledgerTypeLabel(entry) {
|
function ledgerTypeLabel(entry) {
|
||||||
return (
|
if (entry.bizType === "manual_credit" && (entry.direction === "expense" || Number(entry.availableDelta || 0) < 0)) {
|
||||||
ledgerTypeLabels[entry.ledgerType] || bizTypeLabels[entry.bizType] || entry.ledgerType || entry.bizType || "-"
|
return "金币扣除";
|
||||||
);
|
}
|
||||||
|
return bizTypeLabels[entry.bizType] || ledgerTypeLabels[entry.ledgerType] || entry.ledgerType || entry.bizType || "-";
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatNumber(value) {
|
function formatNumber(value) {
|
||||||
return Number(value || 0).toLocaleString("zh-CN");
|
return Number(value || 0).toLocaleString("zh-CN");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function SalaryTransferAmount({ entry }) {
|
||||||
|
if (entry.bizType !== "salary_transfer_to_coin_seller" && entry.ledgerType !== "salary_transfer_received") {
|
||||||
|
return "-";
|
||||||
|
}
|
||||||
|
const amountMinor = Number(entry.transferUsdMinor || entry.metadata?.salary_usd_minor || 0);
|
||||||
|
return amountMinor > 0 ? `$${formatUSDMinor(amountMinor)}` : "-";
|
||||||
|
}
|
||||||
|
|
||||||
|
function PaidUSDTAmount({ entry }) {
|
||||||
|
const stockType = entry.stockType || entry.metadata?.stock_type;
|
||||||
|
if (entry.bizType !== "coin_seller_stock_purchase" && stockType !== "usdt_purchase") {
|
||||||
|
return "-";
|
||||||
|
}
|
||||||
|
const amountMicro = Number(entry.paidAmountMicro || entry.metadata?.paid_amount_micro || 0);
|
||||||
|
return amountMicro > 0 ? formatUSDTMicro(amountMicro) : "-";
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatUSDMinor(value) {
|
||||||
|
return (Number(value || 0) / 100).toFixed(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatUSDTMicro(value) {
|
||||||
|
const amount = Number(value || 0);
|
||||||
|
if (!Number.isFinite(amount) || amount <= 0) {
|
||||||
|
return "0";
|
||||||
|
}
|
||||||
|
const whole = Math.trunc(amount / 1_000_000);
|
||||||
|
const fraction = Math.trunc(Math.abs(amount % 1_000_000));
|
||||||
|
const trimmedFraction = String(fraction).padStart(6, "0").replace(/0+$/, "");
|
||||||
|
return trimmedFraction ? `${whole}.${trimmedFraction}` : String(whole);
|
||||||
|
}
|
||||||
|
|||||||
@ -228,10 +228,15 @@ export interface CoinSellerLedgerDto {
|
|||||||
metadata?: Record<string, unknown>;
|
metadata?: Record<string, unknown>;
|
||||||
operator?: CoinAdjustmentOperatorDto;
|
operator?: CoinAdjustmentOperatorDto;
|
||||||
operatorUserId?: string;
|
operatorUserId?: string;
|
||||||
|
paidAmountMicro?: number;
|
||||||
|
paidCurrencyCode?: string;
|
||||||
receiver: CoinLedgerUserDto;
|
receiver: CoinLedgerUserDto;
|
||||||
|
reason?: string;
|
||||||
seller: CoinLedgerUserDto;
|
seller: CoinLedgerUserDto;
|
||||||
sellerBalanceAfter: number;
|
sellerBalanceAfter: number;
|
||||||
|
stockType?: string;
|
||||||
transactionId: string;
|
transactionId: string;
|
||||||
|
transferUsdMinor?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CoinAdjustmentOperatorDto {
|
export interface CoinAdjustmentOperatorDto {
|
||||||
@ -601,6 +606,21 @@ export interface CoinSellerStockCreditDto {
|
|||||||
transactionId: string;
|
transactionId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CoinSellerStockDebitPayload {
|
||||||
|
coinAmount: number;
|
||||||
|
commandId: string;
|
||||||
|
evidenceRef?: string;
|
||||||
|
reason: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CoinSellerStockDebitDto {
|
||||||
|
availableDelta: number;
|
||||||
|
balanceAfter: number;
|
||||||
|
coinAmount: number;
|
||||||
|
sellerUserId: string;
|
||||||
|
transactionId: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CoinSellerSalaryRateTierDto {
|
export interface CoinSellerSalaryRateTierDto {
|
||||||
coinPerUsd: number;
|
coinPerUsd: number;
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user