zhangwencui
4 天以前 3a0f25f9d38c08e1b78afedb2f67b7bb73f8c84f
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(finReimbursementDto.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(storageBlobVOList / 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);
}