| | |
| | | return roles; |
| | | } |
| | | |
| | | export function buildAutoApprovalFlow(amount, expenseCategory) { |
| | | export function buildAutoApprovalFlow(amount, expenseCategory, previousNodes = []) { |
| | | const roles = resolveApprovalRoles(amount, expenseCategory); |
| | | return roles.map((role, i) => ({ |
| | | approverId: null, |
| | | approverName: APPROVAL_ROLE_LABELS[role] || role, |
| | | roleKey: role, |
| | | sortOrder: i + 1, |
| | | nodeOrder: i + 1, |
| | | nodeStatus: i === 0 ? "process" : "wait", |
| | | approveOpinion: "", |
| | | approveTime: "", |
| | | })); |
| | | const prevByRole = new Map(); |
| | | (previousNodes || []).forEach((n, idx) => { |
| | | if (n?.roleKey) prevByRole.set(n.roleKey, n); |
| | | else if (n?.approverId != null && n.approverId !== "") { |
| | | prevByRole.set(`__idx_${idx}`, n); |
| | | } |
| | | }); |
| | | return roles.map((role, i) => { |
| | | const prev = prevByRole.get(role) || prevByRole.get(`__idx_${i}`); |
| | | const hasApprover = prev?.approverId != null && prev.approverId !== ""; |
| | | return { |
| | | approverId: hasApprover ? prev.approverId : null, |
| | | approverName: hasApprover |
| | | ? prev.approverName || "" |
| | | : APPROVAL_ROLE_LABELS[role] || role, |
| | | roleKey: role, |
| | | signMode: prev?.signMode || "countersign", |
| | | sortOrder: i + 1, |
| | | nodeOrder: i + 1, |
| | | nodeStatus: i === 0 ? "process" : "wait", |
| | | approveOpinion: "", |
| | | approveTime: "", |
| | | }; |
| | | }); |
| | | } |
| | | |
| | | export function getApprovalRuleHint(amount, expenseCategory) { |
| | |
| | | resolveReimbursementDeleteId, |
| | | unwrapFinReimbursementDetail, |
| | | unwrapFinReimbursementPage, |
| | | validateReimbursementApprovalNodes, |
| | | validateReimbursementPersistDto, |
| | | } from "../shared/finReimbursementMappers.js"; |
| | | import { consumeReimburseEditFromApprove } from "../shared/reimburseApproveBridge.js"; |
| | |
| | | |
| | | function autoAssignApprovalFlow() { |
| | | const amount = Number(form.applyAmount) || detailTotalAmount.value || 0; |
| | | form.approvalFlowNodes = buildAutoApprovalFlow(amount, form.expenseCategory || "other"); |
| | | form.approvalFlowNodes = buildAutoApprovalFlow( |
| | | amount, |
| | | form.expenseCategory || "other", |
| | | form.approvalFlowNodes |
| | | ); |
| | | nextTick(() => formRef.value?.validateField?.("approvalFlowNodes")); |
| | | } |
| | | |
| | |
| | | return; |
| | | } |
| | | syncApplyAmountFromDetails(); |
| | | autoAssignApprovalFlow(); |
| | | |
| | | if (submitSaving.value) return; |
| | | const isEdit = formDialog.mode === "edit"; |
| | |
| | | proxy?.$modal?.msgWarning?.(check.message); |
| | | return; |
| | | } |
| | | const nodeCheck = validateReimbursementApprovalNodes(dto); |
| | | if (!nodeCheck.ok) { |
| | | proxy?.$modal?.msgWarning?.(nodeCheck.message); |
| | | return; |
| | | } |
| | | submitSaving.value = true; |
| | | try { |
| | | await persistFinReimbursement(dto, isEdit); |
| | |
| | | }); |
| | | } |
| | | |
| | | /** 表单上的审批流(兼容 approvalFlowNodes / nodes / flowNodes) */ |
| | | export function resolveFormApprovalFlowNodes(form) { |
| | | const list = |
| | | form?.approvalFlowNodes ?? form?.nodes ?? form?.flowNodes ?? []; |
| | | return Array.isArray(list) ? list : []; |
| | | } |
| | | |
| | | /** 页面审批节点 → 接口 nodes */ |
| | | export function mapApprovalFlowNodesToApi(nodes = [], templateId) { |
| | | const list = Array.isArray(nodes) ? nodes : []; |
| | |
| | | if (Array.isArray(n.approvers) && n.approvers.length) { |
| | | approvers = n.approvers |
| | | .filter((a) => a?.approverId != null && a.approverId !== "") |
| | | .map((a, idx) => ({ |
| | | id: a.id, |
| | | nodeId: a.nodeId, |
| | | templateId: a.templateId ?? templateId, |
| | | approverId: toNumber(a.approverId) ?? a.approverId, |
| | | approverName: a.approverName || "", |
| | | sortNo: a.sortNo ?? idx + 1, |
| | | })); |
| | | .map((a, idx) => { |
| | | const item = { |
| | | approverId: toNumber(a.approverId) ?? a.approverId, |
| | | approverName: a.approverName || "", |
| | | sortNo: a.sortNo ?? idx + 1, |
| | | }; |
| | | if (a.id != null) item.id = a.id; |
| | | if (a.nodeId != null) item.nodeId = a.nodeId; |
| | | if (a.templateId != null) item.templateId = a.templateId; |
| | | else if (templateId != null) item.templateId = templateId; |
| | | if (a.roleKey) item.roleKey = a.roleKey; |
| | | return item; |
| | | }); |
| | | } else if (n.approverId != null && n.approverId !== "") { |
| | | approvers = [ |
| | | { |
| | | approverId: toNumber(n.approverId) ?? n.approverId, |
| | | approverName: n.approverName || "", |
| | | sortNo: 1, |
| | | }, |
| | | ]; |
| | | const item = { |
| | | approverId: toNumber(n.approverId) ?? n.approverId, |
| | | approverName: n.approverName || "", |
| | | sortNo: 1, |
| | | }; |
| | | if (n.roleKey) item.roleKey = n.roleKey; |
| | | approvers = [item]; |
| | | } |
| | | if (!approvers.length) return null; |
| | | |
| | | const node = { |
| | | levelNo: n.levelNo ?? n.nodeOrder ?? i + 1, |
| | | levelNo: n.levelNo ?? n.nodeOrder ?? n.sortOrder ?? i + 1, |
| | | approveType: n.approveType || mapSignModeToApi(n.signMode), |
| | | approvers, |
| | | }; |
| | | if (n.id != null) node.id = n.id; |
| | | if (n.templateId != null) node.templateId = n.templateId; |
| | | else if (templateId != null) node.templateId = templateId; |
| | | if (n.roleKey) node.roleKey = n.roleKey; |
| | | return node; |
| | | }) |
| | | .filter(Boolean); |
| | | } |
| | | |
| | | /** 保存前校验 nodes 已配置 */ |
| | | export function validateReimbursementApprovalNodes(dto) { |
| | | if (Array.isArray(dto?.nodes) && dto.nodes.length > 0) { |
| | | return { ok: true }; |
| | | } |
| | | return { ok: false, message: "请配置审批流程并选择审批人" }; |
| | | } |
| | | |
| | | function mapDetailsToApi(details = []) { |
| | |
| | | withinStandard: form.needSpecialApproval ? "0" : "1", |
| | | }, |
| | | details, |
| | | nodes: mapApprovalFlowNodesToApi(form.approvalFlowNodes, form.templateId), |
| | | nodes: mapApprovalFlowNodesToApi( |
| | | resolveFormApprovalFlowNodes(form), |
| | | form.templateId |
| | | ), |
| | | }; |
| | | |
| | | const id = resolveReimbursementId(form); |
| | |
| | | billStatus: "IN_APPROVAL", |
| | | deptId: toNumber(form.deptId), |
| | | details, |
| | | nodes: mapApprovalFlowNodesToApi(form.approvalFlowNodes, form.templateId), |
| | | nodes: mapApprovalFlowNodesToApi( |
| | | resolveFormApprovalFlowNodes(form), |
| | | form.templateId |
| | | ), |
| | | }; |
| | | |
| | | const id = resolveReimbursementId(form); |
| | |
| | | resolveReimbursementDeleteId, |
| | | unwrapFinReimbursementDetail, |
| | | unwrapFinReimbursementPage, |
| | | validateReimbursementApprovalNodes, |
| | | validateReimbursementPersistDto, |
| | | } from "../shared/finReimbursementMappers.js"; |
| | | import { consumeReimburseEditFromApprove } from "../shared/reimburseApproveBridge.js"; |
| | |
| | | proxy?.$modal?.msgWarning?.(check.message); |
| | | return; |
| | | } |
| | | const nodeCheck = validateReimbursementApprovalNodes(dto); |
| | | if (!nodeCheck.ok) { |
| | | proxy?.$modal?.msgWarning?.(nodeCheck.message); |
| | | return; |
| | | } |
| | | submitSaving.value = true; |
| | | try { |
| | | await persistFinReimbursement(dto, isEdit); |