修复报销进度审批节点覆盖(07-27-yang-jun-reimbursement-readiness)
这个提交包含在:
@@ -341,16 +341,52 @@ function isoDateTimeOrNull(value) {
|
||||
return value?.toISOString?.() ?? null;
|
||||
}
|
||||
|
||||
function classifyApprovalTask(task, index) {
|
||||
function isOrdinalApprovalLabel(value) {
|
||||
const label = String(value ?? "").trim();
|
||||
if (!label) return true;
|
||||
const ordinal = "(?:\\d+|[一二三四五六七八九十百]+)";
|
||||
return new RegExp(`^(?:第\\s*)?${ordinal}\\s*(?:步|步骤|节点)$`, "i").test(label)
|
||||
|| new RegExp(`^(?:审批)?(?:步骤?|节点|step)\\s*[-_#::]?\\s*${ordinal}$`, "i").test(label);
|
||||
}
|
||||
|
||||
function resolveApprovalStageLabel(task) {
|
||||
const stepName = String(task?.step_name ?? "").trim();
|
||||
const stepId = String(task?.step_id ?? "").trim();
|
||||
const isTechnicalIdentifier = /^[a-z][a-z0-9]*(?:[_-][a-z0-9]+)+$/i.test(stepName);
|
||||
const isGenericLabel = ["审批", "审核", "复核"].includes(stepName);
|
||||
if (stepName && stepName !== stepId && !isOrdinalApprovalLabel(stepName) && !isTechnicalIdentifier && !isGenericLabel) {
|
||||
return stepName;
|
||||
}
|
||||
|
||||
const role = String(task?.assignee_role ?? "").toLowerCase();
|
||||
const text = `${task?.step_id ?? ""} ${task?.step_name ?? ""}`.toLowerCase();
|
||||
if (["finance", "finance_mgr"].includes(role) || /财务|finance/.test(text)) {
|
||||
return "finance_review";
|
||||
}
|
||||
if (["manager", "ceo"].includes(role) || /主管|经理|manager|ceo/.test(text)) {
|
||||
return "manager_approval";
|
||||
}
|
||||
return index === 0 ? "manager_approval" : index === 1 ? "finance_review" : null;
|
||||
if (role === "manager") return "主管审批";
|
||||
if (["finance", "finance_mgr"].includes(role)) return "财务复核";
|
||||
if (role === "ceo") return "负责人复核";
|
||||
return "审批";
|
||||
}
|
||||
|
||||
function resolveSnapshotApprovalRole(step) {
|
||||
if (step?.assignee_rule?.type === "department_manager") return "manager";
|
||||
if (step?.assignee_rule?.type === "role") return step.assignee_rule.value ?? null;
|
||||
return null;
|
||||
}
|
||||
|
||||
function buildPendingSnapshotApprovalStage(step, index) {
|
||||
return {
|
||||
key: `approval_pending_${index + 1}`,
|
||||
label: resolveApprovalStageLabel({
|
||||
step_id: step?.id,
|
||||
step_name: step?.name,
|
||||
assignee_role: resolveSnapshotApprovalRole(step),
|
||||
}),
|
||||
state: "pending",
|
||||
actorName: null,
|
||||
occurredAt: null,
|
||||
result: null,
|
||||
comment: null,
|
||||
detail: "待处理",
|
||||
targetHref: null,
|
||||
};
|
||||
}
|
||||
|
||||
function buildApprovalProcessingStage(task, fallback) {
|
||||
@@ -436,10 +472,34 @@ function buildProcessingProgress({ reimbursement, workflowInstance, paymentBatch
|
||||
});
|
||||
}
|
||||
|
||||
for (const [index, task] of (workflowInstance?.workflow_tasks ?? []).entries()) {
|
||||
const stageKey = classifyApprovalTask(task, index);
|
||||
if (!stageKey || !stageByKey.has(stageKey)) continue;
|
||||
stageByKey.set(stageKey, buildApprovalProcessingStage(task, stageByKey.get(stageKey)));
|
||||
const approvalStages = [];
|
||||
const workflowTasks = workflowInstance?.workflow_tasks ?? [];
|
||||
for (const [index, task] of workflowTasks.entries()) {
|
||||
const fallback = {
|
||||
key: `approval_${task.id ?? index + 1}`,
|
||||
label: resolveApprovalStageLabel(task),
|
||||
state: "pending",
|
||||
actorName: null,
|
||||
occurredAt: null,
|
||||
result: null,
|
||||
comment: null,
|
||||
detail: "待处理",
|
||||
targetHref: null,
|
||||
};
|
||||
approvalStages.push(buildApprovalProcessingStage(task, fallback));
|
||||
}
|
||||
|
||||
if (workflowInstance) {
|
||||
const snapshotSteps = getWorkflowStepsSnapshot(workflowInstance);
|
||||
const currentStepIndex = snapshotSteps.findIndex((step) => step.id === workflowInstance.current_step_id);
|
||||
const materializedStepIds = new Set(workflowTasks.map((task) => task.step_id).filter(Boolean));
|
||||
const pendingStartIndex = workflowTasks.length === 0 ? 0 : currentStepIndex + 1;
|
||||
for (let index = Math.max(pendingStartIndex, 0); index < snapshotSteps.length; index += 1) {
|
||||
const step = snapshotSteps[index];
|
||||
if (!materializedStepIds.has(step.id)) {
|
||||
approvalStages.push(buildPendingSnapshotApprovalStage(step, index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const batch = paymentBatchItem?.payment_batch ?? null;
|
||||
@@ -516,7 +576,16 @@ function buildProcessingProgress({ reimbursement, workflowInstance, paymentBatch
|
||||
});
|
||||
}
|
||||
|
||||
return { stages: PROCESSING_STAGE_DEFINITIONS.map(({ key }) => stageByKey.get(key)) };
|
||||
return {
|
||||
stages: [
|
||||
stageByKey.get("submitted"),
|
||||
...(approvalStages.length > 0
|
||||
? approvalStages
|
||||
: [stageByKey.get("manager_approval"), stageByKey.get("finance_review")]),
|
||||
stageByKey.get("payment_batch_review"),
|
||||
stageByKey.get("payment_and_receipt"),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function ensureApplicantOrAdmin(request, reimbursement) {
|
||||
|
||||
在新工单中引用
屏蔽一个用户