2026-07-03 19:52:18 +08:00

337 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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();
}