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"),
|
};
|
}
|