4 小时以前 47bae1f938f915206e3934ea960aff975e5738c9
src/views/officeProcessAutomation/ApproveManage/approve-list/approveListConstants.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,612 @@
import { createEmptyNode, formatDisplayTime, mapNodesFromApi, mapSignModeFromApi, mapSignModeToApi, normalizeFlowNodes, nodeSignModeLabel } from "../approve-template/approveTemplateConstants.js";
import { buildFormPayloadFromFields, parseFormConfigToData } from "../approve-template/formConfigUtils.js";
import { isDynamicOptionSource, resolveSelectDisplayLabel } from "../approve-template/selectOptionSource.js";
import { appendDotNotationQuery, buildApprovalInstanceSearchDto } from "../approve-shared/approvalInstanceListSearch.js";
/** å®¡æ‰¹ç±»åž‹ï¼ˆä¸ŽåŽç«¯å­—段 approvalType å¯¹é½ï¼ŒåŽæœŸå¯åŒæ­¥ï¼‰ */
export const APPROVAL_TYPE_OPTIONS = [
  { value: "cost_reimburse", label: "费用报销申请", cellBg: "#e8f8ef", cellColor: "#1a7f4b" },
  { value: "travel_reimburse", label: "差旅报销申请", cellBg: "#f0f2f5", cellColor: "#606266" },
  { value: "overtime", label: "加班申请", cellBg: "#fdf3e8", cellColor: "#c45c26" },
  { value: "leave", label: "请假申请", cellBg: "#fce8f0", cellColor: "#b84d7a" },
  { value: "work_handover", label: "工作交接申请", cellBg: "#f0e8fc", cellColor: "#6b4d9e" },
  { value: "regular", label: "转正申请", cellBg: "#e8f4fc", cellColor: "#2b6cb0" },
  { value: "resign", label: "离职申请", cellBg: "#ffffff", cellColor: "#303133", border: "1px solid #e4e7ed" },
  { value: "transfer", label: "调岗申请", cellBg: "#ffffff", cellColor: "#303133", border: "1px solid #e4e7ed" },
  { value: "out_office", label: "公出申请", cellBg: "#e8f4ff", cellColor: "#409eff" },
  { value: "business_trip", label: "出差申请", cellBg: "#fdf6ec", cellColor: "#e6a23c" },
  { value: "procurement", label: "采购审批", cellBg: "#f4f4f5", cellColor: "#909399" },
  { value: "quotation", label: "报价审批", cellBg: "#f4ecfc", cellColor: "#9b59b6" },
  { value: "shipment", label: "发货审批", cellBg: "#e8faf6", cellColor: "#1abc9c" },
  { value: "enterprise_news", label: "企业新闻", cellBg: "#ecf5ff", cellColor: "#409eff" },
];
/** åˆ—表查询:审批状态(与后端 status æžšä¸¾ä¸€è‡´ï¼‰ */
export const APPROVAL_STATUS_SEARCH_OPTIONS = [
  { value: "DRAFT", label: "草稿" },
  { value: "PENDING", label: "待审批" },
  { value: "APPROVED", label: "已通过" },
  { value: "REJECTED", label: "已驳回" },
];
/**
 * å®¡æ‰¹çŠ¶æ€å±•ç¤ºï¼ˆä¸ŽåŽç«¯ status æžšä¸¾ä¸€è‡´ï¼‰
 * DRAFT→草稿 PENDING→待审批/进行中 APPROVED→已通过/已完成 REJECTED→已驳回
 */
export const APPROVAL_STATUS_OPTIONS = [
  { value: "draft", api: "DRAFT", label: "草稿" },
  { value: "pending", api: "PENDING", label: "待审批" },
  { value: "approved", api: "APPROVED", label: "已通过" },
  { value: "rejected", api: "REJECTED", label: "已驳回" },
  { value: "cancelled", api: "CANCELLED", label: "已撤销" },
];
/** æ•°å­—状态码(部分后端用 0/1/2) */
const STATUS_NUMERIC_MAP = {
  0: "pending",
  1: "approved",
  2: "rejected",
  3: "cancelled",
  4: "cancelled",
};
/** åŽç«¯ status / é¡µé¢ approvalStatus â†’ ç»Ÿä¸€é¡µé¢ key(pending | approved | rejected | cancelled) */
export function normalizeApprovalStatusKey(v) {
  if (v == null || v === "") return "pending";
  if (typeof v === "number" || (typeof v === "string" && /^\d+$/.test(v.trim()))) {
    const numKey = STATUS_NUMERIC_MAP[Number(v)];
    if (numKey) return numKey;
  }
  const s = String(v).trim();
  if (!s) return "pending";
  const upper = s.toUpperCase();
  if (upper === "DRAFT") return "draft";
  if (upper === "PUBLISHED") return "approved";
  if (upper === "OFFLINE") return "cancelled";
  if (upper === "APPROVED" || upper === "APPROVE" || upper === "PASS" || upper === "AGREE") {
    return "approved";
  }
  if (upper === "REJECTED" || upper === "REJECT" || upper === "REFUSE" || upper === "REFUSED" || upper === "DENIED") {
    return "rejected";
  }
  if (upper === "CANCELLED" || upper === "CANCEL" || upper === "REVOKED") return "cancelled";
  if (upper === "PENDING" || upper === "IN_PROGRESS" || upper === "PROCESSING" || upper === "RUNNING" || upper === "WAIT" || upper === "WAITING") {
    return "pending";
  }
  if (s.includes("草稿")) return "draft";
  if (s.includes("驳回") || s.includes("拒绝")) return "rejected";
  if (s.includes("下线")) return "cancelled";
  if (s.includes("撤销")) return "cancelled";
  if (s.includes("发布") || s.includes("通过") || s.includes("完成")) return "approved";
  if (s.includes("待审") || s.includes("进行中") || s.includes("审批中")) return "pending";
  const lower = s.toLowerCase();
  if (["draft", "pending", "approved", "rejected", "cancelled"].includes(lower)) return lower;
  return "pending";
}
/** ä»Žåˆ—表/详情行解析后端原始状态(兼容多字段命名) */
export function resolveInstanceStatusRaw(row) {
  if (!row || typeof row !== "object") return "";
  const candidates = [
    row.status,
    row.statusRaw,
    row.approvalStatus,
    row.statusName,
    row.statusLabel,
    row.approvalStatusName,
    row.statusDesc,
    row.instanceStatus,
    row.approvalInstanceStatus,
    row.approveStatus,
    row.auditStatus,
    row.approvalInstance?.status,
    row.approvalInstanceVo?.status,
  ];
  for (const c of candidates) {
    if (c != null && c !== "") return c;
  }
  const tasks = row.tasks;
  if (Array.isArray(tasks) && tasks.length) {
    const rejected = tasks.some(t => normalizeApprovalStatusKey(t?.status ?? t?.taskStatus) === "rejected");
    if (rejected) return "REJECTED";
    const allApproved = tasks.every(t => normalizeApprovalStatusKey(t?.status ?? t?.taskStatus) === "approved");
    if (allApproved) return "APPROVED";
  }
  return "";
}
/** æäº¤å¼¹çª—:模板卡片(来自后端列表) */
export function mapSubmitTemplateCard(row) {
  const cfg = parseFormConfigToData(row?.formConfig);
  return {
    id: row?.id,
    key: String(row?.id ?? ""),
    businessType: row?.businessType ?? cfg.approvalType ?? row?.approvalType ?? "",
    approvalType: cfg.approvalType || row?.approvalType || "",
    label: row?.templateName || "—",
    summaryPlaceholder: (row?.description || "").trim() || cfg.summaryPlaceholder || "点击填写并提交",
  };
}
export function matchBusinessTypeValue(a, b) {
  if (a == null || a === "" || b == null || b === "") return false;
  return a === b || a === Number(b) || Number(a) === b || String(a) === String(b);
}
/** å®¡æ‰¹è®°å½• approveAction â†’ é¡µé¢ result */
export function mapRecordResultFromApi(action) {
  const s = String(action || "").toUpperCase();
  if (s === "APPROVED" || s === "APPROVE" || s === "PASS") return "approved";
  if (s === "REJECTED" || s === "REJECT" || s === "REFUSE") return "rejected";
  return "pending";
}
/** åŽç«¯ records â†’ æ—¶é—´çº¿å±•示结构 */
export function mapRecordsFromApi(records) {
  const list = Array.isArray(records) ? records : [];
  return list.map(r => ({
    id: r.id,
    operatorName: r.approverName || r.operatorName || r.createUserName || "",
    result: mapRecordResultFromApi(r.approveAction ?? r.action ?? r.status),
    opinion: r.approveComment || r.comment || r.opinion || "",
    time: formatDisplayTime(r.approveTime || r.createTime || r.time || ""),
    raw: r,
  }));
}
export function mapTaskStatusLabel(status) {
  return approvalStatusLabel(status);
}
export function mapTaskStatusTagType(status) {
  return approvalStatusTagType(status);
}
/** åŽç«¯ tasks â†’ é¡µé¢ flowNodes(按 levelNo åˆ†ç»„,供流程编辑/展示) */
export function mapTasksToFlowNodes(tasks) {
  const list = Array.isArray(tasks) ? tasks : [];
  if (!list.length) return [];
  const byLevel = new Map();
  list.forEach(t => {
    const level = Number(t.levelNo ?? t.taskLevel ?? t.nodeOrder ?? 1);
    if (!byLevel.has(level)) {
      byLevel.set(level, {
        id: t.nodeId,
        templateId: t.templateId,
        nodeOrder: level,
        signMode: mapSignModeFromApi(t.approveType),
        approvers: [],
        tasks: [],
      });
    }
    const node = byLevel.get(level);
    node.approvers.push({
      id: t.id,
      nodeId: t.nodeId,
      templateId: t.templateId,
      approverId: t.approverId,
      approverName: t.approverName || "",
      status: t.status,
      approveComment: t.approveComment,
      approveTime: t.approveTime,
    });
    node.tasks.push(t);
    if (t.approveType != null) {
      node.signMode = mapSignModeFromApi(t.approveType);
    }
  });
  return [...byLevel.entries()].sort(([a], [b]) => a - b).map(([, node]) => node);
}
/** é¡µé¢ flowNodes â†’ åŽç«¯ tasks */
export function mapFlowNodesToTasks(flowNodes, { instanceId, templateId } = {}) {
  const nodes = normalizeFlowNodes(flowNodes);
  const tasks = [];
  nodes.forEach(n => {
    const levelNo = n.nodeOrder ?? 1;
    const approveType = mapSignModeToApi(n.signMode);
    n.approvers.forEach((a, idx) => {
      const task = {
        levelNo,
        approveType,
        approverId: a.approverId,
        approverName: a.approverName || "",
        sortNo: a.sortNo ?? idx + 1,
      };
      if (a.id != null) task.id = a.id;
      if (a.nodeId != null) task.nodeId = a.nodeId;
      if (a.templateId != null) task.templateId = a.templateId;
      else if (templateId) task.templateId = templateId;
      if (instanceId) task.instanceId = instanceId;
      if (a.status != null) task.status = a.status;
      tasks.push(task);
    });
  });
  return tasks;
}
function guessFieldTypeFromValue(val) {
  if (Array.isArray(val) && val.length === 2) return "datetimerange";
  if (typeof val === "number") return "number";
  if (typeof val === "string" && /^\d{4}-\d{2}-\d{2}$/.test(val)) return "date";
  if (typeof val === "string" && val.length > 100) return "textarea";
  return "text";
}
/**
 * å•字段展示值(详情只读、列表主表)
 * @param {object} [caches] äººå‘˜/部门下拉缓存,用于解析「人员列表」类字段为姓名
 */
export function formatFieldDisplayValue(field, val, caches) {
  if (val == null || val === "" || (Array.isArray(val) && !val.length)) return "—";
  if (field?.type === "select" && isDynamicOptionSource(field.optionSource)) {
    const label = resolveSelectDisplayLabel(field, val, caches || {});
    if (label && label !== "—") return label;
    return String(val);
  }
  if (field?.type === "select" && field.options?.length) {
    const hit = field.options.find(o => String(o.value) === String(val));
    return hit?.label || String(val);
  }
  if (Array.isArray(val)) return val.join(" è‡³ ");
  return String(val);
}
/**
 * ä»Žè¡Œæ•°æ® / formConfig è§£æžå¡«æŠ¥å­—段定义与 formPayload(与新增提交结构一致)
 */
export function resolveInstanceFormFields(row) {
  const cfg = parseInstanceFormConfig(row?.formConfig);
  let fields = (row?.formFieldDefs?.length ? row.formFieldDefs : cfg.fields) || [];
  const formPayload = {
    ...(fields.length ? buildFormPayloadFromFields(fields) : {}),
    ...cfg.formPayload,
    ...(row?.formPayload || {}),
  };
  if (!fields.length && Object.keys(formPayload).length) {
    fields = Object.keys(formPayload)
      .filter(k => k && k !== "summary")
      .map(k => ({
        key: k,
        label: k,
        type: guessFieldTypeFromValue(formPayload[k]),
        required: false,
        rows: 3,
        min: 0,
        precision: 0,
        options: [],
      }));
  }
  const templateSnapshot = {
    label: row?.templateName || row?.title || "审批",
    approvalType: cfg.approvalType || row?.approvalType || "",
    summaryPlaceholder: cfg.summaryPlaceholder || "",
    templateId: row?.templateId,
    fields,
  };
  return { fields, formPayload, templateSnapshot, formConfigData: cfg };
}
/** è§£æžå®žä¾‹ formConfig */
export function parseInstanceFormConfig(formConfig) {
  let raw = {};
  if (formConfig) {
    if (typeof formConfig === "object") raw = formConfig;
    else {
      try {
        raw = JSON.parse(formConfig);
      } catch {
        raw = {};
      }
    }
  }
  const data = parseFormConfigToData(formConfig);
  const payload = raw.formPayload;
  return {
    summaryPlaceholder: raw.summaryPlaceholder || data.summaryPlaceholder || "",
    approvalType: raw.approvalType || "",
    fields: data.fields || [],
    formPayload: payload && typeof payload === "object" ? payload : {},
  };
}
export function unwrapInstanceDetail(res) {
  const data = res?.data ?? res;
  if (!data || typeof data !== "object") return {};
  if (data.id != null || data.instanceNo) return data;
  if (data.approvalInstanceVo) return data.approvalInstanceVo;
  return data;
}
/** å¡«æŠ¥å†…容 + æ¨¡æ¿å­—段定义 â†’ formConfig JSON */
export function buildInstanceFormConfigJson(templateSnapshot, formPayload) {
  const payload = formPayload || {};
  return JSON.stringify({
    summaryPlaceholder: templateSnapshot?.summaryPlaceholder || "",
    approvalType: templateSnapshot?.approvalType || "",
    fields: templateSnapshot?.fields || [],
    formPayload: payload,
  });
}
/** ç»„装保存/更新审批 DTO */
export function buildInstanceDto({ submitForm, activeTemplate, userStore, flowNodes, existingRow }) {
  const payload = submitForm?.formPayload || {};
  const tpl = activeTemplate || {};
  const title = String(payload.summary || payload.title || "").trim() || tpl.label || submitForm?.templateName || "审批申请";
  const templateId = submitForm?.templateId || tpl.templateId;
  const instanceId = existingRow?.id ?? submitForm?.instanceId;
  const taskList = mapFlowNodesToTasks(flowNodes || submitForm?.flowNodes, {
    instanceId,
    templateId,
  });
  const isUpdate = Boolean(instanceId);
  const dto = {
    templateId,
    templateName: submitForm?.templateName || tpl.label || "",
    businessType: tpl.businessType ?? submitForm?.businessType ?? "",
    title,
    formConfig: buildInstanceFormConfigJson({ ...tpl, fields: tpl.fields || submitForm?.formFieldDefs }, payload),
    tasks: taskList,
  };
  const attachments = (Array.isArray(submitForm?.storageBlobDTOs) && submitForm.storageBlobDTOs.length ? submitForm.storageBlobDTOs : null) || tpl.storageBlobDTOs;
  if (attachments?.length) dto.storageBlobDTOs = attachments;
  if (isUpdate) {
    dto.id = existingRow?.id ?? submitForm?.instanceId;
    dto.instanceNo = existingRow?.instanceNo ?? submitForm?.instanceNo ?? "";
    dto.status = submitForm?.saveStatusApi || existingRow?.statusRaw || mapInstanceStatusToApi(existingRow?.approvalStatus) || "PENDING";
    dto.currentLevel = existingRow?.currentLevel ?? submitForm?.currentLevel ?? 1;
    dto.applicantId = existingRow?.applicantId ?? existingRow?.applicantNo;
    dto.applicantName = existingRow?.applicantName || "";
  } else {
    dto.status = submitForm?.saveStatusApi || "PENDING";
    dto.currentLevel = 1;
    dto.applicantId = userStore?.id;
    dto.applicantName = userStore?.nickName || userStore?.name || "";
  }
  return dto;
}
/** æ ¡éªŒæäº¤å®¡æ‰¹æµç¨‹ï¼ˆä¸Žæ¨¡æ¿é¡µè§„则一致) */
export function validateSubmitFlowNodes(flowNodes) {
  const nodes = normalizeFlowNodes(flowNodes);
  if (!nodes.length) return { ok: false, message: "请至少配置一个审批节点" };
  for (let i = 0; i < nodes.length; i++) {
    if (!nodes[i].approvers.length) {
      return { ok: false, message: `请为第 ${i + 1} ä¸ªèŠ‚ç‚¹é€‰æ‹©è‡³å°‘ä¸€åå®¡æ‰¹äºº` };
    }
  }
  return { ok: true, nodes };
}
/** åŽç«¯ status â†’ é¡µé¢ approvalStatus */
export function mapInstanceStatusFromApi(status) {
  return normalizeApprovalStatusKey(status);
}
/** åˆ—表/详情行 â†’ é¡µé¢ approvalStatus key */
export function mapInstanceApprovalStatusFromRow(row) {
  const raw = resolveInstanceStatusRaw(row);
  return normalizeApprovalStatusKey(raw);
}
/** é¡µé¢ approvalStatus â†’ åŽç«¯ status */
export function mapInstanceStatusToApi(approvalStatus) {
  const key = normalizeApprovalStatusKey(approvalStatus);
  const hit = APPROVAL_STATUS_OPTIONS.find(x => x.value === key);
  return hit?.api || "PENDING";
}
export function unwrapInstancePage(res) {
  const data = res?.data ?? res;
  return {
    records: Array.isArray(data?.records) ? data.records : [],
    total: Number(data?.total ?? 0),
  };
}
/** åˆ†é¡µåˆ—表项 â†’ è¡¨æ ¼è¡Œ */
export function mapInstanceFromApi(row) {
  if (!row) return {};
  const statusRaw = resolveInstanceStatusRaw(row);
  const approvalStatus = normalizeApprovalStatusKey(statusRaw);
  const createTime = formatDisplayTime(row.createTime ?? row.applyTime ?? "");
  const applyTime = formatDisplayTime(row.applyTime ?? "");
  const finishTime = formatDisplayTime(row.finishTime ?? "");
  const resolved = resolveInstanceFormFields(row);
  const { fields, formPayload, templateSnapshot } = resolved;
  const tasks = Array.isArray(row.tasks) ? row.tasks : [];
  const flowNodes = tasks.length ? mapTasksToFlowNodes(tasks) : mapNodesFromApi(row.nodes || row.flowNodes);
  const approvalRecords = mapRecordsFromApi(row.records);
  return {
    id: row.id,
    bizId: row.instanceNo || String(row.id ?? ""),
    instanceNo: row.instanceNo || "",
    templateId: row.templateId,
    templateName: row.templateName || "",
    businessId: row.businessId,
    businessType: row.businessType,
    businessName: row.businessName || "",
    applicantId: row.applicantId,
    applicantNo: row.applicantId != null ? String(row.applicantId) : "",
    applicantName: row.applicantName || "",
    approvalType: row.approvalType || row.templateName || "",
    unread: Boolean(row.isApprove) && approvalStatus === "pending",
    isApprove: Boolean(row.isApprove),
    approvalStatus,
    statusRaw: statusRaw || row.status,
    createTime,
    applyTime: applyTime === "—" ? "" : applyTime,
    finishTime: finishTime === "—" ? "" : finishTime,
    title: row.title || "",
    summary: row.title || row.templateName || "",
    currentLevel: row.currentLevel,
    formConfig: row.formConfig,
    formPayload,
    formFieldDefs: fields,
    templateSnapshot,
    tasks,
    records: Array.isArray(row.records) ? row.records : [],
    storageBlobVOList: row.storageBlobVOList || [],
    storageBlobDTOs: row.storageBlobVOList || row.storageBlobDTOs || [],
    flowNodes,
    approvalFlowNodes: [],
    currentNodeIndex: 0,
    approvalRecords,
    rejectReason: approvalRecords.find(r => r.result === "rejected")?.opinion || "",
    purchaseContractNumber: row.purchaseContractNumber || "",
    quotationNo: row.quotationNo || "",
    shippingNo: row.shippingNo || "",
  };
}
/** å®¡æ‰¹æ“ä½œï¼šä¸ŽåŽç«¯ status æžšä¸¾ä¸€è‡´ */
export const APPROVE_ACTION_APPROVED = "APPROVED";
export const APPROVE_ACTION_REJECTED = "REJECTED";
/** é¡µé¢æ“ä½œ â†’ approveAction */
export function mapApproveActionToApi(uiResult) {
  return uiResult === "rejected" ? APPROVE_ACTION_REJECTED : APPROVE_ACTION_APPROVED;
}
/** ç»„装审批提交 DTO */
export function buildApproveInstanceDto(row, uiResult, comment) {
  const opinion = (comment || "").trim();
  return {
    id: row?.id,
    approveAction: mapApproveActionToApi(uiResult),
    approveComment: opinion || (uiResult === "approved" ? "同意" : ""),
  };
}
export function buildApprovalInstanceListParams({ page, searchForm, businessType, extraParams }) {
  const dto = buildApprovalInstanceSearchDto(searchForm, extraParams);
  const bizType = businessType ?? searchForm?.businessType;
  if (bizType != null && bizType !== "") {
    dto.businessType = bizType;
  }
  const params = {
    current: page.current,
    size: page.size,
    "page.current": page.current,
    "page.size": page.size,
    ...dto,
  };
  appendDotNotationQuery(params, "approvalInstanceDto", dto);
  return params;
}
export function approvalTypeLabel(v) {
  return APPROVAL_TYPE_OPTIONS.find(x => x.value === v)?.label || v || "—";
}
export function approvalTypeStyle(v) {
  const hit = APPROVAL_TYPE_OPTIONS.find(x => x.value === v);
  if (!hit) return {};
  return {
    backgroundColor: hit.cellBg,
    color: hit.cellColor,
    border: hit.border || "none",
  };
}
export function approvalStatusLabel(v) {
  const key = normalizeApprovalStatusKey(v);
  return APPROVAL_STATUS_OPTIONS.find(x => x.value === key)?.label || "—";
}
/** ä¸šåŠ¡ç”³è¯·é¡µçŠ¶æ€æ–‡æ¡ˆï¼šPENDING→进行中 APPROVED→已完成 REJECTED→已驳回 */
export function businessApprovalStatusLabel(v) {
  const key = normalizeApprovalStatusKey(v);
  if (key === "draft") return "草稿";
  if (key === "pending") return "进行中";
  if (key === "approved") return "已完成";
  if (key === "rejected") return "已驳回";
  if (key === "cancelled") return "已撤销";
  return "—";
}
/**
 * ä¸šåŠ¡ç”³è¯·é¡µæ˜¯å¦å…è®¸ä¿®æ”¹ï¼ˆäº”ä¸ªç”³è¯·é¡µï¼‰
 * è¿›è¡Œä¸­(PENDING)、已完成(APPROVED) ä¸å¯ä¿®æ”¹ï¼›å·²é©³å›žã€å·²æ’¤é”€ç­‰å¯ä¿®æ”¹
 */
export function canEditBusinessInstanceRow(row) {
  const key = normalizeApprovalStatusKey(row?.approvalStatus ?? row?.statusRaw ?? row?.status);
  return key !== "pending" && key !== "approved";
}
export function businessApprovalStatusTagType(v) {
  const key = normalizeApprovalStatusKey(v);
  if (key === "draft") return "info";
  if (key === "approved") return "success";
  if (key === "rejected") return "danger";
  if (key === "cancelled") return "info";
  return "warning";
}
export function approvalStatusTagType(v) {
  const key = normalizeApprovalStatusKey(v);
  if (key === "draft") return "info";
  if (key === "approved") return "success";
  if (key === "rejected") return "danger";
  if (key === "cancelled") return "info";
  return "warning";
}
/** åˆ—表行 â†’ ç¼–辑表单(仅用行数据回显) */
export function buildEditFormFromInstanceRow(row) {
  const { fields, formPayload, templateSnapshot } = resolveInstanceFormFields(row);
  const normalized = normalizeFlowNodes(row?.flowNodes?.length ? row.flowNodes : mapTasksToFlowNodes(row?.tasks));
  const flowNodes = normalized.length ? JSON.parse(JSON.stringify(normalized)) : [createEmptyNode(1)];
  return {
    templateKey: String(row?.templateId || ""),
    templateId: row?.templateId,
    templateName: row?.templateName || templateSnapshot.label,
    instanceId: row?.id,
    instanceNo: row?.instanceNo || "",
    statusRaw: row?.statusRaw || row?.status || "PENDING",
    currentLevel: row?.currentLevel ?? 1,
    applicantId: row?.applicantId,
    applicantName: row?.applicantName || "",
    templateSnapshot,
    formFieldDefs: fields,
    formPayload,
    flowNodes,
    templateAttachments: initTemplateAttachmentsFromSnapshot(templateSnapshot),
    storageBlobDTOs: (row?.storageBlobDTOs?.length ? row.storageBlobDTOs : row?.storageBlobVOList || []).map(f => JSON.parse(JSON.stringify(f))),
  };
}
export function createEmptySubmitForm(templateKey, templateOverride, flowNodesOverride) {
  const tpl = templateOverride || null;
  const payload = tpl?.fields?.length ? buildFormPayloadFromFields(tpl.fields) : { summary: "" };
  const normalized = normalizeFlowNodes(flowNodesOverride);
  const flowNodes = normalized.length ? JSON.parse(JSON.stringify(normalized)) : [createEmptyNode(1)];
  return {
    templateKey: templateKey || "",
    templateId: tpl?.templateId || "",
    templateName: tpl?.label || "",
    instanceId: "",
    instanceNo: "",
    statusRaw: "",
    currentLevel: 1,
    applicantId: null,
    applicantName: "",
    templateSnapshot: templateOverride || null,
    formFieldDefs: tpl?.fields || [],
    formPayload: payload,
    flowNodes,
    templateAttachments: tpl?.storageBlobDTOs ? JSON.parse(JSON.stringify(tpl.storageBlobDTOs)) : [],
    storageBlobDTOs: [],
  };
}
export function initTemplateAttachmentsFromSnapshot(templateSnapshot) {
  const list = templateSnapshot?.storageBlobDTOs;
  return list?.length ? JSON.parse(JSON.stringify(list)) : [];
}