From 96ccf3ce0de27a8ced2eee18f578c764fa82d216 Mon Sep 17 00:00:00 2001
From: yyb <995253665@qq.com>
Date: 星期五, 22 五月 2026 10:45:24 +0800
Subject: [PATCH] 通过添加相关功能来增强报销审批流程整合,这些功能旨在充实列表行内容,添加审批流程数据,并更新报销明细充实过程中的映射逻辑。

---
 src/pages/oa/_utils/finReimbursementMappers.js |  270 +++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 259 insertions(+), 11 deletions(-)

diff --git a/src/pages/oa/_utils/finReimbursementMappers.js b/src/pages/oa/_utils/finReimbursementMappers.js
index d582ea0..fcf993c 100644
--- a/src/pages/oa/_utils/finReimbursementMappers.js
+++ b/src/pages/oa/_utils/finReimbursementMappers.js
@@ -13,6 +13,7 @@
 import { EXPENSE_SUBJECT_OPTIONS as TRAVEL_EXPENSE_SUBJECTS } from "../ReimburseManage/_utils/travelReimburseUtils.js";
 import { EXPENSE_SUBJECT_OPTIONS as COST_EXPENSE_SUBJECTS } from "../ReimburseManage/_utils/costReimburseUtils.js";
 import { resolveExpenseSubjectLabel } from "../ReimburseManage/_utils/expenseDetailDisplay.js";
+import { mapTasksToFlowNodes } from "./approveListUtils.js";
 import { applyFinReimbursementDetailEnrichment } from "../ReimburseManage/_utils/finReimbursementDetailExtras.js";
 
 export const FIN_REIMBURSEMENT_TYPE = {
@@ -278,6 +279,13 @@
   );
   const isTravel = type === FIN_REIMBURSEMENT_TYPE.TRAVEL;
   const travel = isTravel ? pickTravelFromRow(row) : {};
+  const apiNodes = resolveRowApiNodes(row);
+  const approvalFlowNodes = mapNodesToFormFlow(apiNodes);
+  const flowSummary = formatApprovalFlowSummary({
+    ...row,
+    nodes: apiNodes,
+    approvalFlowNodes,
+  });
   const instanceId = row.approvalInstanceId ?? row.id;
 
   return {
@@ -308,8 +316,10 @@
     travelEndTime: formatReimbursementDateTime(travel.endTime),
     travel,
     details: row.details || [],
-    nodes: row.nodes || [],
-    flowNodes: row.nodes || [],
+    nodes: apiNodes,
+    flowNodes: apiNodes,
+    approvalFlowNodes,
+    approvalFlowSummary: flowSummary,
     displayRows: buildFinReimbursementDisplayRows(
       {
         billNo: row.billNo,
@@ -319,6 +329,7 @@
         destination: travel.destinationCity,
         expenseType: row.expenseType,
         reason: row.reason,
+        approvalFlowSummary: flowSummary,
       },
       type
     ),
@@ -348,6 +359,9 @@
   }
   if (item.reason) {
     rows.push({ label: "鎶ラ攢鍘熷洜", value: item.reason });
+  }
+  if (item.approvalFlowSummary && item.approvalFlowSummary !== "鈥�") {
+    rows.push({ label: "瀹℃壒娴佺▼", value: item.approvalFlowSummary });
   }
   return rows;
 }
@@ -455,17 +469,150 @@
   return hit?.label || category || "";
 }
 
+export function resolveRowApiNodes(row) {
+  if (!row || typeof row !== "object") return [];
+  const list =
+    row.nodes ||
+    row.flowNodes ||
+    row.approveNodes ||
+    row.finReimbursementNodes ||
+    row.nodeList ||
+    [];
+  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 ?? "",
-      approverName: first?.approverName ?? n.approverName ?? "",
+      approverId:
+        toNumber(first?.approverId ?? n.approverId) ??
+        first?.approverId ??
+        n.approverId ??
+        "",
+      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 {
+        /* ignore */
+      }
+    })
+  );
+
+  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;
+    return mapRow({
+      ...row,
+      ...detail,
+      id: row.id ?? detail.id,
+      reimbursementId: row.reimbursementId ?? row.id ?? detail.id,
+    });
   });
 }
 
@@ -558,6 +705,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;
+}
+
 function applyReimbursementRelations(dto) {
   const rid = dto?.id;
   if (rid == null) return dto;
@@ -569,6 +791,12 @@
       d.reimbursementId = rid;
     });
   }
+  const blobLists = [dto.storageBlobVOList, dto.storageBlobDTOs].filter(Array.isArray);
+  blobLists.forEach(list => {
+    list.forEach(b => {
+      b.reimbursementId = rid;
+    });
+  });
   return dto;
 }
 
@@ -623,9 +851,10 @@
         ? row.travel
         : travel,
     details,
-    nodes: row.nodes || [],
-    approvalFlowNodes: mapNodesToFormFlow(row.nodes),
+    nodes: resolveRowApiNodes(row),
+    approvalFlowNodes: mapNodesToFormFlow(resolveRowApiNodes(row)),
     tasks: row.tasks || [],
+    approvalFlowSummary: formatApprovalFlowSummary(row),
     attachmentList: row.attachmentList || row.invoiceAttachments || [],
   };
 }
@@ -634,6 +863,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,
     id: row.id,
@@ -660,9 +891,14 @@
       mapDetailRowFromApi(d, FIN_REIMBURSEMENT_TYPE.COST)
     ),
     details,
-    nodes: row.nodes || [],
-    approvalFlowNodes: mapNodesToFormFlow(row.nodes),
+    nodes: apiNodes,
+    approvalFlowNodes,
     tasks: row.tasks || [],
+    approvalFlowSummary: formatApprovalFlowSummary({
+      ...row,
+      nodes: apiNodes,
+      approvalFlowNodes,
+    }),
     attachmentList: row.attachmentList || row.invoiceAttachments || [],
   };
 }
@@ -677,8 +913,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),
@@ -739,6 +985,7 @@
   if (form.approveProcessId != null) dto.approveProcessId = toNumber(form.approveProcessId);
   if (form.travel?.id != null) dto.travel.id = toNumber(form.travel.id);
 
+  applyStorageBlobsToSaveDto(dto, form);
   return applyReimbursementRelations(dto);
 }
 
@@ -777,6 +1024,7 @@
   if (form.approvalInstanceId != null) dto.approvalInstanceId = toNumber(form.approvalInstanceId);
   if (form.approveProcessId != null) dto.approveProcessId = toNumber(form.approveProcessId);
 
+  applyStorageBlobsToSaveDto(dto, form);
   return applyReimbursementRelations(dto);
 }
 

--
Gitblit v1.9.3