From eb322fd6b88273f1dada1f850f4473d5f054dd66 Mon Sep 17 00:00:00 2001
From: yyb <995253665@qq.com>
Date: 星期四, 21 五月 2026 17:48:19 +0800
Subject: [PATCH] 差旅报销费用报销

---
 src/pages/oa/_utils/approvalModuleListSearch.js |  447 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 447 insertions(+), 0 deletions(-)

diff --git a/src/pages/oa/_utils/approvalModuleListSearch.js b/src/pages/oa/_utils/approvalModuleListSearch.js
new file mode 100644
index 0000000..a81b174
--- /dev/null
+++ b/src/pages/oa/_utils/approvalModuleListSearch.js
@@ -0,0 +1,447 @@
+import {
+  APPROVAL_MODULE_KEYS,
+  APPROVAL_MODULE_REGISTRY,
+  getApprovalModuleConfig,
+  getModuleMatchingBusinessTypes,
+} from "./approvalModuleRegistry.js";
+import {
+  parseApprovalFormConfig,
+  parseDatetimerangeValue,
+} from "./approvalFormField.js";
+import { matchBusinessTypeValue } from "./approvalTemplateType.js";
+
+/** 涓� Web leave-apply LEAVE_TYPE_OPTIONS 涓�鑷� */
+export const LEAVE_TYPE_OPTIONS = [
+  { label: "骞村亣", value: "annual" },
+  { label: "鐥呭亣", value: "sick" },
+  { label: "浜嬪亣", value: "personal" },
+  { label: "濠氬亣", value: "marriage" },
+  { label: "浜у亣", value: "maternity" },
+  { label: "鍝轰钩鍋�", value: "nursing" },
+  { label: "鎱板攣鍋�", value: "condolence" },
+  { label: "璋冧紤", value: "compensatory" },
+];
+
+/** 涓� Web overtime-apply OVERTIME_TYPE_OPTIONS 涓�鑷� */
+export const OVERTIME_TYPE_OPTIONS = [
+  { label: "宸ヤ綔鏃ュ姞鐝�", value: "weekday" },
+  { label: "浼戞伅鏃ュ姞鐝�", value: "weekend" },
+  { label: "娉曞畾鑺傚亣鏃ュ姞鐝�", value: "holiday" },
+];
+
+export const HANDOVER_STATUS_OPTIONS = [
+  { label: "杩涜涓�", value: "in_progress" },
+  { label: "宸插畬鎴�", value: "completed" },
+  { label: "宸查��鍥�", value: "returned" },
+];
+
+export const HANDOVER_TYPE_OPTIONS = [
+  { label: "绂昏亴浜ゆ帴", value: "resignation" },
+  { label: "璋冨矖浜ゆ帴", value: "transfer" },
+];
+
+function buildFormPayloadFromFields(fields = []) {
+  const payload = {};
+  for (const f of fields) {
+    if (!f?.key) continue;
+    const val = f.value ?? f.defaultValue;
+    if (val !== undefined && val !== null && val !== "") {
+      payload[f.key] = val;
+    }
+  }
+  return payload;
+}
+
+function guessFieldTypeFromValue(val) {
+  if (Array.isArray(val)) return "datetimerange";
+  if (typeof val === "number") return "number";
+  return "text";
+}
+
+/** 瑙f瀽瀹炰緥 formConfig / formPayload锛堜笌 Web resolveInstanceFormFields 瀵归綈锛� */
+export function resolveInstanceFormPayload(row) {
+  const cfg = parseApprovalFormConfig(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,
+        options: [],
+      }));
+  }
+  fields = fields.map(field => ({
+    ...field,
+    value:
+      formPayload[field.key] ?? field.value ?? field.defaultValue ?? "",
+  }));
+  return { fields, formPayload };
+}
+
+/** 宸茬煡涓嬫媺瀛楁 value 鈫� 灞曠ず鏂囨锛堟ā鏉挎湭甯� options 鏃跺厹搴曪級 */
+export function formatKnownSelectLabel(prop, val) {
+  if (val === undefined || val === null || val === "") return "";
+  const maps = {
+    leaveType: LEAVE_TYPE_OPTIONS,
+    overtimeType: OVERTIME_TYPE_OPTIONS,
+    handoverStatus: HANDOVER_STATUS_OPTIONS,
+    handoverType: HANDOVER_TYPE_OPTIONS,
+  };
+  const options = maps[prop];
+  if (!options) return "";
+  const hit = options.find(o => String(o.value) === String(val));
+  return hit?.label || "";
+}
+
+export function getRowPayloadValue(row, keys) {
+  const keyList = Array.isArray(keys) ? keys : [keys];
+  const { formPayload } = resolveInstanceFormPayload(row);
+  for (const k of keyList) {
+    if (row?.[k] != null && row[k] !== "") return row[k];
+    if (formPayload[k] != null && formPayload[k] !== "") return formPayload[k];
+  }
+  return "";
+}
+
+const DATETIME_RANGE_KEYS = [
+  "dateRange",
+  "leaveTime",
+  "overtimeTime",
+  "timeRange",
+];
+
+function pickDatetimerangeRaw(formPayload, fields = []) {
+  for (const key of DATETIME_RANGE_KEYS) {
+    const v = formPayload?.[key];
+    if (v != null && v !== "") return v;
+    const field = (fields || []).find(f => f?.key === key);
+    const fv = field?.value ?? field?.defaultValue;
+    if (fv != null && fv !== "") return fv;
+  }
+  const rangeField = (fields || []).find(
+    f => String(f?.type || "").toLowerCase() === "datetimerange"
+  );
+  if (rangeField?.key) {
+    const v =
+      formPayload?.[rangeField.key] ?? rangeField.value ?? rangeField.defaultValue;
+    if (v != null && v !== "") return v;
+  }
+  return null;
+}
+
+function splitRangeValue(val) {
+  if (val === undefined || val === null || val === "") {
+    return { start: "", end: "" };
+  }
+  if (Array.isArray(val)) {
+    return { start: val[0] || "", end: val[1] || "" };
+  }
+  return parseDatetimerangeValue(val);
+}
+
+/**
+ * 鍒楄〃鍒� prop 涓� formPayload 瀵归綈锛堣鍋� startTime/endTime 鏉ヨ嚜 dateRange锛�
+ */
+export function resolveListFieldRawValue(prop, row, fields = [], formPayload = {}) {
+  const payload = formPayload || {};
+  const direct = payload[prop] ?? row?.[prop];
+
+  if (prop === "startTime" || prop === "endTime") {
+    if (direct != null && direct !== "") return direct;
+    const altStart =
+      payload.start ?? payload.startDate ?? payload.beginTime ?? row?.startTime;
+    const altEnd =
+      payload.end ?? payload.endDate ?? payload.finishTime ?? row?.endTime;
+    if (prop === "startTime" && altStart) return altStart;
+    if (prop === "endTime" && altEnd) return altEnd;
+    const { start, end } = splitRangeValue(pickDatetimerangeRaw(payload, fields));
+    return prop === "startTime" ? start : end;
+  }
+
+  if (prop === "overtimeDate") {
+    const d = payload.overtimeDate ?? payload.date ?? direct;
+    if (d != null && d !== "") return Array.isArray(d) ? d[0] || "" : d;
+    const { start } = splitRangeValue(pickDatetimerangeRaw(payload, fields));
+    return start || "";
+  }
+
+  if (direct != null && direct !== "") return direct;
+  const hit = (fields || []).find(f => f?.key === prop);
+  return hit?.value ?? hit?.defaultValue ?? "";
+}
+
+function pickDateRange(searchForm) {
+  const range =
+    searchForm?.createTimeRange ??
+    searchForm?.applyDateRange ??
+    searchForm?.transferDateRange;
+  if (!Array.isArray(range) || !range[0]) return {};
+  const out = { createTimeStart: range[0] };
+  if (range[1]) out.createTimeEnd = range[1];
+  return out;
+}
+
+/** 鍚勬ā鍧楅粯璁ゆ煡璇㈣〃鍗曪紙涓� Web searchForm 瀛楁涓�鑷达級 */
+export function createModuleSearchForm(moduleKey) {
+  switch (moduleKey) {
+    case APPROVAL_MODULE_KEYS.REGULAR:
+      return { applicantName: "", applyDateRange: null };
+    case APPROVAL_MODULE_KEYS.TRANSFER:
+      return { applicantId: "", transferDateRange: null };
+    case APPROVAL_MODULE_KEYS.WORK_HANDOVER:
+      return { applicantId: "", handoverStatus: "", handoverType: "" };
+    case APPROVAL_MODULE_KEYS.LEAVE:
+      return { applicantKeyword: "", leaveType: "" };
+    case APPROVAL_MODULE_KEYS.OVERTIME:
+      return { applicantKeyword: "", overtimeType: "" };
+    case APPROVAL_MODULE_KEYS.TRAVEL_REIMBURSE:
+    case APPROVAL_MODULE_KEYS.COST_REIMBURSE:
+    case APPROVAL_MODULE_KEYS.ENTERPRISE_NEWS:
+      return { applicantKeyword: "" };
+    default:
+      return {};
+  }
+}
+
+/** 鏈嶅姟绔� listPage DTO 鐗囨锛堜笌 Web buildExtraListParams + buildApprovalInstanceListParams 涓�鑷达級 */
+export function buildModuleListDto(moduleKey, searchForm = {}) {
+  const sf = searchForm || {};
+  const dto = { ...pickDateRange(sf) };
+
+  switch (moduleKey) {
+    case APPROVAL_MODULE_KEYS.REGULAR: {
+      const name = (sf.applicantName || "").trim();
+      if (name) dto.applicantName = name;
+      break;
+    }
+    case APPROVAL_MODULE_KEYS.TRANSFER:
+      break;
+    case APPROVAL_MODULE_KEYS.WORK_HANDOVER:
+      break;
+    case APPROVAL_MODULE_KEYS.LEAVE:
+    case APPROVAL_MODULE_KEYS.OVERTIME:
+      break;
+    default:
+      break;
+  }
+  return dto;
+}
+
+function matchApplicantKeyword(row, keyword) {
+  const kw = (keyword || "").trim().toLowerCase();
+  if (!kw) return true;
+  const parts = [
+    row?.applicantName,
+    row?.applicantNo,
+    row?.applicantId,
+    getRowPayloadValue(row, ["applicant", "applicantName", "applicantId"]),
+  ]
+    .filter(v => v != null && v !== "")
+    .map(v => String(v).toLowerCase());
+  return parts.some(p => p.includes(kw));
+}
+
+function matchSelectValue(row, keys, expected) {
+  if (!expected) return true;
+  const raw = getRowPayloadValue(row, keys);
+  return String(raw) === String(expected);
+}
+
+function matchApplicantId(row, applicantId) {
+  if (!applicantId) return true;
+  const id = String(applicantId);
+  if (row?.applicantId != null && String(row.applicantId) === id) return true;
+  const payloadApplicant = getRowPayloadValue(row, [
+    "applicant",
+    "applicantId",
+    "applicantUserId",
+  ]);
+  return String(payloadApplicant) === id;
+}
+
+/** 鎸夋ā鍧� businessType / 鏍囬褰掑睘杩囨护锛堟湇鍔$鏈敓鏁堟椂鐨勫厹搴曪級 */
+export function filterRowsByModuleBusinessType(moduleKey, rows, typeOptions = []) {
+  const cfg = getApprovalModuleConfig(moduleKey);
+  if (!cfg) return rows;
+
+  const types = getModuleMatchingBusinessTypes(moduleKey, typeOptions);
+  const myLabels = [cfg.label, ...(cfg.typeLabels || [])].filter(Boolean);
+
+  return (rows || []).filter(row => {
+    if (types.length && row?.businessType != null && row.businessType !== "") {
+      if (types.some(t => matchBusinessTypeValue(row.businessType, t))) {
+        return true;
+      }
+    }
+
+    const title = String(row?.title || row?.templateName || "").trim();
+    if (title) {
+      if (myLabels.some(l => title === l || title.includes(l))) return true;
+      for (const [key, other] of Object.entries(APPROVAL_MODULE_REGISTRY)) {
+        if (key === moduleKey) continue;
+        const otherLabels = [other.label, ...(other.typeLabels || [])].filter(Boolean);
+        if (otherLabels.some(l => title === l || (l.length > 2 && title.includes(l)))) {
+          return false;
+        }
+      }
+    }
+
+    return types.length === 0;
+  });
+}
+
+/** 鍓嶇绛涢�夛紙Web 鏈笅鍙戞帴鍙g殑瀛楁涓� Web 琛屼负涓�鑷达級 */
+export function filterRowsByModuleSearch(moduleKey, rows, searchForm = {}) {
+  const sf = searchForm || {};
+  const list = Array.isArray(rows) ? rows : [];
+
+  switch (moduleKey) {
+    case APPROVAL_MODULE_KEYS.TRANSFER:
+      return list.filter(
+        row =>
+          matchApplicantId(row, sf.applicantId) &&
+          matchApplicantKeyword(row, sf.applicantKeyword)
+      );
+    case APPROVAL_MODULE_KEYS.WORK_HANDOVER:
+      return list.filter(
+        row =>
+          matchApplicantId(row, sf.applicantId) &&
+          matchSelectValue(row, ["handoverStatus", "浜ゆ帴鐘舵��"], sf.handoverStatus) &&
+          matchSelectValue(row, ["handoverType", "浜ゆ帴绫诲瀷"], sf.handoverType)
+      );
+    case APPROVAL_MODULE_KEYS.LEAVE:
+      return list.filter(
+        row =>
+          matchApplicantKeyword(row, sf.applicantKeyword) &&
+          matchSelectValue(row, ["leaveType", "璇峰亣绫诲瀷"], sf.leaveType)
+      );
+    case APPROVAL_MODULE_KEYS.OVERTIME:
+      return list.filter(
+        row =>
+          matchApplicantKeyword(row, sf.applicantKeyword) &&
+          matchSelectValue(row, ["overtimeType", "鍔犵彮绫诲瀷"], sf.overtimeType)
+      );
+    case APPROVAL_MODULE_KEYS.TRAVEL_REIMBURSE:
+    case APPROVAL_MODULE_KEYS.COST_REIMBURSE:
+    case APPROVAL_MODULE_KEYS.ENTERPRISE_NEWS:
+      return list.filter(row => matchApplicantKeyword(row, sf.applicantKeyword));
+    default:
+      return list;
+  }
+}
+
+/** 妯″潡绛涢�� UI 閰嶇疆 */
+export function getModuleSearchMeta(moduleKey) {
+  switch (moduleKey) {
+    case APPROVAL_MODULE_KEYS.REGULAR:
+      return {
+        fields: [
+          { key: "applicantName", type: "input", label: "鐢宠浜�", placeholder: "璇疯緭鍏ョ敵璇蜂汉" },
+          { key: "applyDateRange", type: "daterange", label: "鐢宠鏃ユ湡" },
+        ],
+      };
+    case APPROVAL_MODULE_KEYS.TRANSFER:
+      return {
+        fields: [
+          { key: "applicantId", type: "user", label: "鐢宠浜�", placeholder: "璇烽�夋嫨鐢宠浜�" },
+          { key: "transferDateRange", type: "daterange", label: "杞矖鏃堕棿" },
+        ],
+      };
+    case APPROVAL_MODULE_KEYS.WORK_HANDOVER:
+      return {
+        fields: [
+          { key: "applicantId", type: "user", label: "鐢宠浜�", placeholder: "璇烽�夋嫨鐢宠浜�" },
+          {
+            key: "handoverStatus",
+            type: "select",
+            label: "浜ゆ帴鐘舵��",
+            options: HANDOVER_STATUS_OPTIONS,
+          },
+          {
+            key: "handoverType",
+            type: "select",
+            label: "浜ゆ帴绫诲瀷",
+            options: HANDOVER_TYPE_OPTIONS,
+          },
+        ],
+      };
+    case APPROVAL_MODULE_KEYS.LEAVE:
+      return {
+        fields: [
+          {
+            key: "applicantKeyword",
+            type: "input",
+            label: "鐢宠浜�",
+            placeholder: "濮撳悕鎴栫紪鍙�",
+          },
+          {
+            key: "leaveType",
+            type: "select",
+            label: "璇峰亣绫诲瀷",
+            options: LEAVE_TYPE_OPTIONS,
+          },
+        ],
+      };
+    case APPROVAL_MODULE_KEYS.OVERTIME:
+      return {
+        fields: [
+          {
+            key: "applicantKeyword",
+            type: "input",
+            label: "鐢宠浜�",
+            placeholder: "濮撳悕鎴栫紪鍙�",
+          },
+          {
+            key: "overtimeType",
+            type: "select",
+            label: "鍔犵彮绫诲瀷",
+            options: OVERTIME_TYPE_OPTIONS,
+          },
+        ],
+      };
+    case APPROVAL_MODULE_KEYS.TRAVEL_REIMBURSE:
+    case APPROVAL_MODULE_KEYS.COST_REIMBURSE:
+    case APPROVAL_MODULE_KEYS.ENTERPRISE_NEWS:
+      return {
+        fields: [
+          {
+            key: "applicantKeyword",
+            type: "input",
+            label: "鐢宠浜�",
+            placeholder: "濮撳悕鎴栫紪鍙�",
+          },
+        ],
+      };
+    default:
+      return { fields: [] };
+  }
+}
+
+export function resetModuleSearchForm(moduleKey, target) {
+  const defaults = createModuleSearchForm(moduleKey);
+  Object.keys(target).forEach(k => {
+    if (!(k in defaults)) delete target[k];
+  });
+  Object.assign(target, defaults);
+}
+
+export function formatDateRangeLabel(range) {
+  if (!Array.isArray(range) || !range[0]) return "";
+  if (range[1]) return `${range[0]} 鑷� ${range[1]}`;
+  return range[0];
+}
+
+export function userSelectLabel(u) {
+  const nick = u?.nickName || "";
+  const name = u?.userName || "";
+  if (nick && name && nick !== name) return `${nick}锛�${name}锛塦;
+  return nick || name || `鐢ㄦ埛${u?.userId ?? u?.id ?? ""}`;
+}

--
Gitblit v1.9.3