R53 A1-A6:ERP 集成运维可视化与业务同步状态接入

- ErpIntegrationPage Tab 化:主数据同步 / Outbox 队列管理 / ID 映射查看器
- Outbox 分页查询、详情、重试与统计端点
- IntegrationBinding 分页查询、routePath 推导与采购/资产深链
- 报销/采购/资产详情区接入 ERP 同步状态卡片
- 单测覆盖 Outbox 与 Binding 端点
这个提交包含在:
2026-04-07 10:33:58 +08:00
父节点 64d9ded024
当前提交 7cc6dc3397
共修改 9 个文件,包含 1989 行新增和 146 行删除
+132
查看文件
@@ -66,6 +66,99 @@ export type ReimbursementJournalPreview = {
};
};
export type IntegrationOutboxStatus =
| "pending"
| "processing"
| "succeeded"
| "failed"
| "dead_letter";
export type IntegrationOutboxPayloadSummary = {
entityType: string | null;
entityId: string | null;
sourceEventName: string | null;
journalType: string | null;
exportType: string | null;
identityCode: string | null;
};
export type IntegrationOutboxItem = {
id: string;
eventType: string;
status: IntegrationOutboxStatus | string;
payloadSummary: IntegrationOutboxPayloadSummary;
attempts: number;
maxAttempts: number;
nextRetryAt: string | null;
createdAt: string | null;
processedAt: string | null;
deadLetterAt: string | null;
};
export type IntegrationOutboxDetail = IntegrationOutboxItem & {
payload: Record<string, unknown> | null;
lastError: string | null;
};
export type IntegrationOutboxListResponse = {
page: number;
pageSize: number;
total: number;
items: IntegrationOutboxItem[];
};
export type IntegrationOutboxStats = {
pending: number;
processing: number;
succeeded: number;
failed: number;
deadLetter: number;
};
export type IntegrationOutboxQuery = {
page?: number;
pageSize?: number;
status?: IntegrationOutboxStatus;
eventType?: string;
};
export type IntegrationBindingItem = {
id: string;
provider: string;
entityType: string;
localId: string;
externalId: string | null;
externalCode: string | null;
lastSyncedAt: string | null;
routePath: string | null;
};
export type IntegrationBindingListResponse = {
page: number;
pageSize: number;
total: number;
items: IntegrationBindingItem[];
};
export type IntegrationBindingQuery = {
page?: number;
pageSize?: number;
provider?: string;
entityType?: string;
};
function toQueryString(query: Record<string, string | number | boolean | undefined>) {
const params = new URLSearchParams();
for (const [key, value] of Object.entries(query)) {
if (value == null || value === "") {
continue;
}
params.set(key, typeof value === "boolean" ? (value ? "1" : "0") : String(value));
}
const encoded = params.toString();
return encoded ? `?${encoded}` : "";
}
export async function triggerErpSync(reimbursementId: string, runNow = true) {
return apiRequestData<{
outboxId: string;
@@ -107,6 +200,45 @@ export async function exportReimbursementJournal(reimbursementId: string, runNow
});
}
export async function fetchIntegrationOutbox(query: IntegrationOutboxQuery = {}) {
return apiRequestData<IntegrationOutboxListResponse>(
`/api/integrations/outbox${toQueryString({
page: query.page,
pageSize: query.pageSize,
status: query.status,
event_type: query.eventType,
})}`,
);
}
export async function fetchIntegrationOutboxDetail(outboxId: string) {
const response = await apiRequestData<{ item: IntegrationOutboxDetail }>(`/api/integrations/outbox/${outboxId}`);
return response.item;
}
export async function retryIntegrationOutbox(outboxId: string) {
const response = await apiRequestData<{ item: IntegrationOutboxDetail }>(`/api/integrations/outbox/${outboxId}/retry`, {
method: "POST",
body: JSON.stringify({}),
});
return response.item;
}
export async function fetchIntegrationOutboxStats() {
return apiRequestData<IntegrationOutboxStats>("/api/integrations/outbox/stats");
}
export async function fetchIntegrationBindings(query: IntegrationBindingQuery = {}) {
return apiRequestData<IntegrationBindingListResponse>(
`/api/integrations/bindings${toQueryString({
page: query.page,
pageSize: query.pageSize,
provider: query.provider,
entity_type: query.entityType,
})}`,
);
}
export async function importTravelOrders(payload: {
orders: Array<{
source: string;