R52 A1-A6:完成 AP 运营深度操作
- AP 概览页 Tab 工作台化(待付款/异常付款/风险标记/对账异常) - AP 导出端点支持 CSV/XLSX 与筛选 - 风险标记 resolve/dismiss 支持说明审计留痕 - 支付批次深链、高风险复核提示、对账确认权限收口 - OpenClaw 浏览器级回归全部通过
这个提交包含在:
@@ -1,6 +1,48 @@
|
||||
import { apiRequestData } from "./http";
|
||||
import { apiRequestBlob, apiRequestData } from "./http";
|
||||
import type { RiskFlag } from "./feeControl";
|
||||
|
||||
export type ApOperationsExportFormat = "csv" | "xlsx";
|
||||
export type ApOperationsRiskBucket = "all" | "exception" | "budget_overspend";
|
||||
export type ApOperationsPendingEntityType = "reimbursement" | "travel_order";
|
||||
|
||||
export type ApPendingPaymentBatchSummary = {
|
||||
id: string;
|
||||
batchNo: string;
|
||||
entityType: string;
|
||||
name: string;
|
||||
status: string;
|
||||
totalAmount: string;
|
||||
highRisk: boolean;
|
||||
createdAt: string | null;
|
||||
};
|
||||
|
||||
export type ApFailedPaymentBatchItemSummary = {
|
||||
id: string;
|
||||
paymentBatchId: string;
|
||||
entityId: string;
|
||||
entityCode: string;
|
||||
entityTitle: string;
|
||||
amount: string;
|
||||
currencyCode: string;
|
||||
failureCode: string | null;
|
||||
failureMessage: string | null;
|
||||
createdAt: string | null;
|
||||
};
|
||||
|
||||
export type ApPartialMatchItemSummary = {
|
||||
id: string;
|
||||
travelReconciliationBatchId: string;
|
||||
travelOrderId: string | null;
|
||||
travelRequestId: string | null;
|
||||
matchStatus: string;
|
||||
resolutionStatus: string;
|
||||
anomalyCode: string | null;
|
||||
orderAmount: string | null;
|
||||
transactionAmount: string | null;
|
||||
currency: string;
|
||||
createdAt: string | null;
|
||||
};
|
||||
|
||||
export type ApOperationsSummary = {
|
||||
summary: {
|
||||
pendingPaymentCount: number;
|
||||
@@ -8,13 +50,29 @@ export type ApOperationsSummary = {
|
||||
exceptionCount: number;
|
||||
budgetOverspendCount: number;
|
||||
};
|
||||
pendingPaymentBatches: Array<{ id: string; batchNo: string; entityType: string; name: string; status: string; totalAmount: string; highRisk: boolean; createdAt: string | null }>;
|
||||
failedPaymentBatchItems: Array<{ id: string; paymentBatchId: string; entityId: string; entityCode: string; entityTitle: string; amount: string; currencyCode: string; failureCode: string | null; failureMessage: string | null; createdAt: string | null }>;
|
||||
pendingPaymentBatches: ApPendingPaymentBatchSummary[];
|
||||
failedPaymentBatchItems: ApFailedPaymentBatchItemSummary[];
|
||||
exceptionRiskFlags: RiskFlag[];
|
||||
budgetOverspendRisks: RiskFlag[];
|
||||
partialMatchItems: Array<{ id: string; travelReconciliationBatchId: string; travelOrderId: string | null; travelRequestId: string | null; matchStatus: string; resolutionStatus: string; anomalyCode: string | null; orderAmount: string | null; transactionAmount: string | null; currency: string; createdAt: string | null }>;
|
||||
partialMatchItems: ApPartialMatchItemSummary[];
|
||||
};
|
||||
|
||||
export async function fetchApOperations() {
|
||||
return apiRequestData<ApOperationsSummary>("/api/ap-operations");
|
||||
}
|
||||
|
||||
export async function exportApOperations(params: {
|
||||
format: ApOperationsExportFormat;
|
||||
pendingEntityType?: ApOperationsPendingEntityType;
|
||||
pendingCreatedAtFrom?: string;
|
||||
pendingCreatedAtTo?: string;
|
||||
riskBucket?: ApOperationsRiskBucket;
|
||||
}) {
|
||||
return apiRequestBlob("/api/exports/ap-operations", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(params),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -35,6 +35,25 @@ export type PrecheckResponse = {
|
||||
nextActions: string[];
|
||||
};
|
||||
|
||||
export type RiskFlagListResponse = {
|
||||
items: RiskFlag[];
|
||||
};
|
||||
|
||||
function buildRiskFlagsSearch(query: {
|
||||
entityType?: string;
|
||||
entityId?: string;
|
||||
status?: "open" | "resolved" | "dismissed";
|
||||
source?: string;
|
||||
}) {
|
||||
const params = new URLSearchParams();
|
||||
if (query.entityType) params.set("entityType", query.entityType);
|
||||
if (query.entityId) params.set("entityId", query.entityId);
|
||||
if (query.status) params.set("status", query.status);
|
||||
if (query.source) params.set("source", query.source);
|
||||
const search = params.toString();
|
||||
return search ? `?${search}` : "";
|
||||
}
|
||||
|
||||
export async function precheckEntity(payload: {
|
||||
entityType: "reimbursement" | "purchase_request" | "travel_request";
|
||||
entityId: string;
|
||||
@@ -44,3 +63,26 @@ export async function precheckEntity(payload: {
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
}
|
||||
|
||||
export async function fetchRiskFlags(query: {
|
||||
entityType?: string;
|
||||
entityId?: string;
|
||||
status?: "open" | "resolved" | "dismissed";
|
||||
source?: string;
|
||||
} = {}) {
|
||||
return apiRequestData<RiskFlagListResponse>(`/api/risk-flags${buildRiskFlagsSearch(query)}`);
|
||||
}
|
||||
|
||||
export async function resolveRiskFlag(riskFlagId: string, payload: { note?: string | null } = {}) {
|
||||
return apiRequestData<{ item: RiskFlag }>(`/api/risk-flags/${riskFlagId}/resolve`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
}
|
||||
|
||||
export async function dismissRiskFlag(riskFlagId: string, payload: { note?: string | null } = {}) {
|
||||
return apiRequestData<{ item: RiskFlag }>(`/api/risk-flags/${riskFlagId}/dismiss`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
}
|
||||
|
||||
在新工单中引用
屏蔽一个用户