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 ?? ""}`;
|
}
|