| | |
| | | getApprovalModuleConfig, |
| | | getModuleMatchingBusinessTypes, |
| | | } from "./approvalModuleRegistry.js"; |
| | | import { parseApprovalFormConfig } from "./approvalFormField.js"; |
| | | import { |
| | | parseApprovalFormConfig, |
| | | parseDatetimerangeValue, |
| | | } from "./approvalFormField.js"; |
| | | import { matchBusinessTypeValue } from "./approvalTemplateType.js"; |
| | | |
| | | /** 与 Web leave-apply LEAVE_TYPE_OPTIONS 一致 */ |
| | |
| | | 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); |
| | | const fields = (row?.formFieldDefs?.length ? row.formFieldDefs : cfg.fields) || []; |
| | | 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) { |
| | |
| | | return ""; |
| | | } |
| | | |
| | | 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]; |
| | | 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; |
| | | } |
| | | |
| | | /** 各模块默认查询表单(与 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: "" }; |
| | | default: |
| | | return {}; |
| | | } |
| | | function pickInstanceNoFromSearchForm(searchForm = {}) { |
| | | const no = (searchForm?.instanceNo || "").trim(); |
| | | return no ? { instanceNo: no } : {}; |
| | | } |
| | | |
| | | /** 服务端 listPage DTO 片段(与 Web buildExtraListParams + buildApprovalInstanceListParams 一致) */ |
| | | export function buildModuleListDto(moduleKey, searchForm = {}) { |
| | | const sf = searchForm || {}; |
| | | const dto = { ...pickDateRange(sf) }; |
| | | /** 支持审批单号查询的审批申请模块 */ |
| | | 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, |
| | | ]); |
| | | |
| | | 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; |
| | | } |
| | | 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) { |
| | |
| | | }); |
| | | } |
| | | |
| | | /** 前端筛选(Web 未下发接口的字段与 Web 行为一致) */ |
| | | /** 按申请人、审批单号做前端兜底筛选 */ |
| | | export function filterRowsByModuleSearch(moduleKey, rows, searchForm = {}) { |
| | | const sf = searchForm || {}; |
| | | const list = Array.isArray(rows) ? rows : []; |
| | | if (!hasActiveModuleSearch(moduleKey, sf)) return list; |
| | | |
| | | 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) |
| | | ); |
| | | default: |
| | | 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) { |
| | | switch (moduleKey) { |
| | | case APPROVAL_MODULE_KEYS.REGULAR: |
| | | return { |
| | | fields: [ |
| | | if (moduleKey === APPROVAL_MODULE_KEYS.REGULAR) { |
| | | return { |
| | | fields: prependInstanceNoField( |
| | | [ |
| | | { 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, |
| | | }, |
| | | ], |
| | | }; |
| | | default: |
| | | return { fields: [] }; |
| | | 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) { |