From 3a0f25f9d38c08e1b78afedb2f67b7bb73f8c84f Mon Sep 17 00:00:00 2001
From: zhangwencui <1064582902@qq.com>
Date: 星期三, 01 七月 2026 17:15:45 +0800
Subject: [PATCH] 会议审批列表查不出,会议申请参会人列表查不出问题修改

---
 src/pages/oa/_utils/finReimbursementMappers.js |  423 ++++++++++++++++++++++++++++++++++------------------
 1 files changed, 277 insertions(+), 146 deletions(-)

diff --git a/src/pages/oa/_utils/finReimbursementMappers.js b/src/pages/oa/_utils/finReimbursementMappers.js
index d582ea0..147e20c 100644
--- a/src/pages/oa/_utils/finReimbursementMappers.js
+++ b/src/pages/oa/_utils/finReimbursementMappers.js
@@ -1,18 +1,12 @@
 import dayjs from "dayjs";
-import {
-  deleteFinReimbursement,
-  getFinReimbursementDetail,
-  persistFinReimbursement,
-} from "@/api/oa/finReimbursement.js";
+import { deleteFinReimbursement, getFinReimbursementDetail, persistFinReimbursement } from "@/api/oa/finReimbursement.js";
 import { APPROVAL_MODULE_KEYS } from "./approvalModuleRegistry.js";
 import { businessStatusClass, normalizeApprovalStatusKey } from "./approveListUtils.js";
-import {
-  EXPENSE_CATEGORY_OPTIONS,
-  expenseTypeToCategory,
-} from "../ReimburseManage/_utils/costReimburseUtils.js";
+import { EXPENSE_CATEGORY_OPTIONS, expenseTypeToCategory } from "../ReimburseManage/_utils/costReimburseUtils.js";
 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 = {
@@ -56,11 +50,7 @@
 export function resolveReimbursementType(raw, fallback) {
   const fromApi = normalizeReimbursementType(raw?.reimbursementType);
   if (fromApi) return fromApi;
-  return (
-    normalizeReimbursementType(fallback) ||
-    getReimbursementTypeByModuleKey(fallback) ||
-    ""
-  );
+  return normalizeReimbursementType(fallback) || getReimbursementTypeByModuleKey(fallback) || "";
 }
 
 export function isTravelReimbursementType(type) {
@@ -128,7 +118,9 @@
 }
 
 export function mapBillStatusToApprovalKey(billStatus) {
-  const upper = String(billStatus ?? "").trim().toUpperCase();
+  const upper = String(billStatus ?? "")
+    .trim()
+    .toUpperCase();
   if (upper === "DRAFT") return "draft";
   if (upper === "IN_APPROVAL") return "pending";
   if (upper === "APPROVED") return "approved";
@@ -139,7 +131,9 @@
 }
 
 export function billStatusLabel(billStatus) {
-  const upper = String(billStatus ?? "").trim().toUpperCase();
+  const upper = String(billStatus ?? "")
+    .trim()
+    .toUpperCase();
   if (BILL_STATUS_LABEL[upper]) return BILL_STATUS_LABEL[upper];
   const key = mapBillStatusToApprovalKey(billStatus);
   if (key === "draft") return "鑽夌";
@@ -150,54 +144,59 @@
 }
 
 export function billStatusCssClass(item) {
-  return businessStatusClass(
-    mapBillStatusToApprovalKey(item?.billStatus ?? item?.status)
-  );
+  return businessStatusClass(mapBillStatusToApprovalKey(item?.billStatus ?? item?.status));
 }
 
 function pickApplicantQuery(searchForm = {}) {
   const kw = (searchForm.applicantKeyword || "").trim();
   if (!kw) return {};
-  if (/[\u4e00-\u9fa5]/.test(kw)) return { applicantName: kw };
-  return { applicantCode: kw };
+  const out = { applicantName: kw };
+  if (!/[\u4e00-\u9fa5]/.test(kw)) {
+    out.applicantCode = kw;
+  }
+  return out;
 }
 
-export function buildFinReimbursementListParams({
-  page,
-  searchForm,
-  reimbursementType,
-  extraDto = {},
-}) {
+export function hasActiveReimbursementSearch(searchForm = {}) {
+  return Boolean((searchForm?.applicantKeyword || "").trim());
+}
+
+export function filterReimbursementRowsBySearch(rows, searchForm = {}) {
+  const list = Array.isArray(rows) ? rows : [];
+  const kw = (searchForm?.applicantKeyword || "").trim().toLowerCase();
+  if (!kw) return list;
+
+  return list.filter(row => {
+    const parts = [row.applicantName, row.employeeName, row.applicantNo, row.applicantCode, row.employeeNo].filter(v => v != null && v !== "").map(v => String(v).toLowerCase());
+    return parts.some(p => p.includes(kw));
+  });
+}
+
+/** 鎵佸钩鍖栦负 Spring GET 鍙粦瀹氱殑 query锛坒inReimbursementDto.xxx锛屽嬁鐢ㄦ柟鎷彿锛� */
+function appendDotNotationQuery(target, prefix, fields) {
+  if (!fields || typeof fields !== "object") return;
+  for (const [key, value] of Object.entries(fields)) {
+    if (value == null || value === "") continue;
+    target[`${prefix}.${key}`] = value;
+  }
+}
+
+export function buildFinReimbursementListParams({ page, searchForm, reimbursementType, extraDto = {} }) {
   const dto = {
     reimbursementType,
     ...pickApplicantQuery(searchForm),
     ...(extraDto && typeof extraDto === "object" ? extraDto : {}),
   };
 
-  const range = searchForm?.createTimeRange ?? searchForm?.applyDateRange;
-  if (Array.isArray(range) && range[0]) {
-    dto.createTimeStart = range[0];
-  }
-  if (Array.isArray(range) && range[1]) {
-    dto.createTimeEnd = range[1];
-  }
-
-  if (reimbursementType === FIN_REIMBURSEMENT_TYPE.TRAVEL) {
-    if (searchForm?.travelStartFrom) {
-      dto.startTimeStart = searchForm.travelStartFrom;
-    }
-    if (searchForm?.travelEndTo) {
-      dto.endTimeEnd = searchForm.travelEndTo;
-    }
-  }
-
-  return {
-    page: {
-      current: page.current,
-      size: page.size,
-    },
-    finReimbursementDto: dto,
+  const params = {
+    current: page.current,
+    size: page.size,
+    "page.current": page.current,
+    "page.size": page.size,
+    ...dto,
   };
+  appendDotNotationQuery(params, "finReimbursementDto", dto);
+  return params;
 }
 
 function pickTravelField(obj, keys) {
@@ -212,43 +211,14 @@
 /** 鍏煎 list/detail 澶氱宸梾瀛愬璞$粨鏋� */
 export function pickTravelFromRow(row) {
   if (!row || typeof row !== "object") return {};
-  const nested =
-    (row.travel && typeof row.travel === "object" ? row.travel : null) ||
-    row.finReimbursementTravel ||
-    row.finReimbursementTravelDto ||
-    row.travelDto ||
-    row.travelVO ||
-    {};
-  const src =
-    nested && typeof nested === "object" && Object.keys(nested).length
-      ? nested
-      : row;
+  const nested = (row.travel && typeof row.travel === "object" ? row.travel : null) || row.finReimbursementTravel || row.finReimbursementTravelDto || row.travelDto || row.travelVO || {};
+  const src = nested && typeof nested === "object" && Object.keys(nested).length ? nested : row;
   return {
-    startTime: pickTravelField(src, [
-      "startTime",
-      "travelStartTime",
-      "startDate",
-      "travelStartDate",
-      "departureTime",
-    ]),
-    endTime: pickTravelField(src, [
-      "endTime",
-      "travelEndTime",
-      "endDate",
-      "travelEndDate",
-      "returnTime",
-    ]),
+    startTime: pickTravelField(src, ["startTime", "travelStartTime", "startDate", "travelStartDate", "departureTime"]),
+    endTime: pickTravelField(src, ["endTime", "travelEndTime", "endDate", "travelEndDate", "returnTime"]),
     travelDays: src.travelDays,
-    departureCity: pickTravelField(src, [
-      "departureCity",
-      "departurePlace",
-      "departure",
-    ]),
-    destinationCity: pickTravelField(src, [
-      "destinationCity",
-      "destination",
-      "destinationPlace",
-    ]),
+    departureCity: pickTravelField(src, ["departureCity", "departurePlace", "departure"]),
+    destinationCity: pickTravelField(src, ["destinationCity", "destination", "destinationPlace"]),
     hotelStandard: src.hotelStandard,
     lodgingDays: src.lodgingDays ?? src.hotelDays,
     mealAllowance: src.mealAllowance ?? src.livingSubsidy,
@@ -272,17 +242,22 @@
 
 export function mapFinReimbursementFromApi(row, { reimbursementType, moduleKey } = {}) {
   if (!row) return {};
-  const type = resolveReimbursementType(
-    row,
-    reimbursementType || getReimbursementTypeByModuleKey(moduleKey)
-  );
+  const type = resolveReimbursementType(row, reimbursementType || getReimbursementTypeByModuleKey(moduleKey));
   const isTravel = type === FIN_REIMBURSEMENT_TYPE.TRAVEL;
   const travel = isTravel ? pickTravelFromRow(row) : {};
-  const instanceId = row.approvalInstanceId ?? row.id;
+  const apiNodes = resolveRowApiNodes(row);
+  const approvalFlowNodes = mapNodesToFormFlow(apiNodes);
+  const flowSummary = formatApprovalFlowSummary({
+    ...row,
+    nodes: apiNodes,
+    approvalFlowNodes,
+  });
+  const instanceId = row.id ?? row.approvalInstanceId;
+  const reimbursementId = row.reimbursementId ?? row.id;
 
   return {
     ...row,
-    reimbursementId: row.id,
+    reimbursementId,
     id: instanceId,
     approvalInstanceId: row.approvalInstanceId,
     instanceNo: row.billNo || "",
@@ -308,8 +283,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,8 +296,9 @@
         destination: travel.destinationCity,
         expenseType: row.expenseType,
         reason: row.reason,
+        approvalFlowSummary: flowSummary,
       },
-      type
+      type,
     ),
   };
 }
@@ -337,17 +315,15 @@
     { label: "鍗曟嵁鐘舵��", value: billStatusLabel(item.billStatus) },
   ];
   if (isTravel) {
-    rows.splice(
-      1,
-      0,
-      { label: "鍑哄樊鍦�", value: item.departurePlace },
-      { label: "鐩殑鍦�", value: item.destination }
-    );
+    rows.splice(1, 0, { label: "鍑哄樊鍦�", value: item.departurePlace }, { label: "鐩殑鍦�", value: item.destination });
   } else {
     rows.splice(1, 0, { label: "璐圭敤绫诲瀷", value: item.expenseType });
   }
   if (item.reason) {
     rows.push({ label: "鎶ラ攢鍘熷洜", value: item.reason });
+  }
+  if (item.approvalFlowSummary && item.approvalFlowSummary !== "鈥�") {
+    rows.push({ label: "瀹℃壒娴佺▼", value: item.approvalFlowSummary });
   }
   return rows;
 }
@@ -363,7 +339,7 @@
 
 /** 鍒楄〃琛屼富閿紙鍒犻櫎/淇敼鐢� fin_reimbursement.id锛屽嬁鐢� item.id 瀹℃壒瀹炰緥 ID锛� */
 export function resolveReimbursementDeleteId(row) {
-  const raw = row?.reimbursementId;
+  const raw = row?.reimbursementId ?? row?.reimbursementID ?? row?.finReimbursementId;
   if (raw == null || raw === "" || String(raw).startsWith("local_")) {
     return undefined;
   }
@@ -373,11 +349,11 @@
 
 /** 鏄惁鍏佽鍒犻櫎锛堝鎵逛腑銆佸凡閫氳繃銆佸凡浠樻涓嶅彲鍒狅級 */
 export function canDeleteReimbursementRow(row) {
-  const upper = String(row?.billStatus ?? row?.status ?? "").trim().toUpperCase();
+  const upper = String(row?.billStatus ?? row?.status ?? "")
+    .trim()
+    .toUpperCase();
   if (upper === "PAID") return false;
-  const key = mapBillStatusToApprovalKey(
-    row?.billStatus ?? row?.approvalStatus ?? row?.status
-  );
+  const key = mapBillStatusToApprovalKey(row?.billStatus ?? row?.approvalStatus ?? row?.status);
   return key !== "pending" && key !== "approved";
 }
 
@@ -410,7 +386,7 @@
         expenseType: row.expenseCategory || row.expenseType,
         reason: row.reimburseReason || row.reason,
       },
-      type
+      type,
     ),
   };
 }
@@ -426,19 +402,14 @@
 }
 
 function expenseSubjectToCategory(subject) {
-  const hit =
-    TRAVEL_EXPENSE_SUBJECTS.find(x => x.value === subject) ||
-    COST_EXPENSE_SUBJECTS.find(x => x.value === subject);
+  const hit = TRAVEL_EXPENSE_SUBJECTS.find(x => x.value === subject) || COST_EXPENSE_SUBJECTS.find(x => x.value === subject);
   return hit?.label || subject || "";
 }
 
 function mapDetailRowFromApi(d, reimbursementType) {
   const type = normalizeReimbursementType(reimbursementType);
   const raw = d.expenseCategory ?? d.expenseSubject ?? "";
-  const opts =
-    type === FIN_REIMBURSEMENT_TYPE.TRAVEL
-      ? TRAVEL_EXPENSE_SUBJECTS
-      : COST_EXPENSE_SUBJECTS;
+  const opts = type === FIN_REIMBURSEMENT_TYPE.TRAVEL ? TRAVEL_EXPENSE_SUBJECTS : COST_EXPENSE_SUBJECTS;
   const label = resolveExpenseSubjectLabel(raw, {
     isTravel: type === FIN_REIMBURSEMENT_TYPE.TRAVEL,
     subjectOptions: opts,
@@ -455,24 +426,128 @@
   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,
+    });
   });
 }
 
 /** 琛ㄥ崟涓婄殑瀹℃壒娴侊紙鍏煎 approvalFlowNodes / nodes / flowNodes锛� */
 export function resolveFormApprovalFlowNodes(form) {
-  const list =
-    form?.approvalFlowNodes ?? form?.nodes ?? form?.flowNodes ?? [];
+  const list = form?.approvalFlowNodes ?? form?.nodes ?? form?.flowNodes ?? [];
   return Array.isArray(list) ? list : [];
 }
 
@@ -558,6 +633,54 @@
   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 +692,12 @@
       d.reimbursementId = rid;
     });
   }
+  const blobLists = [dto.storageBlobVOList, dto.storageBlobDTOs].filter(Array.isArray);
+  blobLists.forEach(list => {
+    list.forEach(b => {
+      b.reimbursementId = rid;
+    });
+  });
   return dto;
 }
 
@@ -615,17 +744,13 @@
     payeeAccount: row.payeeAccount || "",
     payeeBank: row.payeeBank || "",
     billStatus: row.billStatus,
-    expenseDetails: details.map(d =>
-      mapDetailRowFromApi(d, FIN_REIMBURSEMENT_TYPE.TRAVEL)
-    ),
-    travel:
-      row.travel && typeof row.travel === "object" && Object.keys(row.travel).length
-        ? row.travel
-        : travel,
+    expenseDetails: details.map(d => mapDetailRowFromApi(d, FIN_REIMBURSEMENT_TYPE.TRAVEL)),
+    travel: row.travel && typeof row.travel === "object" && Object.keys(row.travel).length ? 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 +759,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,
@@ -656,13 +783,16 @@
     bankBranch: row.payeeBank || "",
     payeeBank: row.payeeBank || "",
     billStatus: row.billStatus,
-    expenseDetails: details.map(d =>
-      mapDetailRowFromApi(d, FIN_REIMBURSEMENT_TYPE.COST)
-    ),
+    expenseDetails: details.map(d => 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 +807,16 @@
   } 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),
@@ -690,10 +828,7 @@
   const details = mapDetailsToApi(form.expenseDetails);
   const detailTotal = sumDetailAmount(form.expenseDetails);
   const applyAmount = toNumber(form.applyAmount) ?? detailTotal;
-  const travelDays =
-    form.travelDays != null
-      ? toNumber(form.travelDays)
-      : computeTravelDays?.(form.travelStartTime, form.travelEndTime);
+  const travelDays = form.travelDays != null ? toNumber(form.travelDays) : computeTravelDays?.(form.travelStartTime, form.travelEndTime);
 
   const dto = {
     reimbursementType: FIN_REIMBURSEMENT_TYPE.TRAVEL,
@@ -726,10 +861,7 @@
       withinStandard: form.needSpecialApproval ? "0" : "1",
     },
     details,
-    nodes: mapApprovalFlowNodesToApi(
-      resolveFormApprovalFlowNodes(form),
-      form.templateId
-    ),
+    nodes: mapApprovalFlowNodesToApi(resolveFormApprovalFlowNodes(form), form.templateId),
   };
 
   const id = resolveReimbursementId(form);
@@ -739,6 +871,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);
 }
 
@@ -765,10 +898,7 @@
     billStatus: "IN_APPROVAL",
     deptId: toNumber(form.deptId),
     details,
-    nodes: mapApprovalFlowNodesToApi(
-      resolveFormApprovalFlowNodes(form),
-      form.templateId
-    ),
+    nodes: mapApprovalFlowNodesToApi(resolveFormApprovalFlowNodes(form), form.templateId),
   };
 
   const id = resolveReimbursementId(form);
@@ -777,6 +907,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