// 员工转正(M02 /main/regularizations)——由 kd-aiot-frontend-hr-H5/src/api/regularization.ts 迁移,去掉演示模式分支 import { get, postJ, putJ } from "@/utils/request"; import Vue from "vue"; import { regularizationStatus, regularizationStages } from "@/pages/hr/data/manage-status"; import { getRegularizationIds, loadRegularizationBillMap, removeRegularizationBillId, rememberRegularizationId, saveRegularizationBillId, } from "@/utils/hr/storage"; import { getOnlineTemplatePage, loadOnlineTemplateList } from "@/api/hr/onlineDocument"; import { pickPrimaryPosition, resolveManagerName } from "@/api/hr/user"; import { processInstanceCreateAPI, processInstancePage } from "@/api/wt/index.js"; /** BPM 业务标识:与 PC 端 src/api/hr/index.js 保持一致 */ export const REGULARIZATION_APPROVAL_BUSINESS_KEY = "main_regularization_approval"; // ============ 工具 ============ function ok(res) { return res && Number(res.code) === 0; } 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 dateOnly(value) { if (!value) return ""; return String(value).slice(0, 10); } function labelOf(map, raw, fallback = "") { const text = String(raw || "").trim(); if (!text) return fallback; return map[text] || map[text.toUpperCase()] || text; } 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; if (data && typeof data === "object" && data.id != null) return [data]; return []; } function keepBigIntId(value) { if (value === undefined || value === null || value === "") return undefined; const text = String(value).trim(); if (!/^-?\d+$/.test(text)) return value; if (!Number.isSafeInteger(Number(text))) return text; return Number(text); } function extractId(result) { if (result == null) return null; if (typeof result === "object") { return result.id != null ? result.id : result.regularizationId != null ? result.regularizationId : null; } return result; } function firstBigIntId(value) { if (Array.isArray(value)) { return keepBigIntId(value.find((item) => item !== undefined && item !== null && item !== "")); } if (value && typeof value === "object") { return keepBigIntId(value.id != null ? value.id : value.fileId != null ? value.fileId : value.value); } return keepBigIntId(value); } /** * 多附件主键 → 英文逗号拼接字符串(接口字段要求) * 兼容数组 / 单值 / 已拼接好的字符串 */ function joinFileIds(value) { if (Array.isArray(value)) { return value .map((id) => String(id == null ? "" : id).trim()) .filter(Boolean) .join(","); } if (value && typeof value === "object") return joinFileIds(firstBigIntId(value)); const text = String(value == null ? "" : value).trim(); return text; } /** * 签署方式:显式 ONLINE / OFFLINE 优先,否则按线上签署开关推导 * 移动端与 PC 一致:线上模板功能未开通,默认线下签署 */ function resolveSigningMethod(form = {}) { const method = String(form.signingMethod || form.signMethod || "") .trim() .toUpperCase(); if (method === "ONLINE" || method === "OFFLINE") return method; return form.online ? "ONLINE" : "OFFLINE"; } /** 取第一个非空主键(兼容大整数,统一转字符串) */ function pickBig() { for (let i = 0; i < arguments.length; i += 1) { const value = arguments[i]; if (value === undefined || value === null || value === "") continue; return String(value); } return ""; } /** * 接口的 manager 字段有的环境存的是**主管 userId(雪花 id)**、有的存姓名。 * 返回大整数就按 id 用,否则返回空串(当作姓名交给调用方)。 */ function managerIdInField(value) { const text = String(value == null ? "" : value).trim(); return /^\d{10,}$/.test(text) ? text : ""; } /** * 是否线上签署:接口 signingMethod(ONLINE 线上 / OFFLINE 线下)优先, * 老数据未回传该字段时再按线上单据字段推断 */ function resolveOnlineSigning(raw = {}, extras = {}) { const method = String(raw.signingMethod || raw.signMethod || "") .trim() .toUpperCase(); if (method === "ONLINE") return true; if (method === "OFFLINE") return false; return Boolean(raw.onlineDocumentId || raw.onlineDocumentTemplateId || extras.onlineDocumentTemplateId); } // ============ 枚举 ============ // 文案表保留 FAILED:历史单据里可能存在「不予转正」,展示时要能翻译出来 export const REGULARIZATION_TYPE_LABELS = { NORMAL: "按期转正", EARLY: "提前转正", EXTENDED: "延期转正", FAILED: "不予转正", }; /** * **可发起**的转正类型:与 PC 端新建表单一致,只有 3 种。 * 「不予转正(FAILED)」是审批结论,不在申请页提供 —— 之前直接从 LABELS 派生, * 导致下拉里多出一条 PC 没有的「不予转正」。 */ export const REGULARIZATION_TYPE_KEYS = ["NORMAL", "EARLY", "EXTENDED"]; export const REGULARIZATION_TYPE_OPTIONS = REGULARIZATION_TYPE_KEYS.map((value) => ({ value, label: REGULARIZATION_TYPE_LABELS[value], })); export const APPROVAL_STATUS_LABELS = { DRAFT: "草稿", PENDING: "审批中", APPROVING: "审批中", APPROVED: "已通过", REJECTED: "已驳回", WITHDRAWN: "已撤回", CANCELLED: "已取消", }; export const EFFECTIVE_STATUS_LABELS = { PENDING: "待生效", SCHEDULED: "已排期", EFFECTIVE: "已生效", CANCELLED: "已取消", }; export const RESULT_LABELS = { NORMAL: "按期转正", EARLY: "提前转正", EXTENDED: "延期", FAILED: "不通过", }; export const DISPLAY_STATUS_LABELS = { pending_apply: "待申请", draft: "草稿", reviewing: "审批中", done: "已转正", rejected: "已驳回", withdrawn: "已撤回", cancelled: "已取消", }; export const REGULARIZATION_DISPLAY_STATUS = regularizationStatus; export const REGULARIZATION_STAGES = regularizationStages; export const REGULARIZATION_STATUS_FILTERS = [ { value: "all", label: "全部" }, { value: "pending_apply", label: "待申请" }, { value: "draft", label: "草稿" }, { value: "reviewing", label: "审批中" }, { value: "done", label: "已转正" }, { value: "rejected", label: "已驳回" }, { value: "withdrawn", label: "已撤回" }, ]; // ============ 状态机 ============ export function deriveRegularizationDisplayStatus(raw = {}) { if (!raw || (!raw.id && !raw.regularizationId && !raw.approvalStatus)) return "pending_apply"; const approval = String(raw.approvalStatus || "").toUpperCase(); const effective = String(raw.effectiveStatus || "").toUpperCase(); const result = String(raw.result || "").toUpperCase(); if (approval === "CANCELLED" || effective === "CANCELLED") return "cancelled"; if (approval === "WITHDRAWN") return "withdrawn"; if (approval === "REJECTED") return "rejected"; if (effective === "EFFECTIVE" || (approval === "APPROVED" && (result === "NORMAL" || result === "EARLY" || !result))) { return "done"; } if (approval === "PENDING" || approval === "APPROVING" || approval === "APPROVED") return "reviewing"; if (approval === "DRAFT" || !approval) return "draft"; return "pending_apply"; } export const REGULARIZATION_APPLICABLE_STATUS = ["pending_apply", "draft", "rejected", "withdrawn"]; export function isApplicableRegularizationStatus(status) { return REGULARIZATION_APPLICABLE_STATUS.indexOf(status) >= 0; } export function canApplyRegularization(row = {}) { return isApplicableRegularizationStatus(row.status); } export function canEditRegularization(row = {}) { return ["draft", "rejected", "withdrawn"].indexOf(row.status) >= 0; } export function canSubmitRegularization(row = {}) { if (!row || !row.regularizationId) return false; return ["draft", "rejected", "withdrawn"].indexOf(row.status) >= 0; } export function canWithdrawRegularization(row = {}) { return row && row.status === "reviewing" && !!row.regularizationId; } export function canCancelRegularization(row = {}) { if (!row || !row.regularizationId) return false; return ["draft", "pending_apply", "withdrawn", "rejected"].indexOf(row.status) >= 0; } export function matchDisplayStatus(row = {}, filter = "") { if (!filter || filter === "all") return true; return row.status === filter; } function stageKeyOf(displayStatus) { if (displayStatus === "done") return "effective"; if (displayStatus === "reviewing") return "approval"; return "submit"; } function progressOf(displayStatus) { switch (displayStatus) { case "done": return 100; case "reviewing": return 60; case "rejected": return 40; case "withdrawn": case "cancelled": return 30; case "draft": return 20; default: return 10; } } function progressTipOf(displayStatus) { switch (displayStatus) { case "pending_apply": return "试用期员工尚未建立转正单,请发起转正申请。"; case "draft": return "转正申请为草稿,提交后进入审批流程。"; case "reviewing": return "转正审批中,请等待主管与 HR 的审批结果。"; case "done": return "转正审批已通过,员工状态按生效日期更新。"; case "rejected": return "审批已驳回,可修改后重新提交。"; case "withdrawn": return "本次转正申请已撤回。"; case "cancelled": return "本次转正申请已取消。"; default: return ""; } } // ============ VO → 页面形状 ============ export function adaptRegularization(raw = {}, extras = {}) { if (!raw || typeof raw !== "object") return null; const displayStatus = deriveRegularizationDisplayStatus(raw); const statusMeta = REGULARIZATION_DISPLAY_STATUS[displayStatus] || REGULARIZATION_DISPLAY_STATUS.draft; const plannedRegularDate = dateOnly(raw.plannedRegularDate) || dateOnly(extras.plannedRegularDate) || dateOnly(extras.plannedDate) || ""; const effectiveDate = dateOnly(raw.effectiveDate) || dateOnly(extras.effectiveDate) || ""; return Object.assign({}, raw, { id: raw.id, billId: raw.id, regularizationId: raw.id != null ? raw.id : extras.regularizationId, userId: raw.userId != null ? raw.userId : extras.userId, employeeNo: extras.employeeNo || raw.employeeNo || "", name: extras.name || raw.name || raw.employeeName || extras.employeeName || "", avatar: extras.avatar || raw.avatar || "", phone: extras.phone || raw.phone || "", company: extras.company || raw.organizationName || raw.groupName || raw.company || "", department: extras.department || raw.deptName || raw.department || "", position: extras.position || raw.postName || raw.positionName || raw.position || "", level: extras.level || "", // ===== 级联主键 / 名称快照(编辑回填、只读详情共用) ===== groupId: pickBig(extras.groupId, raw.groupId, raw.legalId), deptId: pickBig(extras.deptId, raw.deptId), positionId: pickBig(raw.positionId, extras.positionId), positionSequenceId: pickBig(raw.positionSequenceId, extras.positionSequenceId), positionSequence: raw.positionSequence || raw.positionSequenceName || raw.sequenceName || extras.positionSequence || "", positionLevelId: pickBig(raw.positionLevelId, extras.positionLevelId), positionLevel: raw.positionLevel || raw.positionLevelName || extras.positionLevel || extras.level || "", // 岗位性质:档案值优先(页面下拉用字典 id),接口给的是编码快照 positionType: pickBig(extras.positionType, raw.positionType), // employmentType 是岗位性质字典编码,不能当名称用(PC 同注释) positionTypeName: raw.positionTypeName || extras.positionTypeName || "", // 主管:manager 字段在部分环境里是 userId(雪花 id),不能当姓名用(同 adaptEmployeeBrief) manager: extras.manager || raw.managerUserName || raw.managerName || (managerIdInField(raw.manager) ? "" : raw.manager) || "", managerUserId: pickBig(raw.managerUserId, extras.managerUserId, managerIdInField(raw.manager)), managerUserName: raw.managerUserName || raw.managerName || extras.managerUserName || extras.managerName || "", hireDate: dateOnly(raw.joinDate || raw.hireDate || extras.hireDate) || "", plannedDate: plannedRegularDate, plannedRegularDate, regularDate: effectiveDate || plannedRegularDate, effectiveDate, regularizationType: raw.regularizationType || "NORMAL", regularizationTypeLabel: labelOf(REGULARIZATION_TYPE_LABELS, raw.regularizationType || "NORMAL"), extensionDays: raw.extensionDays, extensionReason: raw.extensionReason || "", newProbationEndDate: dateOnly(raw.newProbationEndDate) || "", userSummary: raw.userSummary || "", managerEvaluation: raw.managerEvaluation || "", hrComment: raw.hrComment || "", remark: raw.userSummary || extras.remark || "", approvalStatus: raw.approvalStatus || "", approvalStatusLabel: labelOf(APPROVAL_STATUS_LABELS, raw.approvalStatus, "未提交"), approvalNode: raw.approvalNode || "", approvalComment: raw.hrComment || raw.managerEvaluation || extras.approvalComment || "", approvalInstanceId: raw.approvalInstanceId || "", // 流程详情弹窗按 processInstanceId → approvalInstanceId 依次解析 processInstanceId: raw.processInstanceId || raw.approvalInstanceId || "", effectiveStatus: raw.effectiveStatus || "", effectiveStatusLabel: labelOf(EFFECTIVE_STATUS_LABELS, raw.effectiveStatus, "-"), result: raw.result || "", resultLabel: labelOf(RESULT_LABELS, raw.result), employmentPeriodId: raw.employmentPeriodId, onlineDocumentId: raw.onlineDocumentId, onlineDocumentTemplateId: raw.onlineDocumentTemplateId || extras.onlineDocumentTemplateId, onlineDocumentContent: raw.onlineDocumentContent || "", onlineDocumentVariableValuesJson: raw.onlineDocumentVariableValuesJson || "", approvalDocumentFileId: joinFileIds(raw.approvalDocumentFileId), signingMethod: resolveSigningMethod(raw), online: resolveOnlineSigning(raw, extras), status: displayStatus, statusLabel: statusMeta.label, statusClass: statusMeta.class, stageKey: stageKeyOf(displayStatus), progress: progressOf(displayStatus), progressTip: progressTipOf(displayStatus), applyDate: dateOnly(raw.applyDate || raw.createTime || raw.submitDate) || "", createTime: raw.createTime || "", source: extras.source || raw.source || "", raw, }); } const EMPLOYEE_FIELDS = [ "id", "employeeNo", "name", "avatar", "phone", "company", "department", "position", "level", "manager", "hireDate", "regularDate", "groupId", "deptId", // 级联回填所需的主键 / 名称快照 "positionId", "positionSequenceId", "positionSequence", "positionLevelId", "positionLevel", "positionType", "positionTypeName", "managerUserId", "managerUserName", ]; function pickEmployeeFields(employee = {}) { return EMPLOYEE_FIELDS.reduce((result, key) => { if (employee[key] !== undefined) result[key] = employee[key]; return result; }, {}); } /** 试用期员工 + 可选转正单 → 列表行 */ export function mergeEmployeeRegularization(employee = {}, bill = null) { const base = Object.assign({}, pickEmployeeFields(employee), { userId: employee.id, id: employee.id, avatar: employee.avatar || String(employee.name || "").charAt(0), company: employee.company || employee.legalEntity || "", department: employee.department || employee.deptName || "", position: employee.position || employee.postName || "", manager: employee.manager || employee.managerUserName || "", hireDate: dateOnly(employee.hireDate), plannedDate: dateOnly(employee.regularDate), plannedRegularDate: dateOnly(employee.regularDate), regularDate: dateOnly(employee.regularDate), }); if (!bill) { const statusMeta = REGULARIZATION_DISPLAY_STATUS.pending_apply; return Object.assign({}, base, { regularizationId: null, status: "pending_apply", statusLabel: statusMeta.label, statusClass: statusMeta.class, approvalStatus: "", approvalStatusLabel: "未提交", approvalComment: "", regularizationType: "NORMAL", regularizationTypeLabel: REGULARIZATION_TYPE_LABELS.NORMAL, effectiveDate: "", effectiveStatus: "", effectiveStatusLabel: "-", stageKey: "submit", progress: progressOf("pending_apply"), progressTip: progressTipOf("pending_apply"), online: true, onlineDocumentTemplateId: null, approvalDocumentFileId: null, remark: "", userSummary: "", managerEvaluation: "", hrComment: "", extensionDays: undefined, extensionReason: "", newProbationEndDate: "", }); } return Object.assign({}, base, adaptRegularization(bill, base), { id: employee.id, userId: employee.id, regularizationId: bill.id, }); } /** 表单字段 → /main/regularizations 请求参数(严格对齐 PC buildRegularizationPayload) */ export function buildRegularizationPayload(form = {}) { const type = form.regularizationType || "NORMAL"; return compact({ id: keepBigIntId(form.regularizationId || form.billId || form.id), userId: keepBigIntId(form.userId), employmentPeriodId: keepBigIntId(form.employmentPeriodId), // 机构 / 部门 / 岗位主键(级联下拉,与录用审批提交字段保持一致) groupId: keepBigIntId(form.groupId), deptId: keepBigIntId(form.deptId), positionId: keepBigIntId(form.positionId), positionName: form.position || form.positionName || undefined, positionSequenceId: keepBigIntId(form.positionSequenceId), positionSequence: form.positionSequence || undefined, positionLevelId: keepBigIntId(form.positionLevelId), positionLevel: form.positionLevel || undefined, positionType: form.positionType || undefined, positionTypeName: form.positionTypeName || undefined, plannedRegularDate: dateOnly(form.plannedRegularDate || form.plannedDate || form.regularDate), effectiveDate: dateOnly(form.effectiveDate || form.regularDate), regularizationType: type, extensionDays: type === "EXTENDED" ? Number(form.extensionDays || 0) || undefined : undefined, extensionReason: type === "EXTENDED" ? form.extensionReason : undefined, newProbationEndDate: type === "EXTENDED" ? dateOnly(form.newProbationEndDate) : undefined, userSummary: form.userSummary || form.remark, managerEvaluation: form.managerEvaluation, hrComment: form.hrComment, managerUserId: keepBigIntId(form.managerUserId), managerUserName: form.managerUserName || form.manager || undefined, // 申请资料附件:多附件主键以英文逗号拼接 approvalDocumentFileId: joinFileIds(form.approvalDocumentFileId) || undefined, // 签署方式(接口字段 signingMethod):ONLINE 线上 / OFFLINE 线下 signingMethod: resolveSigningMethod(form), onlineDocumentTemplateId: form.online ? keepBigIntId(form.onlineDocumentTemplateId || form.templateId) : undefined, onlineDocumentContent: form.onlineDocumentContent, onlineDocumentVariableValuesJson: form.onlineDocumentVariableValuesJson, }); } // ============ 服务端接口 ============ export function adaptEmployeeBrief(raw = {}) { const name = raw.name || raw.userName || ""; const primary = pickPrimaryPosition(raw) || {}; const deptIds = Array.isArray(raw.deptIds) ? raw.deptIds.filter((id) => id != null && id !== "") : []; // 主管:有的接口把「主管」放在 manager 字段里且存的是 **userId(雪花 id)**, // 不能当姓名用 —— 否则「直属上级」会显示成 1933027632794832897。 const managerName = raw.managerUserName || raw.managerName || ""; const rawManager = String(raw.manager || "").trim(); const managerIdFromField = managerIdInField(rawManager); return { id: raw.id, employeeNo: raw.jobNumber || raw.employeeNo || "", name, avatar: raw.avatar || String(name).charAt(0), phone: raw.phone || "", company: raw.groupName || raw.company || "", department: raw.deptName || raw.deptNames || "", position: raw.postName || raw.position || "", level: primary.positionLevelName || raw.positionLevelName || raw.level || "", manager: managerName || (managerIdFromField ? "" : rawManager), hireDate: dateOnly(raw.joinDate || raw.hireDate || raw.entryDate), regularDate: dateOnly(raw.positiveDate || raw.regularDate), // ===== 级联回填所需的组织 / 岗位快照 ===== groupId: pickBig(raw.groupId, raw.legalId), companyName: raw.groupName || raw.company || "", deptId: pickBig(deptIds.length ? deptIds[deptIds.length - 1] : raw.deptId), positionId: pickBig(primary.positionId, raw.positionId), positionName: raw.postName || raw.position || "", positionSequenceId: pickBig(primary.positionSequenceId, raw.positionSequenceId), positionSequence: primary.positionSequenceName || primary.sequenceName || raw.positionSequenceName || "", positionLevelId: pickBig(primary.positionLevelId, raw.positionLevelId), positionLevel: primary.positionLevelName || primary.positionLevel || raw.positionLevelName || raw.positionLevel || "", // 岗位性质:编码优先取主岗位(positionList)里的字典编码;employmentType 是编码不是名称 positionType: pickBig(primary.positionType, raw.positionType), positionTypeName: primary.positionTypeName || raw.positionTypeName || "", // 主管主键:managerUserId / managerId,以及「manager 字段存 id」的兜底 managerUserId: pickBig(raw.managerUserId, raw.managerId, managerIdFromField), managerUserName: managerName, }; } /** 试用期员工分页 GET /main/user/getUserPage */ export async function getProbationUserPage(params = {}, loding = false) { const res = await get( Vue.prototype.apiUrl + "/main/user/getUserPage", compact({ status: 5, isQueryLZ: 0, pageNum: params.pageNum, size: params.size, }), loding ); const data = (res && res.data) || {}; return { list: asList(data), count: Number(data.count != null ? data.count : data.total != null ? data.total : 0), }; } export async function fetchProbationEmployees(options = {}, loding = false) { const pageSize = options.pageSize || 200; const maxPages = options.maxPages || 10; const list = []; let pageNum = 1; let total = Infinity; while (pageNum <= maxPages && list.length < total) { const data = await getProbationUserPage({ pageNum, size: pageSize }, loding); const chunk = Array.isArray(data.list) ? data.list : []; total = Number(data.count != null ? data.count : list.length + chunk.length); list.push(...chunk); if (!chunk.length || chunk.length < pageSize) break; pageNum += 1; } return { list: list.map(adaptEmployeeBrief), count: total === Infinity ? list.length : total, }; } const employeeCache = {}; /** * 员工档案补全:优先 /main/users/{id}/profile(含组织 / 岗位快照,可回填级联), * 失败时退回 /main/user/getById/{id}。 */ export async function fetchRegularizationEmployee(userId, loding = false) { if (userId === undefined || userId === null || userId === "") return null; const key = String(userId); if (key in employeeCache) return employeeCache[key]; let brief = null; try { const res = await get(Vue.prototype.apiUrl + `/main/users/${key}/profile`, {}, loding); if (res && Number(res.code) === 0 && res.data) { const data = res.data; const merged = data.user && typeof data.user === "object" ? Object.assign({}, data, data.user) : data; // 只给了主管主键、没给姓名时,回查一次主管档案补上姓名 // (否则「直属上级」只能空着或显示雪花 id) if (!merged.managerUserName && !merged.managerName && (merged.managerUserId || merged.managerId)) { try { await resolveManagerName({ user: merged }); } catch (error) { // 主管姓名解析失败不阻塞 } } brief = adaptEmployeeBrief(merged); } } catch (error) { brief = null; } if (!brief || !brief.name) { try { const res = await get(Vue.prototype.apiUrl + `/main/user/getById/${key}`, {}, false); const fallback = adaptEmployeeBrief((res && res.data) || {}); brief = brief ? Object.assign({}, fallback, brief) : fallback; } catch (error) { // 员工档案不可用时保留已有结果 } } employeeCache[key] = brief; return brief; } function fetchEmployeeBrief(userId) { return fetchRegularizationEmployee(userId, false); } /** 新建转正草稿 POST /main/regularizations */ export async function createRegularization(payload, loding = true) { const res = await postJ(Vue.prototype.apiUrl + "/main/regularizations", payload, loding); if (!ok(res)) throw new Error(res.message || "创建转正单失败"); const id = extractId(res.data); if (id != null) { rememberRegularizationId(id); if (payload.userId != null) saveRegularizationBillId(payload.userId, id); } return id; } /** 修改转正草稿 PUT /main/regularizations */ export async function updateRegularization(payload, loding = true) { const res = await putJ(Vue.prototype.apiUrl + "/main/regularizations", payload, loding); if (!ok(res)) throw new Error(res.message || "更新转正单失败"); const id = payload.id; if (id != null) { rememberRegularizationId(id); if (payload.userId != null) saveRegularizationBillId(payload.userId, id); } return id; } /** 查询转正单 GET /main/regularizations/{id} */ export async function getRegularizationById(id, extras = {}, loding = true) { const res = await get(Vue.prototype.apiUrl + `/main/regularizations/${id}`, {}, loding); if (!ok(res)) return null; const adapted = adaptRegularization(res.data, extras); if (adapted && adapted.userId != null && adapted.id != null) { saveRegularizationBillId(adapted.userId, adapted.id); } return adapted; } /** * 提交审批。 * 注意:转正模块后端没有 /submit endpoint(PC 端同样留空,走 BPM 公共流程), * 因此这里改为发起 BPM 流程;变量缺省时按转正单字段自动组装。 */ export async function submitRegularization(id, row = {}, loding = true) { if (id == null || id === "") throw new Error("缺少转正单主键"); const source = Object.assign({}, row, { regularizationId: id }); if (!source.regularizationType && id != null) { try { const bill = await getRegularizationById(id, {}, false); if (bill) Object.assign(source, bill); } catch (error) { // 详情取不到时按默认类型提交 } } const variables = buildRegularizationVariables(source, id); return startRegularizationApproval(id, variables, loding); } export async function withdrawRegularization(id, reason = "", loding = true) { const res = await postJ(Vue.prototype.apiUrl + `/main/regularizations/${id}/withdraw`, compact({ reason }), loding); if (!ok(res)) throw new Error(res.message || "撤回失败"); return res.data; } export async function cancelRegularization(id, reason = "", loding = true) { const res = await postJ(Vue.prototype.apiUrl + `/main/regularizations/${id}/cancel`, compact({ reason }), loding); if (!ok(res)) throw new Error(res.message || "取消失败"); return res.data; } /** 任职周期 GET /main/users/{userId}/employment-periods */ export async function getEmploymentPeriods(userId, loding = true) { const res = await get(Vue.prototype.apiUrl + `/main/users/${userId}/employment-periods`, {}, loding); if (!ok(res)) return []; return asList(res.data); } /** 从人事时间轴找回转正单 id */ export async function resolveRegularizationIdFromHistory(userId, loding = false) { const mapped = loadRegularizationBillMap()[String(userId)]; if (mapped) return mapped; try { const res = await get(Vue.prototype.apiUrl + `/main/users/${userId}/history`, {}, loding); const list = asList(res.data); const hit = list .slice() .reverse() .find((item) => { const text = `${(item && item.eventType) || ""} ${(item && item.sourceBusinessType) || ""}`.toUpperCase(); return text.includes("REGULAR"); }); if (hit && hit.sourceBusinessId != null) { saveRegularizationBillId(userId, hit.sourceBusinessId); return hit.sourceBusinessId; } } catch (error) { // 时间轴不可用时不阻塞列表 } return null; } /** 启用态线上 Word 模板分页 */ export async function getRegularizationTemplatePage(params = {}, loding = true) { return getOnlineTemplatePage(Object.assign({ templateStatus: "ENABLED" }, params), loding); } // ============ 对外统一入口 ============ async function mergeBillsIntoEmployees(list = []) { const billMap = loadRegularizationBillMap(); return Promise.all( list.map(async (employee) => { const billId = billMap[String(employee.id)]; if (!billId) return mergeEmployeeRegularization(employee, null); try { const bill = await getRegularizationById(billId, employee, false); return mergeEmployeeRegularization(employee, bill); } catch (error) { removeRegularizationBillId(employee.id); return mergeEmployeeRegularization(employee, null); } }) ); } function currentUserKeys() { const userInfo = uni.getStorageSync("userInfo") || {}; const raw = userInfo.raw || {}; return [userInfo.userId, userInfo.id, userInfo.userName, userInfo.name, raw.userId, raw.id, raw.loginName] .filter((value) => value !== undefined && value !== null && value !== "") .map(String); } function isMine(row = {}) { const mine = currentUserKeys(); if (!mine.length) return true; return [row.userId, row.employeeNo, row.name] .filter((value) => value !== undefined && value !== null && value !== "") .map(String) .some((value) => mine.indexOf(value) >= 0); } function regularizationText(row = {}) { return `${row.name}${row.employeeNo}${row.department}${row.position}${row.regularizationTypeLabel}`; } /** * 转正办理列表。 * scope: 'all' 全部员工(HR 视图,默认);'mine' 只看本人 */ export async function loadRegularizationList(params = {}) { const { scope = "all", status = "all", keyword = "" } = params; const match = (rows) => rows .filter((row) => (scope === "mine" ? isMine(row) : true)) .filter((row) => matchDisplayStatus(row, status)) .filter((row) => (keyword ? regularizationText(row).includes(keyword) : true)); let rows = []; try { const { list } = await fetchProbationEmployees(); rows = await mergeBillsIntoEmployees(list); } catch (error) { rows = []; } const ids = getRegularizationIds(); const attached = new Set(rows.filter((row) => row.regularizationId).map((row) => String(row.regularizationId))); const missing = ids.filter((id) => !attached.has(String(id))); if (missing.length) { const bills = (await Promise.all(missing.map((id) => getRegularizationById(id, {}, false).catch(() => null)))).filter( Boolean ); bills.forEach((bill) => { const index = rows.findIndex((row) => String(row.userId) === String(bill.userId)); if (index >= 0) { rows[index] = mergeEmployeeRegularization(rows[index], bill); } else { rows.push( Object.assign({}, bill, { id: bill.userId, userId: bill.userId, regularizationId: bill.regularizationId || bill.id, }) ); } }); } return { list: match(rows), count: rows.length, source: "api" }; } /** * 转正单详情;传列表行时优先用行内数据兜底。 * 转正单详情接口只回 userId/deptId,姓名 / 岗位 / 组织快照用员工档案补齐, * 并回带机构 / 部门 / 岗位序列 / 岗位层级主键,供编辑态级联定位。 */ export async function loadRegularizationDetail(id, fallbackRow = null, loding = true) { if (!id) { if (!fallbackRow) return null; const brief = await fetchEmployeeBrief(fallbackRow.userId); return adaptRegularization(fallbackRow, Object.assign({}, fallbackRow, brief || {})); } const extras = fallbackRow ? { userId: fallbackRow.userId, employeeNo: fallbackRow.employeeNo, name: fallbackRow.name, avatar: fallbackRow.avatar, phone: fallbackRow.phone, company: fallbackRow.company, department: fallbackRow.department, position: fallbackRow.position, level: fallbackRow.level, manager: fallbackRow.manager, hireDate: fallbackRow.hireDate, groupId: fallbackRow.groupId, deptId: fallbackRow.deptId, positionId: fallbackRow.positionId, positionSequenceId: fallbackRow.positionSequenceId, positionSequence: fallbackRow.positionSequence, positionLevelId: fallbackRow.positionLevelId, positionLevel: fallbackRow.positionLevel, positionType: fallbackRow.positionType, positionTypeName: fallbackRow.positionTypeName, managerUserId: fallbackRow.managerUserId, managerUserName: fallbackRow.managerUserName, } : {}; let bill = await getRegularizationById(id, extras, loding); if (!bill) return null; if (!bill.name || !bill.department || !bill.groupId) { const brief = await fetchEmployeeBrief(bill.userId); if (brief) { bill = adaptRegularization( Object.assign({}, bill.raw || {}, bill), Object.assign({}, brief, extras, { userId: bill.userId, name: bill.name || brief.name, avatar: bill.avatar || brief.avatar, employeeNo: bill.employeeNo || brief.employeeNo, phone: bill.phone || brief.phone, company: bill.company || brief.company || brief.companyName, department: bill.department || brief.department, position: bill.position || brief.position || brief.positionName, level: bill.level || brief.level || brief.positionLevel, manager: bill.manager || brief.manager || brief.managerUserName, hireDate: bill.hireDate || brief.hireDate, }) ); } } return fallbackRow ? Object.assign({}, fallbackRow, bill, { userId: bill.userId != null ? bill.userId : fallbackRow.userId, groupId: bill.groupId || fallbackRow.groupId, }) : bill; } function toApplicableEmployeeOptions(rows = []) { return rows.filter((row) => isApplicableRegularizationStatus(row.status)).map((row) => ({ id: row.userId, userId: row.userId, employeeNo: row.employeeNo || "", name: row.name || "", avatar: row.avatar || String(row.name || "").charAt(0), phone: row.phone || "", company: row.company || "", department: row.department || "", position: row.position || "", level: row.level || "", manager: row.manager || "", hireDate: row.hireDate || "", regularDate: dateOnly(row.plannedRegularDate || row.regularDate), // ===== 级联回填所需的组织 / 岗位快照 ===== groupId: pickBig(row.groupId), deptId: pickBig(row.deptId), positionId: pickBig(row.positionId), positionSequenceId: pickBig(row.positionSequenceId), positionSequence: row.positionSequence || "", positionLevelId: pickBig(row.positionLevelId), positionLevel: row.positionLevel || "", positionType: pickBig(row.positionType), positionTypeName: row.positionTypeName || "", managerUserId: pickBig(row.managerUserId), managerUserName: row.managerUserName || row.manager || "", })); } /** 转正申请表单选项:可办理的试用期员工 + 启用态线上模板 */ export async function loadRegularizationFormOptions() { const [employeePage, templates] = await Promise.all([ fetchProbationEmployees().catch(() => ({ list: [] })), getRegularizationTemplatePage({}, false) .then((result) => result.list) .catch(() => []), ]); const source = Array.isArray(employeePage.list) ? employeePage.list : []; const rows = await mergeBillsIntoEmployees(source); let fallbackTemplates = []; if (!templates.length) { fallbackTemplates = await loadOnlineTemplateList({ templateStatus: "ENABLED" }, false) .then((page) => page.list || []) .catch(() => []); } return { employees: toApplicableEmployeeOptions(rows), templates: templates.length ? templates : fallbackTemplates, }; } /** 某员工的任职周期,用于带出计划转正日与 employmentPeriodId */ export async function loadEmploymentPeriod(userId, loding = false) { try { const list = await getEmploymentPeriods(userId, loding); return list.find((item) => String(item.periodStatus || "").toUpperCase() === "ACTIVE") || list[0] || null; } catch (error) { return null; } } /** 提交(或存草稿)转正申请 */ export async function submitRegularizationApplication({ form, submit = true, existingId = null } = {}) { if (!form) throw new Error("申请参数为空"); const id = existingId || form.regularizationId || form.id || null; if (id && form.status && !canEditRegularization({ status: form.status, regularizationId: id })) { throw new Error("当前单据状态不可编辑或提交"); } if (!submit) { // 仅存草稿 return saveRegularizationDraft(Object.assign({}, form, { regularizationId: id }), true); } const result = await submitRegularizationFlow({ form: Object.assign({}, form, { regularizationId: id }), existingId: id }); if (!result.approvalStarted) throw new Error(result.approvalError || "审批发起失败"); return result.id; } // —— 流程动作 —— export async function actSubmitRegularization(row) { if (!row || !row.regularizationId) throw new Error("缺少转正单主键"); return submitRegularization(row.regularizationId, row); } export async function actWithdrawRegularization(row, reason = "") { if (!row || !row.regularizationId) throw new Error("缺少转正单主键"); return withdrawRegularization(row.regularizationId, reason); } export async function actCancelRegularization(row, reason = "") { if (!row || !row.regularizationId) throw new Error("缺少转正单主键"); const result = await cancelRegularization(row.regularizationId, reason); removeRegularizationBillId(row.userId); return result; } export function currentRegularizationUserId() { const userInfo = uni.getStorageSync("userInfo") || {}; const raw = userInfo.raw || {}; const candidates = [userInfo.userId, userInfo.id, raw.userId, raw.id]; return candidates.find((value) => value !== undefined && value !== null && value !== ""); } /** 解析某员工当前的转正单主键 */ export async function resolveRegularizationId(userId) { if (userId === undefined || userId === null || userId === "") return null; const mapped = loadRegularizationBillMap()[String(userId)]; if (mapped) return mapped; return resolveRegularizationIdFromHistory(userId); } // ============ PC 端新建页对齐:签署方式 / 直属上级 / 回填 / 审批发起 ============ /** 签署方式:移动端页面不再提供切换,统一按 OFFLINE(线下签署)提交;此表仅用于回显旧数据 */ export const SIGNING_METHOD_LABELS = { ONLINE: "线上签署", OFFLINE: "线下签署", }; export function normalizeSigningMethod(raw = "") { const text = String(raw || "") .trim() .toUpperCase(); return text === "ONLINE" ? "ONLINE" : "OFFLINE"; } /** * 直属上级候选搜索(全员):GET /main/user/getUserPage * 与 PC 端 remote-method 的 searchManagers 行为一致,按姓名 / 工号模糊匹配。 */ export async function searchManagerCandidates(keyword = "", loding = false) { try { const res = await get( Vue.prototype.apiUrl + "/main/user/getUserPage", compact({ pageNum: 1, size: 50, userName: String(keyword || "").trim() || undefined, isQueryLZ: 0, }), loding ); const data = (res && res.data) || {}; return asList(data).map((item) => ({ id: item.id, userId: item.id, name: item.name || item.userName || "", employeeNo: item.jobNumber || item.employeeNo || "", label: `${item.name || item.userName || ""}(${item.jobNumber || item.employeeNo || "-"})`, })); } catch (error) { return []; } } /** 列表行 / 员工档案 → 可编辑表单(编辑回填、详情只读共用同一形状) */ export function hydrateRegularizationForm(source = {}, extras = {}) { const raw = source || {}; return Object.assign({}, raw, { regularizationId: raw.regularizationId != null ? raw.regularizationId : raw.billId != null ? raw.billId : raw.id, billId: raw.regularizationId != null ? raw.regularizationId : raw.billId != null ? raw.billId : raw.id, userId: raw.userId != null ? raw.userId : extras.userId, name: raw.name || extras.name || "", employeeNo: raw.employeeNo || extras.employeeNo || "", avatar: raw.avatar || extras.avatar || String(raw.name || extras.name || "").charAt(0), phone: raw.phone || extras.phone || "", company: raw.company || extras.company || "", groupId: raw.groupId || extras.groupId || "", deptId: raw.deptId || extras.deptId || "", department: raw.department || extras.department || "", positionId: raw.positionId || extras.positionId || "", position: raw.position || extras.position || "", positionSequenceId: raw.positionSequenceId || extras.positionSequenceId || "", positionSequence: raw.positionSequence || extras.positionSequence || "", positionLevelId: raw.positionLevelId || extras.positionLevelId || "", positionLevel: raw.positionLevel || extras.positionLevel || "", positionType: raw.positionType || extras.positionType || "", positionTypeName: raw.positionTypeName || extras.positionTypeName || "", managerUserId: raw.managerUserId || extras.managerUserId || "", managerUserName: raw.managerUserName || raw.manager || extras.manager || "", manager: raw.managerUserName || raw.manager || extras.manager || "", hireDate: dateOnly(raw.hireDate || extras.hireDate) || "", employmentPeriodId: raw.employmentPeriodId || extras.employmentPeriodId || "", regularizationType: raw.regularizationType || "NORMAL", plannedRegularDate: dateOnly(raw.plannedRegularDate || raw.plannedDate || raw.regularDate) || "", effectiveDate: dateOnly(raw.effectiveDate) || "", extensionDays: raw.extensionDays != null ? raw.extensionDays : "", extensionReason: raw.extensionReason || "", newProbationEndDate: dateOnly(raw.newProbationEndDate) || "", userSummary: raw.userSummary || raw.remark || "", signingMethod: normalizeSigningMethod(raw.signingMethod || (raw.online ? "ONLINE" : "OFFLINE")), online: resolveOnlineSigning(raw, extras), onlineDocumentTemplateId: raw.onlineDocumentTemplateId || extras.onlineDocumentTemplateId || "", approvalDocumentFileId: joinFileIds(raw.approvalDocumentFileId), }); } /** 保存草稿:有主键走更新,否则新建;返回转正单主键 */ export async function saveRegularizationDraft(form = {}, loding = true) { const existingId = form.regularizationId || form.billId || form.id || null; const payload = buildRegularizationPayload(Object.assign({}, form, { id: existingId })); let billId = existingId; if (existingId != null && existingId !== "") { await updateRegularization(Object.assign({}, payload, { id: billId }), loding); } else { billId = await createRegularization(payload, loding); } if (form.userId != null && billId != null) saveRegularizationBillId(form.userId, billId); return billId; } /** 组装 BPM 发起审批所需变量(与 PC launchApproval 的 variables 一致) */ export function buildRegularizationVariables(form = {}, businessId) { const type = form.regularizationType || "NORMAL"; const typeLabel = labelOf(REGULARIZATION_TYPE_LABELS, type, "转正"); // PC 端为 `${typeLabel}转正`,标签本身已含「转正」时会重复(如「按期转正转正」),这里去重 const businessType = /转正$/.test(typeLabel) ? typeLabel : `${typeLabel}转正`; return compact({ businessCode: businessId != null && businessId !== "" ? String(businessId) : undefined, businessName: form.name || form.employeeName || "", businessType, userId: form.userId != null ? form.userId : undefined, regularizationType: type, regularizationTypeLabel: typeLabel, plannedRegularDate: dateOnly(form.plannedRegularDate || form.plannedDate) || undefined, effectiveDate: dateOnly(form.effectiveDate) || undefined, signingMethod: normalizeSigningMethod(form.signingMethod || (form.online ? "ONLINE" : "OFFLINE")), }); } /** * 发起转正审批流程。 * businessKey 固定 main_regularization_approval,businessId 为转正单主键。 * 与 startAttendanceApproval / startUserChangeApproval 同构: * 先用流程列表接口取 processDefinitionId,再调用 /bpm/process-instance/create。 */ export async function startRegularizationApproval(businessId, variables = {}, loding = false) { if (businessId == null || businessId === "") throw new Error("缺少转正单主键,无法发起审批"); const page = await processInstancePage( { pageNo: 1, pageSize: 1, reset: true, key: REGULARIZATION_APPROVAL_BUSINESS_KEY }, loding ); const first = page && page.list && page.list[0]; const processDefinitionId = first && first.processDefinition ? first.processDefinition.id : ""; if (!processDefinitionId) throw new Error("未匹配到转正审批流程,请联系管理员配置流程定义"); return processInstanceCreateAPI( { businessId, businessKey: REGULARIZATION_APPROVAL_BUSINESS_KEY, formCreateUserId: variables.userId || "", processDefinitionId, variables, }, false ); } /** * 提交转正申请:保存(或更新)转正单后直接发起审批,不再保留草稿态。 * @returns {Promise<{id:number|string, approvalStarted:boolean, approvalError:string}>} */ export async function submitRegularizationFlow({ form, existingId = null, loding = true } = {}) { if (!form) throw new Error("申请参数为空"); const id = await saveRegularizationDraft(Object.assign({}, form, { regularizationId: existingId || form.regularizationId }), loding); const variables = buildRegularizationVariables(Object.assign({}, form, { regularizationId: id }), id); let approvalStarted = false; let approvalError = ""; try { await startRegularizationApproval(id, variables); approvalStarted = true; } catch (error) { approvalError = (error && error.message) || "审批发起失败"; } return { id, approvalStarted, approvalError }; }