onboarding.js 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. import { get, postJ } from "@/utils/request";
  2. import Vue from "vue";
  3. import { onboardStatus } from "@/pages/hr/data/manage-status";
  4. // 用工类型枚举值 → 中文标签(与后端约定一致)
  5. export const EMPLOYMENT_TYPE_LABEL = {
  6. FULL_TIME: "全职",
  7. INTERN: "实习",
  8. PART_TIME: "兼职",
  9. OTHER: "其他",
  10. };
  11. function ok(res) {
  12. return res && Number(res.code) === 0;
  13. }
  14. function asList(data) {
  15. if (Array.isArray(data)) return data;
  16. if (data && Array.isArray(data.list)) return data.list;
  17. if (data && Array.isArray(data.records)) return data.records;
  18. return [];
  19. }
  20. function dateOnly(value) {
  21. if (!value) return "";
  22. return String(value).slice(0, 10);
  23. }
  24. /**
  25. * 把后端入职记录 VO 归一为前端可用形状。
  26. */
  27. export function adaptOnboardingRecord(vo, base = {}) {
  28. if (!vo || typeof vo !== "object") return null;
  29. const id = String(vo.onboardingId != null ? vo.onboardingId : vo.id != null ? vo.id : base.id || "");
  30. const onboardingStatus = vo.onboardingStatus || base.onboardingStatus || "";
  31. const approvalStatus = vo.approvalStatus || base.approvalStatus || "";
  32. const status =
  33. onboardingStatus === "COMPLETED" || approvalStatus === "APPROVED"
  34. ? "done"
  35. : onboardingStatus === "SUBMITTED" || approvalStatus === "APPROVING"
  36. ? "processing"
  37. : base.status || "pending";
  38. return {
  39. id,
  40. onboardingId: id,
  41. invitationId: vo.invitationId != null ? vo.invitationId : base.invitationId || "",
  42. invitationNo: vo.invitationNo || base.invitationNo || "",
  43. employeeNo: vo.employeeNo || base.employeeNo || id,
  44. name: vo.employeeName || base.name || "",
  45. department: vo.deptName || base.department || "",
  46. deptId: vo.deptId != null ? vo.deptId : base.deptId != null ? base.deptId : null,
  47. position: vo.positionName || base.position || "",
  48. positionId: vo.positionId != null ? vo.positionId : base.positionId != null ? base.positionId : null,
  49. level: vo.level || base.level || "",
  50. hireDate: dateOnly(vo.plannedJoinDate || base.hireDate || base.expectedEntryDate || ""),
  51. company: vo.companyName || base.company || "",
  52. legalId: vo.legalId != null ? vo.legalId : base.legalId != null ? base.legalId : null,
  53. phone: vo.phone || base.phone || "",
  54. email: vo.email || base.email || "",
  55. idNumber: vo.idNumber || base.idNumber || "",
  56. gender: vo.gender || base.gender || "",
  57. age: vo.age != null ? vo.age : base.age || "",
  58. employmentType: vo.employmentType || base.employmentType || "FULL_TIME",
  59. probationFlag: vo.probationFlag != null ? vo.probationFlag : base.probationFlag != null ? base.probationFlag : 1,
  60. probationMonths: vo.probationMonths != null ? vo.probationMonths : base.probationMonths != null ? base.probationMonths : 3,
  61. validDays: vo.validDays != null ? vo.validDays : base.validDays != null ? base.validDays : 7,
  62. createMode: vo.createMode || base.createMode || "manual",
  63. status,
  64. flow: onboardingStatus === "SUBMITTED" ? "档案已完善" : base.flow || "待补充档案",
  65. offer: vo.offerStatus || base.offer || "未发起",
  66. contract: vo.contractStatus || base.contract || "未发起",
  67. assets: vo.assetsStatus || base.assets || "未领用",
  68. remark: vo.remark || base.remark || "",
  69. publicPath: vo.publicPath || base.publicPath || "",
  70. publicToken: vo.publicToken || base.publicToken || "",
  71. invitationStatus: vo.invitationStatus || base.invitationStatus || "",
  72. sourceType: vo.sourceType || "API",
  73. rowType: "record",
  74. materials: base.materials || {},
  75. socialSecurity: base.socialSecurity || { status: "未办理", city: "北京", deadline: "" },
  76. training: base.training || { status: "未开始", name: "新员工入职培训", duration: "2天", deadline: "" },
  77. workstation: base.workstation || { status: "待分配", location: "" },
  78. itAccount: base.itAccount || { status: "未开通", items: [] },
  79. stage: base.stage || "offer",
  80. entryDate: dateOnly(vo.actualJoinDate || base.entryDate || ""),
  81. };
  82. }
  83. /** 入职记录 → 列表卡片渲染形状 */
  84. export function mapOnboardingForList(record) {
  85. if (!record || typeof record !== "object") return null;
  86. const id = record.id || record.onboardingId || "";
  87. const name = record.name || record.employeeName || "";
  88. const department = record.department || record.deptName || "";
  89. const position = record.position || record.positionName || "";
  90. const statusInfo = onboardStatus[record.status] || onboardStatus.pending;
  91. const hireDate = record.hireDate || record.entryDate || record.expectedEntryDate || "";
  92. const employeeNo = record.employeeNo || id;
  93. const isEntered = Boolean(record.entryDate);
  94. const materials = record.materials;
  95. let materialsLabel = "";
  96. if (materials && typeof materials === "object") {
  97. const total = Object.keys(materials).length;
  98. if (total) {
  99. const done = Object.values(materials).filter(Boolean).length;
  100. materialsLabel = `${done}/${total}`;
  101. }
  102. }
  103. return {
  104. id,
  105. avatar: record.avatar || String(name).charAt(0) || "",
  106. name,
  107. sub: [department, position].filter(Boolean).join(" · "),
  108. status: statusInfo.label,
  109. statusClass: statusInfo.class,
  110. label1: isEntered ? "入职日期" : "预计入职",
  111. value1: isEntered ? record.entryDate : hireDate,
  112. label2: isEntered ? "员工编号" : materialsLabel ? "资料进度" : "员工编号",
  113. value2: isEntered ? employeeNo : materialsLabel || employeeNo,
  114. };
  115. }
  116. /**
  117. * 员工状态(即岗位性质)枚举:1全职、2兼职、3实习、4正式、5试用
  118. * 直接办理入职时服务端会从启用岗位回填六项岗位快照,前端只传基础项
  119. */
  120. export const USER_STATUS_LABEL = { 1: "全职", 2: "兼职", 3: "实习", 4: "正式", 5: "试用" };
  121. export const USER_STATUS_OPTIONS = [
  122. { value: 1, label: "全职" },
  123. { value: 2, label: "兼职" },
  124. { value: 3, label: "实习" },
  125. ];
  126. /**
  127. * 入职登记表单 → 人员入职保存参数(POST /main/onboardings)
  128. * 字段对齐 PC serializeOnboardingCreate:带岗位序列/层级/性质快照;
  129. * 主键一律原样透传(大整数雪花 id 禁止 Number() 转换,会丢精度),字符串 id 后端可接收。
  130. */
  131. export function buildUserOnboardingPayload(form = {}) {
  132. const toId = (v) => {
  133. if (v == null || v === "") return undefined;
  134. if (typeof v === "number") return Number.isSafeInteger(v) ? v : String(v);
  135. return String(v);
  136. };
  137. const text = (v) => {
  138. const s = String(v == null ? "" : v).trim();
  139. return s || undefined;
  140. };
  141. let attachmentFileIds = form.attachmentIds;
  142. if (Array.isArray(attachmentFileIds)) attachmentFileIds = attachmentFileIds.filter(Boolean).join(",");
  143. attachmentFileIds = attachmentFileIds ? String(attachmentFileIds) : undefined;
  144. const status = Number(form.status) || 1;
  145. return {
  146. name: text(form.name),
  147. joinDate: dateOnly(form.hireDate || form.joinDate) || undefined,
  148. organizationId: toId(form.legalId),
  149. targetDepartmentId: toId(form.deptId),
  150. positionSequenceId: toId(form.positionSequenceId),
  151. positionSequence: text(form.positionSequence),
  152. positionId: toId(form.positionId),
  153. positionLevelId: toId(form.positionLevelId),
  154. positionLevel: text(form.positionLevel),
  155. positionType: text(form.positionType),
  156. positionTypeName: text(form.positionTypeName),
  157. status,
  158. phone: text(form.phone),
  159. idNumber: text(form.idNumber),
  160. attachmentFileIds,
  161. };
  162. }
  163. /** 新增人员入职(直接办理)POST /main/onboardings,成功返回新员工用户主键 */
  164. export async function createUserOnboarding(form, loding = true) {
  165. const res = await postJ(Vue.prototype.apiUrl + "/main/onboardings", buildUserOnboardingPayload(form), loding);
  166. if (!ok(res)) throw new Error(res.message || "入职办理失败");
  167. return res.data;
  168. }
  169. /** 入职记录列表 GET /hr/ess/admin/onboardings/records */
  170. export async function getOnboardingRecords(loding = true) {
  171. const res = await get(Vue.prototype.apiUrl + "/hr/ess/admin/onboardings/records", {}, loding);
  172. if (!ok(res)) return [];
  173. return asList(res.data);
  174. }
  175. /** 入职邀请列表 GET /hr/ess/admin/onboardings/invitations */
  176. export async function getOnboardingInvitationsList(loding = true) {
  177. const res = await get(Vue.prototype.apiUrl + "/hr/ess/admin/onboardings/invitations", {}, loding);
  178. if (!ok(res)) return [];
  179. return asList(res.data);
  180. }
  181. /** 提交入职审批 POST /hr/ess/admin/onboardings/records/{id}/submit */
  182. export async function submitOnboardingRecord(onboardingId, loding = true) {
  183. if (!onboardingId) throw new Error("缺少入职单主键");
  184. const res = await postJ(
  185. Vue.prototype.apiUrl + `/hr/ess/admin/onboardings/records/${encodeURIComponent(onboardingId)}/submit`,
  186. {},
  187. loding
  188. );
  189. if (!ok(res)) throw new Error(res.message || "提交失败");
  190. return res.data || {};
  191. }
  192. /** 入职记录列表(归一化后) */
  193. export async function loadOnboardingRecords(loding = true) {
  194. const rows = await getOnboardingRecords(loding);
  195. return rows.map((vo) => adaptOnboardingRecord(vo) || vo).filter(Boolean);
  196. }
  197. /** 入职记录 / 邀请 详情(归一化后) */
  198. export async function loadOnboardingDetail(id) {
  199. if (!id) return null;
  200. try {
  201. const [records, invitations] = await Promise.all([
  202. getOnboardingRecords(false).catch(() => []),
  203. getOnboardingInvitationsList(false).catch(() => []),
  204. ]);
  205. const key = String(id);
  206. const hit =
  207. records.find((vo) => String(vo.onboardingId != null ? vo.onboardingId : vo.id) === key) ||
  208. invitations.find(
  209. (vo) =>
  210. String(vo.onboardingId != null ? vo.onboardingId : vo.id) === key ||
  211. String(vo.invitationId != null ? vo.invitationId : "") === key
  212. );
  213. if (!hit) return null;
  214. const record = adaptOnboardingRecord(hit);
  215. if (record && !record.createMode) record.createMode = "invite";
  216. return record;
  217. } catch (error) {
  218. return null;
  219. }
  220. }