From 22002c2b5bf09bb769a51448537fa6a572a3ea88 Mon Sep 17 00:00:00 2001
From: yyb <995253665@qq.com>
Date: 星期五, 22 五月 2026 10:25:09 +0800
Subject: [PATCH] 费用报销的审批流程

---
 src/views/officeProcessAutomation/ReimburseManage/travel-reimburse/useTravelReimburse.js |    6 +++
 src/views/officeProcessAutomation/ReimburseManage/cost-reimburse/useCostReimburse.js     |   13 +++++-
 src/views/officeProcessAutomation/ReimburseManage/cost-reimburse/costReimburseUtils.js   |   36 ++++++++++++-----
 src/views/officeProcessAutomation/ReimburseManage/shared/finReimbursementMappers.js      |   63 ++++++++++++++++++++++---------
 4 files changed, 87 insertions(+), 31 deletions(-)

diff --git a/src/views/officeProcessAutomation/ReimburseManage/cost-reimburse/costReimburseUtils.js b/src/views/officeProcessAutomation/ReimburseManage/cost-reimburse/costReimburseUtils.js
index 2d88cd5..1b9c418 100644
--- a/src/views/officeProcessAutomation/ReimburseManage/cost-reimburse/costReimburseUtils.js
+++ b/src/views/officeProcessAutomation/ReimburseManage/cost-reimburse/costReimburseUtils.js
@@ -154,18 +154,32 @@
   return roles;
 }
 
-export function buildAutoApprovalFlow(amount, expenseCategory) {
+export function buildAutoApprovalFlow(amount, expenseCategory, previousNodes = []) {
   const roles = resolveApprovalRoles(amount, expenseCategory);
-  return roles.map((role, i) => ({
-    approverId: null,
-    approverName: APPROVAL_ROLE_LABELS[role] || role,
-    roleKey: role,
-    sortOrder: i + 1,
-    nodeOrder: i + 1,
-    nodeStatus: i === 0 ? "process" : "wait",
-    approveOpinion: "",
-    approveTime: "",
-  }));
+  const prevByRole = new Map();
+  (previousNodes || []).forEach((n, idx) => {
+    if (n?.roleKey) prevByRole.set(n.roleKey, n);
+    else if (n?.approverId != null && n.approverId !== "") {
+      prevByRole.set(`__idx_${idx}`, n);
+    }
+  });
+  return roles.map((role, i) => {
+    const prev = prevByRole.get(role) || prevByRole.get(`__idx_${i}`);
+    const hasApprover = prev?.approverId != null && prev.approverId !== "";
+    return {
+      approverId: hasApprover ? prev.approverId : null,
+      approverName: hasApprover
+        ? prev.approverName || ""
+        : APPROVAL_ROLE_LABELS[role] || role,
+      roleKey: role,
+      signMode: prev?.signMode || "countersign",
+      sortOrder: i + 1,
+      nodeOrder: i + 1,
+      nodeStatus: i === 0 ? "process" : "wait",
+      approveOpinion: "",
+      approveTime: "",
+    };
+  });
 }
 
 export function getApprovalRuleHint(amount, expenseCategory) {
diff --git a/src/views/officeProcessAutomation/ReimburseManage/cost-reimburse/useCostReimburse.js b/src/views/officeProcessAutomation/ReimburseManage/cost-reimburse/useCostReimburse.js
index 638d533..04b26ff 100644
--- a/src/views/officeProcessAutomation/ReimburseManage/cost-reimburse/useCostReimburse.js
+++ b/src/views/officeProcessAutomation/ReimburseManage/cost-reimburse/useCostReimburse.js
@@ -21,6 +21,7 @@
   resolveReimbursementDeleteId,
   unwrapFinReimbursementDetail,
   unwrapFinReimbursementPage,
+  validateReimbursementApprovalNodes,
   validateReimbursementPersistDto,
 } from "../shared/finReimbursementMappers.js";
 import { consumeReimburseEditFromApprove } from "../shared/reimburseApproveBridge.js";
@@ -264,7 +265,11 @@
 
   function autoAssignApprovalFlow() {
     const amount = Number(form.applyAmount) || detailTotalAmount.value || 0;
-    form.approvalFlowNodes = buildAutoApprovalFlow(amount, form.expenseCategory || "other");
+    form.approvalFlowNodes = buildAutoApprovalFlow(
+      amount,
+      form.expenseCategory || "other",
+      form.approvalFlowNodes
+    );
     nextTick(() => formRef.value?.validateField?.("approvalFlowNodes"));
   }
 
@@ -448,7 +453,6 @@
       return;
     }
     syncApplyAmountFromDetails();
-    autoAssignApprovalFlow();
 
     if (submitSaving.value) return;
     const isEdit = formDialog.mode === "edit";
@@ -458,6 +462,11 @@
       proxy?.$modal?.msgWarning?.(check.message);
       return;
     }
+    const nodeCheck = validateReimbursementApprovalNodes(dto);
+    if (!nodeCheck.ok) {
+      proxy?.$modal?.msgWarning?.(nodeCheck.message);
+      return;
+    }
     submitSaving.value = true;
     try {
       await persistFinReimbursement(dto, isEdit);
diff --git a/src/views/officeProcessAutomation/ReimburseManage/shared/finReimbursementMappers.js b/src/views/officeProcessAutomation/ReimburseManage/shared/finReimbursementMappers.js
index 7a82873..c75f6d1 100644
--- a/src/views/officeProcessAutomation/ReimburseManage/shared/finReimbursementMappers.js
+++ b/src/views/officeProcessAutomation/ReimburseManage/shared/finReimbursementMappers.js
@@ -399,6 +399,13 @@
   });
 }
 
+/** 琛ㄥ崟涓婄殑瀹℃壒娴侊紙鍏煎 approvalFlowNodes / nodes / flowNodes锛� */
+export function resolveFormApprovalFlowNodes(form) {
+  const list =
+    form?.approvalFlowNodes ?? form?.nodes ?? form?.flowNodes ?? [];
+  return Array.isArray(list) ? list : [];
+}
+
 /** 椤甸潰瀹℃壒鑺傜偣 鈫� 鎺ュ彛 nodes */
 export function mapApprovalFlowNodesToApi(nodes = [], templateId) {
   const list = Array.isArray(nodes) ? nodes : [];
@@ -408,36 +415,50 @@
       if (Array.isArray(n.approvers) && n.approvers.length) {
         approvers = n.approvers
           .filter((a) => a?.approverId != null && a.approverId !== "")
-          .map((a, idx) => ({
-            id: a.id,
-            nodeId: a.nodeId,
-            templateId: a.templateId ?? templateId,
-            approverId: toNumber(a.approverId) ?? a.approverId,
-            approverName: a.approverName || "",
-            sortNo: a.sortNo ?? idx + 1,
-          }));
+          .map((a, idx) => {
+            const item = {
+              approverId: toNumber(a.approverId) ?? a.approverId,
+              approverName: a.approverName || "",
+              sortNo: a.sortNo ?? idx + 1,
+            };
+            if (a.id != null) item.id = a.id;
+            if (a.nodeId != null) item.nodeId = a.nodeId;
+            if (a.templateId != null) item.templateId = a.templateId;
+            else if (templateId != null) item.templateId = templateId;
+            if (a.roleKey) item.roleKey = a.roleKey;
+            return item;
+          });
       } else if (n.approverId != null && n.approverId !== "") {
-        approvers = [
-          {
-            approverId: toNumber(n.approverId) ?? n.approverId,
-            approverName: n.approverName || "",
-            sortNo: 1,
-          },
-        ];
+        const item = {
+          approverId: toNumber(n.approverId) ?? n.approverId,
+          approverName: n.approverName || "",
+          sortNo: 1,
+        };
+        if (n.roleKey) item.roleKey = n.roleKey;
+        approvers = [item];
       }
       if (!approvers.length) return null;
 
       const node = {
-        levelNo: n.levelNo ?? n.nodeOrder ?? i + 1,
+        levelNo: n.levelNo ?? n.nodeOrder ?? n.sortOrder ?? i + 1,
         approveType: n.approveType || mapSignModeToApi(n.signMode),
         approvers,
       };
       if (n.id != null) node.id = n.id;
       if (n.templateId != null) node.templateId = n.templateId;
       else if (templateId != null) node.templateId = templateId;
+      if (n.roleKey) node.roleKey = n.roleKey;
       return node;
     })
     .filter(Boolean);
+}
+
+/** 淇濆瓨鍓嶆牎楠� nodes 宸查厤缃� */
+export function validateReimbursementApprovalNodes(dto) {
+  if (Array.isArray(dto?.nodes) && dto.nodes.length > 0) {
+    return { ok: true };
+  }
+  return { ok: false, message: "璇烽厤缃鎵规祦绋嬪苟閫夋嫨瀹℃壒浜�" };
 }
 
 function mapDetailsToApi(details = []) {
@@ -532,7 +553,10 @@
       withinStandard: form.needSpecialApproval ? "0" : "1",
     },
     details,
-    nodes: mapApprovalFlowNodesToApi(form.approvalFlowNodes, form.templateId),
+    nodes: mapApprovalFlowNodesToApi(
+      resolveFormApprovalFlowNodes(form),
+      form.templateId
+    ),
   };
 
   const id = resolveReimbursementId(form);
@@ -574,7 +598,10 @@
     billStatus: "IN_APPROVAL",
     deptId: toNumber(form.deptId),
     details,
-    nodes: mapApprovalFlowNodesToApi(form.approvalFlowNodes, form.templateId),
+    nodes: mapApprovalFlowNodesToApi(
+      resolveFormApprovalFlowNodes(form),
+      form.templateId
+    ),
   };
 
   const id = resolveReimbursementId(form);
diff --git a/src/views/officeProcessAutomation/ReimburseManage/travel-reimburse/useTravelReimburse.js b/src/views/officeProcessAutomation/ReimburseManage/travel-reimburse/useTravelReimburse.js
index 9aa6294..0df94e5 100644
--- a/src/views/officeProcessAutomation/ReimburseManage/travel-reimburse/useTravelReimburse.js
+++ b/src/views/officeProcessAutomation/ReimburseManage/travel-reimburse/useTravelReimburse.js
@@ -21,6 +21,7 @@
   resolveReimbursementDeleteId,
   unwrapFinReimbursementDetail,
   unwrapFinReimbursementPage,
+  validateReimbursementApprovalNodes,
   validateReimbursementPersistDto,
 } from "../shared/finReimbursementMappers.js";
 import { consumeReimburseEditFromApprove } from "../shared/reimburseApproveBridge.js";
@@ -517,6 +518,11 @@
       proxy?.$modal?.msgWarning?.(check.message);
       return;
     }
+    const nodeCheck = validateReimbursementApprovalNodes(dto);
+    if (!nodeCheck.ok) {
+      proxy?.$modal?.msgWarning?.(nodeCheck.message);
+      return;
+    }
     submitSaving.value = true;
     try {
       await persistFinReimbursement(dto, isEdit);

--
Gitblit v1.9.3