From 79e3513056ffee0a07f77c6330be8d5986cf71ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E8=87=AA=E8=BE=BE?= <980324341@qq.com> Date: Mon, 31 Aug 2026 19:48:49 +0800 Subject: [PATCH] =?UTF-8?q?task-21:=20=E6=8B=86=E5=88=86=20withdraw=20/=20?= =?UTF-8?q?patrol-delete=20=E6=A8=A1=E5=9D=97=20API=20=E8=87=B3=20types/mo?= =?UTF-8?q?dules/withdraw.ts=EF=BC=8Cjava-modules.ts=20re-export?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend-vue/src/shared/api/java-modules.ts | 434 +-------------- .../src/shared/api/types/modules/withdraw.ts | 504 ++++++++++++++++++ frontend-vue/tests/modules-withdraw.test.ts | 260 +++++++++ 3 files changed, 765 insertions(+), 433 deletions(-) create mode 100644 frontend-vue/src/shared/api/types/modules/withdraw.ts create mode 100644 frontend-vue/tests/modules-withdraw.test.ts diff --git a/frontend-vue/src/shared/api/java-modules.ts b/frontend-vue/src/shared/api/java-modules.ts index 152f777e..dc892422 100644 --- a/frontend-vue/src/shared/api/java-modules.ts +++ b/frontend-vue/src/shared/api/java-modules.ts @@ -20,6 +20,7 @@ export * from "./types/modules/delete-brand.ts"; export * from "./types/modules/price-track.ts"; export * from "./types/modules/shop-match.ts"; export * from "./types/modules/query-asin.ts"; +export * from "./types/modules/withdraw.ts"; import type { ProductRiskCandidateVo, ProductRiskDashboardVo, @@ -164,439 +165,6 @@ export async function uploadTempFileToJava( } -export type PatrolDeleteCandidateVo = ProductRiskCandidateVo; -export type PatrolDeleteDashboardVo = ProductRiskDashboardVo; -export type PatrolDeleteShopQueueItem = ProductRiskShopQueueItem; - -export interface PatrolDeleteConditionVo { - id: number; - conditionText: string; - createdAt?: string; -} - -export interface PatrolDeleteCountryMetricRow { - status: string; - quantity: string; - deleteQuantity: string; - processStatus: string; -} - -export interface PatrolDeleteCountrySection { - country: string; - rows: PatrolDeleteCountryMetricRow[]; -} - -export interface PatrolDeleteCartRatio { - country: string; - ratio: string; -} - -export interface PatrolDeleteTaskItem { - shopName?: string; - matched?: boolean; - shopId?: string; - platform?: string; - companyName?: string; - matchStatus?: string; - matchMessage?: string; - countrySections: PatrolDeleteCountrySection[]; - cartRatios: PatrolDeleteCartRatio[]; -} - -export interface PatrolDeleteHistoryItem { - resultId?: number; - taskId?: number; - shopName?: string; - shopId?: string; - platform?: string; - companyName?: string; - matched?: boolean; - matchStatus?: string; - matchMessage?: string; - taskStatus?: string; - success?: boolean; - error?: string; - createdAt?: string; - finishedAt?: string; - outputFilename?: string; - downloadUrl?: string; - fileJobId?: number; - fileStatus?: string; - fileError?: string; - fileReady?: boolean; - countrySections: PatrolDeleteCountrySection[]; - cartRatios: PatrolDeleteCartRatio[]; -} - -export interface PatrolDeleteHistoryVo { - items: PatrolDeleteHistoryItem[]; -} - -export interface PatrolDeleteTaskBatchVo { - items: PatrolDeleteHistoryItem[]; - missingTaskIds: number[]; -} - -export interface PatrolDeleteCreateTaskVo { - taskId: number; - items: PatrolDeleteHistoryItem[]; -} - -export function listPatrolDeleteCandidates() { - return unwrapJavaResponse( - get>( - `${JAVA_API_PREFIX}/patrol-delete/candidates`, - { - params: { user_id: getCurrentUserId() }, - }, - ), - ); -} - -export function addPatrolDeleteCandidate(shopName: string) { - return unwrapJavaResponse( - post< - JavaApiResponse, - { user_id: number; shop_name: string } - >(`${JAVA_API_PREFIX}/patrol-delete/candidates`, { - user_id: getCurrentUserId(), - shop_name: shopName, - }), - ); -} - -export function deletePatrolDeleteCandidate(id: number) { - return unwrapJavaResponse( - del>( - `${JAVA_API_PREFIX}/patrol-delete/candidates/${id}`, - { - params: { user_id: getCurrentUserId() }, - }, - ), - ); -} - -export function listPatrolDeleteConditions() { - return unwrapJavaResponse( - get>( - `${JAVA_API_PREFIX}/patrol-delete/conditions`, - { - params: { user_id: getCurrentUserId() }, - }, - ), - ); -} - -export function addPatrolDeleteCondition(conditionText: string) { - return unwrapJavaResponse( - post< - JavaApiResponse, - { user_id: number; condition_text: string } - >(`${JAVA_API_PREFIX}/patrol-delete/conditions`, { - user_id: getCurrentUserId(), - condition_text: conditionText, - }), - ); -} - -export function deletePatrolDeleteCondition(id: number) { - return unwrapJavaResponse( - del>( - `${JAVA_API_PREFIX}/patrol-delete/conditions/${id}`, - { - params: { user_id: getCurrentUserId() }, - }, - ), - ); -} - -export function matchPatrolDeleteShops(shopNames: string[]) { - return unwrapJavaResponse( - post< - JavaApiResponse, - { user_id: number; shop_names: string[] } - >(`${JAVA_API_PREFIX}/patrol-delete/match-shops`, { - user_id: getCurrentUserId(), - shop_names: shopNames, - }), - ); -} - -export function getPatrolDeleteDashboard() { - return unwrapJavaResponse( - get>( - `${JAVA_API_PREFIX}/patrol-delete/dashboard`, - { - params: { user_id: getCurrentUserId() }, - }, - ), - ); -} - -export function getPatrolDeleteHistory() { - return unwrapJavaResponse( - get>( - `${JAVA_API_PREFIX}/patrol-delete/history`, - { - params: { user_id: getCurrentUserId() }, - }, - ), - ); -} - -export function getPatrolDeleteTaskProgressBatch(taskIds: number[]) { - return postTaskProgressBatch( - `${JAVA_API_PREFIX}/patrol-delete/tasks/progress/batch`, - taskIds, - ); -} - -export function createPatrolDeleteTask(items: PatrolDeleteTaskItem[]) { - return unwrapJavaResponse( - post< - JavaApiResponse, - { user_id: number; items: PatrolDeleteTaskItem[] } - >(`${JAVA_API_PREFIX}/patrol-delete/tasks`, { - user_id: getCurrentUserId(), - items, - }), - ); -} - -export function submitPatrolDeleteTaskResult( - taskId: number, - payload: { - shops: Array<{ - shopName: string; - error?: string; - countrySections: PatrolDeleteCountrySection[]; - cartRatios: PatrolDeleteCartRatio[]; - shopDone?: boolean; - submissionId?: string; - chunkIndex?: number; - chunkTotal?: number; - }>; - }, -) { - return unwrapJavaResponse( - post< - JavaApiResponse, - { - shops: Array<{ - shopName: string; - error?: string; - countrySections: PatrolDeleteCountrySection[]; - cartRatios: PatrolDeleteCartRatio[]; - shopDone?: boolean; - submissionId?: string; - chunkIndex?: number; - chunkTotal?: number; - }>; - } - >(`${JAVA_API_PREFIX}/patrol-delete/tasks/${taskId}/result`, payload), - ); -} - -export function getPatrolDeleteResultDownloadUrl(resultId: number) { - return getJavaDownloadUrl(`/patrol-delete/results/${resultId}/download`); -} - -export function deletePatrolDeleteTask(taskId: number) { - return unwrapJavaResponse( - del>( - `${JAVA_API_PREFIX}/patrol-delete/tasks/${taskId}`, - { - params: { user_id: getCurrentUserId() }, - }, - ), - ); -} - -export function deletePatrolDeleteHistory(resultId: number) { - return unwrapJavaResponse( - del>( - `${JAVA_API_PREFIX}/patrol-delete/history/${resultId}`, - { - params: { user_id: getCurrentUserId() }, - }, - ), - ); -} - -// ========== 取款 ========== - -export type WithdrawCandidateVo = QueryAsinCandidateVo; -export type WithdrawDashboardVo = QueryAsinDashboardVo; -export type WithdrawShopQueueItem = ProductRiskShopQueueItem; - -export type WithdrawStatusCode = - | "ZERO_AVAILABLE" - | "SUCCESS" - | "BALANCE_FORBIDDEN" - | "NEGATIVE_WITHDRAW_BLANK"; - -export interface WithdrawRow { - country?: string; - shopAmount?: number | string | null; - withdrawAmount?: number | string | null; - status?: WithdrawStatusCode | string; -} - -export interface WithdrawTaskItem { - shopName: string; - matched: boolean; - shopId?: string; - platform?: string; - companyName?: string; - matchStatus?: string; - matchMessage?: string; -} - -export interface WithdrawHistoryItem extends WithdrawTaskItem { - resultId?: number; - taskId?: number; - taskStatus?: string; - reservedAmount?: number | string | null; - success?: boolean; - error?: string; - outputFilename?: string; - downloadUrl?: string; - fileJobId?: number; - fileStatus?: string; - fileError?: string; - fileReady?: boolean; - createdAt?: string; - finishedAt?: string; - rows?: WithdrawRow[]; - shops?: WithdrawHistoryItem[]; -} - -export interface WithdrawHistoryVo { - items: WithdrawHistoryItem[]; -} - -export interface WithdrawTaskBatchVo { - items: WithdrawHistoryItem[]; - missingTaskIds: number[]; -} - -export interface WithdrawCreateTaskVo { - taskId: number; - items: WithdrawHistoryItem[]; -} - -export function listWithdrawCandidates() { - return unwrapJavaResponse( - get>(`${JAVA_API_PREFIX}/withdraw/candidates`, { - params: { user_id: getCurrentUserId() }, - }), - ); -} - -export function addWithdrawCandidate(shopName: string) { - return unwrapJavaResponse( - post, { user_id: number; shop_name: string }>( - `${JAVA_API_PREFIX}/withdraw/candidates`, - { user_id: getCurrentUserId(), shop_name: shopName }, - ), - ); -} - -export function deleteWithdrawCandidate(id: number) { - return unwrapJavaResponse( - del>(`${JAVA_API_PREFIX}/withdraw/candidates/${id}`, { - params: { user_id: getCurrentUserId() }, - }), - ); -} - -export function clearWithdrawCandidates(shopNames: string[]) { - return unwrapJavaResponse( - post, { user_id: number; shop_names: string[] }>( - `${JAVA_API_PREFIX}/withdraw/candidates/clear`, - { user_id: getCurrentUserId(), shop_names: shopNames }, - ), - ); -} - -export function matchWithdrawShops(shopNames: string[]) { - return unwrapJavaResponse( - post, { user_id: number; shop_names: string[] }>( - `${JAVA_API_PREFIX}/withdraw/match-shops`, - { user_id: getCurrentUserId(), shop_names: shopNames }, - ), - ); -} - -export function getWithdrawDashboard() { - return unwrapJavaResponse( - get>(`${JAVA_API_PREFIX}/withdraw/dashboard`, { - params: { user_id: getCurrentUserId() }, - }), - ); -} - -export function getWithdrawHistory() { - return unwrapJavaResponse( - get>(`${JAVA_API_PREFIX}/withdraw/history`, { - params: { user_id: getCurrentUserId() }, - }), - ); -} - -export function getWithdrawTaskProgressBatch(taskIds: number[]) { - return postTaskProgressBatch( - `${JAVA_API_PREFIX}/withdraw/tasks/progress/batch`, - taskIds, - ); -} - -export function createWithdrawTask(items: WithdrawTaskItem[], reservedAmount: number | string | null) { - return unwrapJavaResponse( - post, { user_id: number; reserved_amount: number | string | null; items: WithdrawTaskItem[] }>( - `${JAVA_API_PREFIX}/withdraw/tasks`, - { user_id: getCurrentUserId(), reserved_amount: reservedAmount, items }, - ), - ); -} - -export function submitWithdrawTaskResult( - taskId: number, - payload: { - shops: Array<{ - shopName: string; - error?: string; - rows?: WithdrawRow[]; - shopDone?: boolean; - submissionId?: string; - }>; - }, -) { - return unwrapJavaResponse( - post, typeof payload>(`${JAVA_API_PREFIX}/withdraw/tasks/${taskId}/result`, payload), - ); -} - -export function getWithdrawResultDownloadUrl(resultId: number) { - return getJavaDownloadUrl(`/withdraw/results/${resultId}/download`); -} - -export function deleteWithdrawTask(taskId: number) { - return unwrapJavaResponse( - del>(`${JAVA_API_PREFIX}/withdraw/tasks/${taskId}`, { - params: { user_id: getCurrentUserId() }, - }), - ); -} - -export function deleteWithdrawHistory(resultId: number) { - return unwrapJavaResponse( - del>(`${JAVA_API_PREFIX}/withdraw/history/${resultId}`, { - params: { user_id: getCurrentUserId() }, - }), - ); -} - // ========== 视频复刻 / 图生视频 ========== export interface ImageVideoDouyinCopyVo { diff --git a/frontend-vue/src/shared/api/types/modules/withdraw.ts b/frontend-vue/src/shared/api/types/modules/withdraw.ts new file mode 100644 index 00000000..7f108c7f --- /dev/null +++ b/frontend-vue/src/shared/api/types/modules/withdraw.ts @@ -0,0 +1,504 @@ +import { get, post, del, type JavaApiResponse, unwrapJavaResponse } from '../../http.ts' +import { buildJavaUrl, JAVA_API_PREFIX } from '../../url.ts' +import { getCurrentUserId } from '../../user.ts' +import { getJavaDownloadUrl } from '../../download-url.ts' +import { getTaskProgressCacheTtlMs, getTaskProgressTimeoutMs } from '../../../task-progress-config.ts' +import { createTaskProgressRequestCache } from '../../../task-progress-request-cache.ts' +import type { + ProductRiskCandidateVo, + ProductRiskDashboardVo, + ProductRiskMatchShopsVo, + ProductRiskShopQueueItem, +} from './shop-match.ts' +import type { + QueryAsinCandidateVo, + QueryAsinDashboardVo, +} from './query-asin.ts' +import { API_ENDPOINTS } from '../../endpoints.ts' + +export type WithdrawCandidateVo = QueryAsinCandidateVo; +export type WithdrawDashboardVo = QueryAsinDashboardVo; +export type WithdrawShopQueueItem = ProductRiskShopQueueItem; + +export type WithdrawStatusCode = + | "ZERO_AVAILABLE" + | "SUCCESS" + | "BALANCE_FORBIDDEN" + | "NEGATIVE_WITHDRAW_BLANK"; + +export interface WithdrawRow { + country?: string; + shopAmount?: number | string | null; + withdrawAmount?: number | string | null; + status?: WithdrawStatusCode | string; +} + +export interface WithdrawTaskItem { + shopName: string; + matched: boolean; + shopId?: string; + platform?: string; + companyName?: string; + matchStatus?: string; + matchMessage?: string; +} + +export interface WithdrawHistoryItem extends WithdrawTaskItem { + resultId?: number; + taskId?: number; + taskStatus?: string; + reservedAmount?: number | string | null; + success?: boolean; + error?: string; + outputFilename?: string; + downloadUrl?: string; + fileJobId?: number; + fileStatus?: string; + fileError?: string; + fileReady?: boolean; + createdAt?: string; + finishedAt?: string; + rows?: WithdrawRow[]; + shops?: WithdrawHistoryItem[]; +} + +export interface WithdrawHistoryVo { + items: WithdrawHistoryItem[]; +} + +export interface WithdrawTaskBatchVo { + items: WithdrawHistoryItem[]; + missingTaskIds: number[]; +} + +export interface WithdrawCreateTaskVo { + taskId: number; + items: WithdrawHistoryItem[]; +} + +export type PatrolDeleteCandidateVo = ProductRiskCandidateVo; +export type PatrolDeleteDashboardVo = ProductRiskDashboardVo; +export type PatrolDeleteShopQueueItem = ProductRiskShopQueueItem; + +export interface PatrolDeleteConditionVo { + id: number; + conditionText: string; + createdAt?: string; +} + +export interface PatrolDeleteCountryMetricRow { + status: string; + quantity: string; + deleteQuantity: string; + processStatus: string; +} + +export interface PatrolDeleteCountrySection { + country: string; + rows: PatrolDeleteCountryMetricRow[]; +} + +export interface PatrolDeleteCartRatio { + country: string; + ratio: string; +} + +export interface PatrolDeleteTaskItem { + shopName?: string; + matched?: boolean; + shopId?: string; + platform?: string; + companyName?: string; + matchStatus?: string; + matchMessage?: string; + countrySections: PatrolDeleteCountrySection[]; + cartRatios: PatrolDeleteCartRatio[]; +} + +export interface PatrolDeleteHistoryItem { + resultId?: number; + taskId?: number; + shopName?: string; + shopId?: string; + platform?: string; + companyName?: string; + matched?: boolean; + matchStatus?: string; + matchMessage?: string; + taskStatus?: string; + success?: boolean; + error?: string; + createdAt?: string; + finishedAt?: string; + outputFilename?: string; + downloadUrl?: string; + fileJobId?: number; + fileStatus?: string; + fileError?: string; + fileReady?: boolean; + countrySections: PatrolDeleteCountrySection[]; + cartRatios: PatrolDeleteCartRatio[]; +} + +export interface PatrolDeleteHistoryVo { + items: PatrolDeleteHistoryItem[]; +} + +export interface PatrolDeleteTaskBatchVo { + items: PatrolDeleteHistoryItem[]; + missingTaskIds: number[]; +} + +export interface PatrolDeleteCreateTaskVo { + taskId: number; + items: PatrolDeleteHistoryItem[]; +} + +export interface PatrolDeleteSubmitShopPayload { + shopName: string; + error?: string; + countrySections: PatrolDeleteCountrySection[]; + cartRatios: PatrolDeleteCartRatio[]; + shopDone?: boolean; + submissionId?: string; + chunkIndex?: number; + chunkTotal?: number; +} + +const taskProgressResponseCache = createTaskProgressRequestCache({ + ttlMs: () => getTaskProgressCacheTtlMs(), + maxEntries: 100, + maxInflight: 16, +}); + +function normalizeTaskIds(taskIds: number[]) { + return Array.from( + new Set( + (taskIds || []) + .map((taskId) => Number(taskId)) + .filter((taskId) => Number.isFinite(taskId) && taskId > 0), + ), + ).sort((left, right) => left - right); +} + +function buildTaskProgressRequestKey(path: string, taskIds: number[]) { + return `${path}::${taskIds.join(",")}`; +} + +async function postTaskProgressBatch( + path: string, + taskIds: number[], +) { + const normalizedTaskIds = normalizeTaskIds(taskIds); + if (!normalizedTaskIds.length) { + return { items: [], missingTaskIds: [] } as T; + } + + const cacheKey = buildTaskProgressRequestKey(path, normalizedTaskIds); + const cached = taskProgressResponseCache.get(cacheKey); + if (cached !== undefined) { + return cached as T; + } + + const inflight = taskProgressResponseCache.getInflight(cacheKey); + if (inflight) { + return (await inflight) as T; + } + + const requestPromise = unwrapJavaResponse( + post, { taskIds: number[] }>(path, { taskIds: normalizedTaskIds }, { + timeout: getTaskProgressTimeoutMs(), + }), + ) + .then((data) => { + taskProgressResponseCache.set(cacheKey, data); + return data; + }) + .finally(() => { + taskProgressResponseCache.endInflight(cacheKey); + }); + + taskProgressResponseCache.startInflight(cacheKey, requestPromise); + return requestPromise; +} + +export function listWithdrawCandidates() { + return unwrapJavaResponse( + get>(`${JAVA_API_PREFIX}/withdraw/candidates`, { + params: { user_id: getCurrentUserId() }, + }), + ); +} + +export function addWithdrawCandidate(shopName: string) { + return unwrapJavaResponse( + post, { user_id: number; shop_name: string }>( + `${JAVA_API_PREFIX}/withdraw/candidates`, + { user_id: getCurrentUserId(), shop_name: shopName }, + ), + ); +} + +export function deleteWithdrawCandidate(id: number) { + return unwrapJavaResponse( + del>(`${JAVA_API_PREFIX}/withdraw/candidates/${id}`, { + params: { user_id: getCurrentUserId() }, + }), + ); +} + +export function clearWithdrawCandidates(shopNames: string[]) { + return unwrapJavaResponse( + post, { user_id: number; shop_names: string[] }>( + `${JAVA_API_PREFIX}/withdraw/candidates/clear`, + { user_id: getCurrentUserId(), shop_names: shopNames }, + ), + ); +} + +export function matchWithdrawShops(shopNames: string[]) { + return unwrapJavaResponse( + post, { user_id: number; shop_names: string[] }>( + `${JAVA_API_PREFIX}/withdraw/match-shops`, + { user_id: getCurrentUserId(), shop_names: shopNames }, + ), + ); +} + +export function getWithdrawDashboard() { + return unwrapJavaResponse( + get>(`${JAVA_API_PREFIX}/withdraw/dashboard`, { + params: { user_id: getCurrentUserId() }, + }), + ); +} + +export function getWithdrawHistory() { + return unwrapJavaResponse( + get>(`${JAVA_API_PREFIX}/withdraw/history`, { + params: { user_id: getCurrentUserId() }, + }), + ); +} + +export function getWithdrawTaskProgressBatch(taskIds: number[]) { + return postTaskProgressBatch( + `${JAVA_API_PREFIX}/withdraw/tasks/progress/batch`, + taskIds, + ); +} + +export function createWithdrawTask(items: WithdrawTaskItem[], reservedAmount: number | string | null) { + return unwrapJavaResponse( + post< + JavaApiResponse, + { user_id: number; reserved_amount: number | string | null; items: WithdrawTaskItem[] } + >(`${JAVA_API_PREFIX}/withdraw/tasks`, { + user_id: getCurrentUserId(), + reserved_amount: reservedAmount, + items, + }), + ); +} + +export function submitWithdrawTaskResult( + taskId: number, + payload: { + shops: Array<{ + shopName: string; + error?: string; + rows?: WithdrawRow[]; + shopDone?: boolean; + submissionId?: string; + }>; + }, +) { + return unwrapJavaResponse( + post, typeof payload>(`${JAVA_API_PREFIX}/withdraw/tasks/${taskId}/result`, payload), + ); +} + +export function getWithdrawResultDownloadUrl(resultId: number) { + return getJavaDownloadUrl( + API_ENDPOINTS.withdraw.resultDownload.replace('{resultId}', String(resultId)), + ); +} + +export function deleteWithdrawTask(taskId: number) { + return unwrapJavaResponse( + del>(`${JAVA_API_PREFIX}/withdraw/tasks/${taskId}`, { + params: { user_id: getCurrentUserId() }, + }), + ); +} + +export function deleteWithdrawHistory(resultId: number) { + return unwrapJavaResponse( + del>(`${JAVA_API_PREFIX}/withdraw/history/${resultId}`, { + params: { user_id: getCurrentUserId() }, + }), + ); +} + +export function listPatrolDeleteCandidates() { + return unwrapJavaResponse( + get>( + `${JAVA_API_PREFIX}/patrol-delete/candidates`, + { + params: { user_id: getCurrentUserId() }, + }, + ), + ); +} + +export function addPatrolDeleteCandidate(shopName: string) { + return unwrapJavaResponse( + post< + JavaApiResponse, + { user_id: number; shop_name: string } + >(`${JAVA_API_PREFIX}/patrol-delete/candidates`, { + user_id: getCurrentUserId(), + shop_name: shopName, + }), + ); +} + +export function deletePatrolDeleteCandidate(id: number) { + return unwrapJavaResponse( + del>( + `${JAVA_API_PREFIX}/patrol-delete/candidates/${id}`, + { + params: { user_id: getCurrentUserId() }, + }, + ), + ); +} + +export function listPatrolDeleteConditions() { + return unwrapJavaResponse( + get>( + `${JAVA_API_PREFIX}/patrol-delete/conditions`, + { + params: { user_id: getCurrentUserId() }, + }, + ), + ); +} + +export function addPatrolDeleteCondition(conditionText: string) { + return unwrapJavaResponse( + post< + JavaApiResponse, + { user_id: number; conditionText: string } + >(`${JAVA_API_PREFIX}/patrol-delete/conditions`, { + user_id: getCurrentUserId(), + conditionText, + }), + ); +} + +export function deletePatrolDeleteCondition(id: number) { + return unwrapJavaResponse( + del>( + `${JAVA_API_PREFIX}/patrol-delete/conditions/${id}`, + { + params: { user_id: getCurrentUserId() }, + }, + ), + ); +} + +export function matchPatrolDeleteShops(shopNames: string[]) { + return unwrapJavaResponse( + post< + JavaApiResponse, + { user_id: number; shop_names: string[] } + >(`${JAVA_API_PREFIX}/patrol-delete/match-shops`, { + user_id: getCurrentUserId(), + shop_names: shopNames, + }), + ); +} + +export function getPatrolDeleteDashboard() { + return unwrapJavaResponse( + get>( + `${JAVA_API_PREFIX}/patrol-delete/dashboard`, + { + params: { user_id: getCurrentUserId() }, + }, + ), + ); +} + +export function getPatrolDeleteHistory() { + return unwrapJavaResponse( + get>( + `${JAVA_API_PREFIX}/patrol-delete/history`, + { + params: { user_id: getCurrentUserId() }, + }, + ), + ); +} + +export function getPatrolDeleteTaskProgressBatch(taskIds: number[]) { + return postTaskProgressBatch( + `${JAVA_API_PREFIX}/patrol-delete/tasks/progress/batch`, + taskIds, + ); +} + +export function createPatrolDeleteTask(items: PatrolDeleteTaskItem[]) { + return unwrapJavaResponse( + post< + JavaApiResponse, + { user_id: number; items: PatrolDeleteTaskItem[] } + >(`${JAVA_API_PREFIX}/patrol-delete/tasks`, { + user_id: getCurrentUserId(), + items, + }), + ); +} + +export function submitPatrolDeleteTaskResult( + taskId: number, + payload: { + shops: PatrolDeleteSubmitShopPayload[]; + }, +) { + return unwrapJavaResponse( + post< + JavaApiResponse, + { shops: PatrolDeleteSubmitShopPayload[] } + >(`${JAVA_API_PREFIX}/patrol-delete/tasks/${taskId}/result`, payload), + ); +} + +export function getPatrolDeleteResultDownloadUrl(resultId: number) { + return getJavaDownloadUrl( + API_ENDPOINTS.patrolDelete.resultDownload.replace('{resultId}', String(resultId)), + ); +} + +export function deletePatrolDeleteTask(taskId: number) { + return unwrapJavaResponse( + del>( + `${JAVA_API_PREFIX}/patrol-delete/tasks/${taskId}`, + { + params: { user_id: getCurrentUserId() }, + }, + ), + ); +} + +export function deletePatrolDeleteHistory(resultId: number) { + return unwrapJavaResponse( + del>( + `${JAVA_API_PREFIX}/patrol-delete/history/${resultId}`, + { + params: { user_id: getCurrentUserId() }, + }, + ), + ); +} diff --git a/frontend-vue/tests/modules-withdraw.test.ts b/frontend-vue/tests/modules-withdraw.test.ts new file mode 100644 index 00000000..e4635158 --- /dev/null +++ b/frontend-vue/tests/modules-withdraw.test.ts @@ -0,0 +1,260 @@ +import { test } from 'node:test' +import assert from 'node:assert/strict' +import { http } from '../src/shared/api/http.ts' +import { + listWithdrawCandidates, + addWithdrawCandidate, + deleteWithdrawCandidate, + clearWithdrawCandidates, + matchWithdrawShops, + getWithdrawDashboard, + getWithdrawHistory, + getWithdrawTaskProgressBatch, + createWithdrawTask, + submitWithdrawTaskResult, + getWithdrawResultDownloadUrl, + deleteWithdrawTask, + deleteWithdrawHistory, + listPatrolDeleteCandidates, + addPatrolDeleteCandidate, + deletePatrolDeleteCandidate, + listPatrolDeleteConditions, + addPatrolDeleteCondition, + deletePatrolDeleteCondition, + matchPatrolDeleteShops, + getPatrolDeleteDashboard, + getPatrolDeleteHistory, + getPatrolDeleteTaskProgressBatch, + createPatrolDeleteTask, + submitPatrolDeleteTaskResult, + getPatrolDeleteResultDownloadUrl, + deletePatrolDeleteTask, + deletePatrolDeleteHistory, +} from '../src/shared/api/types/modules/withdraw.ts' + +function setupWindow() { + ;(globalThis as Record).window = { + localStorage: { getItem: () => '42' }, + location: { origin: 'http://localhost' }, + } +} + +function mockRequest( + t: Parameters[1] extends (t: infer T) => unknown ? T : never, + impl: (config: { url?: string; method?: string; params?: Record; data?: unknown; timeout?: number }) => Promise, +) { + t.mock.method(http, 'request', impl as never) +} + +const okResponse = (data: unknown) => Promise.resolve({ data: { success: true, message: 'ok', data } }) + +test('test_withdraw_candidates', async (t) => { + setupWindow() + const calls: { url?: string; method?: string; params?: Record; data?: unknown }[] = [] + mockRequest(t, (config) => { + calls.push(config) + return okResponse({ id: 1, shop_name: 's1' }) + }) + await listWithdrawCandidates() + await addWithdrawCandidate('店铺A') + await deleteWithdrawCandidate(3) + await clearWithdrawCandidates(['店铺A', '店铺B']) + assert.equal(calls[0].url, '/newApi/api/withdraw/candidates') + assert.deepEqual(calls[0].params, { user_id: 42 }) + assert.equal(calls[1].url, '/newApi/api/withdraw/candidates') + assert.equal(calls[1].method, 'POST') + assert.deepEqual(calls[1].data, { user_id: 42, shop_name: '店铺A' }) + assert.equal(calls[2].url, '/newApi/api/withdraw/candidates/3') + assert.equal(calls[2].method, 'DELETE') + assert.equal(calls[3].url, '/newApi/api/withdraw/candidates/clear') + assert.equal(calls[3].method, 'POST') + assert.deepEqual(calls[3].data, { user_id: 42, shop_names: ['店铺A', '店铺B'] }) +}) + +test('test_withdraw_create', async (t) => { + setupWindow() + const calls: { url?: string; method?: string; data?: unknown }[] = [] + mockRequest(t, (config) => { + calls.push(config) + return okResponse({ taskId: 1, items: [] }) + }) + await createWithdrawTask([{ shopName: '店铺A', matched: true }], 100) + assert.equal(calls[0].url, '/newApi/api/withdraw/tasks') + assert.equal(calls[0].method, 'POST') + assert.deepEqual(calls[0].data, { user_id: 42, reserved_amount: 100, items: [{ shopName: '店铺A', matched: true }] }) +}) + +test('test_withdraw_submit_result', async (t) => { + setupWindow() + const calls: { url?: string; method?: string; data?: unknown }[] = [] + mockRequest(t, (config) => { + calls.push(config) + return okResponse(null) + }) + const payload = { + shops: [{ shopName: '店铺A', rows: [{ country: 'US', withdrawAmount: 10, status: 'SUCCESS' }], shopDone: true }], + } + await submitWithdrawTaskResult(7, payload) + assert.equal(calls[0].url, '/newApi/api/withdraw/tasks/7/result') + assert.equal(calls[0].method, 'POST') + assert.deepEqual(calls[0].data, payload) +}) + +test('test_withdraw_progress_dashboard_history', async (t) => { + setupWindow() + const calls: { url?: string; method?: string; data?: unknown; timeout?: number; params?: Record }[] = [] + mockRequest(t, (config) => { + calls.push(config) + return okResponse({ items: [], missingTaskIds: [] }) + }) + await getWithdrawTaskProgressBatch([5, 3]) + await getWithdrawDashboard() + await getWithdrawHistory() + await deleteWithdrawTask(7) + await deleteWithdrawHistory(9) + assert.equal(calls[0].url, '/newApi/api/withdraw/tasks/progress/batch') + assert.equal(calls[0].method, 'POST') + assert.deepEqual(calls[0].data, { taskIds: [3, 5] }) + assert.equal(calls[0].timeout, 10000) + assert.equal(calls[1].url, '/newApi/api/withdraw/dashboard') + assert.deepEqual(calls[1].params, { user_id: 42 }) + assert.equal(calls[2].url, '/newApi/api/withdraw/history') + assert.equal(calls[3].url, '/newApi/api/withdraw/tasks/7') + assert.equal(calls[3].method, 'DELETE') + assert.equal(calls[4].url, '/newApi/api/withdraw/history/9') + assert.equal(calls[4].method, 'DELETE') + assert.equal( + getWithdrawResultDownloadUrl(7), + 'http://localhost/newApi/api/withdraw/results/7/download?user_id=42', + ) +}) + +test('test_withdraw_match', async (t) => { + setupWindow() + const calls: { url?: string; data?: unknown }[] = [] + mockRequest(t, (config) => { + calls.push(config) + return okResponse({ items: [] }) + }) + await matchWithdrawShops(['店铺A']) + assert.equal(calls[0].url, '/newApi/api/withdraw/match-shops') + assert.deepEqual(calls[0].data, { user_id: 42, shop_names: ['店铺A'] }) +}) + +test('test_patrol_delete_candidates_conditions', async (t) => { + setupWindow() + const calls: { url?: string; method?: string; params?: Record; data?: unknown }[] = [] + mockRequest(t, (config) => { + calls.push(config) + return okResponse({ id: 1, shop_name: 's1' }) + }) + await listPatrolDeleteCandidates() + await addPatrolDeleteCandidate('店铺A') + await deletePatrolDeleteCandidate(3) + await listPatrolDeleteConditions() + await addPatrolDeleteCondition('条件X') + await deletePatrolDeleteCondition(5) + assert.equal(calls[0].url, '/newApi/api/patrol-delete/candidates') + assert.deepEqual(calls[0].params, { user_id: 42 }) + assert.equal(calls[1].url, '/newApi/api/patrol-delete/candidates') + assert.equal(calls[1].method, 'POST') + assert.deepEqual(calls[1].data, { user_id: 42, shop_name: '店铺A' }) + assert.equal(calls[2].url, '/newApi/api/patrol-delete/candidates/3') + assert.equal(calls[2].method, 'DELETE') + assert.equal(calls[3].url, '/newApi/api/patrol-delete/conditions') + assert.deepEqual(calls[3].params, { user_id: 42 }) + assert.equal(calls[4].url, '/newApi/api/patrol-delete/conditions') + assert.equal(calls[4].method, 'POST') + assert.deepEqual(calls[4].data, { user_id: 42, conditionText: '条件X' }) + assert.equal(calls[5].url, '/newApi/api/patrol-delete/conditions/5') + assert.equal(calls[5].method, 'DELETE') +}) + +test('test_patrol_delete_create_submit', async (t) => { + setupWindow() + const calls: { url?: string; method?: string; data?: unknown }[] = [] + mockRequest(t, (config) => { + calls.push(config) + return okResponse({ taskId: 1, items: [] }) + }) + const item = { + shopName: '店铺A', + matched: true, + countrySections: [{ country: 'US', rows: [{ status: 'ok', quantity: '1', deleteQuantity: '0', processStatus: 'done' }] }], + cartRatios: [{ country: 'US', ratio: '0.5' }], + } + await createPatrolDeleteTask([item]) + assert.equal(calls[0].url, '/newApi/api/patrol-delete/tasks') + assert.equal(calls[0].method, 'POST') + assert.deepEqual(calls[0].data, { user_id: 42, items: [item] }) + const payload = { shops: [{ shopName: '店铺A', countrySections: item.countrySections, cartRatios: item.cartRatios, shopDone: true, chunkIndex: 0, chunkTotal: 1 }] } + await submitPatrolDeleteTaskResult(7, payload) + assert.equal(calls[1].url, '/newApi/api/patrol-delete/tasks/7/result') + assert.deepEqual(calls[1].data, payload) +}) + +test('test_patrol_delete_progress_dashboard_download', async (t) => { + setupWindow() + const calls: { url?: string; method?: string; data?: unknown; timeout?: number; params?: Record }[] = [] + mockRequest(t, (config) => { + calls.push(config) + return okResponse({ items: [], missingTaskIds: [] }) + }) + await getPatrolDeleteTaskProgressBatch([5, 3]) + await getPatrolDeleteDashboard() + await getPatrolDeleteHistory() + await matchPatrolDeleteShops(['店铺A']) + await deletePatrolDeleteTask(7) + await deletePatrolDeleteHistory(9) + assert.equal(calls[0].url, '/newApi/api/patrol-delete/tasks/progress/batch') + assert.equal(calls[0].method, 'POST') + assert.deepEqual(calls[0].data, { taskIds: [3, 5] }) + assert.equal(calls[0].timeout, 10000) + assert.equal(calls[1].url, '/newApi/api/patrol-delete/dashboard') + assert.deepEqual(calls[1].params, { user_id: 42 }) + assert.equal(calls[2].url, '/newApi/api/patrol-delete/history') + assert.equal(calls[3].url, '/newApi/api/patrol-delete/match-shops') + assert.deepEqual(calls[3].data, { user_id: 42, shop_names: ['店铺A'] }) + assert.equal(calls[4].url, '/newApi/api/patrol-delete/tasks/7') + assert.equal(calls[4].method, 'DELETE') + assert.equal(calls[5].url, '/newApi/api/patrol-delete/history/9') + assert.equal(calls[5].method, 'DELETE') + assert.equal( + getPatrolDeleteResultDownloadUrl(7), + 'http://localhost/newApi/api/patrol-delete/results/7/download?user_id=42', + ) +}) + +test('test_withdraw_patrol_export_compat', async (t) => { + setupWindow() + const fromJavaModules = await import('../src/shared/api/java-modules.ts') + const fromWithdraw = await import('../src/shared/api/types/modules/withdraw.ts') + const names = [ + 'listWithdrawCandidates', 'addWithdrawCandidate', 'deleteWithdrawCandidate', + 'clearWithdrawCandidates', 'matchWithdrawShops', 'getWithdrawDashboard', + 'getWithdrawHistory', 'getWithdrawTaskProgressBatch', 'createWithdrawTask', + 'submitWithdrawTaskResult', 'getWithdrawResultDownloadUrl', + 'deleteWithdrawTask', 'deleteWithdrawHistory', + 'listPatrolDeleteCandidates', 'addPatrolDeleteCandidate', 'deletePatrolDeleteCandidate', + 'listPatrolDeleteConditions', 'addPatrolDeleteCondition', 'deletePatrolDeleteCondition', + 'matchPatrolDeleteShops', 'getPatrolDeleteDashboard', 'getPatrolDeleteHistory', + 'getPatrolDeleteTaskProgressBatch', 'createPatrolDeleteTask', + 'submitPatrolDeleteTaskResult', 'getPatrolDeleteResultDownloadUrl', + 'deletePatrolDeleteTask', 'deletePatrolDeleteHistory', + ] + for (const name of names) { + assert.equal(fromJavaModules[name], fromWithdraw[name], `${name} 应为同一引用`) + } + const row = { country: 'US', withdrawAmount: 10, status: 'SUCCESS' } + assert.equal(row.status, 'SUCCESS') +}) + +test('test_withdraw_unwrap_and_error', async (t) => { + setupWindow() + mockRequest(t, () => okResponse({ taskId: 1, items: [] })) + const result = await createWithdrawTask([], null) + assert.equal(result.taskId, 1) + + mockRequest(t, () => Promise.resolve({ data: { success: false, message: '余额不足' } })) + await assert.rejects(addWithdrawCandidate('x'), /余额不足/) +})