| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290 |
- import request from "@/utils/request";
- import { downloadBlobResponse } from "@/utils/download";
- import {
- adaptContractFlowSignNodes,
- contractFlowSignStatusLabel,
- contractSigningStatusLabel,
- contractStatusLabel,
- pickFlowSignStatus,
- } from "./contractStatus";
- function unwrap(response) {
- const result = response?.data || {};
- if (Number(result.code) === 0) return result.data;
- const error = new Error(result.message || "服务调用失败");
- if (response?.__toastShown) error.__toastShown = true;
- return Promise.reject(error);
- }
- /**
- * 推荐培训(岗位层级配置 trainings):接口契约为 [{ trainingId, trainingName }],两者都必填。
- * - toTrainingIds:回显用,下拉 value 是字符串化的 id;
- * - toTrainingNameMap:id → 名称快照,提交时按接口要求带上 trainingName。
- */
- function toTrainingIds(trainings) {
- return (Array.isArray(trainings) ? trainings : [])
- .map((item) => (item && typeof item === "object" ? (item.trainingId ?? item.id) : item))
- .filter((value) => value !== null && value !== undefined && value !== "")
- .map((value) => String(value));
- }
- function toTrainingNameMap(trainings) {
- return (Array.isArray(trainings) ? trainings : []).reduce((map, item) => {
- const id = item?.trainingId ?? item?.id;
- const name = String(item?.trainingName ?? "").trim();
- if (id !== null && id !== undefined && id !== "" && name) {
- map[String(id)] = name;
- }
- return map;
- }, {});
- }
- 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;
- }, {});
- }
- /** 大整数主键保持字符串,避免 Number 精度丢失(如雪花 ID) */
- function keepBigIntId(value) {
- if (value === undefined || value === null || value === "") return undefined;
- const text = String(value).trim();
- if (!/^-?\d+$/.test(text)) return value;
- // 超过安全整数必须用字符串提交,否则 1457 → 1500
- if (!Number.isSafeInteger(Number(text))) return text;
- return Number(text);
- }
- /* 文件流下载统一走 utils/download 的 downloadBlobResponse:
- 文件名解码(filename= 里的 percent-encoding 不 decode 会乱码)+ JSON 业务错误拦截 */
- const STATUS_TO_API = { 1: 1, 2: 0, 3: 2 };
- const STATUS_FROM_API = { 0: 2, 1: 1, 2: 3 };
- const SEQUENCE_CODES = {
- 管理: "M",
- 管理序列: "M",
- 专业: "P",
- 专业序列: "P",
- 技术: "T",
- 技术序列: "T",
- 操作: "O",
- 操作序列: "O",
- 运营: "O",
- 运营序列: "O",
- 营销: "S",
- 营销序列: "S",
- };
- const SEQUENCE_NAMES = {
- M: "管理",
- P: "专业",
- T: "技术",
- O: "操作",
- S: "营销",
- };
- export function sequenceCode(value = "") {
- const text = String(value).trim();
- return (
- SEQUENCE_CODES[text] ||
- (/^[A-Za-z]/.test(text) ? text.charAt(0).toUpperCase() : text)
- );
- }
- export function sequenceName(value = "") {
- const code = sequenceCode(value);
- return SEQUENCE_NAMES[code] || String(value).replace(/序列$/, "") || code;
- }
- function levelNumber(value, sequence = "") {
- const text = String(value ?? "");
- const number = Number(text.match(/\d+/)?.[0]);
- if (number) return number;
- const chinese = {
- 一: 1,
- 二: 2,
- 三: 3,
- 四: 4,
- 五: 5,
- 六: 6,
- 七: 7,
- 八: 8,
- 九: 9,
- 十: 10,
- };
- const matched = Object.keys(chinese).find((key) => text.includes(key));
- return matched ? chinese[matched] : 1;
- }
- function apiLevel(value, sequence = "") {
- const code = sequenceCode(sequence);
- const text = String(value ?? "").trim();
- if (/^[A-Za-z]+\d+$/i.test(text)) return text.toUpperCase();
- return `${code}${levelNumber(text, code)}`;
- }
- /** 汇总岗位上的层级名字(positionLevels / positionLevel / levelConfigs.positionLevel) */
- function collectPositionLevelNames(raw = {}) {
- const names = [];
- const add = (value) => {
- const text = String(value || "").trim();
- if (text && !names.includes(text)) names.push(text);
- };
- if (Array.isArray(raw.positionLevels)) {
- raw.positionLevels.forEach(add);
- }
- add(raw.positionLevel);
- if (Array.isArray(raw.levelConfigs)) {
- raw.levelConfigs.forEach((cfg) => add(cfg?.positionLevel));
- }
- return names;
- }
- /**
- * 汇总岗位可选层级对象 [{id, name}]
- * id 从 levelConfigs[i].id / positionLevels[i].id 取,name 同 collectPositionLevelNames 规则
- * 用于岗位序列/层级联动选择,提交时把选中的 id 作为 positionLevelId 传给后端
- */
- function collectPositionLevels(raw = {}) {
- const result = [];
- const seen = new Set();
- const push = (id, name) => {
- const text = String(name || "").trim();
- if (!text) return;
- const idText = id != null && id !== "" ? String(id) : "";
- const key = `${idText}|${text}`;
- if (seen.has(key)) return;
- seen.add(key);
- result.push({ id: idText, name: text });
- };
- if (Array.isArray(raw.levelConfigs)) {
- raw.levelConfigs.forEach((cfg) => push(cfg?.id, cfg?.positionLevel));
- }
- if (Array.isArray(raw.positionLevels)) {
- raw.positionLevels.forEach((item) => {
- if (item && typeof item === "object") push(item.id, item.positionLevel || item.level || item.name);
- else push(null, item);
- });
- }
- if (raw.positionLevel) push(raw.levelConfigId ?? null, raw.positionLevel);
- return result;
- }
- /**
- * 职责文本 → 多条数组:按换行或分号拆分,**行内内容原样保留**。
- * ★ 2026-10-28 去掉原先「剥掉行首序号」的那条正则(匹配 "开头空白 + 数字 + 顿号/点" 并删除):
- * 它会把用户手写的「1、初级职责」剥成「初级职责」,回填到弹框再保存就永久丢序号
- * (提交和接口返回都是「1、初级职责」,页面却只剩「初级职责」)。
- * 前端不做内容清洗,序号属于用户原文。
- */
- function splitText(value) {
- if (Array.isArray(value)) return value.filter(Boolean);
- return String(value || "")
- .split(/\r?\n|[;;]/)
- .map((item) => item.trim())
- .filter(Boolean);
- }
- // 岗位序列层级配置 → 弹窗每层级字段
- function adaptLevelConfig(cfg = {}) {
- const salary = cfg.salary || {};
- // 后端 VO 的 promotions 既带 name 也带 id;前端 path 同时存 name 与 id,
- // 避免保存时再去 options 里按 name 二次解析(options 接口偶发漏项)
- const paths = (cfg.promotions || [])
- .map((item) => {
- const seqLevelName = (seq, lvl) =>
- [seq, lvl].filter(Boolean).join(" · ") || "";
- return {
- up: item.upwardPromotionPositionName || seqLevelName(item.upwardPromotionPositionSequence, item.upwardPromotionPositionLevel),
- upId: item.upwardPromotionPositionId ?? null,
- down: item.downwardDemotionPositionName || seqLevelName(item.downwardDemotionPositionSequence, item.downwardDemotionPositionLevel),
- downId: item.downwardDemotionPositionId ?? null,
- lateral: item.lateralRotationPositionName || seqLevelName(item.lateralRotationPositionSequence, item.lateralRotationPositionLevel),
- lateralId: item.lateralRotationPositionId ?? null,
- };
- })
- .filter((path) => path.up || path.down || path.lateral);
- const conditions = [
- ...new Set(
- (cfg.promotions || [])
- .flatMap((item) =>
- (item.conditions || []).map((condition) => condition.conditionName),
- )
- .filter(Boolean),
- ),
- ];
- const duties = splitText(salary.dutyContent);
- return {
- level: cfg.positionLevel || "",
- levelId: cfg.positionLevelId ?? null,
- salaryMin: Number(salary.salaryMin ?? 0),
- salaryMid: Number(salary.salaryMid ?? 0),
- salaryMax: Number(salary.salaryMax ?? 0),
- salaryTemplateId: salary.salaryTemplate || "",
- perfSchemeId: salary.performanceScheme || "",
- eduReq: salary.educationRequirement || "",
- genderReq: salary.genderRequirement || "不限",
- expReq: Number(salary.experienceYears ?? 0),
- ageRange: [Number(salary.ageMin ?? 18), Number(salary.ageMax ?? 60)],
- effectDate: salary.birthDateRequirement || "",
- workEnv: salary.workEnvironment || "",
- workShift: salary.workShift || "",
- skills: (cfg.skills || []).map((item) => ({
- ...item,
- name: item.skillName || "",
- level: item.skillLevel || "",
- required: String(item.essentialStatus) === "1",
- matchRate: Number(item.matchRate ?? 0),
- code: item.skillCode || "",
- skillId: item.skillId,
- certOrg: item.certOrg || "",
- certRequired: Number(item.certRequired || 0),
- certificateId: item.certificateId,
- })),
- certificates: (cfg.certificates || []).map((item) => ({
- ...item,
- name: item.certificateName || "",
- category: item.certificateType || "",
- certificateId: item.certificateId,
- })),
- // 推荐培训:接口按 trainingId/trainingName 回显(两者都必填),同 levelConfigs 口径
- trainings: toTrainingIds(cfg.trainings),
- trainingNameById: toTrainingNameMap(cfg.trainings),
- promote: {
- paths: paths.length ? paths : [{ up: "", down: "", lateral: "" }],
- conditions,
- },
- duties,
- /* 回填用原始 dutyContent(未经拆分/去分隔符),保证弹框里看到的和接口返回一字不差 */
- dutyText: salary.dutyContent || duties.join("\n"),
- };
- }
- export function adaptPosition(raw = {}) {
- const salary = raw.salary || {};
- const relation = raw.departmentPosition || {};
- const seq = sequenceCode(raw.positionSequence || raw.sequenceName);
- const promotions = raw.promotions || [];
- // 晋升路径改为多条:每条包含向上/向下/平级流动层级
- // 同时保留后端返回的 *PositionId,保存时优先复用避免 options 二次解析失败
- const paths = promotions
- .map((item) => {
- const seqLevelName = (seqField, lvlField, nameField) =>
- nameField || [seqField, lvlField].filter(Boolean).join(" · ") || "";
- return {
- up: seqLevelName(item.upwardPromotionPositionSequence, item.upwardPromotionPositionLevel, item.upwardPromotionPositionName),
- upId: item.upwardPromotionPositionId ?? null,
- down: seqLevelName(item.downwardDemotionPositionSequence, item.downwardDemotionPositionLevel, item.downwardDemotionPositionName),
- downId: item.downwardDemotionPositionId ?? null,
- lateral: seqLevelName(item.lateralRotationPositionSequence, item.lateralRotationPositionLevel, item.lateralRotationPositionName),
- lateralId: item.lateralRotationPositionId ?? null,
- };
- })
- .filter((path) => path.up || path.down || path.lateral);
- // 编辑时至少保留一条空路径(同时带 id 结构,避免 buildLevelConfig 缺字段)
- const pathsForForm = paths.length
- ? paths
- : [{ up: "", upId: null, down: "", downId: null, lateral: "", lateralId: null }];
- const up = paths.map((item) => item.up).filter(Boolean);
- const down = paths.map((item) => item.down).filter(Boolean);
- const lateral = paths.map((item) => item.lateral).filter(Boolean);
- // 公共条件:优先取顶层 conditions,其次兼容旧的 promotions[].conditions
- const conditions = [
- ...new Set(
- (Array.isArray(raw.conditions) ? raw.conditions : [])
- .map((condition) =>
- typeof condition === "string"
- ? condition
- : condition.conditionName,
- )
- .concat(
- promotions.flatMap((item) =>
- (item.conditions || []).map(
- (condition) => condition.conditionName,
- ),
- ),
- )
- .filter(Boolean),
- ),
- ];
- const version = raw.documentVersions?.[0]?.versionNo || 1;
- // 更新/删除主键必须用 page、getById 返回的 id,且始终保持字符串防精度丢失
- const primaryId =
- raw.id != null && raw.id !== "" ? raw.id : raw.positionId;
- const primaryIdText =
- primaryId == null || primaryId === "" ? primaryId : String(primaryId);
- return {
- ...raw,
- id: primaryIdText,
- positionId:
- raw.positionId != null && raw.positionId !== ""
- ? String(raw.positionId)
- : primaryIdText,
- code: raw.positionCode || "",
- positionCode: raw.positionCode || "",
- name: raw.positionName || "",
- legalId: raw.organizationId,
- legalName: raw.organizationName || "",
- deptId: raw.deptId,
- deptName: raw.deptName || "",
- seq,
- // 历史单层级字段;完整集合见 positionLevels(一岗可配置多层级,值为层级名字)
- level: apiLevel(raw.positionLevel, seq),
- positionLevel: raw.positionLevel || "",
- positionLevels: collectPositionLevelNames(raw),
- positionLevelOptions: collectPositionLevels(raw),
- positionSequence: raw.positionSequence || raw.sequenceName || "",
- sequenceId: raw.sequenceId || relation.sequenceId,
- positionSequenceId: raw.sequenceId || relation.sequenceId,
- type: raw.positionType || "",
- positionType: raw.positionType || "",
- staffing: Number(
- raw.establishmentCount ?? relation.establishmentCount ?? 0,
- ),
- actual: Number(raw.occupancyCount ?? relation.occupancyCount ?? 0),
- salaryMin: Number(salary.salaryMin ?? raw.salaryMin ?? 0),
- salaryMid: Number(salary.salaryMid ?? 0),
- salaryMax: Number(salary.salaryMax ?? raw.salaryMax ?? 0),
- salaryTemplateId: salary.salaryTemplate || "",
- perfSchemeId: salary.performanceScheme || "",
- status: STATUS_FROM_API[Number(raw.status)] || 2,
- revokeDate: raw.revokeDate || "",
- workShift: salary.workShift || "",
- workEnv: salary.workEnvironment || "",
- ageRange: [Number(salary.ageMin ?? 18), Number(salary.ageMax ?? 60)],
- genderReq: salary.genderRequirement || "不限",
- eduReq: salary.educationRequirement || "",
- expReq: Number(salary.experienceYears ?? 0),
- effectDate: salary.birthDateRequirement || "",
- version,
- duties: splitText(salary.dutyContent),
- /* 职责任务弹框回填直接用原始文本:splitText 会按「;」拆条,
- 拿 join("\n") 回填相当于擅自把用户的分号改成换行(2026-10-28) */
- dutyText: salary.dutyContent || "",
- skills: (raw.skills || []).map((item) => ({
- ...item,
- name: item.skillName || "",
- level: item.skillLevel || "",
- required: String(item.essentialStatus) === "1",
- matchRate: Number(item.matchRate ?? 0),
- code: item.skillCode || "",
- certOrg: item.certOrg || "",
- certRequired: Number(item.certRequired || 0),
- })),
- certificates: (raw.certificates || []).map((item) => ({
- ...item,
- name: item.certificateName || "",
- category: item.certificateType || "",
- })),
- // 推荐培训:接口按 trainingId/trainingName 返回(两者都必填),下拉 value 用 id,
- // 名称另存 trainingNameById,提交时按接口要求带 trainingName(名称快照)
- trainings: toTrainingIds(raw.trainings),
- trainingNameById: toTrainingNameMap(raw.trainings),
- promote: { paths: pathsForForm, up, down, lateral, conditions },
- promotions,
- employees: (raw.activeEmployees || []).map((item) => ({
- ...item,
- entryDate: item.joinDate || "",
- performance: item.performance || "-",
- skillRate: Number(item.skillRate ?? 0),
- })),
- history: (raw.changeHistory || []).map((item) => ({
- ...item,
- time: item.createTime || "",
- title: item.changeTitle || item.changeTypeName || "岗位变更",
- content: item.changeSummary || "",
- })),
- updatedAt: String(raw.lastChangeTime || raw.createTime || "").slice(0, 10),
- // 岗位序列下每层级的独立配置(接口 levelConfigs)
- levels: (Array.isArray(raw.levelConfigs) ? raw.levelConfigs : []).map(
- (cfg) => adaptLevelConfig(cfg),
- ),
- };
- }
- // 从层级配置或表单顶层字段解析岗位职责文本(dutyContent 必填,允许空串)
- function resolveDutyContent(level = {}, fallback = "") {
- const normalize = (text) =>
- String(text || "")
- .split("\n")
- .map((item) => item.trim())
- .filter(Boolean)
- .join("\n");
- const fromText = normalize(level.dutyText);
- if (fromText) return fromText;
- if (Array.isArray(level.duties) && level.duties.length) {
- const fromDuties = level.duties.filter(Boolean).join("\n");
- if (fromDuties) return fromDuties;
- }
- return normalize(fallback);
- }
- // 弹窗每层级字段 → 接口「岗位序列层级配置参数」
- // 文档:positionLevel、positionSequence、salary(含 dutyContent) 必填
- function buildLevelConfig(level = {}, positionList = [], positionSequence = "") {
- // ===== 晋升路径暂时下线(2026-09-29):不再向接口提交 promotions 参数,恢复时取消本段注释 =====
- // const targetId = (name) =>
- // keepBigIntId(positionTarget(positionList, name)?.id) || null;
- // const conditions = (level.promote?.conditions || [])
- // .map((conditionName) =>
- // typeof conditionName === "string"
- // ? conditionName
- // : conditionName?.conditionName,
- // )
- // .filter(Boolean)
- // .map((conditionName) => ({ conditionName }));
- // // 优先用 path 上已有的 id(adaptLevelConfig 从后端 VO 保留),
- // // 找不到再去 options 里按 name 二次解析(兼容手动编辑 / 旧数据)
- // const resolveTargetId = (pathId, pathName) =>
- // keepBigIntId(pathId) || keepBigIntId(positionTarget(positionList, pathName)?.id) || null;
- // let promotions = (level.promote?.paths || [])
- // .map((path) =>
- // compact({
- // upwardPromotionPositionId: resolveTargetId(path.upId, path.up),
- // upwardPromotionPositionName: path?.up,
- // downwardDemotionPositionId: resolveTargetId(path.downId, path.down),
- // downwardDemotionPositionName: path?.down,
- // lateralRotationPositionId: resolveTargetId(path.lateralId, path.lateral),
- // lateralRotationPositionName: path?.lateral,
- // conditions,
- // }),
- // )
- // .filter(
- // (item) =>
- // item.upwardPromotionPositionName ||
- // item.downwardDemotionPositionName ||
- // item.lateralRotationPositionName,
- // );
- // // 无路径但有条件时仍提交一条,避免晋升条件丢失
- // if (!promotions.length && conditions.length) {
- // promotions = [{ conditions }];
- // }
- // ===== 晋升路径暂时下线结束 =====
- const salary = compact({
- salaryMin: Number(level.salaryMin || 0),
- salaryMid: Number(level.salaryMid || 0),
- salaryMax: Number(level.salaryMax || 0),
- salaryTemplate: String(level.salaryTemplateId || ""),
- // 绩效方案:表单已临时隐藏,暂不提交该字段;恢复时取消下一行注释
- // performanceScheme: String(level.perfSchemeId || ""),
- ageMin: Number(level.ageRange?.[0] ?? 18),
- ageMax: Number(level.ageRange?.[1] ?? 60),
- birthDateRequirement: level.effectDate || undefined,
- educationRequirement: level.eduReq || "",
- experienceYears: Number(level.expReq || 0),
- genderRequirement: level.genderReq || "不限",
- workEnvironment: level.workEnv || "",
- workShift: level.workShift || "",
- industryExperience: level.industryExperience || "",
- professionalAbility: level.professionalAbility || "",
- otherRequirement: level.otherRequirement || "",
- });
- // dutyContent 为必填字段,即使空串也必须提交(不可被 compact 去掉)
- salary.dutyContent = resolveDutyContent(level, level.fallbackDutyContent);
- const skills = (level.skills || [])
- .filter((item) => item.name)
- .map((item, index) =>
- compact({
- skillId: item.skillId || undefined,
- // skillCode: item.code || item.skillCode || `SKILL-${index + 1}`,
- skillName: item.name,
- skillLevel: item.level || item.skillLevel || "",
- // 文档:1-必备、0-非必备、2-加分
- essentialStatus: item.required ? "1" : "0",
- certRequired: Number(item.certRequired || 0),
- certOrg: item.certOrg || "",
- certificateId: item.certificateId || undefined,
- coreSkill: item.coreSkill,
- skillWeight: item.skillWeight,
- affectsPostEligibility: item.affectsPostEligibility,
- minimumGuaranteeCount: item.minimumGuaranteeCount,
- }),
- );
- const certificates = (level.certificates || [])
- .filter((item) => item.name)
- .map((item, index) =>
- compact({
- certificateId: item.certificateId || undefined,
- certificateName: item.name,
- certificateType: item.category || item.certificateType || "",
- sortNo: index + 1,
- }),
- );
- /* 推荐培训:接口要求 trainingId + trainingName 同时必填(保存时固化名称快照)。
- 名称取表单里随下拉选项/回显数据维护的 trainingNameById,取不到就不提交名称(不编造,
- 由后端按缺必填项报错暴露问题)。 */
- const trainingNameById = level.trainingNameById || {};
- const trainings = (level.trainings || [])
- .map((item) =>
- item && typeof item === "object" ? (item.trainingId ?? item.id) : item,
- )
- .map((value) => keepBigIntId(value))
- .filter((value) => value !== undefined && value !== null && value !== "")
- .map((trainingId) => ({
- trainingId,
- trainingName:
- String(trainingNameById[String(trainingId)] ?? "").trim() || undefined,
- }));
- const sequenceText = String(
- positionSequence || level.positionSequence || "",
- ).trim();
- const config = {
- positionLevel: level.level || level.positionLevel || "",
- salary,
- };
- // 层级主键:关联 t_hr_position_sequence_level.id(文档「岗位序列层级配置」positionLevelId)
- const levelId = keepBigIntId(level.levelId);
- if (levelId) config.positionLevelId = levelId;
- // 层级配置同样需要岗位序列,否则后端会报 positionSequence is required
- if (sequenceText) config.positionSequence = sequenceText;
- if (skills.length) config.skills = skills;
- if (certificates.length) config.certificates = certificates;
- if (trainings.length) config.trainings = trainings;
- // 晋升路径暂时下线,不再提交 promotions;恢复时取消下一行注释
- // if (promotions.length) config.promotions = promotions;
- return config;
- }
- // 无 levels 时,用表单顶层字段兜底成一条层级配置(兼容旧数据)
- function fallbackLevelFromForm(form = {}) {
- return {
- level: form.positionLevel || apiLevel(form.level, form.seq) || "",
- positionLevel: form.positionLevel || apiLevel(form.level, form.seq) || "",
- salaryMin: form.salaryMin,
- salaryMid: form.salaryMid,
- salaryMax: form.salaryMax,
- salaryTemplateId: form.salaryTemplateId,
- perfSchemeId: form.perfSchemeId,
- eduReq: form.eduReq,
- genderReq: form.genderReq,
- expReq: form.expReq,
- ageRange: form.ageRange,
- effectDate: form.effectDate,
- workEnv: form.workEnv,
- workShift: form.workShift,
- skills: form.skills,
- certificates: form.certificates,
- trainings: form.trainings,
- /* 推荐培训名称快照(提交时接口要求 trainingId + trainingName 都必填) */
- trainingNameById: form.trainingNameById,
- promote: form.promote,
- duties: form.duties,
- dutyText:
- form.dutyText ||
- (Array.isArray(form.duties) ? form.duties.join("\n") : ""),
- };
- }
- /**
- * 序列化为「完整岗位档案新增/修改参数」
- * 文档约定:业务数据以 levelConfigs 为准;顶层 salary/skills/certificates/promotions 为历史兼容字段,新建修改请勿依赖
- */
- function serializePosition(form, positionList = []) {
- const levels =
- Array.isArray(form.levels) && form.levels.length
- ? form.levels
- : [fallbackLevelFromForm(form)];
- const plannedCount = Number(
- form.plannedRecruitmentCount ?? form.headcount ?? 0,
- );
- const rawId = form.id;
- const positionSequence = String(
- form.positionSequence || form.sequence || sequenceName(form.seq) || "",
- ).trim();
- const payload = compact({
- id: keepBigIntId(rawId),
- deptId: keepBigIntId(form.deptId),
- organizationId: keepBigIntId(form.legalId),
- positionCode: form.positionCode || form.code,
- positionName: form.name,
- positionType: form.type || "",
- status: STATUS_TO_API[form.status] ?? 0,
- // 待撤销(前端 status=3 → API=2) 必须带计划撤销日期
- revokeDate: form.status === 3 ? form.revokeDate || undefined : undefined,
- // 文档:plannedRecruitmentCount minimum 1
- plannedRecruitmentCount: plannedCount >= 1 ? plannedCount : undefined,
- });
- // 岗位序列为必填:不可被 compact 去掉空串后导致后端报 positionSequence is required
- payload.positionSequence = positionSequence;
- // 部门岗位关联仅含 establishmentCount;档案保存不写入 Main 编制表,仅作快照可选提交
- if (form.staffing !== undefined && form.staffing !== null && form.staffing !== "") {
- payload.departmentPosition = {
- establishmentCount: Number(form.staffing || 0),
- };
- }
- const dutyContent = resolveDutyContent({
- dutyText: form.dutyText,
- duties: form.duties,
- });
- const targets = [
- ...(positionList || []),
- ...(form.promoteTargetOptions || []),
- ];
- payload.levelConfigs = levels.map((level) =>
- buildLevelConfig(
- {
- ...level,
- dutyText: resolveDutyContent(level, dutyContent),
- duties:
- Array.isArray(level.duties) && level.duties.length
- ? level.duties
- : form.duties,
- fallbackDutyContent: dutyContent,
- },
- targets,
- positionSequence,
- ),
- );
- // 后端接收顶层 salary.dutyContent,同时与当前层级配置保持一致
- payload.salary = {
- ...(payload.levelConfigs[0]?.salary || {}),
- dutyContent:
- payload.levelConfigs[0]?.salary?.dutyContent || dutyContent,
- };
- return payload;
- }
- /** 晋升通道地图:List«晋升通道分组» */
- // 岗位管理
- export const getPositionDashboard = async () =>
- unwrap(await request.get("/hr/position/dashboard"));
- export const getPositionPage = async (data = {}) =>
- unwrap(await request.post("/hr/position/page", compact(data)));
- export const getPositionById = async (id) =>
- adaptPosition(
- await unwrap(
- await request.get(
- `/hr/position/getById/${encodeURIComponent(String(keepBigIntId(id) ?? id))}`,
- ),
- ),
- );
- // ========== 部门岗位关联表(/fm/tmaindepartmentposition)级联查询 ==========
- // deptId 入参均为入职部门 ID;响应统一 { code, data: [], message }
- /** 查询部门岗位序列(入职部门 → 岗位序列) */
- export const getDepartmentPositionSequences = async (deptId) =>
- unwrap(
- await request.get("/fm/tmaindepartmentposition/sequence/list", {
- params: compact({ deptId: keepBigIntId(deptId) }),
- }),
- );
- /** 查询序列下岗位(入职部门 + 岗位序列 → 岗位) */
- export const getDepartmentPositions = async ({ deptId, positionSequenceId }) =>
- unwrap(
- await request.get("/fm/tmaindepartmentposition/position/list", {
- params: compact({
- deptId: keepBigIntId(deptId),
- positionSequenceId: keepBigIntId(positionSequenceId),
- }),
- }),
- );
- /** 查询岗位性质(入职部门 + 岗位 + 岗位序列 → 岗位性质数组,调用方取第一条) */
- export const getDepartmentPositionType = async ({
- deptId,
- positionId,
- positionSequenceId,
- }) =>
- unwrap(
- await request.get("/fm/tmaindepartmentposition/positionType/list", {
- params: compact({
- deptId: keepBigIntId(deptId),
- positionId: keepBigIntId(positionId),
- positionSequenceId: keepBigIntId(positionSequenceId),
- }),
- }),
- );
- /** 查询岗位层级(入职部门 + 岗位 + 岗位序列 + 岗位性质编码 → 岗位层级) */
- export const getDepartmentPositionLevels = async ({
- deptId,
- positionId,
- positionSequenceId,
- positionType,
- }) =>
- unwrap(
- await request.get("/fm/tmaindepartmentposition/positionLevel/list", {
- params: compact({
- deptId: keepBigIntId(deptId),
- positionId: keepBigIntId(positionId),
- positionSequenceId: keepBigIntId(positionSequenceId),
- positionType,
- }),
- }),
- );
- export const savePosition = async (form, list) =>
- unwrap(
- await request.post("/hr/position/save", serializePosition(form, list)),
- );
- export const updatePosition = async (form, list) =>
- unwrap(
- await request.put("/hr/position/update", serializePosition(form, list)),
- );
- export const updatePositionStatus = async (data) =>
- unwrap(
- await request.put("/hr/position/updateStatus", {
- ...data,
- id: keepBigIntId(data?.id) ?? data?.id,
- }),
- );
- export const deletePosition = async (id) =>
- unwrap(
- await request.get(
- `/hr/position/delete/${encodeURIComponent(String(keepBigIntId(id) ?? id))}`,
- ),
- );
- export const synchronizePositionOccupancy = async () =>
- unwrap(await request.post("/hr/position/occupancy/synchronize"));
- export const getPositionChangeHistory = async (id) =>
- unwrap(
- await request.get(
- `/hr/position/${encodeURIComponent(String(keepBigIntId(id) ?? id))}/changeHistory`,
- ),
- );
- export const getPromotionTargets = async (type, params = {}) =>
- unwrap(
- await request.get(`/hr/positionSequence/promotion-levels/${type}`, {
- params: compact(params),
- }),
- );
- /** 流动岗位序列层级下拉 → 扁平选项(序列 · 层级) */
- export function adaptPromotionLevelOptions(list = []) {
- const options = [];
- (Array.isArray(list) ? list : []).forEach((seq) => {
- const sequenceName = seq.sequence || "";
- const sequenceId = seq.id;
- (seq.levelArrayList || []).forEach((level) => {
- const levelText = level.level || level.levelName || "";
- if (!levelText) return;
- if (level.status != null && Number(level.status) === 0) return;
- const name = sequenceName ? `${sequenceName} · ${levelText}` : levelText;
- options.push({
- id: level.id ?? `${sequenceId || "seq"}-${levelText}`,
- name,
- positionName: name,
- positionSequence: sequenceName,
- positionLevel: levelText,
- sequenceId,
- levelId: level.id,
- levelGrade: Number(level.levelGrade) || 0,
- });
- });
- });
- return options;
- }
- export const generatePositionDocument = async (id) =>
- unwrap(await request.post(`/hr/position/${id}/document`));
- export async function downloadPositionDocument(id) {
- downloadBlobResponse(
- await request.get(`/hr/position/document/${id}/download`, {
- responseType: "blob",
- }),
- "岗位说明书.pdf",
- );
- }
- export async function downloadPositionImportTemplate() {
- downloadBlobResponse(
- await request.get("/hr/position/import/template", { responseType: "blob" }),
- "岗位导入模板.xlsx",
- );
- }
- export async function importPositions(file) {
- const data = new FormData();
- data.append("file", file);
- return unwrap(
- await request.post("/hr/position/import", data, {
- headers: { "Content-Type": "multipart/form-data" },
- }),
- );
- }
- // 晋升(岗位晋升通道:地图 / 导出 / 申请分页;审批流由 BPM 负责,评审类接口已下线)
- export async function exportPromotionChannels() {
- downloadBlobResponse(
- await request.post(
- "/hr/promotion/channels/export",
- {},
- { responseType: "blob" },
- ),
- "晋升通道说明书.xlsx",
- );
- }
- // 岗位序列
- export const getPositionSequenceList = async (params = {}) => {
- // 查询岗位序列列表:支持按所属机构过滤(groupId)
- // 返回 List<岗位序列列表> { id, sequence, status, groupId, levelList:[{level,status}] }
- const list = unwrap(
- await request.get("/hr/positionSequence/getPositionSequenceList", {
- params: compact(params),
- }),
- );
- return (Array.isArray(list) ? list : []).map((item) => ({
- id: item.id,
- sequence: item.sequence || "",
- status: item.status,
- /* 序列归属机构:后端可能返回 groupId 或 legalId,供左侧按机构过滤 */
- groupId: item.groupId ?? item.legalId ?? null,
- levelList: (item.levelList || []).map((level) => {
- // 仅保留数字主键;勿用层级文案(P1/T4)冒充 id,否则人员档案无法提交 positionLevelId
- const rawId = level.id != null ? String(level.id).trim() : "";
- const id = /^-?\d+$/.test(rawId) ? rawId : "";
- return {
- id,
- label: level.level || level.levelName || "",
- // 岗位层级文本(如 P1/T4):岗位申请 positionLevel 需为该序列启用层级文本
- value: level.level || level.levelName || "",
- levelGrade: Number(level.levelGrade) || 0,
- status: level.status,
- };
- }),
- }));
- };
- export const getPositionSequenceLevels = async (sequence) =>
- unwrap(
- await request.get("/hr/positionSequence/levels", {
- params: { sequence },
- }),
- );
- export const getPositionSequencePage = async (data = {}) =>
- unwrap(await request.post("/hr/positionSequence/page", compact(data)));
- export const savePositionSequence = async (data) =>
- unwrap(await request.post("/hr/positionSequence/save", data));
- export const updatePositionSequence = async (data) =>
- unwrap(await request.put("/hr/positionSequence/update", data));
- export const isPositionSequenceInUse = async (id) =>
- unwrap(await request.get(`/hr/positionSequence/${id}/in-use`));
- export const savePositionSequenceLevels = async (data) =>
- unwrap(await request.post("/hr/positionSequence/levels/save", data));
- // 劳动合同模板
- export const getContractTemplatePage = async (data = {}) =>
- unwrap(await request.get("/hr/contracts/templates", { params: compact(data) }));
- export const getContractTemplateById = async (id) =>
- unwrap(await request.get(`/hr/contracts/templates/${id}`));
- export const saveContractTemplate = async (data) =>
- unwrap(await request.post("/hr/contracts/templates", data));
- export const updateContractTemplate = async (data) =>
- unwrap(await request.put(`/hr/contracts/templates/${data.id}`, data));
- export const deleteContractTemplate = async (id) =>
- unwrap(await request.get(`/hr/contracts/templates/delete/${id}`));
- export const disableContractTemplate = async (id) =>
- unwrap(await request.post(`/hr/contracts/templates/${id}/disable`));
- export const enableContractTemplate = async (id) =>
- unwrap(await request.post(`/hr/contracts/templates/${id}/enable`));
- export const submitContractTemplate = async (id) =>
- unwrap(await request.post(`/hr/contracts/templates/${id}/submit`));
- export const getContractTemplateVersions = async (id) =>
- unwrap(await request.get(`/hr/contracts/templates/${id}/versions`));
- export const createContractTemplateVersion = async (id, data) =>
- unwrap(await request.post(`/hr/contracts/templates/${id}/versions`, data));
- export {
- CONTRACT_FLOW_SIGN_STATUS_LABELS,
- CONTRACT_FLOW_SIGN_STATUS_OPTIONS,
- CONTRACT_SIGNING_STATUS_LABELS,
- CONTRACT_SIGNING_STATUS_OPTIONS,
- CONTRACT_STATUS_LABELS,
- CONTRACT_STATUS_OPTIONS,
- adaptContractFlowSignNodes,
- contractFlowSignStatusLabel,
- contractSigningStatusLabel,
- contractStatusLabel,
- pickFlowSignStatus,
- } from "./contractStatus";
- // 劳动合同管理
- export const getContractPage = async (data = {}) =>
- unwrap(await request.get("/hr/contracts/page", { params: compact(data) }));
- /**
- * 导出劳动合同台账 POST /hr/contracts/export
- * ★ 复用分页查询的组织范围与**全部业务筛选条件**;pageNum / size 不传(文档:不限制导出范围)。
- * 接口直接返回 .xlsx 文件流,故 responseType=blob + 放宽 timeout;
- * downloadBlob 会拦截「HTTP 200 + JSON 业务错误」,避免把错误 JSON 存成 xlsx。
- * @param {{keyword?:string, contractType?:string, contractStatus?:string, departmentId?:*, legalEntityId?:*,
- * positionId?:*, signingMethod?:string, signingStatus?:string, startDate?:string, endDate?:string,
- * expiringOnly?:boolean}} params
- */
- export const exportContractLedger = async (params = {}) => {
- const payload = compact({
- keyword: params.keyword || undefined,
- contractType: params.contractType || undefined,
- contractStatus: params.contractStatus || undefined,
- departmentId: keepBigIntId(params.departmentId),
- legalEntityId: keepBigIntId(params.legalEntityId),
- positionId: keepBigIntId(params.positionId),
- signingMethod: params.signingMethod || undefined,
- signingStatus: params.signingStatus || undefined,
- startDate: params.startDate ? String(params.startDate).slice(0, 10) : undefined,
- endDate: params.endDate ? String(params.endDate).slice(0, 10) : undefined,
- // 仅勾选「即将到期」时才传 true(不传 = 不限制)
- expiringOnly: params.expiringOnly === true ? true : undefined,
- });
- await downloadBlobResponse(
- await request.post("/hr/contracts/export", payload, {
- responseType: "blob",
- timeout: 120000,
- }),
- `劳动合同台账_${new Date().toISOString().slice(0, 10)}.xlsx`,
- );
- };
- /** 新建劳动合同 POST /hr/contracts/save */
- export const CONTRACT_APPROVAL_BUSINESS_KEY = "hr_contract_approval";
- /** 劳动合同「解除/终止」审批的业务标识(与合同审批区分开) */
- export const CONTRACT_TERMINATION_APPROVAL_BUSINESS_KEY =
- "hr_contract_termination_approval";
- /**
- * 劳动合同「变更」审批的业务标识(2026-10-17 后端确认):
- * 流程为 —— 先 POST /hr/contracts/{id}/changes 保存变更草稿(返回变更单主键),
- * 再以该业务标识 + **变更单主键** 调 /bpm/process-instance/create 发起审批;
- * 审批终态通过后才把变更字段应用到合同上。
- */
- export const CONTRACT_CHANGE_APPROVAL_BUSINESS_KEY = "hr_contract_change_approval";
- export const REGULARIZATION_APPROVAL_BUSINESS_KEY = "main_regularization_approval";
- export const saveContract = async (data) =>
- unwrap(await request.post("/hr/contracts/save", data));
- /** @deprecated 使用 saveContract,接口已改为 /hr/contracts/save */
- /** 修改劳动合同草稿 PUT /hr/contracts/{id},参数同新建 + id */
- export const updateContract = async (id, data) =>
- unwrap(await request.put(`/hr/contracts/${id}`, compact({ ...data, id })));
- export const getContractById = async (id) =>
- unwrap(await request.get(`/hr/contracts/${id}`));
- export const matchContractTemplate = async (data) =>
- unwrap(await request.post("/hr/contracts/template-match", data));
- export const changeContract = async (id, data) =>
- unwrap(await request.post(`/hr/contracts/${id}/changes`, data));
- /** 查询合同变更历史 GET /hr/contracts/{id}/changes(含 DRAFT 草稿,afterSnapshot 可回显变更后值) */
- export const getContractChanges = async (id) =>
- unwrap(await request.get(`/hr/contracts/${id}/changes`));
- export const renewContract = async (id, data) =>
- unwrap(await request.post(`/hr/contracts/${id}/renew`, data));
- export const terminateContract = async (id, data) =>
- unwrap(await request.post(`/hr/contracts/${id}/termination`, data));
- export const archiveContractOffline = async (id, data) =>
- unwrap(await request.post(`/hr/contracts/${id}/offline/archive`, data));
- export const voidContract = async (id, data) =>
- unwrap(await request.post(`/hr/contracts/${id}/void`, data));
- /* 作废三类「未提交审批」的单据草稿(2026-10-20 契约确认):
- 变更草稿 / 续签草稿 / 解除终止草稿各自挂在原合同下,id 是原合同主键,第二个路径参数是草稿主键。
- ★ 变更、解除的作废接口只有路径参数、无请求体;续签作废收「劳动合同作废参数」{ reason }。
- ★ 不要再用 /hr/contracts/{id}/void 传草稿主键顶替合同主键。 */
- export const voidContractChange = async (id, changeId) =>
- unwrap(await request.post(`/hr/contracts/${id}/changes/${changeId}/void`));
- export const voidContractRenewal = async (id, renewalContractId, data) =>
- unwrap(await request.post(`/hr/contracts/${id}/renewals/${renewalContractId}/void`, data));
- export const voidContractTermination = async (id, terminationId) =>
- unwrap(await request.post(`/hr/contracts/${id}/terminations/${terminationId}/void`));
- export const getContractUnrenewedReport = async (data = {}) =>
- unwrap(await request.get("/hr/contracts/reports/unrenewed", { params: compact(data) }));
- /**
- * 查询员工合同流程状态 GET /hr/contracts/employee/{employeeId}/status
- * 返回当前员工合同所处流程节点:
- * - 无合同:返回 null / 空
- * - 草稿(已保存未提交):{ contractId, statusCode: 'DRAFT', ... }
- * - 审批中 / 待签署等:{ contractId, statusCode: 'PENDING_APPROVAL', flowNode, ... }
- */
- export const getEmployeeContractStatus = async (employeeId) =>
- unwrap(await request.get(`/hr/contracts/employee/${employeeId}/status`));
- /** 合同类型 code → 中文标签 */
- const CONTRACT_TYPE_LABELS = {
- FIXED: "固定期限劳动合同",
- NON_FIXED: "无固定期限劳动合同",
- TASK: "以完成一定任务为期限",
- DISPATCH: "劳务派遣合同",
- INTERN: "实习协议",
- CONFIDENTIAL: "保密协议",
- NON_COMPETE: "竞业限制协议",
- APPRENTICE: "学徒合同",
- OTHER: "其他",
- };
- export function contractTypeLabel(code) {
- return CONTRACT_TYPE_LABELS[String(code || "").toUpperCase()] || String(code || "");
- }
- /* 签署方式不再本地翻译:接口已返回 signingMethodName,直接展示 */
- function fmtDate(v) {
- if (!v) return "";
- const d = new Date(v);
- if (isNaN(d.getTime())) return String(v);
- const pad = (n) => String(n).padStart(2, "0");
- return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
- }
- function fmtDateTime(v) {
- if (!v) return "";
- const d = new Date(v);
- if (isNaN(d.getTime())) return String(v);
- const pad = (n) => String(n).padStart(2, "0");
- return `${fmtDate(v)} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
- }
- /** 合同附件文件主键 */
- function collectAttachmentFileIds(item = {}) {
- const fromIds = String(item.attachmentIds || "").split(",").map((s) => s.trim()).filter(Boolean);
- if (fromIds.length) return fromIds;
- const files = Array.isArray(item.files) ? item.files : [];
- const attach = files.filter((f) => ["ATTACHMENT", "SCAN", "ARCHIVE"].includes(f.filePurpose));
- const source = attach.length ? attach : files;
- const fromFiles = source.map((f) => f.fileId || f.id).filter((id) => id != null && id !== "").map(String);
- if (fromFiles.length) return fromFiles;
- const extra = [item.offlineFileId, item.fileId].filter((id) => id != null && id !== "");
- return extra.map(String);
- }
- /**
- * 试用期展示文案:合同详情回显字段是 probationMonths(月数)
- * 0 / 空 → "无试用期";大于 0 → "N个月"(如 3个月)
- */
- export function probationMonthsText(value) {
- const months = Number(value);
- if (!Number.isFinite(months) || months <= 0) return "无试用期";
- return `${months}个月`;
- }
- /**
- * 合同后端 DTO → 前端展示对象
- * 合同管理列表、合同详情、入职签署合同节点共用
- */
- export function adaptContract(item = {}) {
- const startDate = fmtDate(item.startDate);
- const endDate = fmtDate(item.endDate);
- const termText =
- item.contractTerm ||
- (item.contractTermMonths != null && item.contractTermMonths !== ""
- ? `${item.contractTermMonths}个月`
- : "");
- const processInstanceId =
- item.processInstanceId ||
- item.approvalInstanceId ||
- item.bpmProcessInstanceId ||
- item.processInstance ||
- item.processInstanceIdStr ||
- "";
- const approvalInstanceId = item.approvalInstanceId || item.processInstanceId || "";
- return {
- ...item,
- employeeNo: item.employeeCode || item.employeeNo || "",
- employeeCode: item.employeeCode || item.employeeNo || "",
- legalEntity: item.legalEntityName || "",
- department: item.departmentName || "",
- contractTypeCode: item.contractType || "",
- /* ★ 接口已补中文字段(contractTypeName / contractStatusName / signingStatusName /
- signingMethodName),列表与详情直接展示接口值,不再本地枚举翻译 */
- contractType: item.contractTypeName || "",
- signMethod: item.signingMethodName || "",
- signingStatus: item.signingStatus || "",
- signingStatusLabel: item.signingStatusName || "",
- flowSignStatus: pickFlowSignStatus(item),
- flowSignStatusLabel: contractFlowSignStatusLabel(pickFlowSignStatus(item)),
- flowSignNodes: adaptContractFlowSignNodes(item),
- status: item.contractStatusName || "",
- statusCode: item.contractStatus || "",
- processInstanceId,
- approvalInstanceId,
- salary: item.salaryStandard,
- signDate: fmtDate(item.signingDate),
- startDate,
- endDate,
- contractTerm: termText,
- remainingDays:
- item.remainingDays === null || item.remainingDays === undefined || item.remainingDays === ""
- ? null
- : Number(item.remainingDays),
- probationMonths: item.probationMonths,
- /* 详情展示用文案:0/空 = 无试用期,>0 = N个月 */
- probationText: probationMonthsText(item.probationMonths),
- createdAt: fmtDateTime(item.createTime),
- probationEnd: fmtDate(item.probationEndDate),
- attachmentFileIds: collectAttachmentFileIds(item),
- level: item.positionLevel || item.level || "",
- positionSequence: item.positionSequence || item.sequenceName || "",
- positionSequenceId: item.positionSequenceId || item.sequenceId || "",
- positionLevel: item.positionLevel || item.level || "",
- positionLevelId: item.positionLevelId || "",
- positionType: item.positionType || "",
- /* 名称只取真正的名称字段:employmentType 是岗位性质字典编码,当名称用会显示成 "1" */
- positionTypeName: item.positionTypeName || "",
- employmentType: item.employmentType || "",
- manager: item.managerName || item.manager || "",
- /* 合同变更状态:字段是 changeApprovalStatus / changeApprovalStatusName(2026-10-17 抓包确认)。
- ★ 只有发生过变更的合同才返回该字段,没变更的合同没有 —— 字段缺失 = 未发生变更(用户口径),
- 不是接口异常。给了中文名就用中文名,只给编码时原样展示编码(不编造文案)。 */
- changeStatusCode: item.changeApprovalStatus || "",
- changeStatusLabel: item.changeApprovalStatusName || "",
- /* 变更单主键:发起变更审批(hr_contract_change_approval)时作为 businessId */
- changeId: item.changeId || "",
- /* 变更审批流程实例:变更审批中的行查看「变更审批记录」用 */
- changeProcessInstanceId: item.changeProcessInstanceId || "",
- /* 最近一次续签状态:DRAFT/PENDING_APPROVAL/PENDING_SIGN/SIGNED_WAIT_EFFECTIVE/
- EFFECTIVE/EXPIRING/EXPIRED/RENEWED/CHANGED/CANCELLED;从未发起时为空(2026-10-17 抓包) */
- renewalStatusCode: item.renewalStatus || "",
- renewalStatusLabel: item.renewalStatusName || "",
- /* 最近一次解除终止审批状态:DRAFT/PENDING_APPROVAL/SIGNING/EFFECTIVE/REJECTED/CANCELLED;
- 从未发起时为空 */
- terminationStatusCode: item.terminationApprovalStatus || "",
- terminationStatusLabel: item.terminationApprovalStatusName || "",
- /* 续签生成的新合同主键(若列表行返回):续签审批的 businessId;
- 字段名以后端实际返回为准,没返回时前端明确提示,不拿原合同 id 顶替 */
- renewalContractId: item.renewalContractId || "",
- /* 续签审批流程实例:续签审批中的行查看「审批记录」用(列表未返回时回落到新合同详情) */
- renewalProcessInstanceId: item.renewalProcessInstanceId || "",
- /* 解除终止单主键(2026-10-19 契约确认):解除审批(hr_contract_termination_approval)
- 的 businessId;编辑解除草稿时也用它原地修改 */
- terminationId: item.terminationId || "",
- /* 解除审批流程实例:解除审批中的行查看「审批记录」用 */
- terminationProcessInstanceId: item.terminationProcessInstanceId || "",
- };
- }
- // 招聘需求
- export const getRecruitDemandPage = async (data = {}) =>
- unwrap(await request.get("/recruit/demands", { params: compact(data) }));
- export const submitRecruitDemand = async (data) =>
- unwrap(await request.post("/recruit/demands", compact(data)));
- export const getRecruitDemandById = async (id) =>
- unwrap(await request.get(`/recruit/demands/${id}`));
- // 岗位申请
- export const createPositionApplication = async (data) =>
- unwrap(await request.post("/hr/position/apply", data));
- export const getPositionApplicationPage = async (data = {}) =>
- unwrap(await request.post("/hr/position/apply/page", compact(data)));
- export const getPositionApplicationById = async (id) =>
- unwrap(await request.get(`/hr/position/apply/${id}`));
- export const updatePositionApplication = async (id, data) =>
- unwrap(await request.put(`/hr/position/apply/${id}`, data));
- export const deletePositionApplication = async (id) =>
- unwrap(await request.delete(`/hr/position/apply/${id}`));
- export const submitPositionApplication = async (id) =>
- unwrap(await request.post(`/hr/position/apply/${id}/submit`));
- // 岗位申请列表项:后端列表字段 → 页面字段(列表不含职责/薪资/技能,由详情接口补齐)
- export function adaptPositionApplication(raw = {}) {
- return {
- ...raw,
- id: raw.id,
- code: raw.positionCode || "",
- documentCode: raw.documentCode || "",
- positionName: raw.positionName || "",
- organizationId: raw.organizationId,
- organizationName: raw.organizationName || "",
- deptId: raw.deptId,
- deptName: raw.deptName || "",
- // 岗位层级为序列层级文本(如 P1/T4),保持字符串展示,字段与后端 positionLevel 对齐
- positionLevel: raw.positionLevel || "",
- positionSequence: raw.positionSequence || "",
- type: raw.positionType || "",
- headcount: Number(raw.plannedRecruitmentCount ?? 0),
- applicant: raw.applicantName || "",
- status: raw.status || "DRAFT",
- updatedAt: raw.updateTime || raw.createTime || "",
- reviewer: raw.reviewerName || "",
- reviewTime: raw.reviewTime || "",
- processInstanceId: raw.processInstanceId || raw.approvalInstanceId || "",
- approvalInstanceId: raw.approvalInstanceId || raw.processInstanceId || "",
- effectivePositionId: raw.effectivePositionId,
- };
- }
- // 岗位申请详情:在列表字段基础上补职责、薪资区间、技能要求与审批记录
- export function adaptPositionApplicationDetail(raw = {}) {
- const salary = raw.salary || {};
- const duties = splitText(salary.dutyContent);
- return {
- ...adaptPositionApplication(raw),
- duties,
- /* 展示用原始 dutyContent,避免 join("\n") 把用户写的「;」改成换行 */
- dutiesText: salary.dutyContent || duties.join("\n"),
- salaryMin: Number(salary.salaryMin ?? 0),
- salaryMid: Number(salary.salaryMid ?? 0),
- salaryMax: Number(salary.salaryMax ?? 0),
- expYears: Number(salary.experienceYears ?? 0),
- education: salary.educationRequirement || "",
- industryExp: salary.industryExperience || "",
- professionalAbility: salary.professionalAbility || "",
- otherRequirement: salary.otherRequirement || "",
- skills: (raw.skills || []).map((item) => ({
- ...item,
- name: item.skillName || "",
- level: item.skillLevel || "",
- required: String(item.essentialStatus) === "1",
- })),
- certificates: (raw.certificates || []).map((item) => ({
- ...item,
- name: item.certificateName || "",
- category: item.certificateType || "",
- })),
- reviewOpinion: raw.reviewOpinion || "",
- operationLogs: raw.operationLogs || [],
- };
- }
- // 岗位申请表单 → 提交参数:基本信息 + 定薪定责 + 技能/证书要求
- export function serializePositionApplication(form) {
- const duties = (form.dutiesText || "")
- .split("\n")
- .map((item) => item.trim())
- .filter(Boolean);
- const salary = compact({
- dutyContent: duties.join("\n"),
- // 薪资字段统一数字类型
- salaryMin: Number(form.salaryMin || 0),
- salaryMid: Number(form.salaryMid || 0),
- salaryMax: Number(form.salaryMax || 0),
- experienceYears: Number(form.expYears || 0),
- educationRequirement: form.education || "",
- industryExperience: form.industryExp || "",
- professionalAbility: form.professionalAbility || "",
- otherRequirement: form.otherRequirement || "",
- });
- const payload = {
- organizationId: form.organizationId,
- deptId: form.deptId,
- positionName: form.positionName,
- positionLevel: form.positionLevel ? String(form.positionLevel) : "",
- positionSequence: form.positionSequence || "",
- positionType: form.type || "",
- plannedRecruitmentCount: Number(form.headcount || 0),
- };
- if (Object.keys(salary).length) payload.salary = salary;
- const skills = (form.skills || [])
- .filter((item) => item.name)
- .map((item) => ({
- skillName: item.name,
- skillLevel: item.level,
- essentialStatus: item.required ? "1" : "2",
- }));
- if (skills.length) payload.skills = skills;
- const certificates = (form.certificates || [])
- .filter((item) => item.name)
- .map((item, index) => ({
- certificateName: item.name,
- certificateType: item.category,
- sortNo: index + 1,
- }));
- if (certificates.length) payload.certificates = certificates;
- if (form.id) payload.id = form.id;
- return compact(payload);
- }
|