R55 A1-A6:报销统计仪表盘与批量审批

这个提交包含在:
2026-04-08 09:53:47 +08:00
父节点 3e694fa60c
当前提交 40c3b7632a
共修改 12 个文件,包含 2668 行新增和 544 行删除
+82
查看文件
@@ -53,6 +53,17 @@ export type ReimbursementListItem = {
projectNo: string;
name: string;
} | null;
pendingWorkflow: {
instanceId: string;
stepId: string;
allowedActions: Array<"approve" | "reject" | "return" | "transfer">;
task: {
id: string;
stepName: string;
assigneeId: string | null;
assigneeRole: string | null;
} | null;
} | null;
createdAt: string | null;
updatedAt: string | null;
};
@@ -147,6 +158,49 @@ export type ReimbursementListQuery = {
scope?: "own" | "all";
};
export type ReimbursementStatsQuery = {
dateFrom?: string;
dateTo?: string;
departmentId?: string;
};
export type ReimbursementStatsGranularity = "month" | "week";
export type ReimbursementStatsSummary = {
totalCount: number;
totalAmount: string;
byStatus: Record<
ReimbursementStatus,
{
count: number;
totalAmount: string;
}
>;
avgProcessingDays: number | null;
};
export type ReimbursementStatsTrendItem = {
period: string | null;
count: number;
amount: string;
paidCount: number;
paidAmount: string;
};
export type ReimbursementTopExpenseTypeItem = {
expenseTypeId: string;
expenseTypeName: string;
count: number;
totalAmount: string;
};
export type ReimbursementApprovalBottleneckItem = {
assigneeId: string | null;
assigneeName: string;
pendingCount: number;
oldestPendingDays: number;
};
export type ReimbursementPayPayload = {
paymentVoucherNo?: string;
paymentDate?: string;
@@ -214,6 +268,34 @@ export async function fetchReimbursements(query: ReimbursementListQuery = {}) {
);
}
export async function fetchReimbursementStatsSummary(query: ReimbursementStatsQuery = {}) {
return apiRequestData<ReimbursementStatsSummary>(
`/api/reimbursements/stats/summary${toQueryString(query as Record<string, string | number | undefined>)}`,
);
}
export async function fetchReimbursementStatsTrend(
query: ReimbursementStatsQuery & { granularity?: ReimbursementStatsGranularity } = {},
) {
return apiRequestData<{ items: ReimbursementStatsTrendItem[] }>(
`/api/reimbursements/stats/trend${toQueryString(query as Record<string, string | number | undefined>)}`,
);
}
export async function fetchReimbursementTopExpenseTypes(
query: ReimbursementStatsQuery & { limit?: number } = {},
) {
return apiRequestData<{ items: ReimbursementTopExpenseTypeItem[] }>(
`/api/reimbursements/stats/top-expense-types${toQueryString(query as Record<string, string | number | undefined>)}`,
);
}
export async function fetchReimbursementApprovalBottleneck(query: ReimbursementStatsQuery = {}) {
return apiRequestData<{ items: ReimbursementApprovalBottleneckItem[] }>(
`/api/reimbursements/stats/approval-bottleneck${toQueryString(query as Record<string, string | number | undefined>)}`,
);
}
export async function fetchReimbursementDetail(reimbursementId: string) {
return apiRequestData<ReimbursementDetail>(`/api/reimbursements/${reimbursementId}`);
}
+32
查看文件
@@ -201,6 +201,24 @@ export type WorkflowApproverQuery = {
pageSize?: number;
};
export type WorkflowBatchActionResult = {
taskId: string;
outcome: "approved" | "rejected" | "skipped" | "error";
reason?: string;
};
export type WorkflowBatchActionSummary = {
approved: number;
rejected?: number;
skipped: number;
errored: number;
};
export type WorkflowBatchActionResponse = {
results: WorkflowBatchActionResult[];
summary: WorkflowBatchActionSummary;
};
function toQueryString(query: Record<string, string | number | undefined>) {
const params = new URLSearchParams();
for (const [key, value] of Object.entries(query)) {
@@ -255,3 +273,17 @@ export async function transferWorkflowTask(taskId: string, toApproverId: string,
body: JSON.stringify({ toApproverId, reason }),
});
}
export async function batchApproveWorkflowTasks(payload: { taskIds: string[]; comment?: string }) {
return apiRequestData<WorkflowBatchActionResponse>("/api/workflow/batch-approve", {
method: "POST",
body: JSON.stringify(payload),
});
}
export async function batchRejectWorkflowTasks(payload: { taskIds: string[]; comment: string }) {
return apiRequestData<WorkflowBatchActionResponse>("/api/workflow/batch-reject", {
method: "POST",
body: JSON.stringify(payload),
});
}