337 lines
12 KiB
JavaScript
337 lines
12 KiB
JavaScript
import { getMe, refreshSession } from "@/features/auth/api";
|
||
import { API_ENDPOINTS, API_OPERATIONS, apiEndpointPath } from "@/shared/api/generated/endpoints";
|
||
import { apiRequest, getAccessToken } from "@/shared/api/request";
|
||
import { FINANCE_OPERATIONS, FINANCE_PERMISSIONS, WALLET_IDENTITIES } from "./constants.js";
|
||
import {
|
||
mergeRechargeOverviews,
|
||
mergeRechargeSummaries,
|
||
normalizeApplicationPage,
|
||
normalizeRechargeBillPage,
|
||
normalizeRechargeOverview,
|
||
normalizeRechargeRegions,
|
||
normalizeRechargeSummary,
|
||
normalizeWithdrawalApplicationPage
|
||
} from "./format.js";
|
||
|
||
const appCodeHeader = "X-App-Code";
|
||
|
||
export async function fetchFinanceSession() {
|
||
const session = await currentSession();
|
||
const permissions = Array.isArray(session.permissions) ? session.permissions : [];
|
||
const canCreateApplication = permissions.includes(FINANCE_PERMISSIONS.createApplication);
|
||
const canAuditApplication = permissions.includes(FINANCE_PERMISSIONS.auditApplication);
|
||
const canAuditWithdrawalApplications =
|
||
permissions.includes(FINANCE_PERMISSIONS.auditWithdrawalApplications) || permissions.includes(FINANCE_PERMISSIONS.auditApplication);
|
||
const canViewWithdrawalApplications = permissions.includes(FINANCE_PERMISSIONS.viewWithdrawalApplications) || canAuditWithdrawalApplications;
|
||
const canViewAppRechargeDetails = permissions.includes(FINANCE_PERMISSIONS.viewAppRechargeDetails);
|
||
|
||
return {
|
||
...session,
|
||
canAccessFinance: canCreateApplication || canAuditApplication || canViewWithdrawalApplications || canViewAppRechargeDetails,
|
||
canAuditApplication,
|
||
canAuditWithdrawalApplications,
|
||
canCreateApplication,
|
||
canViewAppRechargeDetails,
|
||
canViewWithdrawalApplications,
|
||
permissions
|
||
};
|
||
}
|
||
|
||
export async function fetchFinanceApplicationOptions() {
|
||
const endpoint = API_ENDPOINTS.listApps;
|
||
const data = await apiRequest(apiEndpointPath(API_OPERATIONS.listApps), {
|
||
method: endpoint.method
|
||
});
|
||
return normalizeOptions({ apps: data });
|
||
}
|
||
|
||
// 充值详情的 App 目录与其他财务页不同:包含 Yumi 这类 legacy 外部账单源,只用于账单查询。
|
||
export async function fetchFinanceRechargeApps() {
|
||
const endpoint = API_ENDPOINTS.listRechargeApps;
|
||
const data = await apiRequest(apiEndpointPath(API_OPERATIONS.listRechargeApps), {
|
||
method: endpoint.method
|
||
});
|
||
return normalizeApps(data);
|
||
}
|
||
|
||
export async function listFinanceApplications(query = {}) {
|
||
const endpoint = API_ENDPOINTS.listFinanceApplications;
|
||
const data = await apiRequest(apiEndpointPath(API_OPERATIONS.listFinanceApplications), {
|
||
method: endpoint.method,
|
||
query
|
||
});
|
||
return normalizeApplicationPage(data);
|
||
}
|
||
|
||
export async function listMyFinanceApplications(query = {}) {
|
||
const endpoint = API_ENDPOINTS.listMyFinanceApplications;
|
||
const data = await apiRequest(apiEndpointPath(API_OPERATIONS.listMyFinanceApplications), {
|
||
method: endpoint.method,
|
||
query
|
||
});
|
||
return normalizeApplicationPage(data);
|
||
}
|
||
|
||
export async function listFinanceWithdrawalApplications(query = {}) {
|
||
const endpoint = API_ENDPOINTS.listFinanceWithdrawalApplications;
|
||
const data = await apiRequest(apiEndpointPath(API_OPERATIONS.listFinanceWithdrawalApplications), {
|
||
method: endpoint.method,
|
||
query
|
||
});
|
||
return normalizeWithdrawalApplicationPage(data);
|
||
}
|
||
|
||
export async function listFinanceRechargeDetails(query = {}, apps = []) {
|
||
const page = positiveInteger(query.page, 1);
|
||
const pageSize = positiveInteger(query.page_size ?? query.pageSize, 50);
|
||
const selectedAppCode = stringValue(query.app_code ?? query.appCode);
|
||
const cleanQuery = cleanRechargeBillQuery(query);
|
||
|
||
if (selectedAppCode) {
|
||
const data = await listRechargeBillsForApp(selectedAppCode, { ...cleanQuery, page, page_size: pageSize });
|
||
return { ...data, page, pageSize };
|
||
}
|
||
|
||
const appCodes = normalizeApps(apps).map((app) => app.appCode);
|
||
if (!appCodes.length) {
|
||
return { items: [], page, pageSize, total: 0 };
|
||
}
|
||
|
||
const mergedPageSize = page * pageSize;
|
||
const pageResults = await Promise.allSettled(
|
||
appCodes.map((appCode) => listRechargeBillsForApp(appCode, { ...cleanQuery, page: 1, page_size: mergedPageSize }))
|
||
);
|
||
const pages = pageResults.filter((result) => result.status === "fulfilled").map((result) => result.value);
|
||
const items = pages
|
||
.flatMap((data) => data.items || [])
|
||
.sort((left, right) => (right.createdAtMs || 0) - (left.createdAtMs || 0) || String(right.transactionId).localeCompare(String(left.transactionId)));
|
||
const start = (page - 1) * pageSize;
|
||
|
||
return {
|
||
items: items.slice(start, start + pageSize),
|
||
page,
|
||
pageSize,
|
||
total: pages.reduce((sum, data) => sum + Number(data.total || 0), 0)
|
||
};
|
||
}
|
||
|
||
export async function fetchFinanceRechargeSummary(query = {}, apps = []) {
|
||
const selectedAppCode = stringValue(query.app_code ?? query.appCode);
|
||
const cleanQuery = cleanRechargeBillQuery(query);
|
||
|
||
if (selectedAppCode) {
|
||
return getRechargeSummaryForApp(selectedAppCode, cleanQuery);
|
||
}
|
||
|
||
const appCodes = normalizeApps(apps).map((app) => app.appCode);
|
||
if (!appCodes.length) {
|
||
return mergeRechargeSummaries([]);
|
||
}
|
||
const summaries = await Promise.all(appCodes.map((appCode) => getRechargeSummaryForApp(appCode, cleanQuery)));
|
||
return mergeRechargeSummaries(summaries);
|
||
}
|
||
|
||
export async function listFinanceRechargeRegions(appCode) {
|
||
const endpoint = API_ENDPOINTS.listRechargeRegions;
|
||
const data = await apiRequest(apiEndpointPath(API_OPERATIONS.listRechargeRegions), {
|
||
headers: { [appCodeHeader]: appCode },
|
||
method: endpoint.method
|
||
});
|
||
return normalizeRechargeRegions(data);
|
||
}
|
||
|
||
// fetchFinanceRechargeSummaries 返回逐 App 的汇总(App 对比表用)和全局合并值。
|
||
export async function fetchFinanceRechargeSummaries(query = {}, apps = []) {
|
||
const selectedAppCode = stringValue(query.app_code ?? query.appCode);
|
||
const cleanQuery = cleanRechargeBillQuery(query);
|
||
const appList = selectedAppCode
|
||
? normalizeApps(apps).filter((app) => app.appCode === selectedAppCode)
|
||
: normalizeApps(apps);
|
||
if (!appList.length && selectedAppCode) {
|
||
appList.push({ appCode: selectedAppCode, appName: selectedAppCode });
|
||
}
|
||
// 单个 App 查询失败(如后端灰度中)按空汇总降级,不拖垮其余 App 的数据。
|
||
const results = await Promise.allSettled(appList.map((app) => getRechargeSummaryForApp(app.appCode, cleanQuery)));
|
||
const summaries = results.map((result) =>
|
||
result.status === "fulfilled" ? result.value : normalizeRechargeSummary({})
|
||
);
|
||
const perApp = appList.map((app, index) => ({ ...app, summary: summaries[index] }));
|
||
return { merged: mergeRechargeSummaries(summaries), perApp };
|
||
}
|
||
|
||
export async function fetchFinanceRechargeOverview(query = {}, apps = []) {
|
||
const selectedAppCode = stringValue(query.app_code ?? query.appCode);
|
||
const cleanQuery = { ...cleanRechargeBillQuery(query), tz_offset_minutes: 480 };
|
||
if (selectedAppCode) {
|
||
return getRechargeOverviewForApp(selectedAppCode, cleanQuery);
|
||
}
|
||
const appCodes = normalizeApps(apps).map((app) => app.appCode);
|
||
if (!appCodes.length) {
|
||
return normalizeRechargeOverview({});
|
||
}
|
||
const results = await Promise.allSettled(appCodes.map((appCode) => getRechargeOverviewForApp(appCode, cleanQuery)));
|
||
return mergeRechargeOverviews(
|
||
results.filter((result) => result.status === "fulfilled").map((result) => result.value)
|
||
);
|
||
}
|
||
|
||
export async function exportFinanceRechargeBills(appCode, query = {}) {
|
||
const endpoint = API_ENDPOINTS.exportRechargeBills;
|
||
return apiRequest(apiEndpointPath(API_OPERATIONS.exportRechargeBills), {
|
||
headers: { [appCodeHeader]: appCode },
|
||
method: endpoint.method,
|
||
query: cleanRechargeBillQuery(query),
|
||
raw: true
|
||
});
|
||
}
|
||
|
||
export async function refreshFinanceGoogleRechargePaid(appCode, transactionIds) {
|
||
const endpoint = API_ENDPOINTS.refreshGoogleRechargePaid;
|
||
const data = await apiRequest(apiEndpointPath(API_OPERATIONS.refreshGoogleRechargePaid), {
|
||
body: { transactionIds },
|
||
headers: { [appCodeHeader]: appCode },
|
||
method: endpoint.method
|
||
});
|
||
return Array.isArray(data?.items) ? data.items : [];
|
||
}
|
||
|
||
export async function createFinanceApplication(payload) {
|
||
const endpoint = API_ENDPOINTS.createFinanceApplication;
|
||
return apiRequest(apiEndpointPath(API_OPERATIONS.createFinanceApplication), {
|
||
body: payload,
|
||
method: endpoint.method
|
||
});
|
||
}
|
||
|
||
export async function approveFinanceApplication(applicationId, payload) {
|
||
const endpoint = API_ENDPOINTS.approveFinanceApplication;
|
||
return apiRequest(apiEndpointPath(API_OPERATIONS.approveFinanceApplication, { application_id: applicationId }), {
|
||
body: payload,
|
||
method: endpoint.method
|
||
});
|
||
}
|
||
|
||
export async function rejectFinanceApplication(applicationId, payload) {
|
||
const endpoint = API_ENDPOINTS.rejectFinanceApplication;
|
||
return apiRequest(apiEndpointPath(API_OPERATIONS.rejectFinanceApplication, { application_id: applicationId }), {
|
||
body: payload,
|
||
method: endpoint.method
|
||
});
|
||
}
|
||
|
||
export async function approveFinanceWithdrawalApplication(applicationId, payload) {
|
||
const endpoint = API_ENDPOINTS.approveFinanceWithdrawalApplication;
|
||
return apiRequest(apiEndpointPath(API_OPERATIONS.approveFinanceWithdrawalApplication, { application_id: applicationId }), {
|
||
body: payload,
|
||
method: endpoint.method
|
||
});
|
||
}
|
||
|
||
export async function rejectFinanceWithdrawalApplication(applicationId, payload) {
|
||
const endpoint = API_ENDPOINTS.rejectFinanceWithdrawalApplication;
|
||
return apiRequest(apiEndpointPath(API_OPERATIONS.rejectFinanceWithdrawalApplication, { application_id: applicationId }), {
|
||
body: payload,
|
||
method: endpoint.method
|
||
});
|
||
}
|
||
|
||
export function filterOperationsByPermissions(operations, permissions) {
|
||
const allowed = new Set(permissions || []);
|
||
return (operations || []).filter((operation) => !operation.permissionCode || allowed.has(operation.permissionCode));
|
||
}
|
||
|
||
async function currentSession() {
|
||
try {
|
||
return await refreshSession();
|
||
} catch {
|
||
if (getAccessToken()) {
|
||
return getMe();
|
||
}
|
||
throw new Error("登录已失效");
|
||
}
|
||
}
|
||
|
||
async function getRechargeOverviewForApp(appCode, query = {}) {
|
||
const endpoint = API_ENDPOINTS.getRechargeBillOverview;
|
||
const data = await apiRequest(apiEndpointPath(API_OPERATIONS.getRechargeBillOverview), {
|
||
headers: { [appCodeHeader]: appCode },
|
||
method: endpoint.method,
|
||
query
|
||
});
|
||
return normalizeRechargeOverview(data);
|
||
}
|
||
|
||
async function getRechargeSummaryForApp(appCode, query = {}) {
|
||
const endpoint = API_ENDPOINTS.getRechargeBillSummary;
|
||
const data = await apiRequest(apiEndpointPath(API_OPERATIONS.getRechargeBillSummary), {
|
||
headers: { [appCodeHeader]: appCode },
|
||
method: endpoint.method,
|
||
query
|
||
});
|
||
return normalizeRechargeSummary(data);
|
||
}
|
||
|
||
async function listRechargeBillsForApp(appCode, query = {}) {
|
||
const endpoint = API_ENDPOINTS.listRechargeBills;
|
||
const data = await apiRequest(apiEndpointPath(API_OPERATIONS.listRechargeBills), {
|
||
headers: { [appCodeHeader]: appCode },
|
||
method: endpoint.method,
|
||
query
|
||
});
|
||
return normalizeRechargeBillPage(data);
|
||
}
|
||
|
||
function normalizeOptions(data = {}) {
|
||
return {
|
||
apps: normalizeApps(data.apps),
|
||
operations: FINANCE_OPERATIONS,
|
||
walletIdentities: WALLET_IDENTITIES
|
||
};
|
||
}
|
||
|
||
function normalizeApps(data) {
|
||
return listItems(data)
|
||
.map((item) => {
|
||
const appCode = stringValue(item.appCode ?? item.app_code ?? item.code);
|
||
if (!appCode) {
|
||
return null;
|
||
}
|
||
return {
|
||
appCode,
|
||
appName: stringValue(item.appName ?? item.app_name ?? item.name) || appCode
|
||
};
|
||
})
|
||
.filter(Boolean);
|
||
}
|
||
|
||
function listItems(data) {
|
||
if (Array.isArray(data)) {
|
||
return data;
|
||
}
|
||
if (Array.isArray(data?.items)) {
|
||
return data.items;
|
||
}
|
||
return [];
|
||
}
|
||
|
||
function cleanRechargeBillQuery(query = {}) {
|
||
const {
|
||
app_code: _appCode,
|
||
appCode: _camelAppCode,
|
||
page: _page,
|
||
page_size: _pageSize,
|
||
pageSize: _camelPageSize,
|
||
...rest
|
||
} = query;
|
||
return rest;
|
||
}
|
||
|
||
function positiveInteger(value, fallback) {
|
||
const number = Number(value);
|
||
return Number.isInteger(number) && number > 0 ? number : fallback;
|
||
}
|
||
|
||
function stringValue(value) {
|
||
return String(value ?? "").trim();
|
||
}
|