From 1160de5142cd2bc08ebc61c247a4857f0c4ab7f1 Mon Sep 17 00:00:00 2001
From: yyb <995253665@qq.com>
Date: 星期五, 22 五月 2026 10:45:11 +0800
Subject: [PATCH] 特色功能:丰富报销清单并添加审批流程详情——新增功能以丰富报销清单行内容,为费用和差旅报销提供审批流程详情。——引入新的实用函数来处理审批流程节点和汇总信息。——更新组件以利用丰富后的审批流程数据,从而更好地展示审批进度。

---
 src/views/officeProcessAutomation/ReimburseManage/shared/finReimbursementMappers.js |  324 +++++++++++++++++++++++++++++++++++++++++++++++++----
 1 files changed, 296 insertions(+), 28 deletions(-)

diff --git a/src/views/officeProcessAutomation/ReimburseManage/shared/finReimbursementMappers.js b/src/views/officeProcessAutomation/ReimburseManage/shared/finReimbursementMappers.js
index 7a82873..44fd1b4 100644
--- a/src/views/officeProcessAutomation/ReimburseManage/shared/finReimbursementMappers.js
+++ b/src/views/officeProcessAutomation/ReimburseManage/shared/finReimbursementMappers.js
@@ -1,8 +1,10 @@
 import dayjs from "dayjs";
+import { getFinReimbursementDetail } from "@/api/officeProcessAutomation/finReimbursement.js";
 import { APPROVAL_MODULE_KEYS } from "../../ApproveManage/approve-shared/approvalModuleRegistry.js";
 import { mapSignModeToApi } from "../../ApproveManage/approve-template/approveTemplateConstants.js";
 import { EXPENSE_CATEGORY_OPTIONS } from "../cost-reimburse/costReimburseUtils.js";
 import { EXPENSE_SUBJECT_OPTIONS } from "../travel-reimburse/travelReimburseUtils.js";
+import { mapTasksToFlowNodes } from "../../ApproveManage/approve-list/approveListConstants.js";
 import { applyFinReimbursementDetailEnrichment } from "./finReimbursementDetailExtras.js";
 
 /** 鎶ラ攢绫诲瀷锛�1-宸梾鎶ラ攢锛�2-璐圭敤鎶ラ攢 */
@@ -130,8 +132,18 @@
   } else {
     mapped = raw || {};
   }
+
+  let formApprovalFlowNodes = mapNodesToFormFlow(resolveRowApiNodes(raw));
+  if (!formApprovalFlowNodes.length && Array.isArray(raw?.tasks) && raw.tasks.length) {
+    formApprovalFlowNodes = mapNodesToFormFlow(mapTasksToFlowNodes(raw.tasks));
+  }
+
+  const enriched = applyFinReimbursementDetailEnrichment(mapped, raw);
   return {
-    ...applyFinReimbursementDetailEnrichment(mapped, raw),
+    ...enriched,
+    approvalFlowNodes: formApprovalFlowNodes.length
+      ? formApprovalFlowNodes
+      : enriched.approvalFlowNodes,
     reimbursementType: type,
     reimbursementTypeLabel: reimbursementTypeLabel(type),
     moduleKey: getModuleKeyByReimbursementType(type),
@@ -324,9 +336,10 @@
         ? row.travel
         : travel,
     details,
-    nodes: row.nodes || [],
-    approvalFlowNodes: mapNodesToFormFlow(row.nodes),
+    nodes: resolveRowApiNodes(row),
+    approvalFlowNodes: mapNodesToFormFlow(resolveRowApiNodes(row)),
     tasks: row.tasks || [],
+    approvalFlowSummary: buildApprovalFlowSummaryForRow(row),
   };
   return base;
 }
@@ -335,6 +348,8 @@
 export function mapCostReimbursementRow(row) {
   if (!row) return {};
   const details = Array.isArray(row.details) ? row.details : [];
+  const apiNodes = resolveRowApiNodes(row);
+  const approvalFlowNodes = mapNodesToFormFlow(apiNodes);
 
   return {
     ...row,
@@ -351,7 +366,7 @@
     reimburseReason: row.reason || "",
     expenseCategory: row.expenseType || "",
     applyAmount: row.applyAmount,
-    applyTime: row.createTime || "",
+    applyTime: formatReimbursementDateTime(row.createTime),
     payee: row.payeeName || "",
     payeeAccount: row.payeeAccount || "",
     bankBranch: row.payeeBank || "",
@@ -363,9 +378,14 @@
       expenseSubject: d.expenseCategory,
     })),
     details,
-    nodes: row.nodes || [],
-    approvalFlowNodes: mapNodesToFormFlow(row.nodes),
+    nodes: apiNodes,
+    approvalFlowNodes,
     tasks: row.tasks || [],
+    approvalFlowSummary: buildApprovalFlowSummaryForRow({
+      ...row,
+      nodes: apiNodes,
+      approvalFlowNodes,
+    }),
   };
 }
 
@@ -385,18 +405,163 @@
   return hit?.label || category || "";
 }
 
+/** 鍒楄〃/璇︽儏琛屼笂鐨勫鎵硅妭鐐癸紙listPage 甯镐笉杩斿洖锛岄渶璇︽儏琛ュ叏锛� */
+export function resolveRowApiNodes(row) {
+  if (!row || typeof row !== "object") return [];
+  const list =
+    row.nodes ||
+    row.flowNodes ||
+    row.approveNodes ||
+    row.finReimbursementNodes ||
+    row.nodeList ||
+    row.reimbursementNodeList ||
+    [];
+  return Array.isArray(list) ? list : [];
+}
+
+function sortFlowNodesByLevel(nodes = []) {
+  return [...(Array.isArray(nodes) ? nodes : [])].sort((a, b) => {
+    const la = Number(a?.levelNo ?? a?.nodeOrder ?? a?.sortOrder ?? 0);
+    const lb = Number(b?.levelNo ?? b?.nodeOrder ?? b?.sortOrder ?? 0);
+    return la - lb;
+  });
+}
+
+function formatApiNodeApproverLabel(node, index) {
+  if (!node || typeof node !== "object") return "";
+  const approvers = Array.isArray(node.approvers) ? node.approvers : [];
+  const names = approvers
+    .map((a) => (a?.approverName || "").trim())
+    .filter(Boolean);
+  if (names.length) return names.join("/");
+  return (node.approverName || "").trim() || `鑺傜偣${index + 1}`;
+}
+
 /** 鎺ュ彛 nodes 鈫� 椤甸潰瀹℃壒娴侊紙鍗曞鎵逛汉鑺傜偣锛� */
 export function mapNodesToFormFlow(nodes = []) {
-  return (Array.isArray(nodes) ? nodes : []).map((n, i) => {
-    const first = Array.isArray(n.approvers) ? n.approvers[0] : null;
+  return sortFlowNodesByLevel(nodes).map((n, i) => {
+    const approvers = Array.isArray(n.approvers) ? n.approvers : [];
+    const first = approvers[0] || null;
+    const names = approvers
+      .map((a) => (a?.approverName || "").trim())
+      .filter(Boolean);
     return {
       ...n,
       nodeOrder: n.levelNo ?? n.nodeOrder ?? i + 1,
       signMode: String(n.approveType || "").toUpperCase() === "OR" ? "or_sign" : "countersign",
-      approverId: first?.approverId ?? n.approverId ?? null,
-      approverName: first?.approverName ?? n.approverName ?? "",
+      approverId:
+        toNumber(first?.approverId ?? n.approverId) ??
+        first?.approverId ??
+        n.approverId ??
+        null,
+      approverName:
+        names.join("銆�") || first?.approverName || n.approverName || "",
+      nodeStatus: n.nodeStatus,
     };
   });
+}
+
+function formatTasksToFlowSummary(tasks = []) {
+  const list = sortFlowNodesByLevel(
+    (Array.isArray(tasks) ? tasks : []).map((t, i) => ({
+      levelNo: t.levelNo ?? t.taskLevel ?? i + 1,
+      approverName:
+        (t.approverName || t.operatorName || t.createUserName || "").trim() ||
+        "",
+    }))
+  );
+  const parts = list.map((t) => t.approverName).filter(Boolean);
+  return parts.length ? parts.join(" 鈫� ") : "";
+}
+
+function buildApprovalFlowSummaryForRow(row) {
+  const apiNodes = sortFlowNodesByLevel(resolveRowApiNodes(row));
+  let flowNodes =
+    row?.approvalFlowNodes?.length > 0
+      ? sortFlowNodesByLevel(row.approvalFlowNodes)
+      : mapNodesToFormFlow(apiNodes);
+
+  if (!flowNodes.length && apiNodes.length) {
+    const line = apiNodes
+      .map((n, i) => formatApiNodeApproverLabel(n, i))
+      .filter(Boolean)
+      .join(" 鈫� ");
+    if (line) return line;
+  }
+
+  if (!flowNodes.length) {
+    const fromTasks = formatTasksToFlowSummary(row?.tasks);
+    if (fromTasks) return fromTasks;
+    return "鈥�";
+  }
+
+  return flowNodes
+    .map((n, i) => {
+      const name = (n.approverName || "").trim() || `鑺傜偣${i + 1}`;
+      if (n.nodeStatus === "finish") return `${name}鉁揱;
+      if (n.nodeStatus === "error") return `${name}鉁梎;
+      if (n.nodeStatus === "process") return `${name}鈥;
+      return name;
+    })
+    .join(" 鈫� ");
+}
+
+/** 鍒楄〃銆屽鎵规祦绋嬨�嶅垪鏂囨 */
+export function formatApprovalFlowSummary(row) {
+  return buildApprovalFlowSummaryForRow(row);
+}
+
+/** listPage 甯镐笉甯﹀畬鏁� nodes锛屽垪琛ㄥ姞杞藉悗缁熶竴鎷夎鎯呰ˉ鍏ㄥ绾у鎵规祦绋� */
+export async function enrichReimbursementListRowsWithApprovalFlow(
+  rows,
+  reimbursementType
+) {
+  const list = Array.isArray(rows) ? rows : [];
+  if (!list.length) return list;
+
+  const needIds = list
+    .map((r) => resolveReimbursementDeleteId(r))
+    .filter((id) => id != null);
+
+  if (!needIds.length) return list;
+
+  const detailById = new Map();
+  await Promise.all(
+    needIds.map(async (id) => {
+      try {
+        const res = await getFinReimbursementDetail(id);
+        detailById.set(String(id), unwrapFinReimbursementDetail(res));
+      } catch {
+        /* 鍗曡澶辫触涓嶅奖鍝嶅垪琛� */
+      }
+    })
+  );
+
+  const mapRow =
+    reimbursementType === FIN_REIMBURSEMENT_TYPE.TRAVEL
+      ? mapTravelReimbursementRow
+      : mapCostReimbursementRow;
+
+  return list.map((row) => {
+    const id = resolveReimbursementDeleteId(row);
+    const detail = id != null ? detailById.get(String(id)) : null;
+    if (!detail) return row;
+    const merged = {
+      ...row,
+      ...detail,
+      id: row.id ?? detail.id,
+      reimbursementId: row.reimbursementId ?? row.id ?? detail.id,
+      reimbursementType: detail.reimbursementType ?? row.reimbursementType,
+    };
+    return mapRow(merged);
+  });
+}
+
+/** 琛ㄥ崟涓婄殑瀹℃壒娴侊紙鍏煎 approvalFlowNodes / nodes / flowNodes锛� */
+export function resolveFormApprovalFlowNodes(form) {
+  const list =
+    form?.approvalFlowNodes ?? form?.nodes ?? form?.flowNodes ?? [];
+  return Array.isArray(list) ? list : [];
 }
 
 /** 椤甸潰瀹℃壒鑺傜偣 鈫� 鎺ュ彛 nodes */
@@ -408,36 +573,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 = []) {
@@ -468,6 +647,81 @@
   return Math.round(sum * 100) / 100;
 }
 
+/** 琛ㄥ崟闄勪欢鍒楄〃锛堝吋瀹瑰绉嶅瓧娈靛悕锛� */
+export function resolveFormAttachmentList(form) {
+  const list =
+    form?.attachmentList ??
+    form?.storageBlobDTOs ??
+    form?.storageBlobVOList ??
+    form?.invoiceAttachments ??
+    [];
+  return Array.isArray(list) ? list : [];
+}
+
+/** 椤甸潰闄勪欢 鈫� 淇濆瓨 DTO锛坰torageBlobVOList / storageBlobDTOs锛� */
+export function mapFormAttachmentsToApi(list = [], reimbursementId) {
+  const rid =
+    reimbursementId != null
+      ? toNumber(reimbursementId) ?? reimbursementId
+      : undefined;
+
+  return (list || [])
+    .map((item, i) => {
+      if (!item) return null;
+      const url =
+        item.url ||
+        item.fileUrl ||
+        item.downloadUrl ||
+        item.downloadURL ||
+        item.previewUrl ||
+        item.previewURL ||
+        item.link ||
+        "";
+      const name =
+        item.fileName ||
+        item.originalFilename ||
+        item.originalFileName ||
+        item.blobName ||
+        item.name ||
+        `闄勪欢${i + 1}`;
+
+      const idRaw = item.id ?? item.blobId;
+      const isTempId =
+        idRaw != null &&
+        /^(inv_|att_|ed_|local_)/.test(String(idRaw));
+
+      if (!url && (idRaw == null || isTempId)) return null;
+
+      const blob = {
+        fileName: name,
+        originalFilename: name,
+        fileUrl: url || undefined,
+        url: url || undefined,
+      };
+
+      if (idRaw != null && !isTempId) {
+        const n = toNumber(idRaw);
+        blob.id = n != null ? n : idRaw;
+        blob.blobId = blob.id;
+      }
+      if (rid != null) blob.reimbursementId = rid;
+      return blob;
+    })
+    .filter(Boolean);
+}
+
+function applyStorageBlobsToSaveDto(dto, form) {
+  const blobs = mapFormAttachmentsToApi(
+    resolveFormAttachmentList(form),
+    dto?.id ?? form?.reimbursementId ?? form?.id
+  );
+  if (blobs.length) {
+    dto.storageBlobVOList = blobs;
+    dto.storageBlobDTOs = blobs;
+  }
+  return dto;
+}
+
 /** 淇敼鏃惰ˉ榻愪富琛ㄤ笌瀛愯〃鍏宠仈 ID */
 function applyReimbursementRelations(dto) {
   const rid = dto?.id;
@@ -480,6 +734,12 @@
       d.reimbursementId = rid;
     });
   }
+  const blobLists = [dto.storageBlobVOList, dto.storageBlobDTOs].filter(Array.isArray);
+  blobLists.forEach((list) => {
+    list.forEach((b) => {
+      b.reimbursementId = rid;
+    });
+  });
   return dto;
 }
 
@@ -532,7 +792,10 @@
       withinStandard: form.needSpecialApproval ? "0" : "1",
     },
     details,
-    nodes: mapApprovalFlowNodesToApi(form.approvalFlowNodes, form.templateId),
+    nodes: mapApprovalFlowNodesToApi(
+      resolveFormApprovalFlowNodes(form),
+      form.templateId
+    ),
   };
 
   const id = resolveReimbursementId(form);
@@ -548,6 +811,7 @@
   }
   if (form.travel?.id != null) dto.travel.id = toNumber(form.travel.id);
 
+  applyStorageBlobsToSaveDto(dto, form);
   return applyReimbursementRelations(dto);
 }
 
@@ -574,7 +838,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);
@@ -589,6 +856,7 @@
     dto.approveProcessId = toNumber(form.approveProcessId);
   }
 
+  applyStorageBlobsToSaveDto(dto, form);
   return applyReimbursementRelations(dto);
 }
 

--
Gitblit v1.9.3