From 19f2e3bdbe04e7ea79c6a0bdc8c7318d4837b189 Mon Sep 17 00:00:00 2001
From: gongchunyi <deslre0381@gmail.com>
Date: 星期四, 28 五月 2026 17:36:45 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/dev_NEW_pro' into dev_pro_山西_晋和园
---
src/views/officeProcessAutomation/ReimburseManage/cost-reimburse/costReimburseUtils.js | 313 ++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 313 insertions(+), 0 deletions(-)
diff --git a/src/views/officeProcessAutomation/ReimburseManage/cost-reimburse/costReimburseUtils.js b/src/views/officeProcessAutomation/ReimburseManage/cost-reimburse/costReimburseUtils.js
new file mode 100644
index 0000000..1736b3e
--- /dev/null
+++ b/src/views/officeProcessAutomation/ReimburseManage/cost-reimburse/costReimburseUtils.js
@@ -0,0 +1,313 @@
+import dayjs from "dayjs";
+
+/** 璐圭敤鎶ラ攢澶х被 */
+export const EXPENSE_CATEGORY_OPTIONS = [
+ { label: "宸梾", value: "travel" },
+ { label: "鍔炲叕閲囪喘", value: "office_procurement" },
+ { label: "涓氬姟鎷涘緟", value: "business_entertainment" },
+ { label: "浜ら�氳垂", value: "transport" },
+ { label: "閫氳璐�", value: "communication" },
+ { label: "鍏朵粬", value: "other" },
+];
+
+/** 鏄庣粏璐圭敤绉戠洰 */
+export const EXPENSE_SUBJECT_OPTIONS = [
+ { label: "浜ら�氳垂", value: "transport" },
+ { label: "浣忓璐�", value: "hotel" },
+ { label: "椁愰ギ璐�", value: "meal" },
+ { label: "鍔炲叕鐢ㄥ搧", value: "office_supply" },
+ { label: "鎷涘緟璐�", value: "entertainment" },
+ { label: "閫氳璐�", value: "phone" },
+ { label: "鍏朵粬", value: "other" },
+];
+
+/** 鍒嗙被濉姤妯℃澘锛堜竴閿皟鐢級 */
+export const CATEGORY_TEMPLATES = {
+ travel: {
+ label: "宸梾璐圭敤",
+ reason: "鍥犲叕鍑哄樊浜х敓鐨勪氦閫氥�佷綇瀹裤�侀楗瓑璐圭敤鎶ラ攢銆�",
+ details: [
+ { expenseSubject: "transport", description: "寰�杩斾氦閫氳垂" },
+ { expenseSubject: "hotel", description: "浣忓璐�" },
+ { expenseSubject: "meal", description: "鍑哄樊椁愰ギ" },
+ ],
+ },
+ office_procurement: {
+ label: "鍔炲叕閲囪喘",
+ reason: "閮ㄩ棬鏃ュ父鍔炲叕鐢ㄥ搧銆佽�楁潗閲囪喘鎶ラ攢銆�",
+ details: [
+ { expenseSubject: "office_supply", description: "鍔炲叕鐢ㄥ搧閲囪喘" },
+ { expenseSubject: "office_supply", description: "鎵撳嵃鑰楁潗" },
+ ],
+ },
+ business_entertainment: {
+ label: "涓氬姟鎷涘緟",
+ reason: "瀹㈡埛鎺ュ緟銆佸晢鍔″璇风瓑璐圭敤鎶ラ攢銆�",
+ details: [
+ { expenseSubject: "entertainment", description: "瀹㈡埛鎺ュ緟椁愯垂" },
+ { expenseSubject: "entertainment", description: "鍟嗗姟绀煎搧" },
+ ],
+ },
+ transport: {
+ label: "浜ら�氳垂",
+ reason: "甯傚唴閫氬嫟銆佹墦杞︺�佸仠杞︾瓑浜ら�氳垂鐢ㄦ姤閿�銆�",
+ details: [{ expenseSubject: "transport", description: "甯傚唴浜ら��" }],
+ },
+ communication: {
+ label: "閫氳璐�",
+ reason: "鍥犲叕閫氳銆佹祦閲忋�佽瘽璐硅ˉ璐存姤閿�銆�",
+ details: [{ expenseSubject: "phone", description: "璇濊垂/娴侀噺" }],
+ },
+ other: {
+ label: "鍏朵粬璐圭敤",
+ reason: "鍏朵粬鍥犲叕鏀嚭璐圭敤鎶ラ攢銆�",
+ details: [{ expenseSubject: "other", description: "鍏朵粬璐圭敤" }],
+ },
+};
+
+/** 瀹℃壒瑙掕壊灞曠ず鍚嶏紙鑺傜偣瀹℃壒浜洪』鍦ㄥ墠绔�夋嫨锛� */
+export const APPROVAL_ROLE_LABELS = {
+ direct_supervisor: "鐩村睘涓婄骇",
+ dept_manager: "閮ㄩ棬缁忕悊",
+ cfo: "璐㈠姟鎬荤洃",
+ compliance: "鍚堣瀹℃牳",
+};
+
+/** 鎸夐噾棰濋璁惧鎵归摼 */
+export const APPROVAL_AMOUNT_RULES = [
+ {
+ maxAmount: 500,
+ description: "500鍏冧互鍐咃細鐩村睘涓婄骇瀹℃壒",
+ roles: ["direct_supervisor"],
+ },
+ {
+ maxAmount: 5000,
+ description: "500锝�5000鍏冿細鐩村睘涓婄骇 + 閮ㄩ棬缁忕悊",
+ roles: ["direct_supervisor", "dept_manager"],
+ },
+ {
+ maxAmount: Infinity,
+ description: "瓒�5000鍏冿細鐩村睘涓婄骇 + 閮ㄩ棬缁忕悊 + 璐㈠姟鎬荤洃澶嶆牳",
+ roles: ["direct_supervisor", "dept_manager", "cfo"],
+ },
+];
+
+/** 閮ㄥ垎鍝佺被棰濆瀹℃壒鑺傜偣 */
+export const CATEGORY_EXTRA_APPROVAL = {
+ business_entertainment: ["compliance"],
+ office_procurement: [],
+};
+
+export function expenseCategoryLabel(v) {
+ return EXPENSE_CATEGORY_OPTIONS.find((x) => x.value === v)?.label || "鈥�";
+}
+
+export function expenseSubjectLabel(v) {
+ return EXPENSE_SUBJECT_OPTIONS.find((x) => x.value === v)?.label || "鈥�";
+}
+
+export function statusLabel(v) {
+ if (v === "draft") return "鑽夌";
+ if (v === "approved") return "宸查�氳繃";
+ if (v === "paid") return "宸蹭粯娆�";
+ if (v === "rejected") return "宸查┏鍥�";
+ if (v === "cancelled") return "宸叉挙鍥�";
+ return "瀹℃牳涓�";
+}
+
+export function statusTagType(v) {
+ if (v === "draft") return "info";
+ if (v === "approved" || v === "paid") return "success";
+ if (v === "rejected") return "danger";
+ if (v === "cancelled") return "info";
+ return "warning";
+}
+
+export { formatApprovalFlowSummary } from "../shared/finReimbursementMappers.js";
+
+export function resolveApprovalRoles(amount, expenseCategory) {
+ const amt = Number(amount) || 0;
+ let roles = [];
+ for (const rule of APPROVAL_AMOUNT_RULES) {
+ if (amt <= rule.maxAmount) {
+ roles = [...rule.roles];
+ break;
+ }
+ }
+ if (!roles.length) roles = ["direct_supervisor"];
+ const extra = CATEGORY_EXTRA_APPROVAL[expenseCategory] || [];
+ extra.forEach((r) => {
+ if (!roles.includes(r)) roles.push(r);
+ });
+ return roles;
+}
+
+export function buildAutoApprovalFlow(amount, expenseCategory, previousNodes = []) {
+ const roles = resolveApprovalRoles(amount, expenseCategory);
+ 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) {
+ const amt = Number(amount) || 0;
+ const rule = APPROVAL_AMOUNT_RULES.find((r) => amt <= r.maxAmount) || APPROVAL_AMOUNT_RULES[APPROVAL_AMOUNT_RULES.length - 1];
+ const extra = CATEGORY_EXTRA_APPROVAL[expenseCategory] || [];
+ const extraText = extra.length
+ ? `锛�${expenseCategoryLabel(expenseCategory)}绫诲彟闇�锛�${extra.map((r) => APPROVAL_ROLE_LABELS[r] || r).join("銆�")}`
+ : "";
+ return `${rule.description}${extraText}`;
+}
+
+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: "",
+ expenseCategory: "",
+ reimburseReason: "",
+ applyAmount: undefined,
+ payee: "",
+ payeeAccount: "",
+ bankBranch: "",
+ expenseDetails: [],
+ attachmentList: [],
+ approvalFlowNodes: [],
+ currentNodeIndex: 0,
+ approvalResult: "pending",
+ rejectReason: "",
+ deptId: "",
+ deptName: "",
+ };
+}
+
+export function applyCategoryTemplate(form, category) {
+ const tpl = CATEGORY_TEMPLATES[category];
+ if (!tpl) return;
+ form.expenseCategory = category;
+ if (!form.reimburseReason?.trim()) form.reimburseReason = tpl.reason;
+ form.expenseDetails = (tpl.details || []).map((d) => ({
+ ...createEmptyExpenseDetail(),
+ expenseSubject: d.expenseSubject,
+ description: d.description,
+ invoiceDate: dayjs().format("YYYY-MM-DD"),
+ }));
+}
+
+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", rejectReason: "" };
+ }
+ nodes[next] = { ...nodes[next], nodeStatus: "process" };
+ return { nodes, currentNodeIndex: next, approvalResult: "pending", rejectReason: "" };
+}
+
+export function rejectApprovalFlow(row, opinion) {
+ const nodes = [...(row.approvalFlowNodes || [])];
+ const idx = row.currentNodeIndex ?? 0;
+ const now = dayjs().format("YYYY-MM-DD HH:mm:ss");
+ const reason = (opinion || "").trim() || "椹冲洖";
+ if (nodes[idx]) {
+ nodes[idx] = {
+ ...nodes[idx],
+ nodeStatus: "error",
+ approveOpinion: reason,
+ approveTime: now,
+ };
+ }
+ return { nodes, currentNodeIndex: idx, approvalResult: "rejected", rejectReason: reason };
+}
+
+export function normalizeImportedRow(raw, idx) {
+ const id = raw.id != null && String(raw.id).length ? `imp_${String(raw.id)}_${idx}` : `imp_${Date.now()}_${idx}`;
+ const expenseDetails = Array.isArray(raw.expenseDetails) ? raw.expenseDetails : [];
+ const applyAmount = raw.applyAmount ?? expenseDetails.reduce((s, d) => s + (Number(d.amount) || 0), 0);
+ const expenseCategory = raw.expenseCategory || "other";
+ const approvalFlowNodes =
+ Array.isArray(raw.approvalFlowNodes) && raw.approvalFlowNodes.length
+ ? raw.approvalFlowNodes
+ : buildAutoApprovalFlow(applyAmount, expenseCategory);
+
+ return {
+ id,
+ reimburseNo: raw.reimburseNo || `CR${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 ?? "鏈煡",
+ expenseCategory,
+ reimburseReason: raw.reimburseReason ?? "",
+ applyAmount,
+ payee: raw.payee ?? "",
+ payeeAccount: raw.payeeAccount ?? "",
+ bankBranch: raw.bankBranch ?? "",
+ expenseDetails,
+ attachmentList: Array.isArray(raw.attachmentList) ? raw.attachmentList : [],
+ invoiceAttachments: Array.isArray(raw.invoiceAttachments) ? raw.invoiceAttachments : [],
+ 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 : [],
+ applyTime: raw.applyTime || raw.createTime || dayjs().format("YYYY-MM-DD HH:mm:ss"),
+ createTime: raw.createTime || dayjs().format("YYYY-MM-DD HH:mm:ss"),
+ deptId: raw.deptId ?? "",
+ deptName: raw.deptName ?? "",
+ };
+}
--
Gitblit v1.9.3