R55 A1-A6:报销统计仪表盘与批量审批
这个提交包含在:
@@ -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}`);
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
});
|
||||
}
|
||||
|
||||
在新工单中引用
屏蔽一个用户