/** * M09 员工自助「统一申请」接口层 * * 员工端:GET /hr/ess/applications (只返回本人申请) * GET /hr/ess/applications/{id} * 管理端:GET /hr/ess/admin/applications (当前公司组织树内全部申请) * GET /hr/ess/admin/applications/{id} * * 说明:该视图只返回「来源引用 + 脱敏摘要 + 状态 + 当前节点」, * 业务明细需按 businessType 走各业务来源接口(本文件的 resolveSourceRoute 负责分发)。 */ import { get } from "@/utils/request"; import Vue from "vue"; // ============ 业务类型 → 提交页 / 展示元数据 ============ /** * 每种业务类型对应的「提交页」。type 为该页接受的表单类型参数。 * path 与 pages.json 注册路径保持一致。 */ export const ESS_BUSINESS_META = { ATTENDANCE_LEAVE: { type: "leave", path: "/pages/hr/apply/form", title: "请假申请", icon: "假", color: "#157a2c", }, ATTENDANCE_OVERTIME: { type: "overtime", path: "/pages/hr/apply/form", title: "加班申请", icon: "班", color: "#61cdbb", }, ATTENDANCE_OUTING: { type: "field", path: "/pages/hr/apply/form", title: "外勤申请", icon: "外", color: "#4b7902", }, ATTENDANCE_TRIP: { type: "trip", path: "/pages/hr/apply/form", title: "出差申请", icon: "差", color: "#f6bd16", }, ATTENDANCE_CARD_REPAIR: { type: "check", path: "/pages/hr/apply/form", title: "补卡申请", icon: "卡", color: "#07a44d", }, RESIGNATION: { type: "resign", path: "/pages/hr/apply/form", title: "离职申请", icon: "离", color: "#ee6666", }, MAIN_USER_CHANGE: { type: "transfer", path: "/pages/hr/manage/transfer-create", title: "调岗申请", icon: "岗", color: "#9270ca", }, REGULARIZATION: { type: "regular", path: "/pages/hr/manage/regular-create", title: "转正申请", icon: "正", color: "#269a99", }, }; // 历史/别名编码 → 标准编码(后端枚举命名不完全统一时兜底) const BUSINESS_TYPE_ALIAS = { RESIGN: "RESIGNATION", EMPLOYEE_RESIGNATION: "RESIGNATION", USER_CHANGE: "MAIN_USER_CHANGE", USERCHANGE: "MAIN_USER_CHANGE", TRANSFER: "MAIN_USER_CHANGE", POSITION_TRANSFER: "MAIN_USER_CHANGE", REGULAR: "REGULARIZATION", PROBATION_REGULARIZATION: "REGULARIZATION", ATTENDANCE_REPAIR: "ATTENDANCE_CARD_REPAIR", ATTENDANCE_FIELD: "ATTENDANCE_OUTING", }; /** 业务类型编码归一化 */ export function normalizeBusinessType(value) { const code = String(value || "").trim().toUpperCase(); if (!code) return ""; return BUSINESS_TYPE_ALIAS[code] || code; } /** 取业务类型元数据;未知编码返回 null(页面可提示「暂不支持查看」) */ export function businessMetaOf(businessType) { return ESS_BUSINESS_META[normalizeBusinessType(businessType)] || null; } // ============ 状态字典 ============ /** 业务状态 businessStatus */ export const BUSINESS_STATUS_LABELS = { DRAFT: "草稿", SUBMITTED: "已提交", APPROVING: "审批中", APPROVED: "已通过", REJECTED: "已驳回", WITHDRAWN: "已撤回", CANCELLED: "已取消", COMPLETED: "已完成", }; /** 审批状态 approvalStatus */ export const APPROVAL_STATUS_LABELS = { PENDING: "审批中", SUBMITTED: "已提交", APPROVING: "审批中", APPROVED: "已通过", REJECTED: "已驳回", WITHDRAWN: "已撤回", CANCELLED: "已取消", }; /** 状态标签配色键:pending 审批中 / approved 已通过 / rejected 已驳回 / cancelled 已取消 / draft 草稿 */ export const ESS_STATUS_CLASS = { DRAFT: "draft", SUBMITTED: "pending", APPROVING: "pending", APPROVED: "approved", REJECTED: "rejected", WITHDRAWN: "cancelled", CANCELLED: "cancelled", PENDING: "pending", COMPLETED: "approved", }; function labelOf(map, code, fallback = "") { const key = String(code || "").trim().toUpperCase(); if (!key) return fallback; return map[key] || key; } // ============ 请求封装 ============ // 去掉空串 / null / undefined,避免后端把空值当有效筛选条件 function compact(object = {}) { return Object.keys(object).reduce((result, key) => { const value = object[key]; if (value !== "" && value !== null && value !== undefined) result[key] = value; return result; }, {}); } function asList(data) { if (Array.isArray(data)) return data; if (data && Array.isArray(data.list)) return data.list; if (data && Array.isArray(data.records)) return data.records; return []; } /** 大整数主键保持字符串,避免 JS 精度丢失(后端 Long 序列化可能是数字) */ export function essIdOf(value) { if (value === undefined || value === null || value === "") return ""; return String(value); } function normalizePage(res) { const data = res.data || {}; const list = asList(data); const total = Number( data.count !== undefined ? data.count : data.total !== undefined ? data.total : list.length ); return { list, total }; } function pageParams(params = {}) { return compact({ pageNum: params.pageNum || 1, size: params.size || 20, businessType: normalizeBusinessType(params.businessType) || undefined, businessStatus: params.businessStatus, approvalStatus: params.approvalStatus, keyword: params.keyword, // 管理端专用 applicantUserId: params.applicantUserId, organizationId: params.organizationId, }); } /** * 我的申请分页 * GET /hr/ess/applications */ export async function listMyApplications(params = {}, loding = true) { const res = await get( Vue.prototype.apiUrl + "/hr/ess/applications", pageParams(params), loding ); if (res.code == 0) return normalizePage(res); return Promise.reject(new Error(res.message || "查询我的申请失败")); } /** * 我的申请详情 * GET /hr/ess/applications/{id} */ export async function getMyApplicationDetail(id, loding = true) { if (!essIdOf(id)) return null; const res = await get( Vue.prototype.apiUrl + `/hr/ess/applications/${encodeURIComponent(essIdOf(id))}`, {}, loding ); if (res.code == 0) return res.data || null; return Promise.reject(new Error(res.message || "查询申请详情失败")); } /** * 管理端:公司组织树内统一申请分页 * GET /hr/ess/admin/applications */ export async function listAdminApplications(params = {}, loding = true) { const res = await get( Vue.prototype.apiUrl + "/hr/ess/admin/applications", pageParams(params), loding ); if (res.code == 0) return normalizePage(res); return Promise.reject(new Error(res.message || "查询申请记录失败")); } /** * 管理端:统一申请详情 * GET /hr/ess/admin/applications/{id} */ export async function getAdminApplicationDetail(id, loding = true) { if (!essIdOf(id)) return null; const res = await get( Vue.prototype.apiUrl + `/hr/ess/admin/applications/${encodeURIComponent(essIdOf(id))}`, {}, loding ); if (res.code == 0) return res.data || null; return Promise.reject(new Error(res.message || "查询申请详情失败")); } // ============ 适配与判定 ============ function formatTime(value) { if (!value) return ""; const text = String(value).replace("T", " "); return text.length >= 16 ? text.slice(0, 16) : text; } /** * 统一申请 VO → 列表/详情展示结构 */ export function adaptEssApplication(vo = {}) { const businessType = normalizeBusinessType(vo.businessType); const meta = ESS_BUSINESS_META[businessType] || null; const businessStatus = String(vo.businessStatus || "").toUpperCase(); const approvalStatus = String(vo.approvalStatus || "").toUpperCase(); // 状态优先取业务状态;业务状态缺失时退回审批状态 const statusCode = businessStatus || approvalStatus; const statusLabel = labelOf(BUSINESS_STATUS_LABELS, businessStatus) || labelOf(APPROVAL_STATUS_LABELS, approvalStatus); return { id: essIdOf(vo.id), applicationNo: vo.applicationNo || "", title: vo.title || (meta && meta.title) || vo.businessTypeName || "申请", businessType, businessTypeName: vo.businessTypeName || (meta && meta.title) || businessType || "", businessId: essIdOf(vo.businessId), businessStatus, approvalStatus, statusCode, statusLabel: statusLabel || "—", statusClass: ESS_STATUS_CLASS[statusCode] || "pending", currentNode: vo.currentNode || "", maskedSummary: vo.maskedSummary || "", applicantName: vo.applicantName || "", applicantJobNumber: vo.applicantJobNumber || "", applicantPic: vo.applicantPic || "", applicantUserId: essIdOf(vo.applicantUserId), organizationId: essIdOf(vo.organizationId), organizationName: vo.organizationName || "", processInstanceId: vo.processInstanceId || "", sourceTerminal: vo.sourceTerminal || "", createTime: vo.createTime || "", createdAt: formatTime(vo.createTime), icon: (meta && meta.icon) || "申", color: (meta && meta.color) || "#157a2c", supported: Boolean(meta), raw: vo, }; } /** * 是否允许「编辑后重新提交」:仅已驳回(含审批状态为已驳回)可编辑。 * @param {object} row adaptEssApplication 的结果,或原始 VO * @param {boolean} admin 管理端查看时恒为只读 */ export function canEditEssApplication(row = {}, admin = false) { if (admin) return false; const businessStatus = String(row.businessStatus || "").toUpperCase(); const approvalStatus = String(row.approvalStatus || "").toUpperCase(); return businessStatus === "REJECTED" || approvalStatus === "REJECTED"; } function buildQuery(params = {}) { return Object.keys(params) .filter((key) => params[key] !== undefined && params[key] !== null && params[key] !== "") .map((key) => `${key}=${encodeURIComponent(String(params[key]))}`) .join("&"); } /** * 列表项 → 目标提交页路由(详情模式)。 * @param {object} row 原始 VO 或 adaptEssApplication 结果 * @param {{admin?: boolean}} options * @returns {string} 可直接 uni.navigateTo 的 url;不支持的业务类型返回 "" */ export function resolveEssRoute(row = {}, options = {}) { const meta = businessMetaOf(row.businessType); if (!meta) return ""; const query = buildQuery({ mode: "detail", id: essIdOf(row.id), businessId: essIdOf(row.businessId), businessType: normalizeBusinessType(row.businessType), processInstanceId: row.processInstanceId || "", type: meta.type, admin: options.admin ? 1 : "", }); return query ? `${meta.path}?${query}` : meta.path; } /** * 移动端没有详情页的业务类型 → 提示文案。 * 后端还有一类业务(如「劳动合同申请」)没有移动端详情页,原来只提示一句 * 「不支持移动端查看」,用户不知道去哪看;这里改成明确指出去 PC 端。 */ const PC_ONLY_HINT = { // 劳动合同:只有 PC 端 HR 模块能看详情 LABOR_CONTRACT: "劳动合同查看详情请在 PC 端 HR 查看", HR_CONTRACT: "劳动合同查看详情请在 PC 端 HR 查看", CONTRACT_APPLY: "劳动合同查看详情请在 PC 端 HR 查看", }; /** 按业务编码 / 展示名称给出「去 PC 端看」的提示文案 */ export function pcOnlyHintOf(row = {}) { const code = String(row.businessType || "").toUpperCase(); const name = String(row.businessTypeName || row.title || row.businessType || "该类型").trim(); if (PC_ONLY_HINT[code]) return PC_ONLY_HINT[code]; // 后端业务编码未统一时,按名称兜底识别劳动合同(该业务只有 PC 端有页面) if (/劳动合同/.test(name)) return "劳动合同查看详情请在 PC 端 HR 查看"; return `${name}查看详情请在 PC 端查看`; } /** * 打开申请详情;不支持的类型给出提示 * @returns {boolean} 是否已跳转 */ export function openEssApplication(row = {}, options = {}) { const url = resolveEssRoute(row, options); if (!url) { uni.showToast({ title: pcOnlyHintOf(row), icon: "none" }); return false; } uni.navigateTo({ url }); return true; } /** * businessType → apply/form 的表单类型(apply/form 用 type 参数决定渲染哪套字段) */ export function formTypeOf(businessType) { const meta = businessMetaOf(businessType); return meta ? meta.type : ""; } /** * 审批状态筛选选项(列表页 tab 用) */ export const APPROVAL_STATUS_FILTERS = [ { label: "全部", value: "" }, { label: "审批中", value: "APPROVING" }, { label: "已通过", value: "APPROVED" }, { label: "已驳回", value: "REJECTED" }, ];