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"; } /** 解析实例 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 ?? ""; } /** 扁平化为 Spring GET 可绑定的 query(approvalInstanceDto.xxx,勿用方括号) */ export function appendDotNotationQuery(target, prefix, fields) { if (!fields || typeof fields !== "object") return; for (const [key, value] of Object.entries(fields)) { if (value == null || value === "") continue; target[`${prefix}.${key}`] = value; } } function pickApplicantFromSearchForm(searchForm = {}) { const out = {}; const sf = searchForm || {}; const name = (sf.applicantName || "").trim(); const kw = (sf.applicantKeyword || "").trim(); const id = sf.applicantId; if (name) out.applicantName = name; if (kw) { out.applicantName = kw; if (/^\d+$/.test(kw)) out.applicantId = Number(kw); } if (id != null && id !== "") { out.applicantId = typeof id === "number" ? id : Number(id) || id; } return out; } function pickInstanceNoFromSearchForm(searchForm = {}) { const no = (searchForm?.instanceNo || "").trim(); return no ? { instanceNo: no } : {}; } /** 支持审批单号查询的审批申请模块 */ export const INSTANCE_NO_SEARCH_MODULE_KEYS = new Set([ APPROVAL_MODULE_KEYS.REGULAR, APPROVAL_MODULE_KEYS.TRANSFER, APPROVAL_MODULE_KEYS.WORK_HANDOVER, APPROVAL_MODULE_KEYS.LEAVE, APPROVAL_MODULE_KEYS.OVERTIME, ]); const INSTANCE_NO_SEARCH_FIELD = { key: "instanceNo", type: "input", label: "审批单号", placeholder: "请输入审批单号", }; /** 组装 approvalInstanceDto 查询片段(申请人 + 审批单号) */ export function buildApprovalInstanceSearchDto(searchForm = {}, extraParams = {}) { const dto = { ...(extraParams && typeof extraParams === "object" ? extraParams : {}), }; Object.assign(dto, pickApplicantFromSearchForm(searchForm)); Object.assign(dto, pickInstanceNoFromSearchForm(searchForm)); delete dto.createTime; delete dto.createTimeStart; delete dto.createTimeEnd; return dto; } function pickDateRange(searchForm) { return buildApprovalInstanceSearchDto(searchForm); } /** 各模块默认查询表单(与 Web searchForm 字段一致) */ const APPLICANT_ONLY_MODULE_KEYS = new Set([ APPROVAL_MODULE_KEYS.REGULAR, APPROVAL_MODULE_KEYS.TRANSFER, APPROVAL_MODULE_KEYS.WORK_HANDOVER, APPROVAL_MODULE_KEYS.LEAVE, APPROVAL_MODULE_KEYS.OVERTIME, APPROVAL_MODULE_KEYS.TRAVEL_REIMBURSE, APPROVAL_MODULE_KEYS.COST_REIMBURSE, ]); function withInstanceNoSearch(moduleKey, base) { if (INSTANCE_NO_SEARCH_MODULE_KEYS.has(moduleKey)) { return { instanceNo: "", ...base }; } return base; } function applicantOnlySearchForm(moduleKey) { if (moduleKey === APPROVAL_MODULE_KEYS.REGULAR) { return withInstanceNoSearch(moduleKey, { applicantName: "" }); } if ( moduleKey === APPROVAL_MODULE_KEYS.TRANSFER || moduleKey === APPROVAL_MODULE_KEYS.WORK_HANDOVER ) { return withInstanceNoSearch(moduleKey, { applicantId: "" }); } if (APPLICANT_ONLY_MODULE_KEYS.has(moduleKey)) { return withInstanceNoSearch(moduleKey, { applicantKeyword: "" }); } return {}; } export function createModuleSearchForm(moduleKey) { if (APPLICANT_ONLY_MODULE_KEYS.has(moduleKey)) { return applicantOnlySearchForm(moduleKey); } if (moduleKey === APPROVAL_MODULE_KEYS.ENTERPRISE_NEWS) { return { applicantKeyword: "" }; } return {}; } /** 服务端 listPage DTO 片段(与 Web buildApprovalInstanceListParams 一致) */ export function buildModuleListDto(moduleKey, searchForm = {}) { return buildApprovalInstanceSearchDto(searchForm); } function matchInstanceNo(row, instanceNo) { const kw = (instanceNo || "").trim().toLowerCase(); if (!kw) return true; const parts = [row?.instanceNo, row?.bizId] .filter(v => v != null && v !== "") .map(v => String(v).toLowerCase()); return parts.some(p => p.includes(kw)); } export function hasActiveModuleSearch(moduleKey, searchForm = {}) { const sf = searchForm || {}; if ((sf.instanceNo || "").trim()) return true; if ((sf.applicantKeyword || "").trim()) return true; if ((sf.applicantName || "").trim()) return true; return sf.applicantId != null && sf.applicantId !== ""; } 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; }); } /** 按申请人、审批单号做前端兜底筛选 */ export function filterRowsByModuleSearch(moduleKey, rows, searchForm = {}) { const sf = searchForm || {}; const list = Array.isArray(rows) ? rows : []; if (!hasActiveModuleSearch(moduleKey, sf)) return list; return list.filter( row => matchInstanceNo(row, sf.instanceNo) && matchApplicantId(row, sf.applicantId) && matchApplicantKeyword(row, sf.applicantKeyword || sf.applicantName) ); } function prependInstanceNoField(fields, moduleKey) { if (!INSTANCE_NO_SEARCH_MODULE_KEYS.has(moduleKey)) return fields; return [INSTANCE_NO_SEARCH_FIELD, ...fields]; } /** 模块筛选 UI 配置 */ export function getModuleSearchMeta(moduleKey) { if (moduleKey === APPROVAL_MODULE_KEYS.REGULAR) { return { fields: prependInstanceNoField( [ { key: "applicantName", type: "input", label: "申请人", placeholder: "请输入申请人" }, ], moduleKey ), }; } if ( moduleKey === APPROVAL_MODULE_KEYS.TRANSFER || moduleKey === APPROVAL_MODULE_KEYS.WORK_HANDOVER ) { return { fields: prependInstanceNoField( [ { key: "applicantId", type: "user", label: "申请人", placeholder: "请选择申请人" }, ], moduleKey ), }; } if (APPLICANT_ONLY_MODULE_KEYS.has(moduleKey)) { return { fields: prependInstanceNoField( [ { key: "applicantKeyword", type: "input", label: "申请人", placeholder: "姓名或编号", }, ], moduleKey ), }; } 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 ?? ""}`; }