index.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929
  1. import request from "@/utils/request";
  2. import { contractStatusLabel, contractSigningStatusLabel } from "@/api/hr/contractStatus";
  3. function unwrap(response) {
  4. const result = response.data;
  5. if (result.code == 0) {
  6. return result.data;
  7. }
  8. const error = new Error(result.message || "服务调用失败");
  9. if (response?.__toastShown) error.__toastShown = true;
  10. return Promise.reject(error);
  11. }
  12. function compact(object) {
  13. return Object.keys(object).reduce((result, key) => {
  14. const value = object[key];
  15. if (value !== undefined && value !== null && value !== "") result[key] = value;
  16. return result;
  17. }, {});
  18. }
  19. function formatDate(value) {
  20. if (!value) return "";
  21. const text = String(value);
  22. if (/^\d{4}-\d{2}-\d{2}/.test(text)) return text.slice(0, 10);
  23. const date = new Date(value);
  24. if (Number.isNaN(date.getTime())) return text;
  25. const y = date.getFullYear();
  26. const m = String(date.getMonth() + 1).padStart(2, "0");
  27. const d = String(date.getDate()).padStart(2, "0");
  28. return `${y}-${m}-${d}`;
  29. }
  30. function downloadBlob(response, fallbackName) {
  31. const blob = new Blob([response.data], {
  32. type: response.headers["content-type"] || "application/octet-stream",
  33. });
  34. const disposition = response.headers["content-disposition"] || "";
  35. const matched = disposition.match(/filename\*?=(?:UTF-8''|")?([^";]+)/i);
  36. const fileName = matched
  37. ? decodeURIComponent(matched[1].replace(/"/g, ""))
  38. : fallbackName;
  39. const url = URL.createObjectURL(blob);
  40. const link = document.createElement("a");
  41. link.href = url;
  42. link.download = fileName;
  43. document.body.appendChild(link);
  44. link.click();
  45. document.body.removeChild(link);
  46. URL.revokeObjectURL(url);
  47. }
  48. export async function listOrganizations(params) {
  49. return unwrap(await request.get("/main/group/getGroupList", { params }));
  50. }
  51. /** 工厂区域分页:type=1 工厂 */
  52. export async function getFactoryAreaPage(params = {}) {
  53. return unwrap(
  54. await request.get("/main/factoryarea/page", {
  55. params: {
  56. pageNum: 1,
  57. size: 999,
  58. type: 1,
  59. ...params,
  60. },
  61. }),
  62. );
  63. }
  64. export async function getOrganizationTree(params) {
  65. return unwrap(await request.get("/main/group/getGroupTree", { params }));
  66. }
  67. export async function getGroupUserTree(params) {
  68. return unwrap(await request.get("/main/group/getGroupUserTree", { params }));
  69. }
  70. export async function getOrganization(id) {
  71. return unwrap(await request.get("/main/group/getById/" + id));
  72. }
  73. export async function getOrganizationCode() {
  74. return unwrap(await request.get("/main/codemanage/getCode/group_code"));
  75. }
  76. export async function addOrganization(data) {
  77. const response = await request.post("/main/group/save", data);
  78. if (response.data.code == 0) {
  79. return response.data.data ?? response.data.message;
  80. }
  81. return Promise.reject(new Error(response.data.message || "新增失败"));
  82. }
  83. export async function updateOrganization(data) {
  84. const response = await request.put("/main/group/update", data);
  85. if (response.data.code == 0) {
  86. return response.data.data ?? response.data.message;
  87. }
  88. return Promise.reject(new Error(response.data.message || "修改失败"));
  89. }
  90. export async function updateDepartmentPosition(data) {
  91. return unwrap(await request.put("/fm/tmaindepartmentposition/updateBatch", data));
  92. }
  93. export async function saveBatchDepartmentPosition(data) {
  94. return unwrap(await request.post("/fm/tmaindepartmentposition/saveBatch", data));
  95. }
  96. export async function pageDepartmentPosition(params) {
  97. return unwrap(await request.post("/fm/tmaindepartmentposition/page", params));
  98. }
  99. export async function removeOrganization(ids) {
  100. const response = await request.post("/main/group/delete", ids);
  101. if (response.data.code == 0) {
  102. return response.data.message;
  103. }
  104. return Promise.reject(new Error(response.data.message || "删除失败"));
  105. }
  106. export async function getUserPage(params) {
  107. return unwrap(
  108. await request.get("/main/user/getUserPage", {
  109. params,
  110. }),
  111. );
  112. }
  113. /** 分页拉全量(有上限,避免一次性打爆接口) */
  114. export async function fetchAllUsers(params = {}, options = {}) {
  115. const pageSize = options.pageSize || 200;
  116. const maxPages = options.maxPages || 50;
  117. const list = [];
  118. let pageNum = 1;
  119. let total = Infinity;
  120. while (pageNum <= maxPages && list.length < total) {
  121. const data = await getUserPage({
  122. ...params,
  123. pageNum,
  124. size: pageSize,
  125. });
  126. const chunk = Array.isArray(data?.list) ? data.list : [];
  127. total = Number(data?.count ?? list.length + chunk.length);
  128. list.push(...chunk);
  129. if (!chunk.length || chunk.length < pageSize) break;
  130. pageNum += 1;
  131. }
  132. return { list, count: total === Infinity ? list.length : total };
  133. }
  134. /**
  135. * 关键词联合检索:并行 name / phone / jobNumber,再按 id 去重
  136. * (后端 getUserPage 无统一 keyword 字段)
  137. */
  138. export async function searchUsersByKeyword(keyword, baseParams = {}) {
  139. const text = String(keyword || "").trim();
  140. if (!text) {
  141. return getUserPage(baseParams);
  142. }
  143. const queries = [{ ...baseParams, name: text }];
  144. if (/^\d+$/.test(text) || /[A-Za-z]/.test(text)) {
  145. queries.push({ ...baseParams, jobNumber: text });
  146. }
  147. if (/^1\d{10}$/.test(text) || text.includes("*")) {
  148. queries.push({ ...baseParams, phone: text });
  149. }
  150. const results = await Promise.all(queries.map((params) => getUserPage(params).catch(() => ({ list: [], count: 0 }))));
  151. const map = new Map();
  152. results.forEach((data) => {
  153. (Array.isArray(data?.list) ? data.list : []).forEach((item) => {
  154. if (item?.id != null) map.set(String(item.id), item);
  155. });
  156. });
  157. const list = Array.from(map.values());
  158. return { list, count: list.length, pageNum: 1, size: list.length };
  159. }
  160. export async function getEmployeeById(id) {
  161. return unwrap(
  162. await request.get(`/main/user/getById/${encodeURIComponent(String(id))}`),
  163. );
  164. }
  165. /** 360 度员工人事档案聚合视图 */
  166. export async function getEmployeeProfile(userId) {
  167. return unwrap(await request.get(`/main/users/${userId}/profile`));
  168. }
  169. export async function getEmployeeHistory(userId) {
  170. return unwrap(await request.get(`/main/users/${userId}/history`));
  171. }
  172. export async function saveEmployee(data) {
  173. return unwrap(await request.post("/main/user/saveNew", data));
  174. }
  175. /**
  176. * 员工信息首次入库归档后创建个人文档区。
  177. * 仅可调用一次:详情已有 jobNumber 时说明已归档,不可再调。
  178. */
  179. export async function createPersonalDirectory(userId) {
  180. const id = String(userId ?? "").trim();
  181. if (!id) return null;
  182. return unwrap(
  183. await request.post(`/fm/directory/createPersonal/${encodeURIComponent(id)}`),
  184. );
  185. }
  186. export function hasJobNumber(record = {}) {
  187. const jobNumber = record.jobNumber ?? record.user?.jobNumber ?? "";
  188. return Boolean(String(jobNumber).trim());
  189. }
  190. export async function removeEmployee(ids) {
  191. return unwrap(await request.post("/main/user/removeById", ids));
  192. }
  193. export async function exportUsers(payload = {}) {
  194. const response = await request.post("/main/user/exportUsers", payload, {
  195. responseType: "blob",
  196. });
  197. downloadBlob(response, `员工档案_${formatDate(new Date())}.xlsx`);
  198. }
  199. export async function downloadUserTemplate() {
  200. const response = await request.post(
  201. "/main/user/downLoadTemplate",
  202. {},
  203. { responseType: "blob" },
  204. );
  205. downloadBlob(response, "人员信息导入模板.xlsx");
  206. }
  207. export async function importUsers(file) {
  208. const formData = new FormData();
  209. formData.append("file", file);
  210. const response = await request.post("/main/excelUpload/importUser", formData);
  211. if (response.data?.code == 0 || typeof response.data === "string") {
  212. return response.data?.data ?? response.data?.message ?? response.data;
  213. }
  214. return Promise.reject(new Error(response.data?.message || "导入失败"));
  215. }
  216. export async function listWorkExperiences(userId) {
  217. return unwrap(await request.get(`/main/users/${userId}/work-experiences`));
  218. }
  219. export async function saveWorkExperience(userId, data) {
  220. return unwrap(await request.post(`/main/users/${userId}/work-experiences`, data));
  221. }
  222. export async function updateWorkExperience(userId, workExperienceId, data) {
  223. return unwrap(
  224. await request.put(
  225. `/main/users/${userId}/work-experiences/${workExperienceId}`,
  226. data,
  227. ),
  228. );
  229. }
  230. export async function removeWorkExperience(userId, workExperienceId) {
  231. return unwrap(
  232. await request.delete(
  233. `/main/users/${userId}/work-experiences/${workExperienceId}`,
  234. ),
  235. );
  236. }
  237. export async function exportOrganizations() {
  238. const response = await request.post(
  239. "/main/group/exportGroups",
  240. {},
  241. { responseType: "blob" },
  242. );
  243. downloadBlob(response, "组织机构列表.xlsx");
  244. }
  245. /** 后端 status → 列表展示态 */
  246. export const EMPLOYEE_STATUS_CODE = {
  247. 1: "全职",
  248. 2: "兼职",
  249. 3: "实习",
  250. 4: "正式",
  251. 5: "试用",
  252. 6: "离职",
  253. };
  254. const EMPLOYEE_STATUS = {
  255. 全职: 1,
  256. 兼职: 2,
  257. 实习: 3,
  258. 正式: 4,
  259. 试用: 5,
  260. 试用期: 5,
  261. 劳务: 4,
  262. 离职: 6,
  263. };
  264. const EMPLOYEE_SEX = { 男: 1, 女: 2, 未知: 0 };
  265. const SEX_LABEL = { 0: "未知", 1: "男", 2: "女" };
  266. const REWARD_STATUS = { 生效: 2, 已撤销: 4, 已过期: 3, 未生效: 0, 审核中: 1 };
  267. const CERT_TYPE = {
  268. 身份证: 1,
  269. 驾驶证: 2,
  270. 军官证: 3,
  271. 护照: 4,
  272. 特种作业证: 4,
  273. 职称证: 4,
  274. 职业资格证: 4,
  275. 其他: 4,
  276. };
  277. const CERT_LEVEL = { 初级: "1", 中级: "2", 高级: "3" };
  278. const CERT_LEVEL_LABEL = { 1: "初级", 2: "中级", 3: "高级", 初级: "初级", 中级: "中级", 高级: "高级" };
  279. /** 列表/详情统一展示状态:active | probation | inactive | disabled */
  280. export function toListStatus(raw = {}) {
  281. if (Number(raw.isEnabled) === 0) return "disabled";
  282. const code = Number(raw.status);
  283. if (code === 6) return "inactive";
  284. if (code === 5) return "probation";
  285. return "active";
  286. }
  287. export function toFormStatus(raw = {}) {
  288. if (Number(raw.isEnabled) === 0) return "离职";
  289. return EMPLOYEE_STATUS_CODE[Number(raw.status)] || "全职";
  290. }
  291. function emptyLeaveInfo() {
  292. return {
  293. enabled: false,
  294. position: "",
  295. department: "",
  296. leaveDate: "",
  297. leaveType: "",
  298. leaveReason: "",
  299. serviceYears: "",
  300. handoverStatus: "",
  301. };
  302. }
  303. function adaptContract(raw) {
  304. const first = Array.isArray(raw?.contracts) ? raw.contracts[0] : null;
  305. if (!first) {
  306. return { category: "", name: "", number: "", signDate: "", startDate: "", endDate: "", files: [], status: "待签署", remark: "" };
  307. }
  308. return {
  309. category: first.contractType || "",
  310. name: first.contractNo || "",
  311. number: first.contractNo || "",
  312. signDate: formatDate(first.effectiveDate || first.startDate),
  313. startDate: formatDate(first.startDate),
  314. endDate: formatDate(first.endDate),
  315. files: [],
  316. status:
  317. contractStatusLabel(first.contractStatus, "") ||
  318. contractSigningStatusLabel(first.signingStatus, "") ||
  319. "生效",
  320. remark: "",
  321. };
  322. }
  323. function adaptBanks(raw) {
  324. if (Array.isArray(raw?.bankCardList) && raw.bankCardList.length) {
  325. return raw.bankCardList.map((item) => ({
  326. id: item.id,
  327. bankName: item.openingBank || "",
  328. bankAccount: item.cardNumber || "",
  329. bankAccountName: item.accountName || raw.name || "",
  330. }));
  331. }
  332. if (raw?.openingBank || raw?.cardNumber) {
  333. return [
  334. {
  335. bankName: raw.openingBank || "",
  336. bankAccount: raw.cardNumber || "",
  337. bankAccountName: raw.name || "",
  338. },
  339. ];
  340. }
  341. return [];
  342. }
  343. function adaptSocial(raw) {
  344. if (Array.isArray(raw?.socialSecurityList) && raw.socialSecurityList.length) {
  345. return raw.socialSecurityList.map((item) => ({
  346. id: item.id,
  347. type: item.socialType || "",
  348. base: item.socialBase ?? 0,
  349. insuredDate: formatDate(item.insuredDate),
  350. stopDate: formatDate(item.stopDate),
  351. stopReason: item.stopRecord || "",
  352. }));
  353. }
  354. return [];
  355. }
  356. function adaptSkills(list = []) {
  357. return (list || []).map((item) => ({
  358. id: item.id,
  359. category: "",
  360. name: item.name || "",
  361. level: item.level || "",
  362. years: item.years ?? 0,
  363. attachment: [],
  364. remark: item.remark || "",
  365. }));
  366. }
  367. function adaptTrainings(list = []) {
  368. const statusMap = { 0: "未开始", 1: "进行中", 2: "已完成", 4: "取消" };
  369. return (list || []).map((item) => ({
  370. id: item.id,
  371. name: item.title || "",
  372. description: item.desce || "",
  373. startTime: formatDate(item.startTime),
  374. endTime: formatDate(item.endTime),
  375. dateRange: [formatDate(item.startTime), formatDate(item.endTime)].filter(Boolean),
  376. status: statusMap[item.status] || "已完成",
  377. organization: "",
  378. hours: "",
  379. result: "",
  380. certificate: "",
  381. remark: item.remark || "",
  382. }));
  383. }
  384. function adaptRewards(list = []) {
  385. const statusMap = { 0: "未生效", 1: "审核中", 2: "生效", 3: "已过期", 4: "已撤销" };
  386. return (list || []).map((item) => ({
  387. id: item.id,
  388. type: Number(item.type) === 2 ? "惩罚" : "奖励",
  389. date: formatDate(item.startTime),
  390. name: item.desce || "",
  391. description: item.desce || "",
  392. startDate: formatDate(item.startTime),
  393. endDate: formatDate(item.endTime),
  394. status: statusMap[item.status] || "生效",
  395. level: "",
  396. issuer: "",
  397. orderNo: "",
  398. remark: item.remark || "",
  399. }));
  400. }
  401. function adaptCertificates(list = []) {
  402. const typeMap = { 1: "身份证", 2: "驾驶证", 3: "军官证", 4: "护照" };
  403. return (list || []).map((item) => ({
  404. id: item.id,
  405. type: typeMap[item.type] || "其他",
  406. number: item.code || "",
  407. level: CERT_LEVEL_LABEL[item.level] || item.level || "",
  408. status: Number(item.status) === 0 ? "失效" : "有效",
  409. startDate: formatDate(item.validityStartTime),
  410. endDate: formatDate(item.validityEndTime),
  411. issueDate: formatDate(item.enactorTime),
  412. attachment: [],
  413. remark: item.remark || "",
  414. }));
  415. }
  416. function adaptWorkTypes(list = []) {
  417. return (list || []).map((item) => ({
  418. id: item.id,
  419. type: item.type || "",
  420. code: item.code || "",
  421. name: item.name || "",
  422. level: item.level || "",
  423. hourCost: item.hourCost ?? 0,
  424. unit: item.unit || "",
  425. hourlyWage: item.hourCost ?? 0,
  426. wageUnit: item.unit || "",
  427. userId: item.userId || item.relatedUserId || "",
  428. userName: item.userName || item.relatedUserName || "",
  429. }));
  430. }
  431. function adaptWorkExperiences(list = []) {
  432. return (list || []).map((item) => ({
  433. id: item.id,
  434. startDate: formatDate(item.workStartDate),
  435. endDate: formatDate(item.workEndDate),
  436. dateRange: [formatDate(item.workStartDate), formatDate(item.workEndDate)].filter(Boolean),
  437. company: item.formerCompanyName || "",
  438. position: item.formerPosition || "",
  439. department: item.formerDepartment || "",
  440. leaveReason: item.resignationReason || "",
  441. description: item.workDescription || "",
  442. }));
  443. }
  444. function adaptHistory(list = []) {
  445. return (list || []).map((item) => ({
  446. date: formatDate(item.eventTime || item.occurTime || item.createTime),
  447. title: item.eventTitle || item.title || item.eventType || "人事事件",
  448. detail: item.eventDesc || item.description || item.summary || "",
  449. orderNo: item.bizNo || item.orderNo || "",
  450. }));
  451. }
  452. /** 兼容 positionList;无名称时用 postName 按序补齐;positionId 支持逗号串 */
  453. function adaptPositionBindings(raw = {}) {
  454. const fallbackNames = String(raw.postName || "")
  455. .split(/[,,、]/)
  456. .map((item) => item.trim())
  457. .filter(Boolean);
  458. if (Array.isArray(raw.positionList) && raw.positionList.length) {
  459. return raw.positionList
  460. .filter((item) => item && (item.positionId != null || item.positionName))
  461. .flatMap((item, index) => {
  462. const rawId = item.positionId != null ? String(item.positionId) : "";
  463. const idParts = rawId
  464. .split(/[,,]/)
  465. .map((part) => part.trim())
  466. .filter(Boolean);
  467. const ids = idParts.length ? idParts : rawId ? [rawId] : [];
  468. if (!ids.length && item.positionName) {
  469. return [
  470. {
  471. id: item.id != null ? String(item.id) : undefined,
  472. positionId: "",
  473. positionName: item.positionName || fallbackNames[index] || "",
  474. positionLevelId:
  475. item.positionLevelId != null ? String(item.positionLevelId) : "",
  476. positionLevelName: item.positionLevelName || "",
  477. positionSequenceId:
  478. item.positionSequenceId != null
  479. ? String(item.positionSequenceId)
  480. : "",
  481. positionSequenceName: item.positionSequenceName || "",
  482. positionType: item.positionType || "",
  483. primaryFlag: Number(item.primaryFlag) === 1 ? 1 : 0,
  484. },
  485. ];
  486. }
  487. return ids.map((positionId, partIndex) => ({
  488. id: item.id != null ? String(item.id) : undefined,
  489. positionId: String(positionId),
  490. positionName:
  491. item.positionName ||
  492. fallbackNames[index + partIndex] ||
  493. fallbackNames[index] ||
  494. "",
  495. positionLevelId:
  496. item.positionLevelId != null ? String(item.positionLevelId) : "",
  497. positionLevelName: item.positionLevelName || "",
  498. positionSequenceId:
  499. item.positionSequenceId != null
  500. ? String(item.positionSequenceId)
  501. : "",
  502. positionSequenceName: item.positionSequenceName || "",
  503. positionType: item.positionType || "",
  504. primaryFlag:
  505. partIndex === 0 && Number(item.primaryFlag) === 1 ? 1 : 0,
  506. }));
  507. })
  508. .sort((a, b) => Number(b.primaryFlag || 0) - Number(a.primaryFlag || 0));
  509. }
  510. const ids = String(raw.postId || "")
  511. .split(/[,,]/)
  512. .map((item) => item.trim())
  513. .filter(Boolean);
  514. return ids.map((positionId, index) => ({
  515. positionId: String(positionId),
  516. positionName: fallbackNames[index] || "",
  517. positionLevelId: "",
  518. positionLevelName: "",
  519. positionSequenceId: "",
  520. positionSequenceName: "",
  521. positionType: "",
  522. primaryFlag: index === 0 ? 1 : 0,
  523. }));
  524. }
  525. function toApiId(value) {
  526. if (value == null || value === "") return undefined;
  527. const text = String(value);
  528. if (/^\d+$/.test(text) && text.length <= 15) return Number(text);
  529. return text;
  530. }
  531. /**
  532. * 后端「用户」→ 员工档案页面模型
  533. * @param {object} raw 用户详情或分页行
  534. * @param {object} [extras] profile / workExperiences 等补充数据
  535. */
  536. export function adaptEmployee(raw = {}, extras = {}) {
  537. const deptIds = Array.from(
  538. new Set(
  539. (Array.isArray(raw.deptIds)
  540. ? raw.deptIds
  541. : raw.deptId != null && raw.deptId !== ""
  542. ? [raw.deptId]
  543. : []
  544. )
  545. .map((id) => String(id).trim())
  546. .filter(Boolean),
  547. ),
  548. );
  549. const deptId = deptIds[0] || "";
  550. const listStatus = toListStatus(raw);
  551. const leaveDate = formatDate(raw.leaveDate);
  552. const positionBindings = adaptPositionBindings(raw);
  553. const primaryPosition =
  554. positionBindings.find((item) => Number(item.primaryFlag) === 1) || positionBindings[0] || null;
  555. const positionIds = positionBindings.map((item) => item.positionId).filter(Boolean);
  556. const positionNames = positionBindings
  557. .map((item) => item.positionName)
  558. .filter(Boolean);
  559. const banks = adaptBanks(raw);
  560. const avatar =
  561. typeof raw.pic === "string"
  562. ? raw.pic
  563. : Array.isArray(raw.avatar) && raw.avatar[0]
  564. ? raw.avatar[0].url || raw.avatar[0].fileUrl || ""
  565. : "";
  566. return {
  567. id: raw.id,
  568. avatar,
  569. name: raw.name || "",
  570. employeeNo: raw.jobNumber || "",
  571. account: raw.loginName || "",
  572. gender: SEX_LABEL[raw.sex] || "未知",
  573. birthDate: formatDate(raw.birthday),
  574. age: raw.age ?? null,
  575. idCard: raw.idCard || raw.idNumber || "",
  576. ethnic: raw.ethnic || "",
  577. politicalStatus: raw.politicsStatus || "",
  578. maritalStatus: raw.maritalStatus || "",
  579. nativePlace: raw.birthplace || "",
  580. education: raw.educationBackground || "",
  581. school: raw.school || "",
  582. major: raw.major || "",
  583. graduationDate: formatDate(raw.graduateDate),
  584. registeredAddress: raw.censusAddress || "",
  585. currentAddress: raw.currentAddress || "",
  586. workAddress: raw.address || "",
  587. phone: raw.phone || "",
  588. email: raw.email || "",
  589. wechat: raw.wxId || "",
  590. emergencyContact: raw.secondLinkName || "",
  591. emergencyPhone: raw.secondLinkPhone || "",
  592. emergencyRelation: raw.secondLinkRelation || "",
  593. groupId: raw.groupId != null && raw.groupId !== "" ? String(raw.groupId) : null,
  594. company: raw.groupName || "",
  595. legalEntity: raw.groupName || "",
  596. factory: raw.factoryName || "",
  597. factoryId: raw.factoryId || raw.factoriesId || null,
  598. deptIds,
  599. deptId: deptId ? String(deptId) : "",
  600. deptName: raw.deptName || raw.deptNames || "",
  601. position: positionNames.join("、") || raw.postName || "",
  602. positionIds,
  603. positionBindings,
  604. positionId: primaryPosition?.positionId || null,
  605. level: primaryPosition?.positionLevelName || "",
  606. managerId:
  607. raw.managerUserId != null && raw.managerUserId !== ""
  608. ? String(raw.managerUserId)
  609. : null,
  610. manager: raw.managerUserName || "",
  611. managerJobNumber: raw.managerJobNumber || "",
  612. employmentType: EMPLOYEE_STATUS_CODE[Number(raw.status)] || "全职",
  613. status: listStatus,
  614. formStatus: toFormStatus(raw),
  615. hireDate: formatDate(raw.joinDate || raw.hireDate || raw.entryDate),
  616. regularDate: formatDate(raw.positiveDate),
  617. enabled: Number(raw.isEnabled) !== 0,
  618. keyProcess:
  619. raw.criticalTaskIds ||
  620. (Array.isArray(raw.criticalTaskList)
  621. ? raw.criticalTaskList
  622. .map((item) => item.taskName || item.taskId)
  623. .filter(Boolean)
  624. .join(",")
  625. : "") ||
  626. "",
  627. remarks: raw.remark || "",
  628. bankName: banks[0]?.bankName || "",
  629. bankAccount: banks[0]?.bankAccount || "",
  630. bankAccountName: banks[0]?.bankAccountName || raw.name || "",
  631. bankAccounts: banks,
  632. socialSecurityRecords: adaptSocial(raw),
  633. skills: adaptSkills(raw.skills),
  634. trainingRecords: adaptTrainings(raw.trainRecords),
  635. rewardPunishmentRecords: adaptRewards(raw.rewardsPunishments),
  636. certificates: adaptCertificates(raw.identityPhotos),
  637. workTypes: adaptWorkTypes(raw.professions),
  638. previousJobs: adaptWorkExperiences(extras.workExperiences || []),
  639. educations: raw.educationBackground
  640. ? [
  641. {
  642. education: raw.educationBackground || "",
  643. degree: "",
  644. graduationDate: formatDate(raw.graduateDate),
  645. school: raw.school || "",
  646. major: raw.major || "",
  647. },
  648. ]
  649. : [],
  650. contract: adaptContract(raw),
  651. workHistory: adaptHistory(extras.history || []),
  652. attendanceRecords: [],
  653. performanceRecords: (extras.performanceRecords || []).map((item) => ({
  654. dateRange: [formatDate(item.periodStart), formatDate(item.periodEnd)].filter(Boolean),
  655. cycle: item.cycleName || item.cycle || "",
  656. result: item.resultLabel || item.result || "",
  657. achievementRate: item.score ?? item.achievementRate,
  658. assessmentDate: formatDate(item.publishTime || item.assessmentDate),
  659. standard: item.standard || "",
  660. remark: item.remark || "",
  661. })),
  662. awards: [],
  663. positionTransfers: (extras.changes || []).map((item) => ({
  664. date: formatDate(item.effectiveDate || item.createTime),
  665. originalPosition: item.fromPositionName || "",
  666. newPosition: item.toPositionName || "",
  667. originalDepartment: item.fromDeptName || "",
  668. newDepartment: item.toDeptName || "",
  669. orderNo: item.bizNo || item.id || "",
  670. })),
  671. contractChanges: [],
  672. familyMembers: [],
  673. files: [],
  674. leaveInfo: {
  675. ...emptyLeaveInfo(),
  676. enabled: listStatus === "inactive",
  677. position: positionNames[0] || raw.postName || "",
  678. department: raw.deptName || "",
  679. leaveDate,
  680. },
  681. raw,
  682. };
  683. }
  684. /** 合并 profile 聚合视图 */
  685. export function adaptEmployeeProfile(profile = {}) {
  686. const user = profile.user || {};
  687. return adaptEmployee(user, {
  688. workExperiences: profile.workExperiences || [],
  689. history: profile.history || [],
  690. performanceRecords: profile.performanceRecords || [],
  691. changes: profile.changes || [],
  692. });
  693. }
  694. /**
  695. * 员工档案表单 → 后端「员工新增或修改参数」
  696. * 合同 contracts 已废弃,不提交;工作履历走独立接口
  697. */
  698. export function serializeEmployee(form = {}) {
  699. const statusText = form.formStatus || form.status;
  700. const statusCode =
  701. EMPLOYEE_STATUS[statusText] ??
  702. (statusText === "inactive" || statusText === "disabled"
  703. ? 6
  704. : statusText === "probation"
  705. ? 5
  706. : Number(statusText) || 1);
  707. const education = form.educations?.[0] || {};
  708. const bankAccounts = form.bankAccounts || [];
  709. const firstBank = bankAccounts[0] || {};
  710. const positionIds = (
  711. form.positionId
  712. ? [form.positionId]
  713. : Array.isArray(form.positionIds)
  714. ? form.positionIds
  715. : []
  716. )
  717. .filter(Boolean)
  718. .map(String)
  719. .slice(0, 1);
  720. const professionIds = (form.workTypes || [])
  721. .map((item) => item.id)
  722. .filter((id) => id != null && id !== "");
  723. const avatarUrl =
  724. typeof form.avatar === "string" && /^https?:\/\//i.test(form.avatar)
  725. ? form.avatar
  726. : undefined;
  727. return compact({
  728. id: form.id || undefined,
  729. name: form.name || "",
  730. jobNumber: form.employeeNo || "",
  731. sex: EMPLOYEE_SEX[form.gender] ?? form.gender,
  732. birthday: form.birthDate || "",
  733. age: Number(form.age) || undefined,
  734. idCard: form.idCard || "",
  735. idNumber: form.idCard || "",
  736. ethnic: form.ethnic || "",
  737. politicsStatus: form.politicalStatus || "",
  738. maritalStatus: form.maritalStatus || "",
  739. birthplace: form.nativePlace || "",
  740. censusAddress: form.registeredAddress || "",
  741. currentAddress: form.currentAddress || "",
  742. address: form.workAddress || "",
  743. educationBackground: education.education || form.education || "",
  744. school: education.school || form.school || "",
  745. major: education.major || form.major || "",
  746. graduateDate: education.graduationDate || form.graduationDate || "",
  747. phone: form.phone || "",
  748. email: form.email || "",
  749. wxId: form.wechat || "",
  750. status: statusCode,
  751. joinDate: form.hireDate || "",
  752. positiveDate: form.regularDate || "",
  753. leaveDate: form.leaveInfo?.leaveDate || "",
  754. isEnabled: form.enabled === false || statusText === "disabled" ? 0 : 1,
  755. openingBank: firstBank.bankName || form.bankName || "",
  756. cardNumber: firstBank.bankAccount || form.bankAccount || "",
  757. bankCardList: bankAccounts.length
  758. ? bankAccounts
  759. .filter((item) => item.bankAccount || item.bankName)
  760. .map((item) => ({
  761. openingBank: item.bankName || "",
  762. cardNumber: item.bankAccount || "",
  763. accountName: item.bankAccountName || form.name || "",
  764. }))
  765. : undefined,
  766. secondLinkName: form.emergencyContact || "",
  767. secondLinkPhone: form.emergencyPhone || "",
  768. secondLinkRelation: form.emergencyRelation || "",
  769. pic: avatarUrl,
  770. professionId: professionIds.length ? professionIds.map(String).join(",") : undefined,
  771. workTypeId: professionIds.length ? toApiId(professionIds[0]) : undefined,
  772. remark: form.remarks || "",
  773. groupId: form.groupId || undefined,
  774. deptIds: (Array.isArray(form.deptIds) && form.deptIds.length
  775. ? form.deptIds
  776. : form.deptId
  777. ? [form.deptId]
  778. : []
  779. )
  780. .map((id) => toApiId(id))
  781. .filter((id) => id != null),
  782. deptName: form.deptName || "",
  783. managerUserId: toApiId(form.managerId || form.managerUserId),
  784. // 岗位:界面只选岗位;提交仍需 positionList 必填的层级/序列主键(由前端按岗位档案解析)
  785. postId: positionIds.length ? positionIds.join(",") : undefined,
  786. postName: form.position || undefined,
  787. positionList: positionIds.length
  788. ? [
  789. (() => {
  790. const id = positionIds[0];
  791. const binding =
  792. (Array.isArray(form.positionBindings) ? form.positionBindings : []).find(
  793. (item) => String(item.positionId) === String(id),
  794. ) || {};
  795. return {
  796. positionId: toApiId(id),
  797. positionLevelId: toApiId(binding.positionLevelId),
  798. positionSequenceId: toApiId(binding.positionSequenceId),
  799. primaryFlag: 1,
  800. };
  801. })(),
  802. ]
  803. : [],
  804. factoryId: form.factoryId ? toApiId(form.factoryId) : undefined,
  805. socialSecurityList: (form.socialSecurityRecords || [])
  806. .filter((item) => item.type || item.insuredDate)
  807. .map((item) => ({
  808. socialType: item.type || "",
  809. socialBase: Number(item.base) || 0,
  810. insuredDate: item.insuredDate || "",
  811. stopDate: item.stopDate || "",
  812. stopRecord: item.stopReason || "",
  813. })),
  814. // skills / trainRecords 已废弃:非空提交会被后端拒绝,技能/培训走独立 HR / EHS 接口
  815. rewardsPunishments: (form.rewardPunishmentRecords || []).map((item) => ({
  816. id: item.id || undefined,
  817. type: /惩罚|处分|警告/.test(item.type) ? 2 : 1,
  818. desce: item.description || item.name || "",
  819. startTime: item.startDate || item.date || "",
  820. endTime: item.endDate || "",
  821. status: REWARD_STATUS[item.status] ?? 2,
  822. remark: item.remark || "",
  823. })),
  824. identityPhotos: (form.certificates || [])
  825. .filter((item) => item.type || item.number)
  826. .map((item) => ({
  827. id: item.id || undefined,
  828. type: CERT_TYPE[item.type] ?? 4,
  829. code: item.number || "",
  830. level: CERT_LEVEL[item.level] || item.level || "",
  831. status: item.status === "失效" ? 0 : 1,
  832. validityStartTime: item.startDate || "",
  833. validityEndTime: item.endDate || "",
  834. enactorTime: item.issueDate || "",
  835. remark: item.remark || "",
  836. holderType: "1",
  837. holder: form.name || "",
  838. })),
  839. });
  840. }
  841. export function serializeWorkExperience(item = {}) {
  842. return compact({
  843. formerCompanyName: item.company || "",
  844. formerDepartment: item.department || "",
  845. formerPosition: item.position || "",
  846. resignationReason: item.leaveReason || "",
  847. workDescription: item.description || "",
  848. workStartDate: item.startDate || item.dateRange?.[0] || "",
  849. workEndDate: item.endDate || item.dateRange?.[1] || "",
  850. });
  851. }
  852. /** 保存员工后同步入职前工作履历(独立接口) */
  853. export async function syncWorkExperiences(userId, jobs = [], existing = []) {
  854. if (!userId) return;
  855. const next = (jobs || []).filter((item) => item.company && item.startDate && item.endDate);
  856. const keptIds = new Set(next.map((item) => item.id).filter(Boolean).map(String));
  857. const removals = (existing || [])
  858. .filter((item) => item.id && !keptIds.has(String(item.id)))
  859. .map((item) => removeWorkExperience(userId, item.id));
  860. await Promise.all(removals);
  861. await Promise.all(
  862. next.map((item) => {
  863. const payload = serializeWorkExperience(item);
  864. if (item.id) return updateWorkExperience(userId, item.id, payload);
  865. return saveWorkExperience(userId, payload);
  866. }),
  867. );
  868. }