import dayjs from "dayjs"; /** 费用科目 */ export const EXPENSE_SUBJECT_OPTIONS = [ { label: "交通费", value: "transport" }, { label: "住宿费", value: "hotel" }, { label: "餐饮费", value: "meal" }, { label: "其他", value: "other" }, ]; const TIER1_CITIES = ["北京", "上海", "广州", "深圳"]; export function expenseSubjectLabel(v) { return EXPENSE_SUBJECT_OPTIONS.find((x) => x.value === v)?.label || "—"; } export function statusLabel(v) { if (v === "approved") return "通过"; if (v === "rejected") return "驳回"; return "审核中"; } export function statusTagType(v) { if (v === "approved") return "success"; if (v === "rejected") return "danger"; return "warning"; } export function detectTravelTier(destination) { const city = (destination || "").trim(); if (!city) return "tier3"; if (TIER1_CITIES.some((c) => city.includes(c))) return "tier1"; const tier2Keywords = ["杭州", "南京", "武汉", "成都", "重庆", "西安", "天津", "苏州", "长沙", "郑州"]; if (tier2Keywords.some((c) => city.includes(c))) return "tier2"; return "tier3"; } export function getTravelStandardByTier(tier) { const map = { tier1: { hotelPerNight: 600, transportPerDay: 80, mealPerDay: 100, label: "一线城市" }, tier2: { hotelPerNight: 450, transportPerDay: 60, mealPerDay: 80, label: "二线城市" }, tier3: { hotelPerNight: 350, transportPerDay: 40, mealPerDay: 60, label: "其他城市" }, }; return map[tier] || map.tier3; } export function computeTravelDays(startStr, endStr) { if (!startStr || !endStr) return null; const t0 = dayjs(startStr); const t1 = dayjs(endStr); if (!t0.isValid() || !t1.isValid() || !t1.isAfter(t0)) return null; const days = Math.ceil(t1.diff(t0, "day", true)); return Math.max(1, days); } export function createEmptyExpenseDetail() { return { id: `ed_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`, invoiceDate: "", expenseSubject: "", amount: undefined, description: "", }; } export function createEmptyForm() { return { id: undefined, reimburseNo: "", applicantId: "", employeeNo: "", employeeName: "", reimburseReason: "", travelStartTime: "", travelEndTime: "", travelDays: undefined, departurePlace: "", destination: "", hotelStandard: undefined, hotelDays: undefined, livingSubsidy: undefined, applyAmount: undefined, payee: "", expenseDetails: [], attachmentList: [], approvalFlowNodes: [], currentNodeIndex: 0, needSpecialApproval: false, deptId: "", deptName: "", travelTier: "tier3", }; } export function initApprovalFlowNodes(nodes) { return (nodes || []).map((n, i) => ({ ...n, sortOrder: i + 1, nodeOrder: i + 1, nodeStatus: i === 0 ? "process" : "wait", approveOpinion: n.approveOpinion || "", approveTime: n.approveTime || "", })); } export function advanceApprovalFlow(row, opinion) { const nodes = [...(row.approvalFlowNodes || [])]; const idx = row.currentNodeIndex ?? 0; if (!nodes.length) return { nodes, currentNodeIndex: idx, approvalResult: row.approvalResult }; const now = dayjs().format("YYYY-MM-DD HH:mm:ss"); nodes[idx] = { ...nodes[idx], nodeStatus: "finish", approveOpinion: opinion || "同意", approveTime: now, }; const next = idx + 1; if (next >= nodes.length) { return { nodes, currentNodeIndex: idx, approvalResult: "approved" }; } nodes[next] = { ...nodes[next], nodeStatus: "process" }; return { nodes, currentNodeIndex: next, approvalResult: "pending" }; } export function rejectApprovalFlow(row, opinion) { const nodes = [...(row.approvalFlowNodes || [])]; const idx = row.currentNodeIndex ?? 0; const now = dayjs().format("YYYY-MM-DD HH:mm:ss"); if (nodes[idx]) { nodes[idx] = { ...nodes[idx], nodeStatus: "error", approveOpinion: opinion || "驳回", approveTime: now, }; } return { nodes, currentNodeIndex: idx, approvalResult: "rejected", rejectReason: opinion || "驳回" }; } /** 模拟部门预算(与预算系统联动占位) */ export function mockDeptBudget(deptId) { const id = String(deptId || "default"); let s = 0; for (let i = 0; i < id.length; i++) s += id.charCodeAt(i); const total = 500000 + (s % 200) * 1000; const used = (s % 80) * 3500; return { deptId: id, totalBudget: total, usedAmount: used, remainingAmount: Math.max(0, total - used), }; } export function normalizeImportedRow(raw, idx) { const id = raw.id != null && String(raw.id).length ? `imp_${String(raw.id)}_${idx}` : `imp_${Date.now()}_${idx}`; const travelDays = raw.travelDays != null ? Number(raw.travelDays) : computeTravelDays(raw.travelStartTime, raw.travelEndTime); return { id, reimburseNo: raw.reimburseNo || `TR${dayjs().format("YYYYMMDD")}${String(idx).padStart(4, "0")}`, applicantId: raw.applicantId != null ? String(raw.applicantId) : `imp_user_${idx}`, employeeNo: raw.employeeNo ?? raw.applicantNo ?? "", employeeName: raw.employeeName ?? raw.applicantName ?? "未知", applicantNo: raw.employeeNo ?? raw.applicantNo ?? "", applicantName: raw.employeeName ?? raw.applicantName ?? "未知", reimburseReason: raw.reimburseReason ?? "", travelStartTime: raw.travelStartTime ?? "", travelEndTime: raw.travelEndTime ?? "", travelDays: travelDays == null || Number.isNaN(travelDays) ? 1 : travelDays, departurePlace: raw.departurePlace ?? "", destination: raw.destination ?? "", hotelStandard: raw.hotelStandard, hotelDays: raw.hotelDays, livingSubsidy: raw.livingSubsidy, applyAmount: raw.applyAmount ?? 0, payee: raw.payee ?? "", expenseDetails: Array.isArray(raw.expenseDetails) ? raw.expenseDetails : [], invoiceAttachments: Array.isArray(raw.invoiceAttachments) ? raw.invoiceAttachments : [], approvalFlowNodes: Array.isArray(raw.approvalFlowNodes) ? raw.approvalFlowNodes : [], currentNodeIndex: raw.currentNodeIndex ?? 0, approvalResult: ["pending", "approved", "rejected"].includes(raw.approvalResult) ? raw.approvalResult : "pending", rejectReason: raw.rejectReason ?? "", approvalRecords: Array.isArray(raw.approvalRecords) ? raw.approvalRecords : [], needSpecialApproval: !!raw.needSpecialApproval, deptId: raw.deptId ?? "", deptName: raw.deptName ?? "", travelTier: raw.travelTier || detectTravelTier(raw.destination), createTime: raw.createTime || dayjs().format("YYYY-MM-DD HH:mm:ss"), }; }