import { createEmptyNode, formatDisplayTime, mapNodesFromApi, mapSignModeFromApi, mapSignModeToApi, normalizeFlowNodes, nodeSignModeLabel, } from "../approve-template/approveTemplateConstants.js"; import { buildFormPayloadFromFields, parseFormConfigToData } from "../approve-template/formConfigUtils.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" }, ]; /** 审批状态 approvalStatus */ export const APPROVAL_STATUS_OPTIONS = [ { value: "pending", label: "审核中" }, { value: "approved", label: "已通过" }, { value: "rejected", label: "已驳回" }, { value: "cancelled", label: "已撤销" }, ]; export const LEGACY_APPROVE_LIST_STORAGE_KEY = "oa_unified_approve_list_v1"; export function clearLegacyApproveListStorage() { try { localStorage.removeItem(LEGACY_APPROVE_LIST_STORAGE_KEY); } catch { /* ignore */ } } /** 提交弹窗:模板卡片(来自后端列表) */ export function mapSubmitTemplateCard(row) { const cfg = parseFormConfigToData(row?.formConfig); return { id: row?.id, key: String(row?.id ?? ""), approvalType: cfg.approvalType || row?.approvalType || "", label: row?.templateName || "—", summaryPlaceholder: (row?.description || "").trim() || cfg.summaryPlaceholder || "点击填写并提交", }; } /** 审批记录 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) { const s = String(status || "").toUpperCase(); if (s === "APPROVED") return "已通过"; if (s === "REJECTED") return "已驳回"; if (s === "PENDING") return "待审批"; if (s === "CANCELLED") return "已撤销"; return status || "—"; } export function mapTaskStatusTagType(status) { const s = String(status || "").toUpperCase(); if (s === "APPROVED") return "success"; if (s === "REJECTED") return "danger"; if (s === "CANCELLED") return "info"; return "warning"; } /** 后端 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"; } /** 单字段展示值(详情只读) */ export function formatFieldDisplayValue(field, val) { if (val == null || val === "" || (Array.isArray(val) && !val.length)) return "—"; 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 || "", title, formConfig: buildInstanceFormConfigJson({ ...tpl, fields: tpl.fields || submitForm?.formFieldDefs }, payload), tasks: taskList, }; if (isUpdate) { dto.id = existingRow?.id ?? submitForm?.instanceId; dto.instanceNo = existingRow?.instanceNo ?? submitForm?.instanceNo ?? ""; dto.status = 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 = "PENDING"; dto.currentLevel = 1; dto.applicantId = userStore?.id; dto.applicantName = userStore?.nickName || userStore?.name || ""; } return dto; } /** @deprecated 使用 buildInstanceDto */ export function buildSaveInstanceDto(params) { return buildInstanceDto(params); } /** 校验提交审批流程(与模板页规则一致) */ 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) { const s = String(status || "").toUpperCase(); if (s === "APPROVED") return "approved"; if (s === "REJECTED") return "rejected"; if (s === "CANCELLED") return "cancelled"; return "pending"; } /** 页面 approvalStatus → 后端 status */ export function mapInstanceStatusToApi(approvalStatus) { const s = String(approvalStatus || "").toLowerCase(); if (s === "approved") return "APPROVED"; if (s === "rejected") return "REJECTED"; if (s === "cancelled") return "CANCELLED"; return "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 approvalStatus = mapInstanceStatusFromApi(row.status); 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, applicantId: row.applicantId, applicantNo: row.applicantId != null ? String(row.applicantId) : "", applicantName: row.applicantName || "", approvalType: row.templateName || "", unread: Boolean(row.isApprove) && approvalStatus === "pending", isApprove: Boolean(row.isApprove), approvalStatus, 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 : [], flowNodes, approvalFlowNodes: [], currentNodeIndex: 0, approvalRecords, rejectReason: approvalRecords.find((r) => r.result === "rejected")?.opinion || "", }; } /** 审批操作:与后端 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 }) { const params = { current: page.current, size: page.size, }; const dto = {}; const kw = (searchForm?.applicantKeyword || "").trim(); if (kw) dto.applicantName = kw; if (searchForm?.approvalType) { const opt = APPROVAL_TYPE_OPTIONS.find((x) => x.value === searchForm.approvalType); if (opt?.label) dto.templateName = opt.label; } if (Object.keys(dto).length) 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) { return APPROVAL_STATUS_OPTIONS.find((x) => x.value === v)?.label || "—"; } export function approvalStatusTagType(v) { if (v === "approved") return "success"; if (v === "rejected") return "danger"; if (v === "cancelled") return "info"; return "primary"; } export function unreadLabel(v) { return v ? "是" : "否"; } /** 列表行 → 编辑表单(仅用行数据回显) */ 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, }; } 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, }; }