Quellcode durchsuchen

```
feat(constants): 职位体系功能构建

- 在 FormConstant 中新增了如 INDEX_KEY、EMP_NUMBER_KEY、REPORTLISTAP、COMPANY_KEY 等字段标识
- 扩展了人员相关实体标识,如 ASSIGNMENT_ENTITYID、NCKD_PERSON、HBSS_DIPLOMA 等
- 增加了职位序列、职级、积分、资格等级等相关常量定义- 新增考评结果枚举值 NO_ASSESSMENT_GRADE 和 GOOD,并提供 getByCode 方法
- 新建 JobSeqEnum 枚举类用于职位序列管理- 在 PersonHelper 中增加获取教育经历、职称信息、技能信息的方法
- 新增 PerformanceManagerHelper用于绩效结果查询
- 新增 PerfRankMgmtConstant 和 PositionStructureConstant 常量类,支持年度绩效排名与职位体系管理```

wyc vor 1 Woche
Ursprung
Commit
64ef527531
28 geänderte Dateien mit 3704 neuen und 22 gelöschten Zeilen
  1. 81 0
      code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/constant/FormConstant.java
  2. 31 1
      code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/enums/AppraisalResultEnum.java
  3. 60 0
      code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/enums/JobSeqEnum.java
  4. 1 0
      code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/utils/StrFormatter.java
  5. 122 0
      code/base/nckd-jxccl-base-helper/src/main/java/nckd/jxccl/base/org/helper/PersonHelper.java
  6. 65 0
      code/base/nckd-jxccl-base-helper/src/main/java/nckd/jxccl/base/pm/helper/PerformanceManagerHelper.java
  7. 0 2
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/hstu/common/HonorStudentConstant.java
  8. 1521 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/business/JobLevelCalculatorService.java
  9. 47 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/common/PerfRankMgmtConstant.java
  10. 176 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/common/PositionStructureConstant.java
  11. 338 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/helper/PositionStructureHelper.java
  12. 0 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/mservice/.gitkeep
  13. 159 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/adjust/NewDynamicAdjustmentDiaLogFormPlugin.java
  14. 28 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/initial/NewHireInitialFormPlugin.java
  15. 81 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/initial/ScoreInitialFormPlugin.java
  16. 29 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/initial/ServingInitialFormPlugin.java
  17. 61 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/initial/UngradedPersonQueryListPlugin.java
  18. 127 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/adjust/NewDynamicAdjustmentOperationPlugIn.java
  19. 322 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/initial/BaseInitialOperationPlugIn.java
  20. 155 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/initial/NewHireInitialOperationPlugIn.java
  21. 169 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/initial/ServingInitialOperationPlugIn.java
  22. 0 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/other/.gitkeep
  23. 67 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/report/adjust/UnAdjustedReportFormPlugin.java
  24. 44 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/report/adjust/UnAdjustedReportReportListDataPlugin.java
  25. 0 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/workflow/.gitkeep
  26. 0 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/webapi/.gitkeep
  27. 3 2
      code/opmc/nckd-jxccl-opmc/src/main/java/nckd/jxccl/opmc/pm/common/PerfManagerFormConstant.java
  28. 17 17
      code/opmc/nckd-jxccl-opmc/src/main/java/nckd/jxccl/opmc/pm/plugin/form/PerfManagerBillPlugin.java

+ 81 - 0
code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/constant/FormConstant.java

@@ -14,6 +14,10 @@ public class FormConstant {
     public static final String NAME_KEY = "name";
     /** 编号标识 */
     public static final String NUMBER_KEY = "number";
+    /** 排序 */
+    public static final String INDEX_KEY = "index";
+    /** 编号标识 */
+    public static final String EMP_NUMBER_KEY = "empnumber";
     /** 分录字段标识 */
     public static final String ENTRY_ENTITY_KEY = "entryentity";
     /** 保存按钮标识 */
@@ -26,6 +30,8 @@ public class FormConstant {
     public static final String CLOSE_KEY = "tblclose";
     /** 工具栏标识 */
     public static final String TOOLBARAP = "toolbarap";
+    /** 报表列表 */
+    public static final String REPORTLISTAP = "reportlistap";
     /**
      * 单据编号
      **/
@@ -66,6 +72,16 @@ public class FormConstant {
     public static final String CREATEORG_KEY = "createorg";
     /** 所属单位*/
     public static final String USEORG_KEY = "useorg";
+    /** 所属公司*/
+    public static final String COMPANY_KEY = "company";
+    /** 岗位*/
+    public static final String POSITION_KEY = "position";
+    /** 学历*/
+    public static final String EDUCATION_KEY = "education";
+    /** 职称级别*/
+    public static final String PROLEVEL_KEY = "prolevel";
+    /** 职业资格等级*/
+    public static final String QUALEVEL_KEY = "qualevel";
     /**
      * 分录行号
      **/
@@ -92,6 +108,18 @@ public class FormConstant {
     public static final String BUSINESS_STATUS = "businessstatus";
     /** 初始状态 */
     public static final String INIT_STATUS = "initstatus";
+    /** 是否主要 */
+    public static final String IS_PRIMARY = "isprimary";
+    /** 是否删除 */
+    public static final String IS_DELETED = "isdeleted";
+    /** 组织分配 */
+    public static final String ASSIGNMENT = "assignment";
+    /** 开始时间 */
+    public static final String STARTDATE = "startdate";
+    /** 结束时间 */
+    public static final String ENDDATE = "enddate";
+    /** 毕业时间 */
+    public static final String GRADUTIONDATE = "gradutiondate";
 
     /**工具栏*/
     public static final String TBMAIN = "tbmain";
@@ -129,6 +157,8 @@ public class FormConstant {
     public static final String STRUCTLONGNUMBER = "structlongnumber";
     /** HR行政组织*/
     public static final String ADMINORGHR_ENTITYID = "haos_adminorghr";
+    /** 组织分配-实体标识 */
+    public static final String ASSIGNMENT_ENTITYID = "hrpi_assignment";
 
     /** 审核操作标识 */
     public static final String OP_BAR_AUDIT = "bar_audit";
@@ -136,4 +166,55 @@ public class FormConstant {
     public static final String OP_BAR_UNAUDIT = "bar_unaudit";
     /** 反审核操作标识*/
     public static final String NCKD_TOOLBARAP = "nckd_toolbarap";
+    /** 人员标识*/
+    public static final String NCKD_PERSON = "nckd_person";
+
+    /**学历-实体标识*/
+    public static final String HBSS_DIPLOMA = "hbss_diploma";
+    /**教育经历-实体标识*/
+    public static final String HRPI_PEREDUEXP = "hrpi_pereduexp";
+    /**任职经历-实体标识*/
+    public static final String HRPI_EMPPOSORGREL = "hrpi_empposorgrel";
+    /**职称级别-实体标识*/
+    public static final String HBSS_PROTITLELEVEL = "hbss_protitlelevel";
+    /**资格等级-实体标识*/
+    public static final String HBSS_OCPQUALLEVEL = "hbss_ocpquallevel";
+    /**职称信息-实体标识*/
+    public static final String HRPI_PERPROTITLE = "hrpi_perprotitle";
+    /**职业资格-实体标识*/
+    public static final String HRPI_PEROCPQUAL = "hrpi_perocpqual";
+    /**职位序列-实体标识*/
+    public static final String HBJM_JOBSEQHR = "hbjm_jobseqhr";
+
+
+    /**职等方案-实体标识*/
+    public static final String HBJM_JOBGRADESCMHR = "hbjm_jobgradescmhr";
+    /**职等-实体标识*/
+    public static final String HBJM_JOBGRADEHR = "hbjm_jobgradehr";
+    /**职级方案-实体标识*/
+    public static final String HBJM_JOBLEVELSCMHR = "hbjm_joblevelscmhr";
+    /**职级-实体标识*/
+    public static final String HBJM_JOBLEVELHR = "hbjm_joblevelhr";
+    /**职级序列*/
+    public static final String JOBLEVELSEQ = "joblevelseq";
+    /** 职位序列*/
+    public static final String NCKD_JOBSEQ = "nckd_jobseq";
+    /** 积分*/
+    public static final String NCKD_SCORE = "NCKD_SCORE";
+    /** 积分*/
+    public static final String JOBSEQ = "jobseq";
+    /** 积分*/
+    public static final String EMPLOYEE_KEY = "employee";
+    /** 系数*/
+    public static final String NCKD_COEFFICIENT = "NCKD_COEFFICIENT";
+    /** 资格级别*/
+    public static final String NCKD_JOBLEVELNUMBER = "NCKD_JOBLEVELNUMBER";
+    /** 岗位*/
+    public static final String HBPM_POSITIONHR = "HBPM_POSITIONHR";
+    /** 服务年限*/
+    public static final String HRPI_PERSERLEN = "HRPI_PERSERLEN";
+    /** 本次加入集团日期*/
+    public static final String JOINCOMDATE_KEY = "JOINCOMDATE";
+    /** 首次加入集团日期*/
+    public static final String FIRSTJOINCOMDATE_KEY = "FIRSTJOINCOMDATE";
 }

+ 31 - 1
code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/enums/AppraisalResultEnum.java

@@ -1,5 +1,8 @@
 package nckd.jxccl.base.common.enums;
 
+import java.util.HashMap;
+import java.util.Map;
+
 /**
 * 【人员考评】-考评结果枚举
 * @author W.Y.C
@@ -19,7 +22,19 @@ public enum AppraisalResultEnum {
     /** 视同合格 */
     DEEMED_QUALIFIED("05", "视同合格"),
     /** 无 */
-    NONE("06", "无");
+    NONE("06", "无"),
+    /** 无考核定级 */
+    NO_ASSESSMENT_GRADE("07", "无考核定级"),
+    /** 良好 */
+    GOOD("08", "良好");
+
+    private static final Map<String, AppraisalResultEnum> CODE_MAP = new HashMap<>();
+
+    static {
+        for (AppraisalResultEnum value : AppraisalResultEnum.values()) {
+            CODE_MAP.put(value.code, value);
+        }
+    }
 
     private final String code;
     private final String name;
@@ -29,6 +44,21 @@ public enum AppraisalResultEnum {
         this.name = name;
     }
 
+    /**
+     * 根据编码获取枚举
+     * @param code 编码
+     * @return: nckd.jxccl.base.common.enums.AppraisalResultEnum
+     * @author W.Y.C
+     * @date: 2025/09/11 14:39
+     */
+    public static AppraisalResultEnum getByCode(String code) {
+        if (code == null) {
+            return null;
+        }
+        return CODE_MAP.get(code);
+    }
+
+
     public String getCode() {
         return code;
     }

+ 60 - 0
code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/enums/JobSeqEnum.java

@@ -0,0 +1,60 @@
+package nckd.jxccl.base.common.enums;
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+* 职位序列-枚举
+* @author W.Y.C
+* @date 2025/9/12 14:31
+* @version 1.0
+*/
+public enum JobSeqEnum {
+
+    /** 技术序列 */
+    TECHNICALS("01", "技术序列"),
+    /** 职能序列 */
+    FUNCTIONAL("02", "职能序列"),
+    /** 技能序列 */
+    SKILL("03", "技能序列");
+
+    private static final Map<String, JobSeqEnum> CODE_MAP = new HashMap<>();
+
+    static {
+        for (JobSeqEnum value : JobSeqEnum.values()) {
+            CODE_MAP.put(value.code, value);
+        }
+    }
+
+    private final String code;
+    private final String name;
+
+    JobSeqEnum(String code, String name) {
+        this.code = code;
+        this.name = name;
+    }
+
+    /**
+     * 根据编码获取枚举
+      * @param code
+     * @return: nckd.jxccl.base.common.enums.JobSeqEnum
+     * @author W.Y.C
+     * @date: 2025/09/12 14:33
+     */
+    public static JobSeqEnum getByCode(String code) {
+        if (code == null) {
+            return null;
+        }
+        return CODE_MAP.get(code);
+    }
+
+
+    public String getCode() {
+        return code;
+    }
+
+    public String getName() {
+        return name;
+    }
+}

+ 1 - 0
code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/utils/StrFormatter.java

@@ -16,6 +16,7 @@ public class StrFormatter {
 	private static final char BACKSLASH = '\\';
 	private static final String EMPTY_JSON = "{}";
 	private static final String NULL_STRING = "null";
+	public static final String LINE_SEPARATOR = "null";
 
 	/**
 	 * 格式化字符串(使用默认占位符 {})

+ 122 - 0
code/base/nckd-jxccl-base-helper/src/main/java/nckd/jxccl/base/org/helper/PersonHelper.java

@@ -1,9 +1,11 @@
 package nckd.jxccl.base.org.helper;
 
+import kd.bos.common.enums.EnableEnum;
 import kd.bos.dataentity.entity.DynamicObject;
 import kd.bos.dataentity.entity.DynamicObjectCollection;
 import kd.bos.orm.query.QCP;
 import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
 import kd.bos.servicehelper.QueryServiceHelper;
 import nckd.jxccl.base.common.constant.FormConstant;
 import nckd.jxccl.base.orm.helper.QFilterCommonHelper;
@@ -12,6 +14,7 @@ import org.apache.commons.lang3.StringUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Date;
 import java.util.List;
 
 /**
@@ -58,4 +61,123 @@ public class PersonHelper {
         return QueryServiceHelper.query(DEP_EMP_ENTITY_ID, selectFields, filterList.toArray(new QFilter[0]));
     }
 
+    /**
+     * 获取人员教育经历信息
+     * @param personId 人员ID
+     * @param gradutionDate 毕业时间(如果传入此时间则取学历的毕业时间小于等于当前传入的时间,否则取最高学历)
+     * @return: kd.bos.dataentity.entity.DynamicObject
+     * @author W.Y.C
+     * @date: 2025/09/12 11:15
+     */
+    public static DynamicObject getEducationExperienceByPerson(Long personId, Date gradutionDate) {
+        QFilter educationFilter = new QFilter("employee.id", QCP.equals, personId);
+        if(gradutionDate != null) {
+            educationFilter.and(new QFilter("gradutiondate", QCP.less_equals, gradutionDate));
+        }else{
+            educationFilter.and(new QFilter("ishighestdegree", QCP.equals, EnableEnum.YES.getCode()));
+        }
+        String selectFields = String.join(",",
+                //入学时间
+                "admissiondate",
+                //毕业时间
+                "gradutiondate",
+                //毕业院校
+                "graduateschool",
+                //学历-编码
+                "education.number",
+                //学历-名称
+                "education.name",
+                //学历-排序号
+                "education.index",
+                //学历-积分
+                "education.nckd_score",
+                //全日制
+                "isfulltime",
+                //学制(年)
+                "schoolsystem",
+                //最高学历
+                "ishighestdegree",
+                //学位-编码
+                "degree.number",
+                //学位-名称
+                "degree.name",
+                //学位顺序号
+                "degree.degreeseq",
+                //海外教育经历
+                "isoverseas"
+        );
+        DynamicObject[] load = BusinessDataServiceHelper.load("hrpi_pereduexp", selectFields, new QFilter[]{educationFilter}, "gradutiondate desc");
+        return load.length > 0 ? load[0] : null;
+    }
+
+    /**
+     * 获取人员聘任的职称信息
+     * TODO [待修改]-目前这里取人员附表的职称后续需要取聘任管理的职称
+     * @param personId 人员ID
+     * @param appointmentDate 聘任时间(如果传入此时间则取聘任时间小于等于当前传入的时间,否则取最最新聘任)
+     * @return: kd.bos.dataentity.entity.DynamicObject
+     * @author W.Y.C
+     * @date: 2025/09/12 11:15
+     */
+    public static DynamicObject getProfessionalTitleByPerson(Long personId, Date appointmentDate) {
+        QFilter filter = new QFilter("employee.id", QCP.equals, personId);
+        if(appointmentDate != null) {
+            filter.and(new QFilter("awardtime", QCP.less_equals, appointmentDate));
+        }else{
+            filter.and(new QFilter("ishigh", QCP.equals, EnableEnum.YES.getCode()));
+        }
+        String selectFields = String.join(",",
+                //职称名称
+                "professional",
+                //授予日期
+                "awardtime",
+                //职称级别-编码
+                "prolevel.number",
+                //职称级别-名称
+                "prolevel.name",
+                //职称级别-排序号
+                "prolevel.index",
+                //职称级别-积分
+                "prolevel.nckd_score"
+        );
+        DynamicObject[] load = BusinessDataServiceHelper.load("hrpi_perprotitle", selectFields, new QFilter[]{filter}, "awardtime desc");
+        return load.length > 0 ? load[0] : null;
+    }
+
+    /**
+     * 获取人员聘任的技能信息
+     * TODO [待修改]-目前这里取人员附表的职称后续需要取聘任管理的技能
+     * @param personId 人员ID
+     * @param appointmentDate 聘任时间(如果传入此时间则取聘任时间小于等于当前传入的时间,否则取最最新聘任)
+     * @return: kd.bos.dataentity.entity.DynamicObject
+     * @author W.Y.C
+     * @date: 2025/09/12 11:15
+     */
+    public static DynamicObject getSkillByPerson(Long personId, Date appointmentDate) {
+        QFilter filter = new QFilter("employee.id", QCP.equals, personId);
+        if(appointmentDate != null) {
+            filter.and(new QFilter("registratedate", QCP.less_equals, appointmentDate));
+        }else{
+            filter.and(new QFilter("ismajor", QCP.less_equals, EnableEnum.YES.getCode()));
+        }
+        String selectFields = String.join(",",
+                //职业资格-名称
+                "qualification.name",
+                //职业资格-编码
+                "qualification.number",
+                //聘任或注册日期
+                "registratedate",
+                //职业资格等级-编码
+                "qualevel.number",
+                //职业资格等级-名称
+                "qualevel.name",
+                //职业资格等级-排序号
+                "qualevel.index",
+                //职业资格等级-积分
+                "qualevel.nckd_score"
+        );
+        DynamicObject[] load = BusinessDataServiceHelper.load("hrpi_perocpqual", selectFields, new QFilter[]{filter}, "registratedate desc");
+        return load.length > 0 ? load[0] : null;
+    }
+
 }

+ 65 - 0
code/base/nckd-jxccl-base-helper/src/main/java/nckd/jxccl/base/pm/helper/PerformanceManagerHelper.java

@@ -0,0 +1,65 @@
+package nckd.jxccl.base.pm.helper;
+
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import nckd.jxccl.base.common.constant.FormConstant;
+import nckd.jxccl.base.common.utils.DateUtil;
+
+import java.time.LocalDateTime;
+import java.util.Date;
+
+/**
+ * 人员考评管理帮助类
+ * @author W.Y.C
+ * @date 2025/9/11 14:21
+ * @version 1.0
+ */
+public class PerformanceManagerHelper {
+
+    /** 人员考评管理实体名称 */
+    public static final String PERFMANAGER_ENTITYID = "nckd_perfmanager";
+    /** 人员考评管理分录实体名称 */
+    public static final String PERFMANAGER_ENTRY_ENTITYID = "nckd_perfmanagerentry";
+    /** 分录-考核年份 */
+    public static final String NCKD_APPRAISALYEAR = "nckd_appraisalyear";
+    /** 分录-考评结果 */
+    public static final String NCKD_APPRAISALRESULT = "nckd_appraisalresult";
+
+
+    /**
+     * 获取某个员工某个年度的考评结果
+     * @param personId 人员ID
+     * @param date 年度
+     * @return: DynamicObject(返回考评结果实体,可能为null)
+     * @author W.Y.C
+     * @date: 2025/09/11 14:25
+     */
+    public static DynamicObject getPerformanceResult(Long personId, LocalDateTime date) {
+        LocalDateTime beginOfYear = DateUtil.beginOfYear(date);
+        LocalDateTime endOfYear = DateUtil.endOfDay(date);
+        QFilter filter = new QFilter(String.join(".",FormConstant.NCKD_PERSON, FormConstant.ID_KEY), QCP.equals,personId)
+                .and(new QFilter(String.join(".",PERFMANAGER_ENTRY_ENTITYID, NCKD_APPRAISALYEAR),QCP.equals,beginOfYear));
+
+        String selectField = String.join(",",
+                String.join(".",PERFMANAGER_ENTRY_ENTITYID, NCKD_APPRAISALYEAR),
+                String.join(".",PERFMANAGER_ENTRY_ENTITYID, NCKD_APPRAISALRESULT)
+        );
+        DynamicObject[] load = BusinessDataServiceHelper.load(PERFMANAGER_ENTITYID, selectField, new QFilter[]{filter}, FormConstant.CREATE_TIME_KEY + " desc");
+        if(load != null && load.length > 0){
+            DynamicObject perfManager = load[0];
+            DynamicObjectCollection perfManagerEntryColl = perfManager.getDynamicObjectCollection(PERFMANAGER_ENTRY_ENTITYID);
+            for (DynamicObject perfManagerEntry : perfManagerEntryColl) {
+                Date year = perfManagerEntry.getDate(NCKD_APPRAISALYEAR);
+                if(DateUtil.isInRange(DateUtil.toLocalDateTime(year),beginOfYear,endOfYear)){
+                    //只返回符合的年度考核结果
+                    return perfManagerEntry.getDynamicObject(NCKD_APPRAISALRESULT);
+                }
+
+            }
+        }
+        return null;
+    }
+}

+ 0 - 2
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/hstu/common/HonorStudentConstant.java

@@ -108,8 +108,6 @@ public class HonorStudentConstant extends FormConstant {
     public static final String NCKD_EVALQUESTTPL = "NCKD_EVALQUESTTPL";
     /** 单据体 */
     public static final String NCKD_EVALQUESTENTRY = "NCKD_EVALQUESTENTRY";
-    /** 员工编码 */
-    public static final String NCKD_PERSON = "NCKD_PERSON";
     /** 所属公司 */
     public static final String NCKD_TEXTFIELD = "NCKD_TEXTFIELD";
     /** 职位 */

+ 1521 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/business/JobLevelCalculatorService.java

@@ -0,0 +1,1521 @@
+package nckd.jxccl.hr.psms.business;
+
+import kd.bos.algo.DataSet;
+import kd.bos.algo.Row;
+import kd.bos.common.enums.EnableEnum;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.entity.constant.StatusEnum;
+import kd.bos.logging.Log;
+import kd.bos.logging.LogFactory;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.servicehelper.QueryServiceHelper;
+import nckd.jxccl.base.common.constant.FormConstant;
+import nckd.jxccl.base.common.enums.AppraisalResultEnum;
+import nckd.jxccl.base.common.enums.JobSeqEnum;
+import nckd.jxccl.base.common.exception.ValidationException;
+import nckd.jxccl.base.common.utils.DateUtil;
+import nckd.jxccl.base.common.utils.StrFormatter;
+import nckd.jxccl.base.orm.helper.QFilterCommonHelper;
+import nckd.jxccl.base.pm.helper.PerformanceManagerHelper;
+import nckd.jxccl.hr.psms.common.PerfRankMgmtConstant;
+import nckd.jxccl.hr.psms.common.PositionStructureConstant;
+import nckd.jxccl.hr.psms.helper.PositionStructureHelper;
+import org.apache.commons.lang3.StringUtils;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.StringJoiner;
+import java.util.stream.Collectors;
+
+/**
+ * 职级计算器,用于计算员工的职级
+ * 功能:根据职称等级、技能等级、学历、年度贡献分、绩效考核结果、R排名等信息,动态计算员工应获得的职级
+ * @author W.Y.C
+ * @date 2025/9/19 13:56
+ * @version 1.0
+ */
+public class JobLevelCalculatorService {
+
+    protected final static Log logger = LogFactory.getLog(JobLevelCalculatorService.class);
+
+    /** 技术序列 */
+    public static final String JS_JOB_SEQ = "01";
+    /** 职能序列 */
+    public static final String ZN_JOB_SEQ = "02";
+    /** 技能序列 */
+    public static final String JN_JOB_SEQ = "03";
+    /** 管理序列 */
+    public static final String GL_JOB_SEQ = "04";
+    /** 职称等级与技能等级映射关系*/
+    public static final Map<String, String> TechPostLevelNumberTojobstatusNumberMap = new HashMap<>();
+
+    static {
+        // 正高级---高级技师
+        TechPostLevelNumberTojobstatusNumberMap.put("001", "1");
+        // 副高级---高级技师
+        TechPostLevelNumberTojobstatusNumberMap.put("002", "1");
+        // 中级---技师
+        TechPostLevelNumberTojobstatusNumberMap.put("003", "2");
+        // 助理级---高级工
+        TechPostLevelNumberTojobstatusNumberMap.put("004", "3");
+    }
+
+
+
+    /**
+     * 计算员工职级(单条生成年度调记录、新建调整查询职级、新建动态调整)
+     * @param positionAppointment 人员最新信息(最新学历、最新聘任....)
+     * @return: java.lang.String
+     * @author W.Y.C
+     * @date: 2025/09/19 14:19
+     */
+    public static JobLevelResult calculateJobLevel(DynamicObject person, Date date,  DynamicObject positionAppointment) {
+        //资格三要素:
+        // • 职称/技能等级:硬性门槛(如工程师职称、技师等级)
+        // • 职业生涯累计积分:量化能力总分(由学历、职称、年度贡献分累计)
+        // • 年度绩效考核结果(R排名):决定升降的关键绩效指标
+        Long personId = person.getLong(FormConstant.ID_KEY);
+        String personName = person.getString(FormConstant.NAME_KEY);
+        //对应SHR:hrjobfamilyid
+        long jobSeqId = positionAppointment.getLong(String.join(".", FormConstant.HBPM_POSITIONHR, FormConstant.NCKD_JOBSEQ,FormConstant.ID_KEY));
+        DynamicObject jobSeq = BusinessDataServiceHelper.loadSingle(jobSeqId, FormConstant.HBJM_JOBSEQHR);
+        JobLevelResult jobLevelResult = new JobLevelResult();
+        //对应SHR:JobGradeid
+        // jobLevelResult.jobLevel
+
+        // 1. 获取最近的职位档案(必须存在,否则抛错)
+        //对应SHR:personpositionfileInfo
+        DynamicObject currentPersonPosFile = PositionStructureHelper.getLatsPersonPosFileByPerson(personId);
+        if(currentPersonPosFile == null){
+            throw new ValidationException(StrFormatter.format("当前无法为【{}】进行调整,因为他/她尚未建立职位档案。请前往“职位及积分初定” -> 进行初定!", person.getString(FormConstant.NAME_KEY)));
+        }
+        //对应SHR:oldHRJobFamilyid
+        long currentJobSeqId = currentPersonPosFile.getLong(String.join(".",PositionStructureConstant.NCKD_JOBLEVELHR,FormConstant.ID_KEY));
+        //oldHRJobFamilynumber
+        String currentJobSeqNumber = currentPersonPosFile.getString(String.join(".",PositionStructureConstant.NCKD_JOBLEVELHR,FormConstant.NUMBER_KEY));
+        DynamicObject appraisalResult = currentPersonPosFile.getDynamicObject(PositionStructureConstant.NCKD_APPRAISALRESULT);
+        //appraisalresultnumber
+        String appraisalResultNumber = appraisalResult != null ? appraisalResult.getString(FormConstant.NUMBER_KEY) : null;
+
+        // 2. 处理职位序列(如果是管理序列,则按职能序列进行变动)
+        //对应SHR:hrjobfamilynumber
+        /*jobSeq = handleJobSeq(jobSeq);
+        // 3. 检查是否缺少聘任条件(没有聘任的职称或技能按最低职级定)
+        if (isInvalidRankingConditions(proTitleLevelNumber, ocpQualLevelNumber, jobSeq.getString(FormConstant.NUMBER_KEY))) {
+            logger.warn("人员【{}】职位序列【{}】无聘任职称/技能,按最低职级", personName,jobSeq.getString(FormConstant.NAME_KEY));
+            DynamicObject lowestJobGrade = getLowestJobLevel(jobSeq);
+            //对应SHR:selMap.put("adjusttype","7")
+            jobLevelResult.adjustType = "7";
+            jobLevelResult.adjustMsg = "不符合三要素【无聘任职称/技能】,定最低职级";
+            return jobLevelResult;
+        }*/
+
+        // 4. 读取并校验上年度考核结果
+        // 对应SHR:874~911
+        appraisalResultNumber = validateAppraisalResultConsistency(date, personId, personName, appraisalResultNumber);
+
+
+        //当前总积分(变动前)
+        BigDecimal sumScore = currentPersonPosFile.getBigDecimal(PositionStructureConstant.NCKD_SUMSCORE);
+
+        //当前职级(变动前)
+        //对应SHR:JobGrade;916~919行
+        DynamicObject currentJobLevel = currentPersonPosFile.getDynamicObject(PositionStructureConstant.NCKD_JOBLEVELHR);
+        if(currentJobLevel == null){
+            throw new ValidationException(StrFormatter.format("数据异常,人员【{}】最新职位档案【档案ID:{}】没有职级。请联系管理员处理!", personName, currentPersonPosFile.getLong(FormConstant.ID_KEY)));
+        }
+        //对应SHR:lastjobgradeindex
+        int currentJobLevelIndex = currentJobLevel.getInt(FormConstant.INDEX_KEY);
+
+        // 5.是否首次聘任
+        //对应SHR:fistPR;922~926行
+        boolean firstPR = isFirstPR(personId);
+
+        // 6. 是否已使用过考核结果
+        //对应SHR:useappraisalresult;926行
+        boolean usedAppraisalResult = useAppraisalResult(personId,date);
+
+        // 7. 计算降级数
+        //对应SHR:minuspersonappraisal;930~945行
+        int minusPersonAppraisal = calcMinusPersonAppraisal(appraisalResultNumber, firstPR, usedAppraisalResult, jobLevelResult);
+
+        // 8. 处理学历积分
+        //对应SHR:DiplomaScore;947~978行
+        BigDecimal diplomaScore = handleDiplomaScore(currentPersonPosFile, positionAppointment);
+
+        // 9.如果是管理序列,则按职能序列进行调整
+        //对应SHR:hrjobfamilynumber;989~993
+        jobSeq = handleJobSeq(jobSeq);
+
+        // 9. 处理序列相关信息
+        //对应SHR:995~1013
+        JobFamilyInfo jobFamilyInfo = processJobFamilyInfo(jobSeq, currentPersonPosFile);
+
+        // 10. 处理职称/技能等级积分
+        // 对应SHR:1015~1036
+        JobScoreInfo jobScoreInfo = handleJobScores(jobSeq, currentPersonPosFile, positionAppointment);
+
+        // 计算总积分;累计积分池的分 + 学历 + (职称分/技能分)
+        // 对应SHR:allsumScore;1041行
+        BigDecimal allSumScore = sumScore.add(diplomaScore)
+                .add(jobScoreInfo.perProTitleScore)
+                .add(jobScoreInfo.quaLevelScore);
+
+        // 11. 获取序列对应的职级
+        DynamicObjectCollection jobLevelByJobSeq = getJobLevelByJobSeq(jobSeq);
+        //对应SHR:jobgradeMap
+        Map<Integer, DynamicObject> jobLevelMap = jobLevelByJobSeq.stream()
+                .collect(Collectors.toMap(
+                        dynamicObject -> dynamicObject.getInt(String.join(".", FormConstant.HBJM_JOBLEVELHR, FormConstant.JOBLEVELSEQ)),
+                        dynamicObject -> dynamicObject)
+                );
+
+        // 12. 根据不同情况计算职级ID
+        //对应SHR:JobGradeInfo;1044~1071行
+        DynamicObject jobLevel = calculateJobGradeId(jobSeq,jobScoreInfo,jobFamilyInfo,allSumScore,currentJobLevelIndex,minusPersonAppraisal,firstPR,jobLevelResult);
+        //对应SHR:newjobgradeindex
+        int jobLevelIndex = jobLevel.getInt(FormConstant.JOBLEVELSEQ);
+        // 13. 处理排名相关信息
+        //对应SHR:allowancerankpercent、toprankpercent、allowancerank、toprank;1073~1109行
+        RankingResult rankingInfo = getRankingInfo(personId, personName, date);
+
+        // 14. 处理序列转换或跨单位调动的特殊逻辑
+        // SHR对应:1110~1136行
+        if (jobFamilyInfo.isSequenceChange) {
+            System.out.println("序列切换,新职级号不能大于原序列职级序号");
+            handleSequenceChange(jobLevelIndex,currentJobLevelIndex,rankingInfo,jobLevelMap,jobLevelResult,jobSeq);
+            jobLevel = jobLevelResult.jobLevel;
+            return jobLevelResult;
+        } else if (jobFamilyInfo.isCrossUnitTransfer) {
+            System.out.println("跨单位调动且未跨序列,平移处理");
+            handleCrossUnitTransfer(jobLevelIndex,currentJobLevelIndex,rankingInfo,jobLevelMap,jobLevelResult,jobSeq);
+            jobLevel = jobLevelResult.jobLevel;
+            return jobLevelResult;
+        }
+
+        // 15. 获取最近一次聘任信息
+        //对应SHR:PR_lastjobgradeindex;1139~1147行
+        int PR_lastjobgradeindex = getLastAppointmentJobGradeIndex(personId, currentJobLevelIndex,jobLevelIndex);
+        if (jobLevelIndex - PR_lastjobgradeindex > 1) {
+            jobLevelIndex = PR_lastjobgradeindex + 1;
+        }
+
+        // 16. 根据考核结果使用情况处理职级计算
+        //对应SHR行:1149~1180行
+        jobLevelIndex = handleAppraisalUsage(jobSeq,rankingInfo,jobLevelIndex,PR_lastjobgradeindex,currentJobLevelIndex,minusPersonAppraisal,firstPR,usedAppraisalResult,jobLevelResult);
+
+        // 17. 处理首次聘任情况 & 处理聘任相关限制
+        //对应SHR:1182~1295行
+        jobLevelIndex = handleAppointmentRestrictions(personId, firstPR,usedAppraisalResult,jobSeq,jobSeqId,currentJobSeqId,currentJobSeqNumber,jobScoreInfo,currentJobLevelIndex,jobLevelIndex,allSumScore,jobLevelResult,minusPersonAppraisal,currentPersonPosFile);
+
+        // 18.最终确定职级ID
+        //对应SHR行:1297~1320行
+        jobLevel = determineFinalJobGradeId(jobLevelIndex, jobScoreInfo, jobLevelMap, jobSeq, jobLevelResult, appraisalResultNumber);
+        jobLevelResult.jobLevel = jobLevel;
+
+        return jobLevelResult;
+    }
+
+    /**
+     * 读取并校验上年度考核结果
+     * 目的:如果不是新年度首次定级,则必须存在上年考核结果;若本年度已有人为创建记录,则要保证“上年考核结果在系统中的值”与上笔记录一致,避免数据不一致导致错误定级。
+     * @param date 日期
+     * @param personId 人员ID
+     * @param personName 人员名称
+     * @param appraisalResultNumber 最近应用的考核结果
+     * @return: java.lang.String 如果当前年度没有记录,则使用去年的考核结果
+     * @author W.Y.C
+     * @date: 2025/09/20 20:41
+     */
+    public static String validateAppraisalResultConsistency(Date date, Long personId, String personName, String appraisalResultNumber) {
+        //SHR原代码:com.kingdee.shr.customer.web.handler.ContributeScore.PersonpositionfileFluctuationListHandler#getJobGradeid:874~911行
+        //对应SHR:beginyear
+        int year = DateUtil.getYear(date);
+        //对应SHR:lastyear
+        LocalDateTime lastYearDateTime = DateUtil.minusYears(DateUtil.toLocalDateTime(date), 1);
+        //对应SHR:selpersonappraisalresultByyear()方法
+        DynamicObject lastYearAppraisalResult = PerformanceManagerHelper.getPerformanceResult(personId, lastYearDateTime);
+        //上年度考核结果;
+        // 对应SHR:personappraisalresultMap
+        String lastYearAppraisalResultNumber = lastYearAppraisalResult != null ? lastYearAppraisalResult.getString(FormConstant.NUMBER_KEY) : null;
+        //当前年度职位档案;
+        // 对应SHR:nowyearpersonpositionfileCollection
+        DynamicObject currentYearPersonPosFileByPerson = PositionStructureHelper.getLatsPersonPosFileByPerson(personId,new QFilter(PositionStructureConstant.NCKD_EXECUTEYEAR,QCP.equals,year));
+        //对应SHR:newpersonfirstrank
+        //对应SHR:878行
+        boolean newPersonFirstRank = isNewPersonFirstRank(date, personId);
+        // 目的:如果不是新年度首次定级,则必须存在上年考核结果;若本年度已有人为创建记录,则要保证“上年考核结果在系统中的值”与上笔记录一致,避免数据不一致导致错误定级。
+        // 检查是否存在去年的考核结果
+        //对应SHR:if (personappraisalresultMap == null || personappraisalresultMap.size() == 0 || personappraisalresultMap.get("appraisalresultnumber") == null);884行
+        boolean hasLastYearAppraisal = StringUtils.isNotBlank(lastYearAppraisalResultNumber);
+
+        // 检查当前年度是否已有职位档案记录
+        //对应SHR:nowyearpersonpositionfileCollection == null || nowyearpersonpositionfileCollection.size() == 0;888行
+        boolean hasCurrentYearRecord = currentYearPersonPosFileByPerson != null && currentYearPersonPosFileByPerson.getDataEntityType() != null;
+
+
+        // 如果不是新员工初定情况,必须有去年考核结果
+        if (!newPersonFirstRank && !hasLastYearAppraisal) {
+            throw new ValidationException(StrFormatter.format("人员【{}】缺少【{}】年考核结果", personName,lastYearDateTime.getYear()));
+        }
+        // 如果当前年度没有记录,则使用去年的考核结果
+        if (!hasCurrentYearRecord) {
+            appraisalResultNumber = hasLastYearAppraisal ? lastYearAppraisalResultNumber : StringUtils.EMPTY;
+        } else {
+            // 如果当前年度已有记录,则验证考核结果是否一致
+            String lastYearAppraisalNumber = hasLastYearAppraisal ? lastYearAppraisalResultNumber : null;
+
+            // 对于新员工,特殊处理"无"考核结果(无考核结果)
+            if (newPersonFirstRank) {
+                // 如果去年没有考核结果,但当前有非"无"的考核结果,则报错
+                if (StringUtils.isBlank(lastYearAppraisalNumber) &&
+                        StringUtils.isNotBlank(appraisalResultNumber) &&
+                        !AppraisalResultEnum.NONE.getCode().equals(appraisalResultNumber)) {
+                    throw new ValidationException(StrFormatter.format("人员【{}】上年考度核结果存在变更,需删除【{}】年度创建的【员工职位档案调整】或 【职位及积分年度调整】记录 ", personName,year));
+                }
+                // 如果去年有考核结果,但与当前不一致,则报错
+                if (StringUtils.isNotBlank(lastYearAppraisalNumber) &&
+                        !lastYearAppraisalNumber.equals(appraisalResultNumber)) {
+                    throw new ValidationException(StrFormatter.format("人员【{}】上年考度核结果存在变更,需删除【{}】年度创建的【员工职位档案调整】或 【职位及积分年度调整】记录 ", personName,year));
+                }
+            } else {
+                // 对于非新员工,必须有去年考核结果且与当前一致
+                if (!lastYearAppraisalNumber.equals(appraisalResultNumber)) {
+                    throw new ValidationException(StrFormatter.format("人员【{}】上年考度核结果存在变更,需删除【{}】年度创建的【员工职位档案调整】或 【职位及积分年度调整】记录 ", personName,year));
+                }
+            }
+        }
+        return appraisalResultNumber;
+    }
+
+    /**
+     * 处理职位序列(如果是管理序列,则按职能序列进行调整)
+     * @param jobSeq 序列
+     * @return: java.lang.String
+     * @author W.Y.C
+     * @date: 2025/09/19 14:07
+     */
+    public static DynamicObject handleJobSeq(DynamicObject jobSeq) {
+        if(GL_JOB_SEQ.equalsIgnoreCase(jobSeq.getString(FormConstant.NUMBER_KEY))){
+            QFilter filter = QFilterCommonHelper.getEnableFilter().and(QFilterCommonHelper.getDataStatusFilter()).and(new QFilter(FormConstant.NUMBER_KEY, QCP.equals, JobSeqEnum.FUNCTIONAL.getCode()));
+            StringJoiner selectFields = new StringJoiner(",");
+            selectFields.add(FormConstant.ID_KEY);
+            selectFields.add(FormConstant.NUMBER_KEY);
+            selectFields.add(FormConstant.NAME_KEY);
+            DynamicObject[] load = BusinessDataServiceHelper.load(FormConstant.HBJM_JOBSEQHR, selectFields.toString(), new QFilter[]{filter});
+            if(load.length == 0){
+                throw new ValidationException(StrFormatter.format("管理序列转职能序列错误,未找到编码为【{}】的职能序列。请维护编码为【{}】的职能序列!", ZN_JOB_SEQ,ZN_JOB_SEQ));
+            }
+            return load[0];
+        }else{
+            //其他不转换
+            return jobSeq;
+        }
+    }
+
+
+    /**
+     * 检查职称/技能等级是否有效(避免职级计算错误)
+     * @param proTitleLevelNumber 职称等级编码
+     * @param ocpQualLevel 技能等级编码
+     * @param jobSeqNumber 职位序列编码
+     * @return: boolean
+     * @author W.Y.C
+     * @date: 2025/09/19 15:35
+     */
+    private boolean isInvalidRankingConditions(String proTitleLevelNumber, String ocpQualLevel, String jobSeqNumber) {
+        // 情况1:无职称且无技能等级(或非技能序列)
+        if (StringUtils.isBlank(proTitleLevelNumber) && (StringUtils.isBlank(ocpQualLevel) || JobSeqEnum.SKILL.getCode().equals(jobSeqNumber))) {
+            return true;
+        }
+
+        // 情况2:技能序列下无技能等级,但有职称等级且职称无对应技能等级
+        return JN_JOB_SEQ.equals(jobSeqNumber) &&
+                StringUtils.isBlank(ocpQualLevel) &&
+                StringUtils.isNotBlank(proTitleLevelNumber) &&
+                TechPostLevelNumberTojobstatusNumberMap.get(proTitleLevelNumber) == null;
+    }
+
+    /**
+     * 获取职位序列最低职级
+     * @param jobSeq 职位序列
+     * @return: kd.bos.dataentity.entity.DynamicObject
+     * @author W.Y.C
+     * @date: 2025/09/20 16:52
+     */
+    public static DynamicObject getLowestJobLevel(DynamicObject jobSeq) {
+
+        StringJoiner selectFields = new StringJoiner(",")
+                //职位序列
+                .add(String.join(".",FormConstant.NCKD_JOBSEQ,FormConstant.ID_KEY))
+                .add(String.join(".",FormConstant.NCKD_JOBSEQ,FormConstant.NAME_KEY))
+                .add(String.join(".",FormConstant.NCKD_JOBSEQ,FormConstant.NUMBER_KEY))
+                //职级
+                .add(String.join(".",FormConstant.HBJM_JOBLEVELHR, FormConstant.ID_KEY))
+                .add(String.join(".",FormConstant.HBJM_JOBLEVELHR, FormConstant.NAME_KEY))
+                .add(String.join(".",FormConstant.HBJM_JOBLEVELHR, FormConstant.NUMBER_KEY))
+                .add(String.join(".",FormConstant.HBJM_JOBLEVELHR, FormConstant.JOBLEVELSEQ))
+                .add(String.join(".",FormConstant.HBJM_JOBLEVELHR, FormConstant.NCKD_COEFFICIENT))
+                .add(String.join(".",FormConstant.HBJM_JOBLEVELHR, FormConstant.NCKD_SCORE));
+
+        QFilter filter = new QFilter(String.join(".",FormConstant.NCKD_JOBSEQ), QCP.equals, jobSeq.getLong(FormConstant.ID_KEY))
+                .and(new QFilter(String.join(".",FormConstant.HBJM_JOBLEVELHR, FormConstant.JOBLEVELSEQ), QCP.is_notnull, null))
+                .and(new QFilter(String.join(".",FormConstant.HBJM_JOBLEVELHR, FormConstant.JOBLEVELSEQ), QCP.large_than, 0));
+        DynamicObjectCollection query = QueryServiceHelper.query(PositionStructureConstant.JOBSEQTOJOBLEVELQUERY,
+                selectFields.toString(),
+                new QFilter[]{filter},
+                String.join(".", FormConstant.HBJM_JOBLEVELHR, FormConstant.JOBLEVELSEQ));
+        return !query.isEmpty() ? query.get(0) : null;
+    }
+
+    /**
+     * 获取职位序列和积分最高职级
+     * @param jobSeq 职位序列
+     * @return: kd.bos.dataentity.entity.DynamicObject
+     * @author W.Y.C
+     * @date: 2025/09/20 16:52
+     */
+    public static DynamicObject getMaxJobLevel(DynamicObject jobSeq,BigDecimal score) {
+
+        StringJoiner selectFields = new StringJoiner(",")
+                //职位序列
+                .add(String.join(".",FormConstant.NCKD_JOBSEQ,FormConstant.ID_KEY))
+                .add(String.join(".",FormConstant.NCKD_JOBSEQ,FormConstant.NAME_KEY))
+                .add(String.join(".",FormConstant.NCKD_JOBSEQ,FormConstant.NUMBER_KEY))
+                //职级
+                .add(String.join(".",FormConstant.HBJM_JOBLEVELHR, FormConstant.ID_KEY))
+                .add(String.join(".",FormConstant.HBJM_JOBLEVELHR, FormConstant.NAME_KEY))
+                .add(String.join(".",FormConstant.HBJM_JOBLEVELHR, FormConstant.NUMBER_KEY))
+                .add(String.join(".",FormConstant.HBJM_JOBLEVELHR, FormConstant.JOBLEVELSEQ))
+                .add(String.join(".",FormConstant.HBJM_JOBLEVELHR, FormConstant.NCKD_COEFFICIENT))
+                .add(String.join(".",FormConstant.HBJM_JOBLEVELHR, FormConstant.NCKD_SCORE));
+
+        QFilter filter = new QFilter(String.join(".",FormConstant.NCKD_JOBSEQ), QCP.equals, jobSeq.getLong(FormConstant.ID_KEY))
+                .and(new QFilter(String.join(".",FormConstant.HBJM_JOBLEVELHR, FormConstant.JOBLEVELSEQ), QCP.is_notnull, null))
+                .and(new QFilter(String.join(".",FormConstant.HBJM_JOBLEVELHR, FormConstant.JOBLEVELSEQ), QCP.large_than, 0))
+                .and(new QFilter(String.join(".",FormConstant.HBJM_JOBLEVELHR, FormConstant.NCKD_SCORE), QCP.less_equals, score));
+
+        DynamicObjectCollection query = QueryServiceHelper.query(PositionStructureConstant.JOBSEQTOJOBLEVELQUERY,
+                selectFields.toString(),
+                new QFilter[]{filter},
+                String.join(".", FormConstant.HBJM_JOBGRADESCMHR, FormConstant.JOBLEVELSEQ +" desc"));
+        return !query.isEmpty() ? query.get(0) : null;
+    }
+
+    /**
+     * 获取职位序列和积分最高职级
+     * @param jobSeq 职位序列
+     * @return: kd.bos.dataentity.entity.DynamicObject
+     * @author W.Y.C
+     * @date: 2025/09/20 16:52
+     */
+    private static DynamicObject getMaxJobLevel(DynamicObject jobSeq,String proTitleLevelNumber,String ocpQualLevelNumber) {
+        jobSeq = handleJobSeq(jobSeq);
+        String jobSeqNumber = jobSeq.getString(FormConstant.NUMBER_KEY);
+        /**
+         * 如果没有职称等级与技能等级,则倒取该序列最低职等 如果没有职称且是非技能序列,则倒取该序列最低职等 如果是技能序列,没有技能等级但有职称等级,但职称等级没有对应的技能等级,则取该序列最低职等
+         */
+        if (StringUtils.isBlank(proTitleLevelNumber) && (StringUtils.isBlank(ocpQualLevelNumber) || !JobSeqEnum.SKILL.getCode().equals(jobSeqNumber))) {
+            return getLowestJobLevel(jobSeq);
+        }
+        else if (JobSeqEnum.SKILL.getCode().equals(jobSeqNumber) && StringUtils.isBlank(ocpQualLevelNumber) && StringUtils.isNotBlank(proTitleLevelNumber) && TechPostLevelNumberTojobstatusNumberMap.get(proTitleLevelNumber) == null) {
+            return getLowestJobLevel(jobSeq);
+        }
+        String likename = "";
+        String likenumber = "";
+        if (JobSeqEnum.SKILL.getCode().equals(jobSeqNumber)) {
+            if (StringUtils.isBlank(ocpQualLevelNumber)) {
+                likenumber = TechPostLevelNumberTojobstatusNumberMap.get(proTitleLevelNumber);
+                likename = "职称等级【" + proTitleLevelNumber + "】对应的技能等级";
+                System.out.println("技能序列但无技能等级,使用职称等级【" + proTitleLevelNumber + "】对应的技能等级,编码【" + likenumber + "】");
+            }
+            else {
+                likenumber = ocpQualLevelNumber;
+                likename = "技能等级";
+                System.out.println("技能序列使用技能等级,编码【" + likenumber + "】");
+            }
+        }
+        else {
+            likenumber = proTitleLevelNumber;
+            likename = "职称等级";
+            System.out.println("非技能序列使用职称等级,编码【" + likenumber + "】");
+        }
+        StringJoiner selectFields = new StringJoiner(",")
+                //职位序列
+                .add(String.join(".", FormConstant.NCKD_JOBSEQ,FormConstant.ID_KEY))
+                .add(String.join(".", FormConstant.NCKD_JOBSEQ,FormConstant.NAME_KEY))
+                .add(String.join(".", FormConstant.NCKD_JOBSEQ,FormConstant.NUMBER_KEY))
+                //职级
+                .add(String.join(".",FormConstant.HBJM_JOBLEVELHR, FormConstant.ID_KEY))
+                .add(String.join(".",FormConstant.HBJM_JOBLEVELHR, FormConstant.NAME_KEY))
+                .add(String.join(".",FormConstant.HBJM_JOBLEVELHR, FormConstant.NUMBER_KEY))
+                .add(String.join(".",FormConstant.HBJM_JOBLEVELHR, FormConstant.JOBLEVELSEQ))
+                .add(String.join(".",FormConstant.HBJM_JOBLEVELHR, FormConstant.NCKD_COEFFICIENT))
+                .add(String.join(".",FormConstant.HBJM_JOBLEVELHR, FormConstant.NCKD_SCORE));
+
+        QFilter filter = new QFilter(String.join(".",FormConstant.NCKD_JOBSEQ), QCP.equals, jobSeq.getLong(FormConstant.ID_KEY))
+                .and(new QFilter(String.join(".",FormConstant.HBJM_JOBLEVELHR, FormConstant.JOBLEVELSEQ), QCP.is_notnull, null))
+                .and(new QFilter(String.join(".",FormConstant.HBJM_JOBLEVELHR, FormConstant.JOBLEVELSEQ), QCP.large_than, 0))
+                .and(new QFilter(String.join(".",FormConstant.HBJM_JOBGRADEHR, FormConstant.NCKD_JOBLEVELNUMBER), QCP.like, "%"+likenumber+"%"));
+        DynamicObjectCollection query = QueryServiceHelper.query(PositionStructureConstant.JOBSEQTOJOBLEVELQUERY,
+                selectFields.toString(),
+                new QFilter[]{filter},
+                String.join(".", FormConstant.HBJM_JOBLEVELHR, FormConstant.JOBLEVELSEQ +" desc"));
+        return !query.isEmpty() ? query.get(0) : null;
+    }
+
+    /**
+     * 获取职位序列对应的职级
+     * @param jobSeq 职位序列
+     * @return: kd.bos.dataentity.entity.DynamicObject
+     * @author W.Y.C
+     * @date: 2025/09/20 16:52
+     */
+    private static DynamicObjectCollection getJobLevelByJobSeq(DynamicObject jobSeq) {
+
+        StringJoiner selectFields = new StringJoiner(",")
+                //职位序列
+                .add(String.join(".",FormConstant.HBJM_JOBGRADESCMHR, FormConstant.NCKD_JOBSEQ,FormConstant.ID_KEY))
+                .add(String.join(".",FormConstant.HBJM_JOBGRADESCMHR, FormConstant.NCKD_JOBSEQ,FormConstant.NAME_KEY))
+                .add(String.join(".",FormConstant.HBJM_JOBGRADESCMHR, FormConstant.NCKD_JOBSEQ,FormConstant.NUMBER_KEY))
+                //职级
+                .add(String.join(".",FormConstant.HBJM_JOBLEVELHR, FormConstant.ID_KEY))
+                .add(String.join(".",FormConstant.HBJM_JOBLEVELHR, FormConstant.NAME_KEY))
+                .add(String.join(".",FormConstant.HBJM_JOBLEVELHR, FormConstant.NUMBER_KEY))
+                .add(String.join(".",FormConstant.HBJM_JOBLEVELHR, FormConstant.JOBLEVELSEQ))
+                .add(String.join(".",FormConstant.HBJM_JOBLEVELHR, FormConstant.NCKD_COEFFICIENT))
+                .add(String.join(".",FormConstant.HBJM_JOBLEVELHR, FormConstant.NCKD_SCORE));
+
+        QFilter filter = new QFilter(String.join(".", FormConstant.NCKD_JOBSEQ), QCP.equals, jobSeq.getLong(FormConstant.ID_KEY))
+                .and(new QFilter(String.join(".",FormConstant.HBJM_JOBLEVELHR, FormConstant.JOBLEVELSEQ), QCP.is_notnull, null))
+                .and(new QFilter(String.join(".",FormConstant.HBJM_JOBLEVELHR, FormConstant.JOBLEVELSEQ), QCP.large_than, 0));
+        DynamicObjectCollection query = QueryServiceHelper.query(PositionStructureConstant.JOBSEQTOJOBLEVELQUERY,
+                selectFields.toString(),
+                new QFilter[]{filter},
+                String.join(".", FormConstant.HBJM_JOBLEVELHR, FormConstant.JOBLEVELSEQ));
+        return query;
+    }
+
+    /**
+     * 检查某个人在特定年份的考核结果是否被使用
+     * 对应SHR:useAppraisalresult方法
+     * @param personId 人员ID
+     * @param date 日期
+     * @return: boolean
+     * @author W.Y.C
+     * @date: 2025/09/20 15:10
+     */
+    private static boolean useAppraisalResult(Long personId, Date date) {
+        //对应SHR代码:com.kingdee.shr.customer.web.handler.ContributeScore.PersonpositionfileFluctuationListHandler#useAppraisalresult
+
+        //查询条件:查询某个人员职位档案非失效状态 并且 (初定 或者 类型状态为:年度调整3;职位调动4) 并且 职称分数或技能分数>0的数据
+        QFilter rankScore = new QFilter(PositionStructureConstant.NCKD_RANKSCORE, QCP.is_notnull, null).and(new QFilter(PositionStructureConstant.NCKD_RANKSCORE, QCP.large_than, 0));
+        QFilter jobScore = new QFilter(PositionStructureConstant.NCKD_JOBSTATUSSCORE, QCP.is_notnull, null).and(new QFilter(PositionStructureConstant.NCKD_JOBSTATUSSCORE, QCP.large_than, 0));
+        QFilter filer = new QFilter(String.join(".", FormConstant.NCKD_PERSON, FormConstant.ID_KEY), QCP.equals, personId)
+                .and(new QFilter(PositionStructureConstant.NCKD_DISABLE, QCP.equals, EnableEnum.NO.getCode()))
+                .and(new QFilter(PositionStructureConstant.NCKD_FIRSTRANK,QCP.equals, EnableEnum.YES.getCode()).or(new QFilter(PositionStructureConstant.NCKD_TYPESTATE,QCP.in, new String[]{"3","4"})))
+                .and(rankScore.or(jobScore));
+        StringJoiner selectFields = new StringJoiner(",")
+                .add(PositionStructureConstant.NCKD_EXECUTEYEAR)
+                .add(PositionStructureConstant.NCKD_FIRSTRANK)
+                .add(PositionStructureConstant.NCKD_ADJUSTTYPE)
+                .add(String.join(".", PositionStructureConstant.NCKD_JOBLEVELHR, FormConstant.JOBLEVELSEQ));
+
+        int year = DateUtil.getYear(date);
+
+        try(DataSet dataSet = QueryServiceHelper.queryDataSet(JobLevelCalculatorService.class.getName()+"#useAppraisalResult", PositionStructureConstant.PERSONPOSFILE_ENTITYID,
+                selectFields.toString(), new QFilter[]{filer}, "nckd_executeyear desc,nckd_begindate desc")){
+
+            // 条件1:如果调整年与初定年相同,则考核结果被使用;返回true
+            if(dataSet
+                    .filter(PositionStructureConstant.NCKD_FIRSTRANK + " = true and " + PositionStructureConstant.NCKD_EXECUTEYEAR + "=" + year)
+                    .count(PositionStructureConstant.NCKD_EXECUTEYEAR, false) == 1){
+                return Boolean.TRUE;
+            }
+
+            //条件2:如果调整年没有任何调整记录,则考核结果未被使用。(Personpositionfiletypestate:3、4);返回false
+            int nowyeardo  = dataSet.copy()
+                    .filter(PositionStructureConstant.NCKD_EXECUTEYEAR + "=" + year)
+                    .count(PositionStructureConstant.NCKD_EXECUTEYEAR, false);
+            if(nowyeardo == 0){
+                return Boolean.FALSE;
+            }
+
+            //条件3:调整年是否有升降级或首聘(adjusttype:0、2),如果调整年有升降级记录(调整类型为0-降级或2-升级),则考核结果被使用。;返回true
+            if(dataSet.copy()
+                    .filter(PositionStructureConstant.NCKD_EXECUTEYEAR + "=" + year +"and "+PositionStructureConstant.NCKD_ADJUSTTYPE + "in ('0','2')")
+                    .count(PositionStructureConstant.NCKD_EXECUTEYEAR, false) > 0){
+                return Boolean.TRUE;
+            }
+
+            //条件4:最近一笔是否为首次聘任(adjusttype:3),如果最近一次调整是首次聘任(调整类型为3),则考核结果未被使用。;返回false
+            DataSet firstRow = dataSet.copy().topBy(1, new String[]{PositionStructureConstant.NCKD_EXECUTEYEAR + " desc", PositionStructureConstant.NCKD_BEGINDATE + " desc"});
+            if (firstRow.hasNext()) {
+                Row row = firstRow.next();
+                String adjustType = row.getString(PositionStructureConstant.NCKD_ADJUSTTYPE);
+                if("3".equalsIgnoreCase(adjustType)){
+                    return Boolean.FALSE;
+                }
+            }
+
+            //条件5:调整年是否只有保级/序列变化等记录,如果调整年只有保级、序列变化、聘任下调、总分不足或无聘任的记录,则考核结果未被使用(adjusttype in ('1','4','5','6','7'));返回false
+            int count5 = dataSet.copy()
+                    .filter(PositionStructureConstant.NCKD_EXECUTEYEAR + "=" + year +"and "+PositionStructureConstant.NCKD_ADJUSTTYPE + "in ('1','4','5','6','7')")
+                    .count(PositionStructureConstant.NCKD_EXECUTEYEAR, false);
+            if(count5 > 0 && count5 == nowyeardo){
+                return Boolean.FALSE;
+            }
+
+
+            //条件6:调整年聘任职级是否变化,如果调整年职级有变化(最大职级-最小职级),则考核结果被使用。返回true
+            try(DataSet finish = dataSet.copy()
+                    .select("max(" + String.join(".", PositionStructureConstant.NCKD_JOBLEVELHR, FormConstant.JOBLEVELSEQ) + ") - min(" + String.join(".", PositionStructureConstant.NCKD_JOBLEVELHR, FormConstant.JOBLEVELSEQ) + ") as differ")
+                    .filter(PositionStructureConstant.NCKD_EXECUTEYEAR + "=" + year)
+                    .groupBy(new String[]{PositionStructureConstant.NCKD_EXECUTEYEAR})
+                    .finish()) {
+                if (finish.hasNext()) {
+                    Row row = finish.next();
+                    Integer differ = row.getInteger("differ");
+                    if (differ != 0) {
+                        return Boolean.TRUE;
+                    }
+                }
+            }
+
+            //条件7:相比往年聘任职级是否有变化,如果调整年相比往年聘任职级有变化,则考核结果被使用。返回true
+            //获取当年最大职级
+            DataSet currentYear = dataSet.copy()
+                    .orderBy(new String[]{PositionStructureConstant.NCKD_EXECUTEYEAR + " desc", PositionStructureConstant.NCKD_BEGINDATE + " desc"})
+                    .filter(PositionStructureConstant.NCKD_EXECUTEYEAR + "=" + year)
+                    .groupBy(new String[]{PositionStructureConstant.NCKD_EXECUTEYEAR}).max(String.join(".", PositionStructureConstant.NCKD_JOBLEVELHR, FormConstant.JOBLEVELSEQ), "maxLevel").finish();
+
+            Integer maxLevel = 0;
+            if (currentYear.hasNext()) {
+                Row row = currentYear.next();
+                maxLevel = row.getInteger("maxLevel");
+            }
+            //获取往年职级
+            DataSet pastYear = dataSet.copy()
+                    .select(String.join(".", PositionStructureConstant.NCKD_JOBLEVELHR, FormConstant.JOBLEVELSEQ) +"as level")
+                    .filter(PositionStructureConstant.NCKD_EXECUTEYEAR + "<" + year)
+                    .topBy(1, new String[]{PositionStructureConstant.NCKD_EXECUTEYEAR + " desc", PositionStructureConstant.NCKD_BEGINDATE + " desc"});
+            Integer level = 0;
+            if (pastYear.hasNext()) {
+                Row row = pastYear.next();
+                level = row.getInteger("level");
+            }
+            if(maxLevel - level != 0){
+                return Boolean.TRUE;
+            }
+
+        }
+        return Boolean.FALSE;
+    }
+
+    /**
+     * 是否首次聘任
+     * @param personId 人员ID
+     * @return: boolean
+     * @author W.Y.C
+     * @date: 2025/09/20 18:20
+     */
+    private static boolean isFirstPR(long personId) {
+        QFilter rankScore = new QFilter(PositionStructureConstant.NCKD_RANKSCORE, QCP.is_notnull, null).and(new QFilter(PositionStructureConstant.NCKD_RANKSCORE, QCP.large_than, 0));
+        QFilter jobScore = new QFilter(PositionStructureConstant.NCKD_JOBSTATUSSCORE, QCP.is_notnull, null).and(new QFilter(PositionStructureConstant.NCKD_JOBSTATUSSCORE, QCP.large_than, 0));
+        QFilter filer = new QFilter(String.join(".", FormConstant.NCKD_PERSON, FormConstant.ID_KEY), QCP.equals, personId)
+                .and(new QFilter(PositionStructureConstant.NCKD_DISABLE, QCP.equals, EnableEnum.NO.getCode()))
+                .and(rankScore.or(jobScore))
+                .and(new QFilter(String.join(".", PositionStructureConstant.NCKD_APPRAISALRESULT,FormConstant.NUMBER_KEY), QCP.not_equals, AppraisalResultEnum.NONE.getCode()));
+        StringJoiner selectFields = new StringJoiner(",")
+                .add(PositionStructureConstant.NCKD_FIRSTRANK)
+                .add(String.join(PositionStructureConstant.NCKD_APPRAISALRESULT,FormConstant.ID_KEY))
+                .add(String.join(PositionStructureConstant.NCKD_APPRAISALRESULT,FormConstant.NUMBER_KEY))
+                .add(String.join(PositionStructureConstant.NCKD_APPRAISALRESULT,FormConstant.NAME_KEY));
+        //对应SHR:PRpersonpositionfileCollection
+        DynamicObjectCollection query = QueryServiceHelper.query(PositionStructureConstant.PERSONPOSFILE_ENTITYID, selectFields.toString(), new QFilter[]{filer}, "nckd_executeyear desc,nckd_begindate desc");
+        //如果没有符合条件的记录,或者只有一条记录且为初定且考核结果为空或为考核结果为“无”,为首次聘任
+        if(query.isEmpty()){
+            return Boolean.TRUE;
+        }else if(query.size() == 1){
+            DynamicObject dynamicObject = query.get(0);
+            boolean firstRank = dynamicObject.getBoolean(PositionStructureConstant.NCKD_FIRSTRANK);
+            long appraisalResult = dynamicObject.getLong(String.join(PositionStructureConstant.NCKD_APPRAISALRESULT, FormConstant.ID_KEY));
+            String appraisalResultNumber = dynamicObject.getString(String.join(PositionStructureConstant.NCKD_APPRAISALRESULT, FormConstant.NUMBER_KEY));
+            //考核结果为空或为考核结果为“无”
+            boolean none = StringUtils.isBlank(appraisalResultNumber)  || AppraisalResultEnum.NONE.getCode().equals(appraisalResultNumber);
+            return firstRank && none;
+
+        }
+        return Boolean.FALSE;
+    }
+
+    /**
+     * 检查是否为新员工初定且时间在第二年内
+     * @param beginYear 日期
+     * @param personId 人员
+     * @return: boolean
+     * @author W.Y.C
+     * @date: 2025/09/20 19:04
+     */
+    private static boolean isNewPersonFirstRank(Date beginYear, long personId) {
+        DynamicObject firstRankDynamicObject = PositionStructureHelper.getFirstRank(personId);
+        if(firstRankDynamicObject == null){
+            throw new ValidationException(StrFormatter.format("未获取到人员【{}】首次初定档案", personId));
+        }
+        String typeState = firstRankDynamicObject.getString(PositionStructureConstant.NCKD_TYPESTATE);
+        boolean firstRank = firstRankDynamicObject.getBoolean(PositionStructureConstant.NCKD_FIRSTRANK);
+        int executeYear = firstRankDynamicObject.getInt(PositionStructureConstant.NCKD_EXECUTEYEAR);
+        int year = DateUtil.getYear(beginYear);
+        if(firstRank && typeState.equals("1")){
+            return Boolean.TRUE;
+        }
+        //新员工初定次年调整也允许没有考核结果
+        if(year - 1 == executeYear){
+            return Boolean.TRUE;
+        }
+        return Boolean.FALSE;
+    }
+
+
+    /**
+     * 计算考核结果对职级的影响
+     * @param appraisalResultNumber 考核结果
+     * @param firstPR 是否首次聘任
+     * @param usedAppraisalResult 是否使用考核结果
+     * @param jobLevelResult 职级计算结果
+     * @return: int
+     * @author W.Y.C
+     * @date: 2025/09/20 21:16
+     */
+    private static int calcMinusPersonAppraisal(String appraisalResultNumber, boolean firstPR,boolean usedAppraisalResult,JobLevelResult jobLevelResult) {
+        int minuspersonappraisal = 0;
+        if (!usedAppraisalResult || firstPR) {
+            if (AppraisalResultEnum.EXCELLENT.getCode().equals(appraisalResultNumber)) {
+                // 优秀 -> 升一级(动态调整阶段允许升)
+                minuspersonappraisal = -1;
+                jobLevelResult.adjustType = "2";
+            } else if (AppraisalResultEnum.BASICALLY_QUALIFIED.getCode().equals(appraisalResultNumber)) {
+                // 基本合格 -> 降一级(但动态调整阶段一般保级)
+                minuspersonappraisal = 1;
+                jobLevelResult.adjustType = "1";
+            } else if (AppraisalResultEnum.UN_QUALIFIED.getCode().equals(appraisalResultNumber)) {
+                // 不合格 -> 降两级(但动态调整阶段一般保级)
+                minuspersonappraisal = 2;
+                jobLevelResult.adjustType = "1";
+            }
+        }
+        System.out.println("考核结果未被使用,所以根据考核结果确定升降" + minuspersonappraisal);
+        return minuspersonappraisal;
+    }
+
+
+
+    /**
+     * 处理学历积分
+     * 根据当前最新学历与上一次职位档案中的学历对比,计算学历积分
+     * 如果学历未变化,则使用原有积分;如果学历有变化,则根据积分差值调整总积分
+     * @param currentPersonPosFileByPerson 最近一次职位档案
+     * @param positionAppointment 人员最信息(最新学历)
+     * @return: int
+     * @author W.Y.C
+     * @date: 2025/09/20 22:06
+     */
+    public static BigDecimal handleDiplomaScore(DynamicObject currentPersonPosFileByPerson, DynamicObject positionAppointment) {
+
+        //获取上一笔职位档案变动记录学历信息
+        //对应SHR:lastDiplomaInfo
+        DynamicObject diploma = currentPersonPosFileByPerson.getDynamicObject(PositionStructureConstant.NCKD_DIPLOMA);
+        //对应SHR:lastDiplomaInfo.getId()
+        long diplomaId = diploma.getLong(FormConstant.ID_KEY);
+        //对应SHR:lastDiplomaInfo.getName()
+        String diplomaName = diploma.getString(FormConstant.NAME_KEY);
+        //对应SHR:lastDiplomaInfo.getDescription()
+        BigDecimal diplomaScore = diploma.getBigDecimal(FormConstant.NCKD_SCORE);
+        //获取上一笔职位档案变动记录学历积分
+        //对应SHR:lastDiplomaScore
+        BigDecimal lastDiplomaScore = currentPersonPosFileByPerson.getBigDecimal(PositionStructureConstant.NCKD_DIPLOMASCORE);
+
+
+        //对应SHR:Diploma
+        Long currentDiplomaId = positionAppointment.getLong(String.join(".", FormConstant.HRPI_PEREDUEXP, FormConstant.EDUCATION_KEY, FormConstant.ID_KEY));
+        //对应SHR:newDiplomaInfo.getName()
+        String currentDiplomaName = positionAppointment.getString(String.join(".", FormConstant.HRPI_PEREDUEXP, FormConstant.EDUCATION_KEY, FormConstant.NAME_KEY));
+        //对应SHR:DiplomaScore
+        //人员最新学历的积分
+        BigDecimal currentDiplomaScore = positionAppointment.getBigDecimal(String.join(".", FormConstant.HRPI_PEREDUEXP, FormConstant.EDUCATION_KEY, FormConstant.NAME_KEY));
+        //对应SHR:DiplomaScore
+        BigDecimal resultScore = currentDiplomaScore;
+
+        logger.info("原学历:ID【{}】,名称【{}】,积分【{}】;最新学历:ID【{}】,名称【{}】,积分【{}】", diplomaId,diplomaName,diplomaScore,currentDiplomaId,currentDiplomaName,currentDiplomaScore);
+        if (currentDiplomaId.equals(diplomaId)) {
+            //学历不变,分数保持不变
+            resultScore = lastDiplomaScore;
+            logger.info("");
+        } else {
+            // 分差 = 最新学历配置分 - 上一笔学历的最新配置分
+            // 本次学历分 = 上一笔学历分 + 分差
+            BigDecimal diff = resultScore.subtract(diplomaScore);
+            resultScore = lastDiplomaScore.add(diff);
+        }
+        return resultScore;
+    }
+
+    /**
+     * 处理序列相关信息
+     */
+    private static JobFamilyInfo processJobFamilyInfo(DynamicObject newJobSeq,
+                                               DynamicObject currentPersonPosFileByPerson) {
+        JobFamilyInfo jobFamilyInfo = new JobFamilyInfo();
+
+        long newJonSeqId = newJobSeq.getLong(FormConstant.ID_KEY);
+        String newJonSeqNumber = newJobSeq.getString(FormConstant.NUMBER_KEY);
+        // 获取原序列信息
+        DynamicObject currentJobSeq = currentPersonPosFileByPerson.getDynamicObject(PositionStructureConstant.NCKD_JOBSEQHR);
+        //如果是管理序列,则按职能序列进行调整
+        //对应SHR:986~993行
+        currentJobSeq = handleJobSeq(currentJobSeq);
+        long currentJonSeqId = currentJobSeq.getLong(FormConstant.ID_KEY);
+        String currentJonSeqNumber = currentJobSeq.getString(FormConstant.NUMBER_KEY);
+
+
+        // 判断是否为序列转换
+        //对应SHR:isadjusttype_4
+        jobFamilyInfo.isSequenceChange = !currentJonSeqNumber.equals(newJonSeqNumber);
+
+        // 判断是否为跨单位调动
+        jobFamilyInfo.isCrossUnitTransfer = false;
+        // TODO【待修改】 这里需要根据具体业务逻辑判断是否为跨单位调动
+
+        return jobFamilyInfo;
+    }
+
+
+    /**
+     * 处理员工职称和技能等级分数
+     * 根据职位序列类型(技能序列/非技能序列)确定采用职称等级分还是技能等级分
+     * @param newJobSeq 最新职位序列
+     * @param currentPersonPosFileByPerson 最近一次职位档案
+     * @param positionAppointment 人员最信息(最新学历)
+     * @return: nckd.jxccl.hr.psms.business.JobLevelCalculatorService.JobScoreInfo
+     * @author W.Y.C
+     * @date: 2025/09/22 09:39
+     */
+    public static JobScoreInfo handleJobScores(DynamicObject newJobSeq,
+                                         DynamicObject currentPersonPosFileByPerson,DynamicObject positionAppointment) {
+        //对应SHR:hrjobfamilynumber
+        String newJobSeqNumber = newJobSeq.getString(FormConstant.NUMBER_KEY);
+
+
+        //当前人员最新职称等级
+        Long newPerProTitleId = positionAppointment.getLong(String.join(".", FormConstant.HRPI_PERPROTITLE, FormConstant.PROLEVEL_KEY, FormConstant.ID_KEY));
+        //对应SHR:selMap.get("zgjbscore")
+        BigDecimal newPerProTitleScore = positionAppointment.getBigDecimal(String.join(".", FormConstant.HRPI_PERPROTITLE, FormConstant.PROLEVEL_KEY, FormConstant.NCKD_SCORE));
+        //对应SHR:selMap.get("zgjbnumber")
+        String newPerProTitleNumber = positionAppointment.getString(String.join(".", FormConstant.HRPI_PERPROTITLE, FormConstant.PROLEVEL_KEY, FormConstant.NUMBER_KEY));
+
+        //当前人员最新技能等级
+        Long newQuaLevelId = positionAppointment.getLong(String.join(".", FormConstant.HRPI_PEROCPQUAL, FormConstant.QUALEVEL_KEY, FormConstant.ID_KEY));
+        //对应SHR:selMap.get("zyjndjscore")
+        BigDecimal newQuaLevelScore = positionAppointment.getBigDecimal(String.join(".", FormConstant.HRPI_PEROCPQUAL, FormConstant.QUALEVEL_KEY, FormConstant.NCKD_SCORE));
+        //对应SHR:selMap.get("zyjndjnumber")
+        String newQuaLevelNumber = positionAppointment.getString(String.join(".", FormConstant.HRPI_PEROCPQUAL, FormConstant.QUALEVEL_KEY, FormConstant.NUMBER_KEY));
+
+
+        JobScoreInfo jobScoreInfo = new JobScoreInfo();
+        jobScoreInfo.isEndGainJobGrade = true;
+
+        // 职称等级分
+        jobScoreInfo.perProTitleScore = BigDecimal.ZERO;
+        // 技能等级分
+        jobScoreInfo.quaLevelScore = BigDecimal.ZERO;
+        jobScoreInfo.perProTitleNumber = "";
+        jobScoreInfo.quaLevelNumber = "";
+
+        if (!JobSeqEnum.SKILL.getCode().equals(newJobSeqNumber) && (newPerProTitleScore != null && newPerProTitleScore.compareTo(BigDecimal.ZERO) > 0)) {
+            // 获取当前最新职称等级分
+            jobScoreInfo.perProTitleScore = newPerProTitleScore;
+            jobScoreInfo.perProTitleId = newPerProTitleId;
+            jobScoreInfo.perProTitleNumber = StringUtils.isNotBlank(newPerProTitleNumber) ? newPerProTitleNumber : "";
+            jobScoreInfo.isEndGainJobGrade = false;
+        } else if (JobSeqEnum.SKILL.getCode().equals(newJobSeqNumber)) {
+            if (newQuaLevelScore != null && newQuaLevelScore.compareTo(BigDecimal.ZERO) > 0) {
+                //获取当前最新技能等级分
+                jobScoreInfo.quaLevelScore = newQuaLevelScore;
+                jobScoreInfo.quaLevelId = newQuaLevelId;
+                jobScoreInfo.quaLevelNumber = StringUtils.isNotBlank(newQuaLevelNumber) ? newQuaLevelNumber: "";
+                jobScoreInfo.isEndGainJobGrade = false;
+            }
+        }
+        System.out.println("职称等级分:::" + jobScoreInfo.perProTitleScore);
+        System.out.println("技能等级分:::" + jobScoreInfo.quaLevelScore);
+
+        return jobScoreInfo;
+    }
+
+    /**
+     * 计算职级ID
+     */
+    private static DynamicObject calculateJobGradeId(DynamicObject newJobSeq, JobScoreInfo jobScoreInfo,JobFamilyInfo jobFamilyInfo,
+                                                          BigDecimal allSumScore, int currentJobLevelIndex, int minusPersonAppraisal, boolean fistPR, JobLevelResult jobLevelResult){
+
+        DynamicObject proTitleLevel = BusinessDataServiceHelper.loadSingle(jobScoreInfo.perProTitleId, FormConstant.HBSS_PROTITLELEVEL);
+        DynamicObject ocpQualLevel = BusinessDataServiceHelper.loadSingle(jobScoreInfo.quaLevelId, FormConstant.HBSS_OCPQUALLEVEL);
+        JobSeqEnum jobSeqEnum = JobSeqEnum.getByCode(newJobSeq.getString(FormConstant.NUMBER_KEY));
+        DynamicObject jobLevel = null;
+        if (jobScoreInfo.isEndGainJobGrade) {
+            System.out.println("缺少聘任,技能序列必需聘任技能等级,其它序列必需聘任职称等级!");
+            //对应SHR:JobGradeid = personpositionfileUtils.endGainJobGrade(ctx, allsumScore, hrjobfamilynumber);
+            jobLevel = getLowestJobLevel(newJobSeq);
+            //对应SHR:selMap.put("adjusttype", "7");
+            jobLevelResult.adjustType = "7";
+        } else if (jobFamilyInfo.isSequenceChange) {
+            //对应SHR:JobGradeid = personpositionfileUtils.GainJobGrade(ctx, allsumScore, hrjobfamilynumber, zgjbnumber, zyjndjnumber, 0);
+            jobLevel = PositionStructureHelper.calculateLevel(jobSeqEnum, allSumScore, proTitleLevel, ocpQualLevel, null, 0, Boolean.FALSE,Boolean.FALSE);
+            //序列转换
+            jobLevelResult.adjustType = "4";
+        } else if (jobFamilyInfo.isCrossUnitTransfer) {
+            //对应SHR:JobGradeid = personpositionfileUtils.GainJobGrade(ctx, allsumScore, hrjobfamilynumber, zgjbnumber, zyjndjnumber, 0);
+            jobLevel = PositionStructureHelper.calculateLevel(jobSeqEnum, allSumScore, proTitleLevel, ocpQualLevel, null, 0, Boolean.FALSE,Boolean.FALSE);
+            //保级
+            jobLevelResult.adjustType = "1";
+        } else if (fistPR) {
+            //对应SHR:JobGradeid = personpositionfileUtils.GainJobGrade(ctx, allsumScore, hrjobfamilynumber, zgjbnumber, zyjndjnumber, minuspersonappraisal);
+            jobLevel = PositionStructureHelper.calculateLevel(jobSeqEnum, allSumScore, proTitleLevel, ocpQualLevel, null, minusPersonAppraisal, Boolean.FALSE,Boolean.FALSE);
+        } else {
+            //对应SHR:JobGradeid = personpositionfileUtils.GainJobGrade(ctx, allsumScore, hrjobfamilynumber, zgjbnumber, zyjndjnumber, minuspersonappraisal < 0 ? minuspersonappraisal : 0);
+            jobLevel = PositionStructureHelper.calculateLevel(jobSeqEnum, allSumScore, proTitleLevel, ocpQualLevel, null, minusPersonAppraisal < 0 ? minusPersonAppraisal : 0, Boolean.FALSE,Boolean.FALSE);
+        }
+        int newJobGradeIndex = jobLevel.getInt(FormConstant.JOBLEVELSEQ);
+        System.out.println("newjobgradeindex:::" + newJobGradeIndex);
+
+        if (minusPersonAppraisal == -1 && currentJobLevelIndex == newJobGradeIndex) {
+            System.out.println("总分不足");
+            jobLevelResult.adjustType = "6";
+        }
+
+        return jobLevel;
+    }
+
+    /**
+     * 获取排名信息
+     * @param personId 人员ID
+     * @param personName 人员名称
+     * @param date 日期
+     * @return: nckd.jxccl.hr.psms.business.JobLevelCalculatorService.RankingResult
+     * @author W.Y.C
+     * @date: 2025/09/22 14:19
+     */
+    public static RankingResult getRankingInfo(Long personId, String personName,Date date) {
+        LocalDateTime lastYearDateTime = DateUtil.minusYears(DateUtil.toLocalDateTime(date), 1);
+        //取上年度绩效排名
+        QFilter groupFilter = new QFilter(PerfRankMgmtConstant.NCKD_THEYEAR,QCP.equals,lastYearDateTime.getYear());
+        groupFilter.and(PerfRankMgmtConstant.BILL_STATUS_KEY,QCP.equals, StatusEnum.C.toString());
+        groupFilter.and(String.join(".",PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY,PerfRankMgmtConstant.NCKD_PERSON),QCP.equals, personId);
+        groupFilter.and(String.join(".",PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY,PerfRankMgmtConstant.NCKD_ISRANKING),QCP.equals,EnableEnum.YES.getCode());
+        //获取该员工上年度所在的排名分组
+        DynamicObject[] groupArray = BusinessDataServiceHelper.load(PerfRankMgmtConstant.NCKD_PERFRANKMGMT_ENTITYID, FormConstant.ID_KEY+","+FormConstant.NAME_KEY,
+                new QFilter[]{groupFilter},
+                FormConstant.CREATE_TIME_KEY + " desc " + FormConstant.AUDIT_DATE_KEY + " desc " + FormConstant.MODIFY_TIME_KEY + " desc");
+        //避免一个员工存在多个排名分组中,只取最新排名分组
+        Long groupId = null;
+        String groupName = null;
+        if(groupArray != null && groupArray.length > 0){
+            groupId = groupArray[0].getLong(FormConstant.ID_KEY);
+            groupName = groupArray[0].getString(FormConstant.NAME_KEY);
+        }
+        if(groupId != null && groupId > 0){
+            QFilter perfRankMgmtFilter = new QFilter(FormConstant.ID_KEY,QCP.equals,groupId);
+            StringJoiner selectFields = new StringJoiner(",")
+                    .add(FormConstant.ID_KEY)
+                    .add(String.join(".",PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY,PerfRankMgmtConstant.NCKD_PERSON,FormConstant.ID_KEY))
+                    .add(String.join(".",PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY,PerfRankMgmtConstant.NCKD_TOPRANK))
+                    .add(String.join(".",PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY,PerfRankMgmtConstant.NCKD_POSTALLOWANCE))
+                    .add(String.join(".",PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY,PerfRankMgmtConstant.NCKD_ALLOWANCERANK))
+                    .add(String.join(".",PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY,PerfRankMgmtConstant.NCKD_APPRAISALRESULT))
+                    .add(String.join(".",PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY,PerfRankMgmtConstant.NCKD_ISRANKING));
+            //对应SHR:allowancerankpercentSQL()方法
+            try(DataSet dataSet = QueryServiceHelper.queryDataSet(JobLevelCalculatorService.class.getName() + "#processRankingInfo", PerfRankMgmtConstant.NCKD_PERFRANKMGMT_ENTITYID,
+                    selectFields.toString(), new QFilter[]{perfRankMgmtFilter}, PerfRankMgmtConstant.NCKD_TOPRANK + "," + PerfRankMgmtConstant.NCKD_ALLOWANCERANK)){
+                //获取R排名总人数
+                //对应SHR:countallowancerank
+                int countR = dataSet.filter(String.join(".",PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY,PerfRankMgmtConstant.NCKD_ISRANKING) +" = true and "+String.join(".",PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY,PerfRankMgmtConstant.NCKD_POSTALLOWANCE)+" = true")
+                        .count(FormConstant.ID_KEY, false);
+                //获取总排名总人数
+                int count = dataSet.filter(String.join(".",PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY,PerfRankMgmtConstant.NCKD_ISRANKING) +" = true")
+                        .count(FormConstant.ID_KEY, false);
+                //获取排名
+                DataSet topRankDataSet = dataSet.select(String.join(".", PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY, PerfRankMgmtConstant.NCKD_TOPRANK))
+                        .filter(String.join(".", PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY, PerfRankMgmtConstant.NCKD_PERSON, FormConstant.ID_KEY) + " = " + personId);
+                //对应SHR:topRank
+                Integer topRank = null;
+                if(topRankDataSet.hasNext()){
+                    Row row = topRankDataSet.next();
+                    topRank = row.getInteger(String.join(".", PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY, PerfRankMgmtConstant.NCKD_TOPRANK));
+                }
+
+                //获取R排名
+                DataSet allowanceRankDataSet = dataSet.select(String.join(".", PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY, PerfRankMgmtConstant.NCKD_ALLOWANCERANK))
+                        .filter(String.join(".", PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY, PerfRankMgmtConstant.NCKD_PERSON, FormConstant.ID_KEY) + " = " + personId);
+                //对应SHR:allowancerank
+                Integer allowanceRank = null;
+                if(allowanceRankDataSet.hasNext()){
+                    Row row = allowanceRankDataSet.next();
+                    allowanceRank = row.getInteger(String.join(".", PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY, PerfRankMgmtConstant.NCKD_ALLOWANCERANK));
+                }
+                //计算公式:R排名百分比 = (个人R排名 / 分组总人数) × 100%
+                //对应SHR:toprankpercent
+                double topRankPercent = (double) topRank / count * 100;
+                if (topRankPercent < 0 || topRankPercent > 100) {
+                    throw new ValidationException(StrFormatter.format("全排名百分比必须在0-100之间,请检查排名分组配置是否有误。人员【{}】-排名分组【{}】-R排名总人数【{}】-排名【{}】",personName,groupName,count,topRank));
+                }
+                //对应SHR:allowancerankpercent
+                double allowanceRankPercent = (double) allowanceRank / countR * 100;
+                if (allowanceRankPercent < 0 || allowanceRankPercent > 100) {
+                    throw new ValidationException(StrFormatter.format("R排名百分比必须在0-100之间,请检查排名分组配置是否有误。人员【{}】-排名分组【{}】-R排名总人数【{}】-排名【{}】",personName,groupName,countR,allowanceRank));
+                }
+
+                RankingResult rankingResult = new RankingResult();
+                //获取R排名最后名次
+                DataSet maxAllowanceRank = dataSet.filter(String.join(".", PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY, PerfRankMgmtConstant.NCKD_ISRANKING) + " = true and " + String.join(".", PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY, PerfRankMgmtConstant.NCKD_POSTALLOWANCE) + " = true")
+                        .groupBy()
+                        .max(String.join(".", PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY, PerfRankMgmtConstant.NCKD_ALLOWANCERANK), "maxAllowanceRank").finish();
+                if(maxAllowanceRank.hasNext()){
+                    Row row = maxAllowanceRank.next();
+                    rankingResult.maxAllowanceRank = row.getInteger("maxAllowanceRank");
+                }
+                rankingResult.topRank = topRank;
+                rankingResult.allowanceRank = allowanceRank;
+                rankingResult.count = count > 0 ? count : null;
+                rankingResult.countR = countR > 0 ? countR : null;
+                rankingResult.topRankPercent = topRankPercent;
+                rankingResult.allowanceRankPercent = allowanceRankPercent;
+                rankingResult.groupName = groupName;
+                return rankingResult;
+            }
+        }else{
+            logger.warn("员工不存在排名分组");
+        }
+        return null;
+    }
+
+    /**
+     * 处理序列转换
+     * 对应SHR:PV.getnewjobgradeindexByrank
+     */
+    private static void handleSequenceChange(int jobLevelIndex,int currentJobLevelIndex,RankingResult rankingResult,Map<Integer, DynamicObject> jobLevelMap,JobLevelResult jobLevelResult,DynamicObject jobSeq){
+        System.out.println("序列切换,新职级号不能大于原序列职级序号");
+        if (jobLevelIndex > currentJobLevelIndex) {
+            jobLevelIndex = currentJobLevelIndex;
+        }
+        jobLevelResult.jobLevel = jobLevelMap.get(jobLevelIndex);
+        // 序列转换
+        jobLevelResult.adjustType = "4";
+        getnewjobgradeindexByrank(jobLevelIndex,currentJobLevelIndex,rankingResult,jobSeq);
+    }
+
+    /**
+     * 处理跨单位调动
+     */
+    private static void handleCrossUnitTransfer(int jobLevelIndex,int currentJobLevelIndex,RankingResult rankingResult,Map<Integer, DynamicObject> jobLevelMap,JobLevelResult jobLevelResult,DynamicObject jobSeq){
+        System.out.println("跨单位调动且未跨序列,平移处理");
+        if (jobLevelIndex > currentJobLevelIndex) {
+            jobLevelIndex = currentJobLevelIndex;
+        }
+        jobLevelResult.jobLevel = jobLevelMap.get(jobLevelIndex);
+        // 保级
+        jobLevelResult.adjustType = "1";
+        getnewjobgradeindexByrank(jobLevelIndex,currentJobLevelIndex,rankingResult,jobSeq);
+    }
+
+    /**
+     * 对应SHR:com.kingdee.shr.customer.web.handler.ContributeScore.PersonpositionfilecreateViewListHandler#getnewjobgradeindexByrank
+     * @param jobLevelIndex
+     * @param currentJobLevelIndex
+     * @param rankingResult
+     * @param jobSeq
+     * @return: int
+     * @author W.Y.C
+     * @date: 2025/09/22 19:47
+     */
+    public static int getnewjobgradeindexByrank(int jobLevelIndex,int currentJobLevelIndex,RankingResult rankingResult,DynamicObject jobSeq){
+
+        JobSeqEnum jobSeqEnum = JobSeqEnum.getByCode(jobSeq.getString(FormConstant.NUMBER_KEY));
+        if(rankingResult == null || rankingResult.countR == null || rankingResult.allowanceRank == null){
+            rankingResult.allowanceRankMark = "无";
+            rankingResult.allowanceRankSel = "无";
+            return jobLevelIndex;
+        }
+        rankingResult.allowanceRankMark = rankingResult.allowanceRank +"/" +rankingResult.countR;
+        Integer allowanceRank = rankingResult.allowanceRank;
+        Integer countR = rankingResult.countR;
+        Integer maxAllowanceRank = rankingResult.maxAllowanceRank;
+        if(rankingResult.countR < 10 && maxAllowanceRank.equals(rankingResult.allowanceRank) && rankingResult.topRankPercent >= 70){
+            jobLevelIndex--;
+        }else{
+            //技术或职能序列
+            if (JobSeqEnum.TECHNICALS.getCode().equals(jobSeqEnum.getCode()) || JobSeqEnum.FUNCTIONAL.getCode().equals(jobSeqEnum.getCode())) {
+
+                if (currentJobLevelIndex > 9) {
+                    // 职级>9:前60%升职,后5%降职
+                    if (allowanceRank <= SelMAXallowancerank(60,countR)) {
+                        // selMap.put("allowanceranksel", "前60%");
+                        jobLevelIndex++;
+                    }
+                    else if (allowanceRank >= SelMinallowancerank(5,countR) && SelMinallowancerank(5,countR) > 0) {
+                        // selMap.put("allowanceranksel", "后5%");
+                        jobLevelIndex--;
+                    }
+                } else if (currentJobLevelIndex > 5 && currentJobLevelIndex <= 9) {
+                    // 职级5-9:前70%升职,后5%降职
+                    if (allowanceRank <= SelMAXallowancerank(70,countR)) {
+                        // selMap.put("allowanceranksel", "前70%");
+                        jobLevelIndex++;
+                    }
+                    else if (allowanceRank >= SelMinallowancerank(5,countR) && SelMinallowancerank(5,countR) > 0) {
+                        // selMap.put("allowanceranksel", "后5%");
+                        jobLevelIndex--;
+                    }
+                }else if (currentJobLevelIndex <= 5) {
+                    // 职级≤5:前80%升职,后3%降职
+                    if (allowanceRank <= SelMAXallowancerank(80,countR)) {
+                        // selMap.put("allowanceranksel", "前80%");
+                        jobLevelIndex++;
+                    }
+                    else if (allowanceRank >= SelMinallowancerank(3,countR) && SelMinallowancerank(3,countR) > 0) {
+                        // selMap.put("allowanceranksel", "后3%");
+                        jobLevelIndex--;
+                    }
+                }
+            }else if(JobSeqEnum.SKILL.getCode().equals(jobSeqEnum.getCode())){
+                // 技能序列:
+                if (currentJobLevelIndex > 8) {
+                    //职级>8:前60%升职,后5%降职
+                    if (allowanceRank <= SelMAXallowancerank(60,countR)) {
+                        // selMap.put("allowanceranksel", "前60%");
+                        jobLevelIndex++;
+                    }
+                    else if (allowanceRank >= SelMinallowancerank(5,countR) && SelMinallowancerank(5,countR) > 0) {
+                        // selMap.put("allowanceranksel", "后5%");
+                        jobLevelIndex--;
+                    }
+                }else if (currentJobLevelIndex > 4 && currentJobLevelIndex <= 8) {
+                    // 职级4-8:前70%升职,后5%降职
+                    if (allowanceRank <= SelMAXallowancerank(70,countR)) {
+                        // selMap.put("allowanceranksel", "前70%");
+                        jobLevelIndex++;
+                    }
+                    else if (allowanceRank >= SelMinallowancerank(5,countR) && SelMinallowancerank(5,countR) > 0) {
+                        // selMap.put("allowanceranksel", "后5%");
+                        jobLevelIndex--;
+                    }
+                } else if (currentJobLevelIndex <= 4) {
+                    // 职级≤4:前80%升职,后3%降职
+                    if (allowanceRank <= SelMAXallowancerank(80,countR)) {
+                        // selMap.put("allowanceranksel", "前80%");
+                        jobLevelIndex++;
+                    }
+                    else if (allowanceRank >= SelMinallowancerank(3,countR) && SelMinallowancerank(3,countR) > 0) {
+                        // selMap.put("allowanceranksel", "后3%");
+                        jobLevelIndex--;
+                    }
+                }
+            }
+        }
+
+        if (currentJobLevelIndex > 1 && jobLevelIndex - currentJobLevelIndex > 1) {
+            jobLevelIndex = currentJobLevelIndex + 1;
+        }
+
+        if (allowanceRank <= SelMAXallowancerank(60, countR)) {
+            rankingResult.allowanceRankSel = "前60%";
+        }
+        else if (allowanceRank <= SelMAXallowancerank(70, countR)) {
+            rankingResult.allowanceRankSel = "前60-70%";
+        }
+        else if (allowanceRank <= SelMAXallowancerank(80, countR)) {
+            rankingResult.allowanceRankSel = "前70-80%";
+        }
+        else if (allowanceRank <= SelMAXallowancerank(95, countR)) {
+            rankingResult.allowanceRankSel = "前80-95%";
+        }
+        else if (allowanceRank >= SelMinallowancerank(3, countR)) {
+            rankingResult.allowanceRankSel = "后3%";
+        }
+        else if (allowanceRank >= SelMinallowancerank(5, countR)) {
+            rankingResult.allowanceRankSel = "后5%";
+        }
+        return jobLevelIndex;
+    }
+
+    public static int SelMAXallowancerank(int percent, int countallowancerank) {
+        int MAXallowancerank = 0;
+        double MAXallowancerankDouble = Double.parseDouble(countallowancerank + "") / 100.0 * Double.parseDouble(percent + "");
+        MAXallowancerank = (int) Math.round(MAXallowancerankDouble);
+        return MAXallowancerank;
+    }
+
+
+    public static int SelMinallowancerank(int percent, int countallowancerank) {
+        int Minallowancerank = 0;
+        double MinallowancerankDouble = Double.parseDouble(countallowancerank + "") / 100.0 * Double.parseDouble(percent + "");
+        Minallowancerank = countallowancerank - ((int) Math.round(MinallowancerankDouble));
+        return Minallowancerank;
+    }
+
+
+
+    /**
+     * 获取最近一次聘任的职级索引
+     */
+    private static int getLastAppointmentJobGradeIndex(Long personId, int currentJobLevelIndex,int jobLevelIndex){
+
+
+        QFilter rankScore = new QFilter(PositionStructureConstant.NCKD_RANKSCORE, QCP.is_notnull, null).and(new QFilter(PositionStructureConstant.NCKD_RANKSCORE, QCP.large_than, 0));
+        QFilter jobScore = new QFilter(PositionStructureConstant.NCKD_JOBSTATUSSCORE, QCP.is_notnull, null).and(new QFilter(PositionStructureConstant.NCKD_JOBSTATUSSCORE, QCP.large_than, 0));
+        QFilter filer = new QFilter(String.join(".", FormConstant.NCKD_PERSON, FormConstant.ID_KEY), QCP.equals, personId)
+                .and(new QFilter(PositionStructureConstant.NCKD_DISABLE, QCP.equals, EnableEnum.NO.getCode()))
+                .and(rankScore.or(jobScore))
+                .and(new QFilter(String.join(".", PositionStructureConstant.NCKD_APPRAISALRESULT,FormConstant.NUMBER_KEY), QCP.not_equals, AppraisalResultEnum.NONE.getCode()));
+        StringJoiner selectFields = new StringJoiner(",")
+                .add(PositionStructureConstant.NCKD_FIRSTRANK)
+                .add(String.join(PositionStructureConstant.NCKD_APPRAISALRESULT,FormConstant.ID_KEY))
+                .add(String.join(PositionStructureConstant.NCKD_APPRAISALRESULT,FormConstant.NUMBER_KEY))
+                .add(String.join(PositionStructureConstant.NCKD_APPRAISALRESULT,FormConstant.NAME_KEY))
+
+                .add(String.join(".",PositionStructureConstant.NCKD_JOBLEVELHR,FormConstant.ID_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_JOBLEVELHR,FormConstant.NAME_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_JOBLEVELHR,FormConstant.NUMBER_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_JOBLEVELHR,FormConstant.JOBLEVELSEQ));
+        //对应SHR:PRpersonpositionfileCollection
+        DynamicObjectCollection query = QueryServiceHelper.query(PositionStructureConstant.PERSONPOSFILE_ENTITYID, selectFields.toString(), new QFilter[]{filer}, "nckd_executeyear desc,nckd_begindate desc");
+        if(!query.isEmpty()){
+            DynamicObject personPosFile = query.get(0);
+            int PR_lastjobgradeindex = personPosFile.getInt(String.join(".", PositionStructureConstant.NCKD_JOBLEVELHR, FormConstant.JOBLEVELSEQ));
+            return PR_lastjobgradeindex;
+        }
+        return currentJobLevelIndex;
+    }
+
+    /**
+     * 根据考核结果使用情况处理职级计算
+     */
+    private static int handleAppraisalUsage(DynamicObject jobSeq, RankingResult rankingResult, int jobLevelIndex,
+                                     int PR_lastjobgradeindex, int currentJobLevelIndex, int minuspersonappraisal, boolean fistPR, boolean usedAppraisalResult, JobLevelResult jobLevelResult){
+        JobSeqEnum jobSeqEnum = JobSeqEnum.getByCode(jobSeq.getString(FormConstant.NUMBER_KEY));
+        if (!usedAppraisalResult) {
+            if (minuspersonappraisal == 0) {
+                if (!fistPR) {
+                    // 考核结果为保级 所以相 等
+                    jobLevelIndex = PR_lastjobgradeindex;
+                }
+                if (fistPR && ((rankingResult.countR == null || rankingResult.countR == 0) || (rankingResult.allowanceRank == null || rankingResult.allowanceRank == 0))) {
+                    System.out.println("首次聘任而且无R排名,保持分数算出来的职级");
+                } else {
+                    System.out.println("应用R排名结果");
+                    jobLevelIndex = getnewjobgradeindexByrank(jobLevelIndex, currentJobLevelIndex, rankingResult, jobSeq);
+                }
+                System.out.println("newjobgradeindex:::" + jobLevelIndex);
+                System.out.println("PR_lastjobgradeindex:::" + PR_lastjobgradeindex);
+                if (currentJobLevelIndex > 1 && jobLevelIndex - PR_lastjobgradeindex == 1) {
+                    // R排名升级
+                    jobLevelResult.adjustType = "2";
+                    System.out.println("R排名升级");
+                } else if (jobLevelIndex == PR_lastjobgradeindex) {
+                    // 保级
+                    jobLevelResult.adjustType = "1";
+                    System.out.println("R排名保级");
+                }
+            } else {
+                getnewjobgradeindexByrank(jobLevelIndex, currentJobLevelIndex, rankingResult, jobSeq);
+            }
+        } else {
+            getnewjobgradeindexByrank(jobLevelIndex, currentJobLevelIndex, rankingResult, jobSeq);
+            // 保级
+            jobLevelResult.adjustType = "1";
+            System.out.println("保级");
+        }
+        return jobLevelIndex;
+    }
+
+    /**
+     * 处理聘任相关限制
+     */
+    private static int handleAppointmentRestrictions(long personId, boolean firstPR,boolean usedAppraisalResult,
+                                               DynamicObject jobSeq,long jobSeqId,long currentJobSeqId,String currentJobSeqNumber, JobScoreInfo jobScoreInfo,
+                                               int currentJobLevelIndex,int jobLevelIndex, BigDecimal allsumScore, JobLevelResult jobLevelResult,int minusPersonAppraisal,DynamicObject currentPersonPosFile) {
+        /*PersonpositionfileCollection PRpersonpositionfileCollection = PersonpositionfileFactory.getLocalInstance(ctx)
+                .getPersonpositionfileCollection("select *,Appraisalresult.*,JobGrade.* where person='" + personid +
+                        "' and (disable is null or disable<>1) and ((jobstatusScore is not null and jobstatusScore >0) or (RankScore is not null and RankScore >0)) " +
+                        "and Appraisalresult.number<>'06' order by beginDate desc, createTime desc");*/
+        QFilter rankScore = new QFilter(PositionStructureConstant.NCKD_RANKSCORE, QCP.is_notnull, null).and(new QFilter(PositionStructureConstant.NCKD_RANKSCORE, QCP.large_than, 0));
+        QFilter jobScore = new QFilter(PositionStructureConstant.NCKD_JOBSTATUSSCORE, QCP.is_notnull, null).and(new QFilter(PositionStructureConstant.NCKD_JOBSTATUSSCORE, QCP.large_than, 0));
+        QFilter filer = new QFilter(String.join(".", FormConstant.NCKD_PERSON, FormConstant.ID_KEY), QCP.equals, personId)
+                .and(new QFilter(PositionStructureConstant.NCKD_DISABLE, QCP.equals, EnableEnum.NO.getCode()))
+                .and(rankScore.or(jobScore))
+                .and(new QFilter(String.join(".", PositionStructureConstant.NCKD_APPRAISALRESULT,FormConstant.NUMBER_KEY), QCP.not_equals, AppraisalResultEnum.NONE.getCode()));
+        StringJoiner selectFields = new StringJoiner(",")
+                .add(PositionStructureConstant.NCKD_FIRSTRANK)
+                .add(String.join(PositionStructureConstant.NCKD_APPRAISALRESULT,FormConstant.ID_KEY))
+                .add(String.join(PositionStructureConstant.NCKD_APPRAISALRESULT,FormConstant.NUMBER_KEY))
+                .add(String.join(PositionStructureConstant.NCKD_APPRAISALRESULT,FormConstant.NAME_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_JOBLEVELHR,FormConstant.ID_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_JOBLEVELHR,FormConstant.NAME_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_JOBLEVELHR,FormConstant.NUMBER_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_JOBLEVELHR,FormConstant.JOBLEVELSEQ));
+        //对应SHR:PRpersonpositionfileCollection
+        DynamicObjectCollection query = QueryServiceHelper.query(PositionStructureConstant.PERSONPOSFILE_ENTITYID, selectFields.toString(), new QFilter[]{filer}, "nckd_executeyear desc,nckd_begindate desc");
+        if (firstPR) {
+            System.out.println("前一笔为初定且考核结果为无,按首次聘任处理,所以放开限制");
+            // 首次聘任
+            jobLevelResult.adjustType = "3";
+        }else if (!query.isEmpty()){
+            long hrjobfamilyid = jobSeqId;
+            long oldHRJobFamilyid = currentJobSeqId;
+            String hrjobfamilynumber = jobSeq.getString(FormConstant.NUMBER_KEY);
+            String oldHRJobFamilynumber = currentJobSeqNumber;
+            BigDecimal currentRankScore = currentPersonPosFile.getBigDecimal(PositionStructureConstant.NCKD_RANKSCORE);
+            BigDecimal currentJobStatusScore = currentPersonPosFile.getBigDecimal(PositionStructureConstant.NCKD_JOBSTATUSSCORE);
+            if (!(currentRankScore.compareTo(BigDecimal.ZERO) > 0 || currentJobStatusScore.compareTo(BigDecimal.ZERO) > 0)) {
+                System.out.println("上条记录未聘任,取最近一条聘任");
+               /* PersonpositionfileInfo prpersonpositionfileInfo = PRpersonpositionfileCollection.get(0);
+                CoreBaseInfo JobGrade = (CoreBaseInfo) prpersonpositionfileInfo.get("JobGrade");*/
+                //对应SHR:prpersonpositionfileInfo
+                DynamicObject personPositionFile = query.get(0);
+                //对应SHR:JobGrade.getInt("index")
+                int index = personPositionFile.getInt(String.join(".", PositionStructureConstant.NCKD_JOBLEVELHR, FormConstant.JOBLEVELSEQ));
+                currentJobLevelIndex = index;
+                // 2021-09-15 补充判断 如果之前有过年度调整,考核结果是否已被应用
+                if (!usedAppraisalResult) {
+                    //对应SHR:appraisalresultnumber
+                    String appraisalResultNumber = personPositionFile.getString(String.join(PositionStructureConstant.NCKD_APPRAISALRESULT,FormConstant.NUMBER_KEY));
+                    if (AppraisalResultEnum.BASICALLY_QUALIFIED.getCode().equals(appraisalResultNumber)) {
+                        currentJobLevelIndex -= 1;
+                        // 降级
+                        jobLevelResult.adjustType = "0";
+                    } else if (AppraisalResultEnum.UN_QUALIFIED.getCode().equals(appraisalResultNumber)) {
+                        currentJobLevelIndex -= 2;
+                        // 降级
+                        jobLevelResult.adjustType = "0";
+                    }
+                }
+            }else{
+                System.out.println("上条记录已聘任");
+            }
+
+            //这里为什么用ID判断又用number判断。因为ID至始至终都没有变过(如管理序列会被转换为职能序列),number可能会被转换。
+            // 换了序列
+            if (hrjobfamilyid != oldHRJobFamilyid) {
+                System.out.println("检查到有序列切换");
+                //对应SHR:if (hrjobfamilynumber.equals(oldHRJobFamilynumber)) {;1214行
+                if (hrjobfamilynumber.equals(oldHRJobFamilynumber)) {
+                    System.out.println("管理序列与职能序列之间切换,不做序列切换限制---pass");
+                    if (!usedAppraisalResult && jobLevelIndex >= currentJobLevelIndex + 1) {
+                        // 升级
+                        jobLevelResult.adjustType = "2";
+                        jobLevelIndex = currentJobLevelIndex + 1;
+                    }
+                    else {
+                        if (StringUtils.isNotBlank(jobLevelResult.adjustType) && "6".equals(jobLevelResult.adjustType)) {
+                            System.out.println("前面判断需要升级但总分未达到");
+                        }
+                        else {
+                            // 保级
+                            jobLevelResult.adjustType = "1";
+                        }
+                        jobLevelIndex = currentJobLevelIndex;
+                    }
+                }
+                else {
+                    // 序列转换
+                    jobLevelResult.adjustType = "4";
+                    System.out.println("序列切换,新职级号不能大于原序列职级序号");
+                    if (jobLevelIndex > currentJobLevelIndex) {
+                        jobLevelIndex = currentJobLevelIndex;
+                    }
+                }
+            } else {
+                if (usedAppraisalResult) {
+                    System.out.println("非年度首次调整,且考核结果做过升限级,所以以原职级做为限定范围");
+                    // 保级
+                    jobLevelResult.adjustType = "1";
+                    jobLevelIndex = currentJobLevelIndex;
+                } else {
+                    if (minusPersonAppraisal > 0 && jobLevelIndex >= currentJobLevelIndex) {
+                        System.out.println("考核结果未使用且为降级,动态调整时按保级处理,在年度调整处理");
+                        // 保级
+                        jobLevelResult.adjustType = "1";
+                        jobLevelIndex = currentJobLevelIndex;
+                    }
+                    else if (jobLevelIndex - currentJobLevelIndex == 1) {
+                        // 升级
+                        jobLevelResult.adjustType = "2";
+                    }
+                }
+            }
+        }else{
+            // 首次聘任
+            jobLevelResult.adjustType = "3";
+            System.out.println("首次聘任,所以放开限制");
+        }
+
+        /**
+         * 确定是否超出总分最高任命职级
+         */
+        //对应SHR:String personMaxjobgradeindexstr = personpositionfileUtils.MaxGainJobGrade(ctx, allsumScore, jobFamilyInfo.processedJobFamilyNumber);
+        DynamicObject maxJobLevel = getMaxJobLevel(jobSeq, allsumScore);
+        //对应SHR:personMaxjobgradeindex
+        int maxJobLevelIndex = maxJobLevel.getInt(String.join(".",FormConstant.HBJM_JOBLEVELHR, FormConstant.JOBLEVELSEQ));
+        // 超出总分能任命最高职职级则该最高职级就是要任命的职级
+        if (jobLevelIndex > maxJobLevelIndex) {
+            // 总分不足
+            jobLevelResult.adjustType = "6";
+            jobLevelIndex = maxJobLevelIndex;
+            if (currentJobLevelIndex > 1 && jobLevelIndex - currentJobLevelIndex == 1) {
+                // R排名升级
+                jobLevelResult.adjustType = "2";
+            }
+        }
+
+        /**
+         * 确定是否超出该职称等级或技能等级最高任命职级
+         */
+        /*String Maxjobgradeindexstr = personpositionfileUtils.getMaxJobLevel(ctx, jobFamilyInfo.processedJobFamilyNumber,
+                jobScoreInfo.zgjbnumber, jobScoreInfo.zyjndjnumber);*/
+        DynamicObject maxJobLevel1 = getMaxJobLevel(jobSeq, jobScoreInfo.perProTitleNumber, jobScoreInfo.quaLevelNumber);
+        int Maxjobgradeindex = maxJobLevel1.getInt(String.join(".",FormConstant.HBJM_JOBLEVELHR, FormConstant.JOBLEVELSEQ));
+        // 职级超出职称等级或技能等级能任命最高职职级则该最高职级就是要任命的职级
+        if (jobLevelIndex > Maxjobgradeindex) {
+            if (StringUtils.isBlank(jobLevelResult.adjustType)) {
+                // 聘任下调
+                jobLevelResult.adjustType = "5";
+            }
+            jobLevelIndex = Maxjobgradeindex;
+            if (currentJobLevelIndex > 1 && jobLevelIndex - currentJobLevelIndex == 1) {
+                // R排名升级
+                jobLevelResult.adjustType = "2";
+            }
+        }
+
+        // 检查聘任分是否减少
+        //对应SHR:personpositionfileInfo.getInt("jobstatusScore")
+        BigDecimal currentJobStatusScore = currentPersonPosFile.getBigDecimal(PositionStructureConstant.NCKD_JOBSTATUSSCORE);
+        //对应SHR:personpositionfileInfo.getInt("RankScore")
+        BigDecimal currentRankScore = currentPersonPosFile.getBigDecimal(PositionStructureConstant.NCKD_RANKSCORE);
+        BigDecimal currentSum = currentJobStatusScore.add(currentRankScore);
+        BigDecimal newSum = jobScoreInfo.perProTitleScore.add(jobScoreInfo.quaLevelScore);
+        System.out.println("上一 笔职称等级分+技能等级分::" + currentSum);
+        System.out.println("当前职称等级分+技能等级分::" + (jobScoreInfo.perProTitleScore.add(jobScoreInfo.quaLevelScore)));
+        if (currentSum.compareTo(newSum) > 0) {
+            //聘任下调
+            jobLevelResult.adjustType = "5";
+        }
+        //currentJobLevelIndex有变更,但是后续没有使用所以就不返回了
+        return jobLevelIndex;
+    }
+
+
+    /**
+     * 最终确定职级ID
+     */
+    private static DynamicObject determineFinalJobGradeId(int jobLevelIndex,
+                                            JobScoreInfo jobScoreInfo, Map<Integer, DynamicObject> jobLevelMap,
+                                            DynamicObject jobSeq, JobLevelResult jobLevelResult,
+                                            String appraisalResultNumber){
+        DynamicObject jobLevel = null;
+
+        if (jobScoreInfo.isEndGainJobGrade) {
+            System.out.println("缺少聘任,技能序列必需聘任技能等级,其它序列必需聘任职称等级!");
+            jobLevel = getLowestJobLevel(jobSeq);
+            jobLevelResult.adjustType = "7";
+        } else if (jobLevelMap.get(jobLevelIndex) != null) {
+            jobLevel = jobLevelMap.get(jobLevelIndex);
+            BigDecimal newSum = jobScoreInfo.perProTitleScore.add(jobScoreInfo.quaLevelScore);
+            if (newSum.compareTo(BigDecimal.ZERO) <= 0 && jobLevelIndex == 1) {
+                jobLevelResult.adjustType = "7";
+            }
+        } else {
+            jobLevel = getLowestJobLevel(jobSeq);
+            jobLevelResult.adjustType = "7";
+        }
+
+        if (StringUtils.isBlank(appraisalResultNumber) || AppraisalResultEnum.NONE.getCode().equals(appraisalResultNumber)) {
+            jobLevel = getLowestJobLevel(jobSeq);
+            jobLevelResult.adjustType = "8";
+        }
+        jobLevelResult.jobLevel = jobLevel;
+        return jobLevel;
+    }
+
+
+    // 辅助类定义
+    private static class JobFamilyInfo {
+        /** 是否为序列转换 */
+        //对应SHR:isadjusttype_4
+        boolean isSequenceChange;
+        /** 是否为跨单位调动*/
+        //对应SHR:isadjusttype_1
+        boolean isCrossUnitTransfer;
+    }
+
+    public static class JobScoreInfo {
+        /** 职称等级分 */
+        public BigDecimal perProTitleScore;
+        /** 技能等级分 */
+        public BigDecimal quaLevelScore;
+        /** 职称级别编码 */
+        Long perProTitleId;
+        /** 技能等级编码 */
+        Long quaLevelId;
+        /** 职称级别编码 */
+        String perProTitleNumber;
+        /** 技能等级编码 */
+        String quaLevelNumber;
+        /** 是否缺少聘任 */
+        boolean isEndGainJobGrade;
+    }
+
+    public static class RankingResult {
+        /** 排名 */
+        Integer topRank;
+        /** R排名 */
+        //对应SHR:allowancerank
+        public Integer allowanceRank;
+        /** 最后名次 */
+        //对应SHR:maxallowancerank
+        public Integer maxAllowanceRank;
+        /** 全排名百分比 */
+        Double topRankPercent;
+        /** R排名百分比 */
+        Double allowanceRankPercent;
+        /** 全排名人数 */
+        Integer count;
+        /** R排名人数 */
+        //对应SHR:countallowancerank
+        public Integer countR;
+        //排名分组名称
+        /** R排名人数 */
+        String groupName;
+        /** R排名名次/R排名总人数 */
+        //对应SHR:allowancerankmark
+        String allowanceRankMark;
+        /** R排名百分比 */
+        //对应SHR:allowanceranksel
+        String allowanceRankSel;
+
+    }
+
+    public static class JobLevelResult {
+        public String adjustType;
+        public String adjustMsg;
+        public DynamicObject jobLevel;
+    }
+}
+
+
+

+ 47 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/common/PerfRankMgmtConstant.java

@@ -0,0 +1,47 @@
+package nckd.jxccl.hr.psms.common;
+
+import nckd.jxccl.base.common.constant.FormConstant;
+
+/**
+ * 年度绩效排名管理常量类
+ *
+ * @author W.Y.C
+ * @version 1.0
+ * @date 2025-09-22 10:08:16
+ */
+public class PerfRankMgmtConstant extends FormConstant {
+    /** 年度绩效排名管理-实体标识 */
+    public static final String NCKD_PERFRANKMGMT_ENTITYID = "nckd_perfrankmgmt";
+    /** 单据体 */
+    public static final String NCKD_PERFRANKMGMTENTRY = "NCKD_PERFRANKMGMTENTRY";
+    /** 员工 */
+    public static final String NCKD_PERSON = "NCKD_PERSON";
+    /** 排名 */
+    public static final String NCKD_TOPRANK = "NCKD_TOPRANK";
+    /** 是否享受职位津贴 */
+    public static final String NCKD_POSTALLOWANCE = "NCKD_POSTALLOWANCE";
+    /** 绩效排名R */
+    public static final String NCKD_ALLOWANCERANK = "NCKD_ALLOWANCERANK";
+    /** 考核结果 */
+    public static final String NCKD_APPRAISALRESULT = "NCKD_APPRAISALRESULT";
+    /** 是否参与排名 */
+    public static final String NCKD_ISRANKING = "NCKD_ISRANKING";
+    /** 不参与排名原因 */
+    public static final String NCKD_EXCLUDERANKREASON = "NCKD_EXCLUDERANKREASON";
+    /** 排名来源 */
+    public static final String NCKD_RANKSOURCE = "NCKD_RANKSOURCE";
+    /** 组名 */
+    public static final String NCKD_GROUPNAME = "NCKD_GROUPNAME";
+    /** 年度 */
+    public static final String NCKD_THEYEAR = "NCKD_THEYEAR";
+    /** 参加排名总人数 */
+    public static final String NCKD_TOPRANKS = "NCKD_TOPRANKS";
+    /** 参加R排名人数 */
+    public static final String NCKD_ALLOWANCERANKS = "NCKD_ALLOWANCERANKS";
+    /** 不合格人数 */
+    public static final String NCKD_FAILS = "NCKD_FAILS";
+    /** 基本合格人数 */
+    public static final String NCKD_BASICS = "NCKD_BASICS";
+    /** 优秀人数 */
+    public static final String NCKD_EXCELLENTS = "NCKD_EXCELLENTS";
+}

+ 176 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/common/PositionStructureConstant.java

@@ -0,0 +1,176 @@
+package nckd.jxccl.hr.psms.common;
+
+import nckd.jxccl.base.common.constant.FormConstant;
+
+/**
+ * 职位体系常量
+ * @author W.Y.C
+ * @date 2025/9/10 16:09
+ * @version 1.0
+ */
+public class PositionStructureConstant extends FormConstant {
+    /*-------------------------------------- 员工职位档案 begin --------------------------------------*/
+    /** 员工职位档案-实体标识 */
+    public static final String PERSONPOSFILE_ENTITYID = "nckd_personposfile";
+    /** 开始日期 */
+    public static final String NCKD_BEGINDATE = "NCKD_BEGINDATE";
+    /** 所属序列 */
+    public static final String NCKD_JOBSEQHR = "NCKD_JOBSEQHR";
+    /** 职级 */
+    public static final String NCKD_JOBLEVELHR = "NCKD_JOBLEVELHR";
+    /** 是否初次定级 */
+    public static final String NCKD_FIRSTRANK = "NCKD_FIRSTRANK";
+    /** 学历 */
+    public static final String NCKD_DIPLOMA = "NCKD_DIPLOMA";
+    /** 岗位 */
+    public static final String NCKD_POSITIONHR = "NCKD_POSITIONHR";
+    /** 职称名称 */
+    public static final String NCKD_RANKNAME = "NCKD_RANKNAME";
+    /** 职称级别 */
+    public static final String NCKD_PROTITLELEVEL = "NCKD_PROTITLELEVEL";
+    /** 技能名称 */
+    public static final String NCKD_JOBSTATUSNAME = "NCKD_JOBSTATUSNAME";
+    /** 技能等级 */
+    public static final String NCKD_OCPQUALLEVEL = "NCKD_OCPQUALLEVEL";
+    /** 上年度绩效考核结果 */
+    public static final String NCKD_APPRAISALRESULT = "NCKD_APPRAISALRESULT";
+    /** 上年度员工职位档案 */
+    public static final String NCKD_LASTPERSONPOSFILE = "NCKD_LASTPERSONPOSFILE";
+    /** 聘任状态 */
+    public static final String NCKD_EMPLOYMENTSTATUS = "NCKD_EMPLOYMENTSTATUS";
+    /** 类型状态(新入职人员初定1;在职人员初定2;年度调整3;职位调动4;管理序列的聘任5;高级职称的聘任6) */
+    public static final String NCKD_TYPESTATE = "NCKD_TYPESTATE";
+    /** 连续聘任年限 */
+    public static final String NCKD_EMPLOYMENTYEARS = "NCKD_EMPLOYMENTYEARS";
+    /** 上年度绩效考核分数 */
+    public static final String NCKD_RESULTSCORE = "NCKD_RESULTSCORE";
+    /** 优秀生分 */
+    public static final String NCKD_ORGINSSCORE = "NCKD_ORGINSSCORE";
+    /** 职称分 */
+    public static final String NCKD_RANKSCORE = "NCKD_RANKSCORE";
+    /** 技能分 */
+    public static final String NCKD_JOBSTATUSSCORE = "NCKD_JOBSTATUSSCORE";
+    /** 学历分 */
+    public static final String NCKD_DIPLOMASCORE = "NCKD_DIPLOMASCORE";
+    /** 是否失效 */
+    public static final String NCKD_DISABLE = "NCKD_DISABLE";
+    /** 上年度贡献综合评价分 */
+    public static final String NCKD_LYRCONTRIBSCORE = "NCKD_LYRCONTRIBSCORE";
+    /** 任命结束日期 */
+    public static final String NCKD_ENDDATE = "NCKD_ENDDATE";
+    /** 任命状态 */
+    public static final String NCKD_APPOINTSTATUS = "NCKD_APPOINTSTATUS";
+    /** 年度 */
+    public static final String NCKD_EXECUTEYEAR = "NCKD_EXECUTEYEAR";
+    /** 调整类别 */
+    public static final String NCKD_ADJUSTTYPE = "NCKD_ADJUSTTYPE";
+    /** 年度调整状态 */
+    public static final String NCKD_ADJUSSTATUS = "NCKD_ADJUSSTATUS";
+    /** 上年度绩效排名R百分比 */
+    public static final String NCKD_ALLOWANCERANKPCT = "NCKD_ALLOWANCERANKPCT";
+    /** 年度科研与创新分 */
+    public static final String NCKD_YEARSCORESUMA = "NCKD_YEARSCORESUMA";
+    /** 年度专利申报分 */
+    public static final String NCKD_YEARSCORESUMB = "NCKD_YEARSCORESUMB";
+    /** 年度论文发表分 */
+    public static final String NCKD_YEARSCORESUMC = "NCKD_YEARSCORESUMC";
+    /** 年度技能竞赛分 */
+    public static final String NCKD_YEARSCORESUMD = "NCKD_YEARSCORESUMD";
+    /** 年度培训教材分 */
+    public static final String NCKD_YEARSCORESUME = "NCKD_YEARSCORESUME";
+    /** 年度技术标准分 */
+    public static final String NCKD_YEARSCORESUMF = "NCKD_YEARSCORESUMF";
+    /** 年度管理规范分 */
+    public static final String NCKD_YEARSCORESUMG = "NCKD_YEARSCORESUMG";
+    /** 年度师带徒分 */
+    public static final String NCKD_YEARSCORESUMH = "NCKD_YEARSCORESUMH";
+    /** 年度培训授课分 */
+    public static final String NCKD_YEARSCORESUMI = "NCKD_YEARSCORESUMI";
+    /** 贡献单据分数之和 */
+    public static final String NCKD_ALLYEARSCORESUM = "NCKD_ALLYEARSCORESUM";
+    /** 累计总积分 */
+    public static final String NCKD_ALLSUMSCORE = "NCKD_ALLSUMSCORE";
+    /** 累计的积分池 */
+    public static final String NCKD_SUMSCORE = "NCKD_SUMSCORE";
+    /** 年度新增的贡献积分 */
+    public static final String NCKD_ADDYCONTRIBSCORE = "NCKD_ADDYCONTRIBSCORE";
+    /** 升降级数 */
+    public static final String NCKD_ADJUSTINT = "NCKD_ADJUSTINT";
+    /** 上年度绩效排名R名次 */
+    public static final String NCKD_ALLOWANCERANK = "NCKD_ALLOWANCERANK";
+    /** 上年度排名名次 */
+    public static final String NCKD_TOPRANK = "NCKD_TOPRANK";
+    /** 上年度排名百分比 */
+    public static final String NCKD_TOPRANKPERCENT = "NCKD_TOPRANKPERCENT";
+    /** 备注 */
+    public static final String KEY_NCKD_CAUSEREMARK = "NCKD_CAUSEREMARK";
+    /** 学历分计算过程 */
+    public static final String NCKD_WHYDIPLOMASCORE = "NCKD_WHYDIPLOMASCORE";
+    /** R排名名次/R排名总人数 */
+    public static final String NCKD_ALLOWANCERANKMARK = "NCKD_ALLOWANCERANKMARK";
+    /** R排名百分比 */
+    public static final String NCKD_ALLOWANCERANKSEL = "NCKD_ALLOWANCERANKSEL";
+    /** 是否当前最新记录 */
+    public static final String NCKD_ISCURRENTNEWEST = "NCKD_ISCURRENTNEWEST";
+    /** 查询人员任职信息及聘任信息*/
+    public static final String POSITIONAPPOINTMENTQUERY = "positionappointmentquery";
+    /*-------------------------------------- 员工职位档案 end --------------------------------------*/
+
+
+    /*-------------------------------------- 职位及积分初定 begin --------------------------------------*/
+    /** 职位及积分初定-实体标识 */
+    public static final String SCOREINITIAL_ENTITYID = "nckd_scoreinitial";
+    /** 页签控件 */
+    public static final String NCKD_TABAP = "NCKD_TABAP";
+    /** 页签控件-已定级人员 */
+    public static final String NCKD_GRADEDPERSON = "NCKD_GRADEDPERSON";
+    /** 页签控件-未定级人员 */
+    public static final String NCKD_UNGRADEDPERSON = "NCKD_UNGRADEDPERSON";
+    /*-------------------------------------- 职位及积分初定 end --------------------------------------*/
+
+
+    /*-------------------------------------- 未定级人员列表 begin --------------------------------------*/
+    /** 未定级人员-实体标识 */
+    public static final String UNGRADEDPERSONQUERY_ENTITYID = "ungradedpersonquery";
+    /** 在职人员初定-操作 */
+    public static final String OP_SETTINGJOBGRADE = "settingjobgrade";
+    /** 新入职人员初定-操作 */
+    public static final String OP_SETTINGJOBGRADENEW = "settingjobgradenew";
+    /*-------------------------------------- 未定级人员列表 end --------------------------------------*/
+
+    /*-------------------------------------- 已定级人员列表 begin --------------------------------------*/
+    /** 职位及积分初定-实体标识 */
+    public static final String GRADEDPERSONQUERY_ENTITYID = "gradedpersonquery";
+    /*-------------------------------------- 已定级人员列表 end --------------------------------------*/
+
+
+    /*-------------------------------------- 在职人员初定(弹窗) begin --------------------------------------*/
+    /** 在职人员初定(弹窗)-实体标识 */
+    public static final String SERVINGINITIAL_ENTITYID = "nckd_servinginitial";
+    /** 职位序列(根据岗位需要自动带出) */
+    public static final String NCKD_JOBSEQ = "nckd_jobseq";
+    /** 优秀生 */
+    public static final String NCKD_EXCELLENT = "NCKD_EXCELLENT";
+    /** 任职经历 */
+    public static final String NCKD_EMPPOSORGREL = "NCKD_EMPPOSORGREL";
+    /*-------------------------------------- 在职人员初定(弹窗) end --------------------------------------*/
+
+
+    /*-------------------------------------- 新入职人员初定(弹窗) begin --------------------------------------*/
+    /** 新入职人员初定(弹窗)-实体标识 */
+    public static final String NEWHIREINITIAL_ENTITYID = "nckd_newhireinitial";
+    /** 本次加入集团日期 */
+    public static final String NCKD_JOINCOMDATE = "nckd_joincomdate";
+    /** 确认定级 */
+    public static final String OP_CONFIRMINITIAL = "confirmInitial";
+    /*-------------------------------------- 新入职人员初定(弹窗) end --------------------------------------*/
+
+    /*-------------------------------------- 新建动态调整(弹窗) begin --------------------------------------*/
+    /** 新建动态调整(弹窗)-实体标识 */
+    public static final String NEWDYNAMICADJUDIALOG_ENTITYID = "nckd_newdynamicadjudialog";
+    /** 本次加入集团日期 */
+    public static final String NCKD_ADJUSTDATE = "nckd_adjustdate";
+    /*-------------------------------------- 新建动态调整(弹窗) end --------------------------------------*/
+    /**职位序列对应职级查询*/
+    public static final String JOBSEQTOJOBLEVELQUERY = "jobseqtojoblevelquery";
+}

+ 338 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/helper/PositionStructureHelper.java

@@ -0,0 +1,338 @@
+package nckd.jxccl.hr.psms.helper;
+
+import kd.bos.common.enums.EnableEnum;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.servicehelper.QueryServiceHelper;
+import nckd.jxccl.base.common.constant.FormConstant;
+import nckd.jxccl.base.common.enums.JobSeqEnum;
+import nckd.jxccl.hr.psms.common.PositionStructureConstant;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.StringJoiner;
+
+/**
+ * 职位体系帮助类
+ *
+ * @author W.Y.C
+ * @version 1.0
+ * @date 2025/9/13 14:07
+ */
+public class PositionStructureHelper {
+
+    /**
+     * 计算职级
+     * 对应SHR:com.kingdee.shr.customer.web.handler.PersonpositionfileUtils#GainJobGrade 和 com.kingdee.shr.customer.web.handler.PersonpositionfileUtils#endGainJobGrade
+     * TODO [待补充完成]
+     * @param jobSeq 职位序列
+     * @param score 积分数
+     * @param proTitleLevel 职称等级
+     * @param ocpQualLevel 技能等级
+     * @param appraisalResult 绩效结果
+     * @param downgradeNum 降级数(根据绩效计算:基本合格:降1级、不合格:降2级)
+     * @param useMinLevel 是否强制按最低职级定级
+     * @param useNonLevel 是否强制按无职级定级(当为true时不满足三要素【聘任职称/技能、考核结果、积分】任意一条件时按“无职级”定级)
+     * @return: kd.bos.dataentity.entity.DynamicObject 返回:匹配的职级
+     * @author W.Y.C
+     * @date: 2025/09/13 14:21
+     */
+    public static DynamicObject calculateLevel(JobSeqEnum jobSeq, BigDecimal score, DynamicObject proTitleLevel,DynamicObject ocpQualLevel,
+                                               DynamicObject appraisalResult,int downgradeNum,boolean useMinLevel,boolean useNonLevel) {
+        //三要素说明:
+        //1、【技术序列、职能序列】:有聘任的职称等级;【技能序列】:有聘任的技能等级
+        //2、有考核结果
+        //3、积分数能够达到职级
+        if(useNonLevel) {
+            boolean nonLevel = Boolean.FALSE;
+            if ((jobSeq == JobSeqEnum.TECHNICALS || jobSeq == JobSeqEnum.FUNCTIONAL) && proTitleLevel == null) {
+                //【技术序列或职能序列】无职称等级
+                nonLevel = Boolean.TRUE;
+            } else if (jobSeq == JobSeqEnum.SKILL && ocpQualLevel == null) {
+                //【技能序列】无技能等级
+                nonLevel = Boolean.TRUE;
+            }
+            if (appraisalResult == null) {
+                //无考核结果
+                nonLevel = Boolean.TRUE;
+            }
+            if(score.compareTo(new BigDecimal(18)) < 0){
+                //积分数小于18
+                nonLevel = Boolean.TRUE;
+            }
+            //查询该序列“无职级”的数据并返回
+        }
+        DynamicObject dynamicObject = BusinessDataServiceHelper.loadSingle("2294386972933752832", "hbjm_joblevelhr");
+        return dynamicObject;
+    }
+
+
+    /**
+     * 判断是否已经初定
+     * @param personId 人员ID
+     * @return: boolean
+     * @author W.Y.C
+     * @date: 2025/09/13 15:40
+     */
+    public static boolean isInitial(Long personId) {
+        QFilter filter = new QFilter(String.join(".", FormConstant.NCKD_PERSON, FormConstant.ID_KEY), QCP.equals, personId)
+                .and(new QFilter(PositionStructureConstant.NCKD_DISABLE,QCP.equals, EnableEnum.NO.getCode()))
+                .and(new QFilter(PositionStructureConstant.NCKD_FIRSTRANK,QCP.equals,EnableEnum.YES.getCode()));
+        return QueryServiceHelper.exists(PositionStructureConstant.PERSONPOSFILE_ENTITYID, new QFilter[]{filter});
+    }
+
+    /**
+     * 根据人员和状态过滤职位档案
+     * @param personId 人员ID
+     * @param typeState 类型状态(新入职人员初定1;在职人员初定2;年度调整3;职位调动4;管理序列的聘任5;高级职称的聘任6)
+     * @param adjustType 调整类别(升级2;保级1;降级0;首次聘任3;序列变化4;聘任下调5;总分不足6;无聘任7;无考核结果8)
+     * @param otherFilter
+     * @return: kd.bos.dataentity.entity.DynamicObject[]
+     * @author W.Y.C
+     * @date: 2025/09/23 21:08
+     */
+    public static DynamicObject[] getPersonPosFileByPersonAndState(Long personId,String[] typeState,String[] adjustType,QFilter otherFilter) {
+        QFilter filer = new QFilter(String.join(".", FormConstant.NCKD_PERSON, FormConstant.ID_KEY), QCP.equals, personId)
+                .and(new QFilter(PositionStructureConstant.NCKD_DISABLE, QCP.equals, EnableEnum.NO.getCode()));
+        if(typeState != null){
+            filer.and(new QFilter(PositionStructureConstant.NCKD_TYPESTATE, QCP.in, typeState));
+        }
+        if(adjustType != null){
+            filer.and(new QFilter(PositionStructureConstant.NCKD_ADJUSSTATUS, QCP.in, adjustType));
+        }
+        if(otherFilter != null){
+            filer.and(otherFilter);
+        }
+        StringJoiner selectFields = new StringJoiner(",")
+                .add(FormConstant.ID_KEY)
+                .add(PositionStructureConstant.NCKD_BEGINDATE)
+                .add(PositionStructureConstant.NCKD_EXECUTEYEAR)
+                .add(PositionStructureConstant.NCKD_TYPESTATE)
+                .add(PositionStructureConstant.NCKD_FIRSTRANK)
+                .add(PositionStructureConstant.NCKD_DIPLOMASCORE)
+                .add(PositionStructureConstant.NCKD_RANKSCORE)
+                .add(PositionStructureConstant.NCKD_JOBSTATUSSCORE)
+                .add(String.join(".",PositionStructureConstant.NCKD_APPRAISALRESULT,FormConstant.ID_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_APPRAISALRESULT,FormConstant.NAME_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_APPRAISALRESULT,FormConstant.NUMBER_KEY))
+                .add(PositionStructureConstant.NCKD_ALLOWANCERANKPCT)
+                .add(PositionStructureConstant.NCKD_ALLOWANCERANKSEL)
+                .add(PositionStructureConstant.NCKD_ALLOWANCERANKMARK)
+                .add(PositionStructureConstant.NCKD_ALLSUMSCORE)
+                .add(PositionStructureConstant.NCKD_SUMSCORE)
+                .add(PositionStructureConstant.NCKD_JOBLEVELHR)
+                .add(PositionStructureConstant.NCKD_RANKNAME)
+                .add(String.join(".",PositionStructureConstant.NCKD_PROTITLELEVEL,FormConstant.ID_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_PROTITLELEVEL,FormConstant.NAME_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_PROTITLELEVEL,FormConstant.NUMBER_KEY))
+                .add(PositionStructureConstant.NCKD_JOBSTATUSNAME)
+                .add(String.join(".",PositionStructureConstant.NCKD_OCPQUALLEVEL,FormConstant.ID_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_OCPQUALLEVEL,FormConstant.NAME_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_OCPQUALLEVEL,FormConstant.NUMBER_KEY))
+
+                .add(String.join(".",PositionStructureConstant.NCKD_JOBLEVELHR,FormConstant.ID_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_JOBLEVELHR,FormConstant.NAME_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_JOBLEVELHR,FormConstant.NUMBER_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_JOBLEVELHR,FormConstant.JOBLEVELSEQ))
+
+                .add(String.join(".",PositionStructureConstant.NCKD_DIPLOMA,FormConstant.ID_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_DIPLOMA,FormConstant.NAME_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_DIPLOMA,FormConstant.NUMBER_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_DIPLOMA,FormConstant.NCKD_SCORE))
+
+                .add(String.join(".",PositionStructureConstant.NCKD_JOBSEQHR,FormConstant.ID_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_JOBSEQHR,FormConstant.NAME_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_JOBSEQHR,FormConstant.NUMBER_KEY));
+        return BusinessDataServiceHelper.load(PositionStructureConstant.PERSONPOSFILE_ENTITYID, selectFields.toString(), new QFilter[]{filer},PositionStructureConstant.NCKD_BEGINDATE + " desc");
+    }
+
+    /**
+     * 获取该人员最新的职位档案
+     * @param personId 人员ID
+     * @return: kd.bos.dataentity.entity.DynamicObject
+     * @author W.Y.C
+     * @date: 2025/09/20 20:09
+     */
+    public static DynamicObject getLatsPersonPosFileByPerson(Long personId) {
+        return getLatsPersonPosFileByPerson(personId,null);
+    }
+    /**
+     * 获取该人员最新的职位档案
+     * @param personId 人员ID
+     * @param otherFilter 其他条件
+     * @return: kd.bos.dataentity.entity.DynamicObject
+     * @author W.Y.C
+     * @date: 2025/09/20 20:09
+     */
+    public static DynamicObject getLatsPersonPosFileByPerson(Long personId,QFilter otherFilter) {
+        QFilter filer = new QFilter(String.join(".", FormConstant.NCKD_PERSON, FormConstant.ID_KEY), QCP.equals, personId)
+                // .and(new QFilter(PositionStructureConstant.NCKD_ISCURRENTNEWEST, QCP.equals, EnableEnum.YES.getCode()))
+                .and(new QFilter(PositionStructureConstant.NCKD_DISABLE, QCP.equals, EnableEnum.NO.getCode()))
+                // 只有状态为:【初定】、【年度调整】和【职位调动】的职位档案才是有效档案;
+                // 条件:(初定 或者 (年度调整 并且 已生效) 或者 职位调动)
+                .and(
+                        new QFilter(PositionStructureConstant.NCKD_FIRSTRANK, QCP.equals, EnableEnum.YES.getCode())
+                                .or(
+                                        new QFilter(PositionStructureConstant.NCKD_TYPESTATE, QCP.equals, "3")
+                                                .and(new QFilter(PositionStructureConstant.NCKD_ADJUSSTATUS, QCP.in, new String[]{"1", "2"})
+                                                )
+                                )
+                                .or(new QFilter(PositionStructureConstant.NCKD_TYPESTATE, QCP.equals, "4"))
+                );
+        if(otherFilter != null){
+            filer.and(otherFilter);
+        }
+        StringJoiner selectFields = new StringJoiner(",")
+                .add(FormConstant.ID_KEY)
+                .add(PositionStructureConstant.NCKD_BEGINDATE)
+                .add(PositionStructureConstant.NCKD_EXECUTEYEAR)
+                .add(PositionStructureConstant.NCKD_TYPESTATE)
+                .add(PositionStructureConstant.NCKD_FIRSTRANK)
+                .add(PositionStructureConstant.NCKD_DIPLOMASCORE)
+                .add(PositionStructureConstant.NCKD_RANKSCORE)
+                .add(PositionStructureConstant.NCKD_JOBSTATUSSCORE)
+                .add(String.join(".",PositionStructureConstant.NCKD_APPRAISALRESULT,FormConstant.ID_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_APPRAISALRESULT,FormConstant.NAME_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_APPRAISALRESULT,FormConstant.NUMBER_KEY))
+                .add(PositionStructureConstant.NCKD_ALLOWANCERANKPCT)
+                .add(PositionStructureConstant.NCKD_ALLOWANCERANKSEL)
+                .add(PositionStructureConstant.NCKD_ALLOWANCERANKMARK)
+                .add(PositionStructureConstant.NCKD_ALLSUMSCORE)
+                .add(PositionStructureConstant.NCKD_JOBLEVELHR)
+                .add(PositionStructureConstant.NCKD_RANKNAME)
+                .add(String.join(".",PositionStructureConstant.NCKD_PROTITLELEVEL,FormConstant.ID_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_PROTITLELEVEL,FormConstant.NAME_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_PROTITLELEVEL,FormConstant.NUMBER_KEY))
+                .add(PositionStructureConstant.NCKD_JOBSTATUSNAME)
+                .add(String.join(".",PositionStructureConstant.NCKD_OCPQUALLEVEL,FormConstant.ID_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_OCPQUALLEVEL,FormConstant.NAME_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_OCPQUALLEVEL,FormConstant.NUMBER_KEY))
+
+                .add(String.join(".",PositionStructureConstant.NCKD_JOBLEVELHR,FormConstant.ID_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_JOBLEVELHR,FormConstant.NAME_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_JOBLEVELHR,FormConstant.NUMBER_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_JOBLEVELHR,FormConstant.JOBLEVELSEQ))
+                //学历
+                .add(String.join(".",PositionStructureConstant.NCKD_DIPLOMA,FormConstant.ID_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_DIPLOMA,FormConstant.NAME_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_DIPLOMA,FormConstant.NUMBER_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_DIPLOMA,FormConstant.NCKD_SCORE))
+
+                .add(String.join(".",PositionStructureConstant.NCKD_JOBSEQHR,FormConstant.ID_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_JOBSEQHR,FormConstant.NAME_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_JOBSEQHR,FormConstant.NUMBER_KEY));
+        DynamicObject[] load = BusinessDataServiceHelper.load(PositionStructureConstant.PERSONPOSFILE_ENTITYID, selectFields.toString(), new QFilter[]{filer},PositionStructureConstant.NCKD_BEGINDATE + " desc");
+        return load.length > 0 ? load[0] : null;
+
+    }
+
+    /**
+     * 获取首次初定的职位档案
+     * @param personId 人员ID
+     * @return: kd.bos.dataentity.entity.DynamicObject
+     * @author W.Y.C
+     * @date: 2025/09/20 18:57
+     */
+    public static DynamicObject getFirstRank(Long personId) {
+        QFilter filer = new QFilter(String.join(".", FormConstant.NCKD_PERSON, FormConstant.ID_KEY), QCP.equals, personId)
+                .and(new QFilter(PositionStructureConstant.NCKD_DISABLE, QCP.equals, EnableEnum.NO.getCode()))
+                .and(new QFilter(PositionStructureConstant.NCKD_FIRSTRANK, QCP.equals, EnableEnum.YES.getCode()));
+        StringJoiner selectFields = new StringJoiner(",")
+                .add(FormConstant.ID_KEY)
+                .add(PositionStructureConstant.NCKD_BEGINDATE)
+                .add(PositionStructureConstant.NCKD_EXECUTEYEAR)
+                .add(PositionStructureConstant.NCKD_TYPESTATE)
+                .add(PositionStructureConstant.NCKD_FIRSTRANK)
+                .add(String.join(".",PositionStructureConstant.NCKD_APPRAISALRESULT,FormConstant.ID_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_APPRAISALRESULT,FormConstant.NAME_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_APPRAISALRESULT,FormConstant.NUMBER_KEY))
+                .add(PositionStructureConstant.NCKD_ALLOWANCERANKPCT)
+                .add(PositionStructureConstant.NCKD_ALLOWANCERANKSEL)
+                .add(PositionStructureConstant.NCKD_ALLOWANCERANKMARK)
+                .add(PositionStructureConstant.NCKD_ALLSUMSCORE)
+                .add(PositionStructureConstant.NCKD_JOBLEVELHR)
+                .add(PositionStructureConstant.NCKD_RANKNAME)
+                .add(String.join(".",PositionStructureConstant.NCKD_PROTITLELEVEL,FormConstant.ID_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_PROTITLELEVEL,FormConstant.NAME_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_PROTITLELEVEL,FormConstant.NUMBER_KEY))
+                .add(PositionStructureConstant.NCKD_JOBSTATUSNAME)
+                .add(String.join(".",PositionStructureConstant.NCKD_OCPQUALLEVEL,FormConstant.ID_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_OCPQUALLEVEL,FormConstant.NAME_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_OCPQUALLEVEL,FormConstant.NUMBER_KEY))
+
+                .add(String.join(".",PositionStructureConstant.NCKD_JOBLEVELHR,FormConstant.ID_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_JOBLEVELHR,FormConstant.NAME_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_JOBLEVELHR,FormConstant.NUMBER_KEY))
+                .add(String.join(".",PositionStructureConstant.NCKD_JOBLEVELHR,FormConstant.JOBLEVELSEQ));
+        DynamicObject[] load = BusinessDataServiceHelper.load(PositionStructureConstant.PERSONPOSFILE_ENTITYID, selectFields.toString(), new QFilter[]{filer},PositionStructureConstant.NCKD_BEGINDATE + " desc");
+        return load.length > 0 ? load[0] : null;
+    }
+
+    /**
+     * 根据时间获取员工任职(所属公司、岗位、职位序列)、聘任信息(职称等级、技能等级)和学历
+     * @param personId 员工id
+     * @param date 需查询的时间(任职经历:查询该时间范围内的任职。职称等级或技能等级:查询该时间聘任的等级。学历:查询毕业时间小于等于该时间的学历)
+     * @return: kd.bos.dataentity.entity.DynamicObject
+     * @author W.Y.C
+     * @date: 2025/09/17 10:27
+     */
+    public static DynamicObject positionAppointmentQuery(Long personId, Date date) {
+        StringJoiner selectFields = new StringJoiner(",")
+                //所属公司
+                .add(String.join(".", FormConstant.COMPANY_KEY, FormConstant.ID_KEY))
+                .add(String.join(".", FormConstant.COMPANY_KEY, FormConstant.NAME_KEY))
+                .add(String.join(".", FormConstant.COMPANY_KEY, FormConstant.NUMBER_KEY))
+                //行政组织(部门)
+                .add(String.join(".", FormConstant.ADMINORG, FormConstant.ID_KEY))
+                .add(String.join(".", FormConstant.ADMINORG, FormConstant.NAME_KEY))
+                .add(String.join(".", FormConstant.ADMINORG, FormConstant.NUMBER_KEY))
+                //岗位
+                .add(String.join(".", FormConstant.POSITION_KEY, FormConstant.ID_KEY))
+                .add(String.join(".", FormConstant.POSITION_KEY, FormConstant.NAME_KEY))
+                .add(String.join(".", FormConstant.POSITION_KEY, FormConstant.NUMBER_KEY))
+                //职位序列
+                .add(String.join(".", FormConstant.HBPM_POSITIONHR, FormConstant.NCKD_JOBSEQ,FormConstant.ID_KEY))
+                .add(String.join(".", FormConstant.HBPM_POSITIONHR, FormConstant.NCKD_JOBSEQ,FormConstant.NAME_KEY))
+                .add(String.join(".", FormConstant.HBPM_POSITIONHR, FormConstant.NCKD_JOBSEQ,FormConstant.NUMBER_KEY))
+                //学历
+                .add(String.join(".", FormConstant.HRPI_PEREDUEXP, FormConstant.EDUCATION_KEY, FormConstant.ID_KEY))
+                .add(String.join(".", FormConstant.HRPI_PEREDUEXP, FormConstant.EDUCATION_KEY, FormConstant.NAME_KEY))
+                .add(String.join(".", FormConstant.HRPI_PEREDUEXP, FormConstant.EDUCATION_KEY, FormConstant.NUMBER_KEY))
+                .add(String.join(".", FormConstant.HRPI_PEREDUEXP, FormConstant.EDUCATION_KEY, FormConstant.NCKD_SCORE))
+                //职称等级
+                .add(String.join(".", FormConstant.HRPI_PERPROTITLE, FormConstant.PROLEVEL_KEY, FormConstant.ID_KEY))
+                .add(String.join(".", FormConstant.HRPI_PERPROTITLE, FormConstant.PROLEVEL_KEY, FormConstant.NAME_KEY))
+                .add(String.join(".", FormConstant.HRPI_PERPROTITLE, FormConstant.PROLEVEL_KEY, FormConstant.NUMBER_KEY))
+                .add(String.join(".", FormConstant.HRPI_PERPROTITLE, FormConstant.PROLEVEL_KEY, FormConstant.NCKD_SCORE))
+                //技能等级
+                .add(String.join(".", FormConstant.HRPI_PEROCPQUAL, FormConstant.QUALEVEL_KEY, FormConstant.ID_KEY))
+                .add(String.join(".", FormConstant.HRPI_PEROCPQUAL, FormConstant.QUALEVEL_KEY, FormConstant.NAME_KEY))
+                .add(String.join(".", FormConstant.HRPI_PEROCPQUAL, FormConstant.QUALEVEL_KEY, FormConstant.NUMBER_KEY))
+                .add(String.join(".", FormConstant.HRPI_PEROCPQUAL, FormConstant.QUALEVEL_KEY, FormConstant.NCKD_SCORE))
+                //服务年限.本次加入集团日期
+                .add(String.join(".", FormConstant.HRPI_PERSERLEN,FormConstant.JOINCOMDATE_KEY))
+                //服务年限.首次加入集团日期
+                .add(String.join(".", FormConstant.HRPI_PERSERLEN,FormConstant.FIRSTJOINCOMDATE_KEY));
+
+        //获取时间范围内的任职信息
+        QFilter filer = new QFilter(String.join(".", FormConstant.EMPLOYEE_KEY,FormConstant.ID_KEY),QCP.equals,personId)
+                .and(new QFilter(String.join(".", FormConstant.IS_PRIMARY),QCP.equals,EnableEnum.YES.getCode()))
+                .and(new QFilter(String.join(".", FormConstant.IS_DELETED),QCP.equals,EnableEnum.NO.getCode()))
+                .and(new QFilter(String.join(".", FormConstant.ASSIGNMENT,FormConstant.IS_PRIMARY),QCP.equals,EnableEnum.YES.getCode()))
+                .and(new QFilter(String.join(".", FormConstant.STARTDATE),QCP.less_equals,date))
+                .and(new QFilter(String.join(".", FormConstant.ENDDATE),QCP.large_equals,date));
+
+        //教育经历毕业时间
+        filer.and(new QFilter(String.join(".", FormConstant.HRPI_PEREDUEXP, FormConstant.GRADUTIONDATE), QCP.less_equals,date)
+                .or(new QFilter(String.join(".", FormConstant.HRPI_PEREDUEXP, FormConstant.ID_KEY), QCP.is_null,null)));
+
+        //TODO 职称聘任时间
+        //TODO 技能聘任时间
+
+        DynamicObjectCollection query = QueryServiceHelper.query(PositionStructureConstant.POSITIONAPPOINTMENTQUERY, selectFields.toString(), new QFilter[]{filer});
+        return !query.isEmpty() ? query.get(0) : null;
+    }
+}

+ 0 - 0
code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/exception/.gitkeep → code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/mservice/.gitkeep


+ 159 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/adjust/NewDynamicAdjustmentDiaLogFormPlugin.java

@@ -0,0 +1,159 @@
+package nckd.jxccl.hr.psms.plugin.form.adjust;
+
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.entity.datamodel.events.ChangeData;
+import kd.bos.entity.datamodel.events.PropertyChangedArgs;
+import kd.bos.form.FormShowParameter;
+import kd.bos.form.plugin.AbstractFormPlugin;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import nckd.jxccl.base.common.constant.FormConstant;
+import nckd.jxccl.base.common.exception.ValidationException;
+import nckd.jxccl.base.common.utils.ConvertUtil;
+import nckd.jxccl.base.common.utils.DateUtil;
+import nckd.jxccl.base.common.utils.StrFormatter;
+import nckd.jxccl.hr.psms.business.JobLevelCalculatorService;
+import nckd.jxccl.hr.psms.common.PositionStructureConstant;
+import nckd.jxccl.hr.psms.helper.PositionStructureHelper;
+
+import java.time.LocalDateTime;
+import java.util.Date;
+import java.util.EventObject;
+import java.util.Objects;
+import java.util.StringJoiner;
+
+/**
+* 新建动态调整(弹窗)-插件
+* @author W.Y.C
+* @date 2025/9/17 15:56
+* @version 1.0
+*/
+public class NewDynamicAdjustmentDiaLogFormPlugin extends AbstractFormPlugin {
+
+    @Override
+    public void registerListener(EventObject e) {
+        super.registerListener(e);
+    }
+
+    @Override
+    public void afterCreateNewData(EventObject e) {
+        // 获取当前页面的FormShowParameter对象
+        FormShowParameter formShowParameter = this.getView().getFormShowParameter();
+        // 获取列表选择的人员
+        Object personId = formShowParameter.getCustomParam(FormConstant.NCKD_PERSON);
+        if(personId != null) {
+            //赋值表单人员
+            this.getModel().setValue(FormConstant.NCKD_PERSON, personId);
+            this.getView().updateView();
+        }
+    }
+
+    @Override
+    public void propertyChanged(PropertyChangedArgs e) {
+        System.out.println();
+        String fieldKey = e.getProperty().getName();
+        ChangeData[] changeSet = e.getChangeSet();
+
+        //【员工】或【调整日期】发生变更则重新获取员工任职信息
+        if (PositionStructureConstant.NCKD_ADJUSTDATE.equalsIgnoreCase(fieldKey) || FormConstant.NCKD_PERSON.equalsIgnoreCase(fieldKey)) {
+            if (changeSet != null && changeSet.length > 0) {
+                ChangeData changeData = changeSet[0];
+                Object oldValue = changeData.getOldValue();
+                Object newValue = changeData.getNewValue();
+                if (!Objects.equals(oldValue, newValue)) {
+                    //【员工】或【调整日期】发生变更
+                    Date adjustDate = ConvertUtil.toDate(this.getModel().getValue(PositionStructureConstant.NCKD_ADJUSTDATE));
+                    DynamicObject person = ConvertUtil.toDynamicObject(this.getModel().getValue(PositionStructureConstant.NCKD_PERSON));
+                    if(adjustDate != null && person.getDataEntityType() != null){
+                        DynamicObject latsPersonPosFileByPerson = PositionStructureHelper.getLatsPersonPosFileByPerson(person.getLong(FormConstant.ID_KEY));
+                        if(latsPersonPosFileByPerson == null){
+                            this.getView().showErrorNotification(StrFormatter.format("当前无法为【{}】进行动态调整,因为他/她尚未建立职位档案。请前往“职位及积分初定” -> 进行初定!", person.getString(FormConstant.NAME_KEY)));
+                            this.getModel().setValue(PositionStructureConstant.PERSONPOSFILE_ENTITYID, null);
+                        }else{
+                            DynamicObject jobLevel = getJobLevel(person, adjustDate);
+                            this.getModel().setValue(PositionStructureConstant.PERSONPOSFILE_ENTITYID, latsPersonPosFileByPerson);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+
+    public static DynamicObject getJobLevel(DynamicObject person,Date date){
+        //移植代码:com.kingdee.shr.customer.web.handler.ContributeScore.PersonpositionfileFluctuationListHandler#selaboutJobGradeAction
+        long personId = person.getLong(FormConstant.ID_KEY);
+        String personName = person.getString(FormConstant.NAME_KEY);
+        // 1、获取员工职位档案信息
+        // 对应SHR:personpositionfileInfo & appraisalCollection
+        DynamicObject currentPersonPosFile = PositionStructureHelper.getLatsPersonPosFileByPerson(personId);
+        if (currentPersonPosFile == null || currentPersonPosFile.getDataEntityType() == null) {
+            throw new ValidationException(StrFormatter.format("当前无法为【{}】进行调整,因为他/她尚未建立职位档案。请前往“职位及积分初定” -> 进行初定!", personName));
+        }
+        // 2、验证调整日期是否在上一次调整日期之后
+        Date lastDate = currentPersonPosFile.getDate(PositionStructureConstant.NCKD_BEGINDATE);
+        if (date.before(lastDate)) {
+            throw new ValidationException(StrFormatter.format("当前无法为【{}】进行调整,因为最近调整时间为【{}】,不能早于最近一次职位调整时间。", personName,DateUtil.format(lastDate,DateUtil.NORM_DATE_PATTERN)));
+        }
+
+        // BigDecimal sumScore = currentPersonPosFile.getBigDecimal(PositionStructureConstant.NCKD_SUMSCORE);
+
+        LocalDateTime endDay = DateUtil.endOfDay(DateUtil.toLocalDateTime(date));
+        // 3、根据时间获取员工信息(任职经历、聘任、学历)
+        //对应SHR:resultMap
+        DynamicObject positionAppointment = PositionStructureHelper.positionAppointmentQuery(personId, DateUtil.toDate(endDay));
+        if(positionAppointment == null || positionAppointment.getDataEntityType() == null){
+            throw new ValidationException(StrFormatter.format("当前无法为【{}】进行调整,因为根据时间【{}】未获取到人员任职和聘任信息!", personName,DateUtil.format(date,DateUtil.NORM_DATE_PATTERN)));
+        }
+
+        // 4、获取职位序列(如是管理序列则转职能序列)
+        /*long jobSeqId = positionAppointment.getLong(String.join(".", FormConstant.HBPM_POSITIONHR, FormConstant.NCKD_JOBSEQ, FormConstant.ID_KEY));
+        if(jobSeqId == 0){
+            throw new ValidationException(StrFormatter.format("当前无法为【{}】进行调整,因为人员主任职没有职位序列", personName));
+        }
+        DynamicObject jobSeq = BusinessDataServiceHelper.loadSingle(jobSeqId, FormConstant.HBJM_JOBSEQHR);
+        DynamicObject newJobSeq = JobLevelCalculatorService.handleJobSeq(jobSeq);
+        //对应SHR:hrjobfamilynumber
+        String jobSeqNumber = newJobSeq.getString(FormConstant.NUMBER_KEY);*/
+
+        // 5、处理职称等级分和技能等级分
+        // JobLevelCalculatorService.JobScoreInfo jobScoreInfo = JobLevelCalculatorService.handleJobScores(newJobSeq, currentPersonPosFile, positionAppointment);
+
+        // 6、处理学历分数计算
+        // BigDecimal diplomaScore = JobLevelCalculatorService.handleDiplomaScore(currentPersonPosFile, positionAppointment);
+
+        /*sumScore = sumScore.add(diplomaScore)
+                .add(jobScoreInfo.perProTitleScore)
+                .add(jobScoreInfo.quaLevelScore);*/
+
+        // 7、获取职级相关信息
+        JobLevelCalculatorService.JobLevelResult jobLevelResult = JobLevelCalculatorService.calculateJobLevel(person, date, positionAppointment);
+
+
+        // 8、检查指定年份是否存在未生效的年度调整记录
+        int currentYear = DateUtil.getYear(new Date());
+        int year = DateUtil.getYear(date);
+        DynamicObject[] personPosFileByPersonAndState = PositionStructureHelper.getPersonPosFileByPersonAndState(personId,
+                new String[]{"3"},
+                null,
+                new QFilter(PositionStructureConstant.NCKD_TYPESTATE, QCP.not_in, new String[]{"1", "2"}).and(new QFilter(PositionStructureConstant.NCKD_EXECUTEYEAR, QCP.in, new Integer[]{currentYear, year})));
+        if(personPosFileByPersonAndState != null && personPosFileByPersonAndState.length > 0){
+            StringJoiner yearJoin = new StringJoiner(",");
+            for (DynamicObject dynamicObject : personPosFileByPersonAndState) {
+                int executeYear = dynamicObject.getInt(PositionStructureConstant.NCKD_EXECUTEYEAR);
+                yearJoin.add(executeYear+"");
+            }
+            throw new ValidationException(StrFormatter.format("当前无法为【{}】进行调整,因为人员存在【{}】年未生效的年度调整记录,请先前往【职位及积分年度调整】中进行处理!,不能早于最近一次职位调整时间。", personName,yearJoin.toString()));
+        }
+
+        // 9、处理考核结果
+        // DynamicObject appraisalResult = currentPersonPosFile.getDynamicObject(PositionStructureConstant.NCKD_APPRAISALRESULT);
+        // String appraisalResultNumber = appraisalResult != null ? appraisalResult.getString(FormConstant.NUMBER_KEY) : null;
+        // String newAppraisalResultNumber = JobLevelCalculatorService.validateAppraisalResultConsistency(date, personId, personName, appraisalResultNumber);
+
+        // 10、处理R排名百分比
+        // JobLevelCalculatorService.RankingResult rankingInfo = JobLevelCalculatorService.getRankingInfo(personId, personName, date);
+
+        return jobLevelResult.jobLevel;
+    }
+}

+ 28 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/initial/NewHireInitialFormPlugin.java

@@ -0,0 +1,28 @@
+package nckd.jxccl.hr.psms.plugin.form.initial;
+
+import kd.bos.form.MessageBoxOptions;
+import kd.bos.form.events.AfterDoOperationEventArgs;
+import kd.bos.form.plugin.AbstractFormPlugin;
+import kd.sdk.plugin.Plugin;
+import nckd.jxccl.hr.psms.common.PositionStructureConstant;
+
+import java.util.Map;
+
+/**
+* 新入职人员初定(弹窗)-弹窗页面插件
+* @author W.Y.C
+* @date 2025/9/15 14:17
+* @version 1.0
+*/
+public class NewHireInitialFormPlugin extends AbstractFormPlugin implements Plugin {
+    @Override
+    public void afterDoOperation(AfterDoOperationEventArgs afterDoOperationEventArgs) {
+        String operateKey = afterDoOperationEventArgs.getOperateKey();
+        boolean success = afterDoOperationEventArgs.getOperationResult() != null && afterDoOperationEventArgs.getOperationResult().isSuccess();
+        if(success && PositionStructureConstant.OP_CONFIRMINITIAL.equalsIgnoreCase(operateKey)){
+            Map<String, String> customData = afterDoOperationEventArgs.getOperationResult().getCustomData();
+            String jobLeveStr = customData.get(PositionStructureConstant.NCKD_JOBLEVELHR);
+            this.getView().showConfirm("提示","初定成功,职位档案已生成。",jobLeveStr, MessageBoxOptions.OK,null,null,null,null);
+        }
+    }
+}

+ 81 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/initial/ScoreInitialFormPlugin.java

@@ -0,0 +1,81 @@
+package nckd.jxccl.hr.psms.plugin.form.initial;
+
+import kd.bos.form.ShowType;
+import kd.bos.form.container.Tab;
+import kd.bos.form.control.events.TabSelectEvent;
+import kd.bos.form.control.events.TabSelectListener;
+import kd.bos.form.plugin.AbstractFormPlugin;
+import kd.bos.list.ListShowParameter;
+import kd.bos.logging.Log;
+import kd.bos.logging.LogFactory;
+import nckd.jxccl.hr.psms.common.PositionStructureConstant;
+
+import java.util.EventObject;
+
+/**
+* 职位体系-职位及积分初定
+* @author W.Y.C
+* @date 2025/9/8 14:25
+* @version 1.0
+*/
+public class ScoreInitialFormPlugin extends AbstractFormPlugin implements TabSelectListener {
+
+    private final static Log logger = LogFactory.getLog(ScoreInitialFormPlugin.class);
+
+    @Override
+    public void registerListener(EventObject e) {
+        super.registerListener(e);
+        // 获取页签控件并添加监听
+        Tab tab = (Tab)this.getControl(PositionStructureConstant.NCKD_TABAP);
+        tab.addTabSelectListener(this);
+    }
+
+    @Override
+    public void afterBindData(EventObject e) {
+        //页面加载完成默认打开已定级人员列表
+        switchTab();
+    }
+    @Override
+    public void tabSelected(TabSelectEvent tabSelectEvent) {
+        //切换tab页签
+        switchTab();
+
+    }
+
+    /**
+     * 切换页签(根据当前页签焦点加载对应页面)
+     * @param
+     * @return: void
+     * @author W.Y.C
+     * @date: 2025/09/10 17:16
+     */
+    private void switchTab() {
+        Tab tab = (Tab)this.getView().getControl(PositionStructureConstant.NCKD_TABAP);
+        String currentTab = tab.getCurrentTab();
+        if (PositionStructureConstant.NCKD_GRADEDPERSON.equalsIgnoreCase(currentTab)) {
+            //打开“已定级”人员列表
+            openTargetPage(PositionStructureConstant.GRADEDPERSONQUERY_ENTITYID, PositionStructureConstant.NCKD_GRADEDPERSON);
+        } else if (PositionStructureConstant.NCKD_UNGRADEDPERSON.equalsIgnoreCase(currentTab)) {
+            //打开“未定级”人员列表
+            openTargetPage(PositionStructureConstant.UNGRADEDPERSONQUERY_ENTITYID,PositionStructureConstant.NCKD_UNGRADEDPERSON);
+        }
+    }
+
+    /**
+     * 在页签中打开表单
+     * @param formId 需要打开的表单标识
+     * @param targetKey 目标容器标识
+     * @return: void
+     * @author W.Y.C
+     * @date: 2025/09/10 17:15
+     */
+    private void openTargetPage(String formId, String targetKey) {
+        ListShowParameter parameter = new ListShowParameter();
+        parameter.setBillFormId(formId);
+        // 在容器内打开
+        parameter.getOpenStyle().setShowType(ShowType.InContainer);
+        // 指定页签容器标识
+        parameter.getOpenStyle().setTargetKey(targetKey);
+        this.getView().showForm(parameter);
+    }
+}

+ 29 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/initial/ServingInitialFormPlugin.java

@@ -0,0 +1,29 @@
+package nckd.jxccl.hr.psms.plugin.form.initial;
+
+import kd.bos.form.MessageBoxOptions;
+import kd.bos.form.events.AfterDoOperationEventArgs;
+import kd.bos.form.plugin.AbstractFormPlugin;
+import kd.sdk.plugin.Plugin;
+import nckd.jxccl.hr.psms.common.PositionStructureConstant;
+
+import java.util.Map;
+
+/**
+* 在职人员初定(弹窗)-弹窗页面插件
+* @author W.Y.C
+* @date 2025/9/15 14:47
+* @version 1.0
+*/
+public class ServingInitialFormPlugin extends AbstractFormPlugin implements Plugin {
+
+    @Override
+    public void afterDoOperation(AfterDoOperationEventArgs afterDoOperationEventArgs) {
+        String operateKey = afterDoOperationEventArgs.getOperateKey();
+        boolean success = afterDoOperationEventArgs.getOperationResult() != null && afterDoOperationEventArgs.getOperationResult().isSuccess();
+        if(success && PositionStructureConstant.OP_CONFIRMINITIAL.equalsIgnoreCase(operateKey)){
+            Map<String, String> customData = afterDoOperationEventArgs.getOperationResult().getCustomData();
+            String jobLeveStr = customData.get(PositionStructureConstant.NCKD_JOBLEVELHR);
+            this.getView().showConfirm("提示","初定成功,职位档案已生成。",jobLeveStr, MessageBoxOptions.OK,null,null,null,null);
+        }
+    }
+}

+ 61 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/initial/UngradedPersonQueryListPlugin.java

@@ -0,0 +1,61 @@
+package nckd.jxccl.hr.psms.plugin.form.initial;
+
+import kd.bos.common.enums.EnableEnum;
+import kd.bos.entity.datamodel.ListSelectedRowCollection;
+import kd.bos.form.FormShowParameter;
+import kd.bos.form.ShowType;
+import kd.bos.form.events.BeforeDoOperationEventArgs;
+import kd.bos.form.events.SetFilterEvent;
+import kd.bos.form.operate.FormOperate;
+import kd.bos.list.plugin.AbstractListPlugin;
+import kd.bos.logging.Log;
+import kd.bos.logging.LogFactory;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.sdk.plugin.Plugin;
+import nckd.jxccl.base.common.constant.FormConstant;
+import nckd.jxccl.hr.psms.common.PositionStructureConstant;
+
+/**
+* 职位体系-未定级人员列表
+* @author W.Y.C
+* @date 2025/9/6 14:05
+* @version 1.0
+*/
+public class UngradedPersonQueryListPlugin extends AbstractListPlugin implements Plugin {
+
+    private final static Log logger = LogFactory.getLog(UngradedPersonQueryListPlugin.class);
+
+    @Override
+    public void setFilter(SetFilterEvent setFilterEvent) {
+        //只查询没有初定的人员
+        setFilterEvent.addCustomQFilter(new QFilter(String.join(".",PositionStructureConstant.PERSONPOSFILE_ENTITYID, FormConstant.ID_KEY), QCP.is_null,null));
+
+        //组织分配为主组织分配的人员
+        setFilterEvent.addCustomQFilter(new QFilter(String.join(".",FormConstant.ASSIGNMENT_ENTITYID, FormConstant.IS_PRIMARY), QCP.equals,EnableEnum.YES.getCode()));
+
+    }
+
+
+    @Override
+    public void beforeDoOperation(BeforeDoOperationEventArgs args) {
+        FormOperate formOperate = (FormOperate) args.getSource();
+        String operateKey = formOperate.getOperateKey();
+        ListSelectedRowCollection selectedRows = this.getSelectedRows();
+        if(PositionStructureConstant.OP_SETTINGJOBGRADE.equals(operateKey)){
+            //弹出在职人员初定窗口
+            FormShowParameter showParameter = new FormShowParameter();
+            showParameter.setFormId(PositionStructureConstant.SERVINGINITIAL_ENTITYID);
+            showParameter.getOpenStyle().setShowType(ShowType.Modal);
+            showParameter.setCaption("在职人员初定-定级信息确认");
+            this.getView().showForm(showParameter);
+        }else if(PositionStructureConstant.OP_SETTINGJOBGRADENEW.equals(operateKey)){
+            //弹出新入职人员初定窗口
+            FormShowParameter showParameter = new FormShowParameter();
+            showParameter.setFormId(PositionStructureConstant.NEWHIREINITIAL_ENTITYID);
+            showParameter.getOpenStyle().setShowType(ShowType.Modal);
+            showParameter.setCaption("新入职人员初定-定级信息确认");
+            this.getView().showForm(showParameter);
+        }
+    }
+}

+ 127 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/adjust/NewDynamicAdjustmentOperationPlugIn.java

@@ -0,0 +1,127 @@
+package nckd.jxccl.hr.psms.plugin.operate.adjust;
+
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.entity.ExtendedDataEntity;
+import kd.bos.entity.plugin.AbstractOperationServicePlugIn;
+import kd.bos.entity.plugin.AddValidatorsEventArgs;
+import kd.bos.entity.plugin.args.BeginOperationTransactionArgs;
+import kd.bos.entity.validate.AbstractValidator;
+import kd.bos.logging.Log;
+import kd.bos.logging.LogFactory;
+import kd.sdk.plugin.Plugin;
+import nckd.jxccl.base.common.constant.FormConstant;
+import nckd.jxccl.base.common.utils.DateUtil;
+import nckd.jxccl.base.common.utils.StrFormatter;
+import nckd.jxccl.base.pm.helper.PerformanceManagerHelper;
+import nckd.jxccl.hr.psms.common.PositionStructureConstant;
+import nckd.jxccl.hr.psms.helper.PositionStructureHelper;
+import nckd.jxccl.hr.psms.plugin.operate.initial.BaseInitialOperationPlugIn;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+* 新建动态调整OP
+* @author W.Y.C
+* @date 2025/9/17 22:02
+* @version 1.0
+*/
+public class NewDynamicAdjustmentOperationPlugIn extends AbstractOperationServicePlugIn{
+
+    protected final static Log logger = LogFactory.getLog(BaseInitialOperationPlugIn.class);
+
+    /** 记录上一次职位档案 */
+    Map<Long,DynamicObject> latsPersonPosFileMap;
+
+    /** 记录上年度考核结果*/
+    Map<Long,DynamicObject> lastYearPerformanceResultMap;
+
+    @Override
+    public void onAddValidators(AddValidatorsEventArgs e){
+        latsPersonPosFileMap = new HashMap<>(e.getDataEntities().length);
+        lastYearPerformanceResultMap = new HashMap<>(e.getDataEntities().length);
+        e.addValidator(new AbstractValidator() {
+            @Override
+            public void validate() {
+                if (!this.getValidateContext().getValidateResults().isSuccess()) {
+                    //前面的校验器失败跳过本校验器
+                    return;
+                }
+                Date currentDate = new Date();
+                for (ExtendedDataEntity rowDataEntity : getDataEntities()) {
+                    DynamicObject data = rowDataEntity.getDataEntity();
+                    Date adjustDate = data.getDate(PositionStructureConstant.NCKD_ADJUSTDATE);
+                    if (adjustDate == null) {
+                        addFatalErrorMessage(rowDataEntity,"调整时间不能为空");
+                        return;
+                    }
+                    if (adjustDate.after(currentDate)) {
+                        addFatalErrorMessage(rowDataEntity,"初定时间不能晚于当前日期,请检查输入的日期是否正确");
+                        return;
+                    }
+                    DynamicObject person = data.getDynamicObject(FormConstant.NCKD_PERSON);
+                    if(person == null){
+                        addFatalErrorMessage(rowDataEntity,"请选择要调整的员工");
+                        return;
+                    }
+                    DynamicObject latsPersonPosFileByPerson = PositionStructureHelper.getLatsPersonPosFileByPerson(person.getLong(FormConstant.ID_KEY));
+                    if(latsPersonPosFileByPerson == null){
+                        addFatalErrorMessage(rowDataEntity, StrFormatter.format("当前无法为【{}】进行动态调整,因为他/她尚未建立职位档案。请前往“职位及积分初定” -> 进行初定!", person.getString(FormConstant.NAME_KEY)));
+                        return;
+                    }
+                    //最近一次职位档案开始时间
+                    Date date = latsPersonPosFileByPerson.getDate(PositionStructureConstant.NCKD_BEGINDATE);
+                    //校验【本次调整时间】不得早于上一笔时间
+                    if (adjustDate.before(date)) {
+                        addFatalErrorMessage(rowDataEntity, StrFormatter.format("【{}】最近一次的职位档案变动时间是【{}】,请勿选择早于该时间段的调整时间!", person.getString(FormConstant.NAME_KEY), DateUtil.format(date,DateUtil.NORM_DATE_PATTERN)));
+                    }
+                    //获取上年度绩效结果
+                    LocalDateTime lastYearDateTime = DateUtil.minusYears(DateUtil.toLocalDateTime(adjustDate), 1);
+                    DynamicObject lastYearPerformanceResult = PerformanceManagerHelper.getPerformanceResult(person.getLong(FormConstant.ID_KEY), lastYearDateTime);
+                    if(lastYearPerformanceResult == null){
+                        addFatalErrorMessage(rowDataEntity,StrFormatter.format("员工【{}】没有【{}】年度考核结果,无法新建调整",DateUtil.getYear(lastYearDateTime),person.getString(FormConstant.NAME_KEY)));
+                        return;
+                    }
+
+                    latsPersonPosFileMap.put(person.getLong(FormConstant.ID_KEY),latsPersonPosFileByPerson);
+                    lastYearPerformanceResultMap.put(person.getLong(FormConstant.ID_KEY),latsPersonPosFileByPerson);
+
+                }
+            }
+        });
+    }
+
+    @Override
+    public void beginOperationTransaction(BeginOperationTransactionArgs e) {
+        logger.info("【职位体系】-动态调整-开始");
+        for (DynamicObject data : e.getDataEntities()) {
+            DynamicObject person = data.getDynamicObject(FormConstant.NCKD_PERSON);
+            long personId = person.getLong(FormConstant.ID_KEY);
+            //最近一次档案
+            DynamicObject latsPersonPosFile = latsPersonPosFileMap.get(personId);
+            //上一次职级
+            DynamicObject jobLevelHr = latsPersonPosFile.getDynamicObject(PositionStructureConstant.NCKD_JOBLEVELHR);
+            //选择的调整时间
+            Date adjustDate = data.getDate(PositionStructureConstant.NCKD_ADJUSTDATE);
+            LocalDateTime adjustDateEnd = DateUtil.endOfDay(DateUtil.toLocalDateTime(adjustDate));
+            //查询员工在调整日期内的最新信息(职位、部门、序列、学历、职称、技能)
+            DynamicObject positionAppointment = PositionStructureHelper.positionAppointmentQuery(personId, DateUtil.toDate(adjustDateEnd));
+            //最新档案总积分
+            BigDecimal allSumScore = latsPersonPosFile.getBigDecimal(PositionStructureConstant.NCKD_ALLSUMSCORE);
+
+
+
+
+            //上年度考核结果
+            DynamicObject lastYearPerformanceResult = lastYearPerformanceResultMap.get(personId);
+
+
+            // 初定当年可以做动态调整,但不能做年度调整
+
+
+        }
+    }
+}

+ 322 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/initial/BaseInitialOperationPlugIn.java

@@ -0,0 +1,322 @@
+package nckd.jxccl.hr.psms.plugin.operate.initial;
+
+import kd.bos.common.enums.EnableEnum;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.entity.constant.StatusEnum;
+import kd.bos.entity.plugin.AbstractOperationServicePlugIn;
+import kd.bos.logging.Log;
+import kd.bos.logging.LogFactory;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.servicehelper.QueryServiceHelper;
+import kd.bos.servicehelper.operation.SaveServiceHelper;
+import nckd.jxccl.base.common.constant.FormConstant;
+import nckd.jxccl.base.common.enums.AppraisalResultEnum;
+import nckd.jxccl.base.common.enums.JobSeqEnum;
+import nckd.jxccl.base.common.utils.DateUtil;
+import nckd.jxccl.base.common.utils.StrFormatter;
+import nckd.jxccl.base.orm.helper.QFilterCommonHelper;
+import nckd.jxccl.base.pm.helper.PerformanceManagerHelper;
+import nckd.jxccl.hr.psms.common.PositionStructureConstant;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 人员初定操作插件基类
+ * @author W.Y.C
+ * @date 2025/9/15
+ * @version 1.0
+ */
+public abstract class BaseInitialOperationPlugIn extends AbstractOperationServicePlugIn {
+    
+    protected final static Log logger = LogFactory.getLog(BaseInitialOperationPlugIn.class);
+
+    /**
+     * 基本信息数据对象
+     */
+    protected static class BaseInitialData {
+        /** 开始时间 */
+        Date beginDate;
+        /** 备注 */
+        String causeRemark;
+        /** 总积分 */
+        BigDecimal allSumScore;
+        /** 聘任年限 */
+        Integer employmentYears;
+        /** 职称名称 */
+        String rankName;
+        /** 技能名称 */
+        String jobStatusName;
+        /** 员工 */
+        DynamicObject person;
+        /** 岗位 */
+        DynamicObject positionHr;
+        /** 所属公司 */
+        DynamicObject company;
+        /** 部门 */
+        DynamicObject dep;
+        /** 职位序列 */
+        DynamicObject jobSeq;
+        /** 职位序列-编码 */
+        String jobSeqNumber;
+        /** 职位序列-名称 */
+        String jobSeqName;
+        /** 职位序列枚举 */
+        JobSeqEnum jobSeqEnum;
+        /** 职称等级 */
+        DynamicObject proTitleLevel;
+        /** 技能等级 */
+        DynamicObject ocpQualLevel;
+        /** 学历 */
+        DynamicObject diploma;
+        /** 任职经历 */
+        DynamicObject empPosOrgRel;
+        /** 本次入集团时间 */
+        Date joinComDate;
+        /** 上年度绩效考核结果*/
+        DynamicObject lastYearAppraisalResult;
+        /** 降级数 */
+        int downgradeNum;
+        /** 上年度绩效考核结果枚举 */
+        AppraisalResultEnum lastYearAppraisalResultEnum;
+        /** 备注 */
+        boolean isExcellent;
+    }
+
+    /**
+     * 积分数据
+     */
+    protected static class ScoreData {
+        BigDecimal educationScore;
+        BigDecimal proTitleScore;
+        BigDecimal perOcpQualScore;
+        DynamicObject dbProTitleLevel;
+        DynamicObject dbOcpQualLevel;
+    }
+
+    /**
+     * 提取基本信息
+     */
+    protected BaseInitialData extractBasicInfo(DynamicObject initialData) {
+        BaseInitialData data = new BaseInitialData();
+
+        data.beginDate = initialData.getDate(PositionStructureConstant.NCKD_BEGINDATE);
+        data.causeRemark = initialData.getString(PositionStructureConstant.KEY_NCKD_CAUSEREMARK);
+        data.rankName = initialData.getString(PositionStructureConstant.NCKD_RANKNAME);
+        data.jobStatusName = initialData.getString(PositionStructureConstant.NCKD_JOBSTATUSNAME);
+        data.person = initialData.getDynamicObject(PositionStructureConstant.NCKD_PERSON);
+        data.positionHr = initialData.getDynamicObject(PositionStructureConstant.NCKD_POSITIONHR);
+
+        // 主任职经历(在业务规则中配置)
+        DynamicObject empPosOrgRel = initialData.getDynamicObject(PositionStructureConstant.NCKD_EMPPOSORGREL);
+        data.empPosOrgRel = empPosOrgRel;
+        if(empPosOrgRel != null) {
+            data.company = empPosOrgRel.getDynamicObject(FormConstant.COMPANY_KEY);
+            data.dep = empPosOrgRel.getDynamicObject(FormConstant.ADMINORG);
+        }
+
+        // 优秀生
+        data.isExcellent = initialData.getBoolean(PositionStructureConstant.NCKD_EXCELLENT);
+
+        // 职位序列
+        data.jobSeq = initialData.getDynamicObject(PositionStructureConstant.NCKD_JOBSEQ);
+        if(data.jobSeq != null) {
+            data.jobSeqNumber = data.jobSeq.getString(FormConstant.NUMBER_KEY);
+            data.jobSeqName = data.jobSeq.getString(FormConstant.NAME_KEY);
+            data.jobSeqEnum = JobSeqEnum.getByCode(data.jobSeqNumber);
+        }
+
+        // 职称等级
+        data.proTitleLevel = initialData.getDynamicObject(PositionStructureConstant.NCKD_PROTITLELEVEL);
+
+        // 技能等级
+        data.ocpQualLevel = initialData.getDynamicObject(PositionStructureConstant.NCKD_OCPQUALLEVEL);
+
+        // 学历
+        data.diploma = initialData.getDynamicObject(PositionStructureConstant.NCKD_DIPLOMA);
+
+        // 特定字段由子类处理
+        extractSpecificFields(data, initialData);
+
+        return data;
+    }
+
+    /**
+     * 提取特定字段(由子类实现)
+     */
+    protected abstract void extractSpecificFields(BaseInitialData data, DynamicObject initialData);
+
+    /**
+     * 获取上年度绩效结果
+     */
+    protected void getPreviousYearPerformance(BaseInitialData data, String logPrefix) {
+        LocalDateTime lastYearDateTime = DateUtil.minusYears(DateUtil.toLocalDateTime(data.beginDate), 1);
+        data.lastYearAppraisalResult = PerformanceManagerHelper.getPerformanceResult(
+                data.person.getLong(FormConstant.ID_KEY), lastYearDateTime);
+        
+        AppraisalResultEnum lastYearAppraisalResultEnum = AppraisalResultEnum.NONE;
+        if(data.lastYearAppraisalResult != null) {
+            String lastAppraisalResultNumber = data.lastYearAppraisalResult.getString(FormConstant.NUMBER_KEY);
+            lastYearAppraisalResultEnum = AppraisalResultEnum.getByCode(lastAppraisalResultNumber);
+        }
+        data.lastYearAppraisalResultEnum = lastYearAppraisalResultEnum;
+        
+        // 降级数
+        data.downgradeNum = 0;
+        if (lastYearAppraisalResultEnum == AppraisalResultEnum.BASICALLY_QUALIFIED) {
+            // "基本合格"降1级
+            data.downgradeNum = 1;
+        } else if (lastYearAppraisalResultEnum == AppraisalResultEnum.UN_QUALIFIED) {
+            // "不合格"降2级
+            data.downgradeNum = 2;
+        }
+
+        logger.info("{}-上年度考核结果【{}】-降级数【{}】",
+                logPrefix, lastYearAppraisalResultEnum.getName(), data.downgradeNum);
+    }
+
+    /**
+     * 获取积分数据
+     */
+    protected ScoreData getScoreData(BaseInitialData data, String logPrefix) {
+        ScoreData scoreData = new ScoreData();
+
+        // 学历积分
+        scoreData.educationScore = BigDecimal.ZERO;
+        if (data.diploma != null) {
+            DynamicObject education = QueryServiceHelper.queryOne(FormConstant.HBSS_DIPLOMA,
+                    "name,nckd_score",
+                    new QFilter[]{QFilterCommonHelper.getIdEqFilter(data.diploma.getLong(FormConstant.ID_KEY))});
+            if (education != null) {
+                scoreData.educationScore = education.getBigDecimal("nckd_score");
+                logger.info("{}-学历【{}】-积分【{}】", logPrefix, education.getString("name"), scoreData.educationScore);
+            } else {
+                logger.warn("{}-未找到学历信息", logPrefix);
+            }
+        } else {
+            logger.warn("{}-无学历信息", logPrefix);
+        }
+
+        // 职称积分
+        scoreData.proTitleScore = BigDecimal.ZERO;
+        // 技能积分
+        scoreData.perOcpQualScore = BigDecimal.ZERO;
+        scoreData.dbProTitleLevel = null;
+        scoreData.dbOcpQualLevel = null;
+
+        if (data.jobSeqEnum != JobSeqEnum.SKILL && data.proTitleLevel != null) {
+            // 非技能序列获取职称积分
+            scoreData.dbProTitleLevel = QueryServiceHelper.queryOne(FormConstant.HBSS_PROTITLELEVEL,
+                    "name,nckd_score",
+                    new QFilter[]{QFilterCommonHelper.getIdEqFilter(data.proTitleLevel.getLong(FormConstant.ID_KEY))});
+            if (scoreData.dbProTitleLevel != null) {
+                scoreData.proTitleScore = scoreData.dbProTitleLevel.getBigDecimal("nckd_score");
+            }
+        } else if (data.jobSeqEnum == JobSeqEnum.SKILL && data.ocpQualLevel != null) {
+            // 技能序列获取技能积分
+            scoreData.dbOcpQualLevel = QueryServiceHelper.queryOne(FormConstant.HBSS_OCPQUALLEVEL,
+                    "name,nckd_score",
+                    new QFilter[]{QFilterCommonHelper.getIdEqFilter(data.ocpQualLevel.getLong(FormConstant.ID_KEY))});
+            if (scoreData.dbOcpQualLevel != null) {
+                scoreData.perOcpQualScore = scoreData.dbOcpQualLevel.getBigDecimal("nckd_score");
+            }
+        }
+
+        logger.info("{}-初定职位序列【{}】-职称积分【{}】-技能积分【{}】",
+                logPrefix, data.jobSeqName, scoreData.proTitleScore, scoreData.perOcpQualScore);
+
+        return scoreData;
+    }
+
+    /**
+     * 计算累计积分池
+     */
+    protected BigDecimal calculateSumScore(BigDecimal allSumScore, ScoreData scoreData, String logPrefix) {
+        // 累计积分池 = 累计总积分【前端传入】 - (职称分/技能分) - 学历分
+        BigDecimal sumScore = allSumScore
+                .subtract(scoreData.proTitleScore != null ? scoreData.proTitleScore : BigDecimal.ZERO)
+                .subtract(scoreData.perOcpQualScore != null ? scoreData.perOcpQualScore : BigDecimal.ZERO)
+                .subtract(scoreData.educationScore != null ? scoreData.educationScore : BigDecimal.ZERO);
+
+        // 如果为负数则赋值为0
+        if (sumScore.compareTo(BigDecimal.ZERO) < 0) {
+            sumScore = BigDecimal.ZERO;
+        }
+
+        logger.info("{}-累计积分计算结果 = {}", logPrefix, sumScore);
+        return sumScore;
+    }
+
+    /**
+     * 创建并保存职位档案
+     */
+    protected void createAndSavePersonPosFile(BaseInitialData data, ScoreData scoreData, 
+                                             BigDecimal allSumScore, BigDecimal sumScore, 
+                                             DynamicObject jobLeve, String logPrefix, String typeState) {
+        // 优秀生得2分
+        BigDecimal excellentScore = data.isExcellent ? new BigDecimal(2) : BigDecimal.ZERO;
+
+        // 构建职位档案并存入数据库
+        DynamicObject personPosFile = BusinessDataServiceHelper.newDynamicObject(
+                PositionStructureConstant.PERSONPOSFILE_ENTITYID);
+
+        personPosFile.set(PositionStructureConstant.NCKD_PERSON, data.person);
+        personPosFile.set(PositionStructureConstant.NCKD_FIRSTRANK, EnableEnum.YES.getCode());
+        personPosFile.set(PositionStructureConstant.NCKD_BEGINDATE, data.beginDate);
+        personPosFile.set(PositionStructureConstant.NCKD_APPRAISALRESULT, data.lastYearAppraisalResult);
+        personPosFile.set(PositionStructureConstant.NCKD_JOBSEQHR, data.jobSeq);
+        personPosFile.set(PositionStructureConstant.NCKD_POSITIONHR, data.positionHr);
+        personPosFile.set(PositionStructureConstant.USEORG_KEY, data.company);
+        personPosFile.set(PositionStructureConstant.ORG_KEY, data.dep);
+        personPosFile.set(PositionStructureConstant.CREATEORG_KEY, data.dep);
+        personPosFile.set(PositionStructureConstant.NCKD_ALLSUMSCORE, allSumScore);
+        personPosFile.set(PositionStructureConstant.NCKD_SUMSCORE, sumScore);
+        personPosFile.set(PositionStructureConstant.NCKD_RANKNAME, data.rankName);
+        personPosFile.set(PositionStructureConstant.NCKD_PROTITLELEVEL, scoreData.dbProTitleLevel);
+        personPosFile.set(PositionStructureConstant.NCKD_RANKSCORE, scoreData.proTitleScore);
+        personPosFile.set(PositionStructureConstant.NCKD_JOBSTATUSNAME, data.jobStatusName);
+        personPosFile.set(PositionStructureConstant.NCKD_OCPQUALLEVEL, scoreData.dbOcpQualLevel);
+        personPosFile.set(PositionStructureConstant.NCKD_JOBSTATUSSCORE, scoreData.perOcpQualScore);
+        personPosFile.set(PositionStructureConstant.NCKD_JOBLEVELHR, jobLeve);
+        personPosFile.set(PositionStructureConstant.NCKD_DIPLOMA, data.diploma);
+        personPosFile.set(PositionStructureConstant.NCKD_TYPESTATE, typeState);
+        personPosFile.set(PositionStructureConstant.NCKD_DIPLOMASCORE, scoreData.educationScore);
+        personPosFile.set(PositionStructureConstant.NCKD_EXECUTEYEAR, DateUtil.getYear(data.beginDate));
+        personPosFile.set(PositionStructureConstant.NCKD_ORGINSSCORE, excellentScore);
+        personPosFile.set(PositionStructureConstant.KEY_NCKD_CAUSEREMARK, data.causeRemark);
+        personPosFile.set(PositionStructureConstant.NCKD_DISABLE, EnableEnum.NO.getCode());
+        personPosFile.set(PositionStructureConstant.NCKD_ISCURRENTNEWEST, EnableEnum.YES.getCode());
+        personPosFile.set(PositionStructureConstant.STATUS, StatusEnum.C.toString());
+        personPosFile.set(PositionStructureConstant.ENABLE, EnableEnum.YES.getCode());
+        personPosFile.set(PositionStructureConstant.ENABLE, EnableEnum.YES.getCode());
+
+        SaveServiceHelper.save(new DynamicObject[]{personPosFile});
+    }
+
+    /**
+     * 设置操作结果中的职级信息
+     */
+    protected void setJobLevelResult(DynamicObject person, DynamicObject jobLeve) {
+        String jobLeveStr = StrFormatter.format("{}:{}({}级)", 
+                person.getString(FormConstant.NAME_KEY),
+                jobLeve.getString(FormConstant.NAME_KEY), 
+                jobLeve.getString(FormConstant.JOBLEVELSEQ));
+        
+        Map<String, String> customData = this.getOperationResult().getCustomData();
+        if(customData == null) {
+            this.getOperationResult().setCustomData(new HashMap<>());
+            customData = this.getOperationResult().getCustomData();
+        }
+        
+        String oldJobLeveStr = customData.get(PositionStructureConstant.NCKD_JOBLEVELHR);
+        if (oldJobLeveStr == null || oldJobLeveStr.isEmpty()) {
+            customData.put(PositionStructureConstant.NCKD_JOBLEVELHR, jobLeveStr);
+        } else {
+            customData.put(PositionStructureConstant.NCKD_JOBLEVELHR, oldJobLeveStr + StrFormatter.LINE_SEPARATOR + jobLeveStr);
+        }
+    }
+}

+ 155 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/initial/NewHireInitialOperationPlugIn.java

@@ -0,0 +1,155 @@
+package nckd.jxccl.hr.psms.plugin.operate.initial;
+
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.entity.ExtendedDataEntity;
+import kd.bos.entity.plugin.AddValidatorsEventArgs;
+import kd.bos.entity.plugin.args.BeginOperationTransactionArgs;
+import kd.bos.entity.plugin.args.EndOperationTransactionArgs;
+import kd.bos.entity.plugin.args.RollbackOperationArgs;
+import kd.bos.entity.validate.AbstractValidator;
+import nckd.jxccl.base.common.constant.FormConstant;
+import nckd.jxccl.base.common.utils.DateUtil;
+import nckd.jxccl.base.common.utils.StrFormatter;
+import nckd.jxccl.hr.psms.common.PositionStructureConstant;
+import nckd.jxccl.hr.psms.helper.PositionStructureHelper;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 新入职人员初定-确认定级操作OP
+ * @author W.Y.C
+ * @date 2025/9/10 16:54
+ * @version 1.0
+ */
+public class NewHireInitialOperationPlugIn extends BaseInitialOperationPlugIn {
+
+    @Override
+    public void onAddValidators(AddValidatorsEventArgs e) {
+        //校验
+        e.addValidator(new AbstractValidator(){
+            @Override
+            public void validate() {
+                if(!this.getValidateContext().getValidateResults().isSuccess()){
+                    //前面的校验器失败跳过本校验器
+                    return;
+                }
+                Date currentDate = new Date();
+                for (ExtendedDataEntity rowDataEntity : getDataEntities()) {
+                    DynamicObject data = rowDataEntity.getDataEntity();
+
+                    //提取信息
+                    BaseInitialData newHireInitialData = (BaseInitialData) extractBasicInfo(data);
+                    Date beginDate = newHireInitialData.beginDate;
+                    DynamicObject person = newHireInitialData.person;
+                    //------ 1.非空校验 ------
+                    if(person == null){
+                        addFatalErrorMessage(rowDataEntity,"请选择人员");
+                        return;
+                    }
+                    if(newHireInitialData.empPosOrgRel == null){
+                        addFatalErrorMessage(rowDataEntity,"该人员无任职信息");
+                        return;
+                    }
+                    if (beginDate == null) {
+                        addFatalErrorMessage(rowDataEntity,"初定时间不能为空");
+                        return;
+                    }
+                    DynamicObject positionHr = newHireInitialData.positionHr;
+                    DynamicObject jobSeq = newHireInitialData.jobSeq;
+                    if(positionHr == null){
+                        addFatalErrorMessage(rowDataEntity,"无岗位,请检查当前员工岗位信息");
+                        return;
+                    }
+                    if (jobSeq == null) {
+                        addFatalErrorMessage(rowDataEntity,StrFormatter.format("无职位序列,请检查当前员工岗位【{}】是否有职位序列",positionHr.getString(FormConstant.NAME_KEY)));
+                        return;
+                    }
+
+                    //------ 3、验证初定时间不能超过当前日期 ------
+                    if (newHireInitialData.joinComDate.after(beginDate)) {
+                        addFatalErrorMessage(rowDataEntity,"初定时间不能早于本次加入集团日期,本次加入集团时间:"+DateUtil.format(newHireInitialData.joinComDate, DateUtil.NORM_DATE_PATTERN));
+                        return;
+                    }
+                    if (beginDate.after(currentDate)) {
+                        addFatalErrorMessage(rowDataEntity,"初定时间不能晚于当前日期,请检查输入的日期是否正确");
+                        return;
+                    }
+                }
+            }
+        });
+    }
+
+    @Override
+    public void beginOperationTransaction(BeginOperationTransactionArgs e) {
+        logger.info("【职位体系】-新入职人员初定-开始");
+        for (DynamicObject servingInitial : e.getDataEntities()) {
+            processNewHireInitialData(servingInitial);
+        }
+    }
+
+    @Override
+    public void endOperationTransaction(EndOperationTransactionArgs e) {
+        logger.info("【职位体系】-新入职人员初定-完成");
+    }
+
+    @Override
+    public void rollbackOperation(RollbackOperationArgs e) {
+        logger.error("【职位体系】-新入职人员初定-异常回滚");
+    }
+
+    /**
+     * 处理单个在职人员初定
+     * @param newHireInitial 新入职人员初定数据对象
+     */
+    private void processNewHireInitialData(DynamicObject newHireInitial) {
+
+        //提取信息
+        BaseInitialData data = (BaseInitialData) extractBasicInfo(newHireInitial);
+
+        String logPrefix = StrFormatter.format("【职位体系】-新入职人员初定-员工【{}({})】",
+                data.person.getString(FormConstant.NAME_KEY),
+                data.person.getString(FormConstant.EMP_NUMBER_KEY));
+        logger.info("{}-初定日期【{}】-职位序列【{}】-岗位【{}】",
+                logPrefix,
+                DateUtil.format(data.beginDate, DateUtil.NORM_DATE_PATTERN),
+                data.jobSeqName,
+                data.positionHr.getString(FormConstant.NAME_KEY));
+
+        // 获取上年度绩效结果
+        getPreviousYearPerformance(data, logPrefix);
+
+        // 获取学历、职称、技能对应的积分
+        ScoreData scoreData = getScoreData(data, logPrefix);
+
+        // 优秀生得2分
+        BigDecimal excellentScore = data.isExcellent ? new BigDecimal(2) : BigDecimal.ZERO;
+
+        // 累计总积分 = 学历分 + 职称/技能分 + 优秀生分
+        BigDecimal allSumScore = scoreData.educationScore.add(scoreData.proTitleScore).add(scoreData.perOcpQualScore).add(excellentScore);
+        logger.info("{}-学历分 + 职称/技能分 + 优秀生分 = {}", logPrefix, allSumScore);
+        // 计算累计积分池(生涯积分)
+        BigDecimal sumScore = calculateSumScore(allSumScore, scoreData, logPrefix);
+
+        // 计算职级
+        //【三期需求】-不满足三要素(聘任职称/技能、考核结果、积分)按"无职级"初定
+        //当考核结果为无时,职级定为最低级
+        boolean useMinLevel = data.lastYearAppraisalResultEnum == nckd.jxccl.base.common.enums.AppraisalResultEnum.NONE;
+        DynamicObject jobLeve = PositionStructureHelper.calculateLevel(
+                data.jobSeqEnum, sumScore, scoreData.dbProTitleLevel,
+                scoreData.dbOcpQualLevel,data.lastYearAppraisalResult,data.downgradeNum,useMinLevel,Boolean.TRUE);
+        if(jobLeve != null) {
+            // 构建职位档案并存入数据库
+            createAndSavePersonPosFile(data, scoreData, allSumScore, sumScore, jobLeve, logPrefix, "1");
+
+            //返回定级后的职级
+            setJobLevelResult(data.person, jobLeve);
+        }
+    }
+
+    @Override
+    protected void extractSpecificFields(BaseInitialData data, DynamicObject initialData) {
+        // 本次加入集团时间
+        data.joinComDate = initialData.getDate(PositionStructureConstant.NCKD_JOINCOMDATE);
+    }
+}

+ 169 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/initial/ServingInitialOperationPlugIn.java

@@ -0,0 +1,169 @@
+package nckd.jxccl.hr.psms.plugin.operate.initial;
+
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.entity.ExtendedDataEntity;
+import kd.bos.entity.plugin.AddValidatorsEventArgs;
+import kd.bos.entity.plugin.args.BeginOperationTransactionArgs;
+import kd.bos.entity.plugin.args.EndOperationTransactionArgs;
+import kd.bos.entity.plugin.args.RollbackOperationArgs;
+import kd.bos.entity.validate.AbstractValidator;
+import nckd.jxccl.base.common.constant.FormConstant;
+import nckd.jxccl.base.common.utils.DateUtil;
+import nckd.jxccl.base.common.utils.StrFormatter;
+import nckd.jxccl.base.pm.helper.PerformanceManagerHelper;
+import nckd.jxccl.hr.psms.common.PositionStructureConstant;
+import nckd.jxccl.hr.psms.helper.PositionStructureHelper;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.Date;
+
+/**
+ * 在职人员初定-确认定级操作OP
+ * @author W.Y.C
+ * @date 2025/9/10 16:54
+ * @version 1.0
+ */
+public class ServingInitialOperationPlugIn extends BaseInitialOperationPlugIn {
+
+    @Override
+    public void onAddValidators(AddValidatorsEventArgs e) {
+        e.addValidator(new AbstractValidator() {
+            @Override
+            public void validate() {
+                if(!this.getValidateContext().getValidateResults().isSuccess()){
+                    //前面的校验器失败跳过本校验器
+                    return;
+                }
+                Date currentDate = new Date();
+                for (ExtendedDataEntity rowDataEntity : getDataEntities()) {
+                    DynamicObject data = rowDataEntity.getDataEntity();
+
+                    //提取信息
+                    BaseInitialData servingInitialData = (BaseInitialData) extractBasicInfo(data);
+                    Date beginDate = servingInitialData.beginDate;
+                    DynamicObject person = servingInitialData.person;
+                    //------ 1.非空校验 ------
+                    if(person == null){
+                        addFatalErrorMessage(rowDataEntity,"请选择人员");
+                        return;
+                    }
+                    if(servingInitialData.empPosOrgRel == null){
+                        addFatalErrorMessage(rowDataEntity,"该人员无任职信息");
+                        return;
+                    }
+                    if (beginDate == null) {
+                        addFatalErrorMessage(rowDataEntity,"初定时间不能为空");
+                        return;
+                    }
+                    BigDecimal allSumScore = servingInitialData.allSumScore;
+                    if(allSumScore == null || allSumScore.compareTo(BigDecimal.ZERO) == 0){
+                        addFatalErrorMessage(rowDataEntity,"请填写总积分");
+                        return;
+                    }
+                    DynamicObject positionHr = servingInitialData.positionHr;
+                    DynamicObject jobSeq = servingInitialData.jobSeq;
+                    if(positionHr == null){
+                        addFatalErrorMessage(rowDataEntity,"无岗位,请检查当前员工岗位信息");
+                        return;
+                    }
+                    if (jobSeq == null) {
+                        addFatalErrorMessage(rowDataEntity,StrFormatter.format("无职位序列,请检查当前员工岗位【{}】是否有职位序列",positionHr.getString(FormConstant.NAME_KEY)));
+                        return;
+                    }
+
+                    //------ 2、判断是否已存在初定记录 ------
+                    boolean exists = PositionStructureHelper.isInitial(person.getLong(FormConstant.ID_KEY));
+                    if(exists){
+                        addFatalErrorMessage(rowDataEntity,StrFormatter.format("人员【{}】已初定,不能重复初定",person.getString(FormConstant.NAME_KEY)));
+                        return;
+                    }
+                    //------ 3、验证初定时间不能超过当前日期 ------
+                    if (beginDate.after(currentDate)) {
+                        addFatalErrorMessage(rowDataEntity,"初定时间不能晚于当前日期,请检查输入的日期是否正确");
+                        return;
+                    }
+
+                    //------ 4.获取上年度绩效结果 ------
+                    LocalDateTime lastYearDateTime = DateUtil.minusYears(DateUtil.toLocalDateTime(beginDate), 1);
+                    DynamicObject lastYearPerformanceResult = PerformanceManagerHelper.getPerformanceResult(person.getLong(FormConstant.ID_KEY), lastYearDateTime);
+                    if(lastYearPerformanceResult == null){
+                        addFatalErrorMessage(rowDataEntity,StrFormatter.format("员工【{}】没有上年度考核结果,无法初定",person.getString(FormConstant.NAME_KEY)));
+                        return;
+                    }
+                }
+            }
+        });
+    }
+
+    @Override
+    public void beginOperationTransaction(BeginOperationTransactionArgs e) {
+        logger.info("【职位体系】-在职人员初定-开始");
+        for (DynamicObject servingInitial : e.getDataEntities()) {
+            processServingInitial(servingInitial);
+        }
+    }
+
+    @Override
+    public void endOperationTransaction(EndOperationTransactionArgs e) {
+        logger.info("【职位体系】-在职人员初定-完成");
+    }
+
+    @Override
+    public void rollbackOperation(RollbackOperationArgs e) {
+        logger.error("【职位体系】-在职人员初定-异常回滚");
+    }
+
+    /**
+     * 处理单个在职人员初定
+     * @param servingInitial 在职人员初定数据对象
+     */
+    private void processServingInitial(DynamicObject servingInitial) {
+        // 提取基本信息
+        BaseInitialData data = extractBasicInfo(servingInitial);
+
+        String logPrefix = StrFormatter.format("【职位体系】-在职人员初定-员工【{}({})】",
+                data.person.getString(FormConstant.NAME_KEY),
+                data.person.getString(FormConstant.EMP_NUMBER_KEY));
+        logger.info("{}-初定日期【{}】-职位序列【{}】-岗位【{}】-总积分【{}】",
+                logPrefix,
+                DateUtil.format(data.beginDate, DateUtil.NORM_DATE_PATTERN),
+                data.jobSeqName,
+                data.positionHr.getString(FormConstant.NAME_KEY),
+                data.allSumScore.toString());
+
+        // 获取上年度绩效结果
+        getPreviousYearPerformance(data, logPrefix);
+
+        // 获取学历、职称、技能对应的积分
+        ScoreData scoreData = getScoreData(data, logPrefix);
+
+        // 计算累计积分池(生涯积分)
+        BigDecimal sumScore = calculateSumScore(data.allSumScore, scoreData, logPrefix);
+
+        // 计算职级
+        //【三期需求】-不满足三要素(聘任职称/技能、考核结果、积分)按"无职级"初定
+        //当考核结果为无时,职级定为最低级
+        boolean useMinLevel = data.lastYearAppraisalResultEnum == nckd.jxccl.base.common.enums.AppraisalResultEnum.NONE;
+        DynamicObject jobLeve = PositionStructureHelper.calculateLevel(
+                data.jobSeqEnum, sumScore, scoreData.dbProTitleLevel,
+                scoreData.dbOcpQualLevel, data.lastYearAppraisalResult, data.downgradeNum,useMinLevel,Boolean.TRUE);
+
+        if(jobLeve != null) {
+            // 构建职位档案并存入数据库
+            createAndSavePersonPosFile(data, scoreData, data.allSumScore, sumScore, jobLeve, logPrefix, "2");
+
+            //返回定级后的职级。张三:职位级为"初级(1)"
+            setJobLevelResult(data.person, jobLeve);
+        }
+    }
+
+    @Override
+    protected void extractSpecificFields(BaseInitialData data, DynamicObject initialData) {
+        // 总积分
+        data.allSumScore = initialData.getBigDecimal(PositionStructureConstant.NCKD_ALLSUMSCORE);
+        // 工作年限
+        data.employmentYears = initialData.getInt(PositionStructureConstant.NCKD_EMPLOYMENTYEARS);
+    }
+    
+}

+ 0 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/other/.gitkeep


+ 67 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/report/adjust/UnAdjustedReportFormPlugin.java

@@ -0,0 +1,67 @@
+package nckd.jxccl.hr.psms.plugin.report.adjust;
+
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.form.FormShowParameter;
+import kd.bos.form.ShowType;
+import kd.bos.form.control.events.ItemClickEvent;
+import kd.bos.form.control.events.ItemClickListener;
+import kd.bos.report.ReportList;
+import kd.bos.report.plugin.AbstractReportFormPlugin;
+import nckd.jxccl.base.common.constant.FormConstant;
+import nckd.jxccl.hr.psms.common.PositionStructureConstant;
+
+import java.util.EventObject;
+
+/**
+* 未生成动态调整-报表插件
+* @author W.Y.C
+* @date 2025/9/17 17:04
+* @version 1.0
+*/
+public class UnAdjustedReportFormPlugin extends AbstractReportFormPlugin implements ItemClickListener {
+
+    @Override
+    public void afterBindData(EventObject e) {
+        /*super.afterBindData(e);
+        getView().refresh();*/
+    }
+
+    @Override
+    public void registerListener(EventObject e) {
+        //监听工具栏
+        this.addItemClickListeners(FormConstant.TOOLBARAP);
+    }
+
+    @Override
+    public void itemClick(ItemClickEvent evt) {
+        String itemKey = evt.getItemKey();
+        String operationKey = evt.getOperationKey();
+        if("nckd_new".equalsIgnoreCase(itemKey)){
+            //创建调整
+            ReportList reportList = this.getView().getControl(FormConstant.REPORTLISTAP);
+            //获取报表选中的行数据
+            int[] selectedRowIndexes = reportList.getEntryState().getSelectedRows();
+            if(selectedRowIndexes.length == 1){
+                DynamicObject rowData = reportList.getReportModel().getRowData(selectedRowIndexes[0]);
+                //单人调整
+                FormShowParameter showParameter = new FormShowParameter();
+                showParameter.setFormId(PositionStructureConstant.NEWDYNAMICADJUDIALOG_ENTITYID);
+                showParameter.getOpenStyle().setShowType(ShowType.Modal);
+                showParameter.setCaption("新建调整");
+                showParameter.setCustomParam(FormConstant.NCKD_PERSON,rowData.getDynamicObject(FormConstant.NCKD_PERSON).getLong(FormConstant.ID_KEY));
+                this.getView().showForm(showParameter);
+            }else if(selectedRowIndexes.length > 1){
+                //多人调整
+                FormShowParameter showParameter = new FormShowParameter();
+                showParameter.setFormId(PositionStructureConstant.SERVINGINITIAL_ENTITYID);
+                showParameter.getOpenStyle().setShowType(ShowType.Modal);
+                showParameter.setCaption("批量调整");
+                this.getView().showForm(showParameter);
+            }else{
+                this.getView().showErrorNotification("请选择要调整的员工");
+                return;
+            }
+
+        }
+    }
+}

+ 44 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/report/adjust/UnAdjustedReportReportListDataPlugin.java

@@ -0,0 +1,44 @@
+package nckd.jxccl.hr.psms.plugin.report.adjust;
+
+import kd.bos.algo.DataSet;
+import kd.bos.entity.report.AbstractReportListDataPluginExt;
+import kd.bos.event.AfterQueryEvent;
+
+import java.util.StringJoiner;
+
+/**
+* 未生成动态调整-查询扩展插件
+* @author W.Y.C
+* @date 2025/9/17 17:01
+* @version 1.0
+*/
+public class UnAdjustedReportReportListDataPlugin extends AbstractReportListDataPluginExt {
+
+    @Override
+    public void afterQuery(AfterQueryEvent event) {
+        //由于普通查询列表无法对两个实体的字段对比过滤,这里使用报表方式实现。查询结果之后使用algo进一步过滤数据
+
+        //TODO 【待修改】-快速筛选不生效
+        DataSet dataSet = event.getDataSet();
+        // 对标准报表结果进行扩展:新增过滤条件
+        StringJoiner filters = new StringJoiner(" and ");
+        //主组织分配
+        filters.add("hrpi_assignment.isprimary = true");
+        //职位档案不为空
+        filters.add("nckd_personposfile.nckd_person is not null");
+        StringJoiner joinOr = new StringJoiner(" or ");
+        //职称等级变化
+        joinOr.add("nckd_personposfile.nckd_protitlelevel <> hrpi_perprotitle.prolevel");
+        //技能等级不变化
+        joinOr.add("nckd_personposfile.nckd_ocpquallevel <> hrpi_perocpqual.qualification");
+        //学历变化
+        joinOr.add("nckd_personposfile.nckd_diploma <> hrpi_pereduexp.education");
+        //公司变化
+        joinOr.add("nckd_personposfile.useorg <> hrpi_empposorgrel.company");
+        //序列变化
+        // filters.add("nckd_personposfile.nckd_diploma <> hrpi_pereduexp.education");
+        filters.add("("+joinOr.toString()+")");
+        DataSet filter = dataSet.filter(filters.toString());
+        event.setDataSet(filter);
+    }
+}

+ 0 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/workflow/.gitkeep


+ 0 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/webapi/.gitkeep


+ 3 - 2
code/opmc/nckd-jxccl-opmc/src/main/java/nckd/jxccl/opmc/pm/common/PerfManagerFormConstant.java

@@ -20,8 +20,9 @@ public class PerfManagerFormConstant extends FormConstant {
     public static final String END_YEAR_KEY = "nckd_endyear";
     /** 分录-考核年份 */
     public static final String APPRAISAL_YEAR_KEY = "nckd_appraisalyear";
-    /** 人员 */
-    public static final String PERSON_ID_KEY = "nckd_person";
     /**生成调薪按钮*/
     public static final String GENERATE_KEY = "nckd_generate";
+
+    /** 人员考评管理分录实体名称 */
+    public static final String PERFMANAGER_ENTRY_ENTITYID = "nckd_perfmanagerentry";
 }

+ 17 - 17
code/opmc/nckd-jxccl-opmc/src/main/java/nckd/jxccl/opmc/pm/plugin/form/PerfManagerBillPlugin.java

@@ -65,16 +65,16 @@ public class PerfManagerBillPlugin extends AbstractBillPlugIn implements Plugin
     public void beforeDoOperation(BeforeDoOperationEventArgs e) {
         String operateKey = ((FormOperate) e.getSource()).getOperateKey();
         DynamicObject dataEntity = this.getModel().getDataEntity();
-        DynamicObjectCollection entrys = dataEntity.getDynamicObjectCollection(FormConstant.ENTRY_ENTITY_KEY);
-        List<Date> dateList = entrys.stream().map(entry -> entry.getDate(nckd.jxccl.opmc.pm.common.PerfManagerFormConstant.APPRAISAL_YEAR_KEY)).collect(Collectors.toList());
+        DynamicObjectCollection entrys = dataEntity.getDynamicObjectCollection(PerfManagerFormConstant.PERFMANAGER_ENTRY_ENTITYID);
+        List<Date> dateList = entrys.stream().map(entry -> entry.getDate(PerfManagerFormConstant.APPRAISAL_YEAR_KEY)).collect(Collectors.toList());
         if(FormConstant.SAVE_KEY.equals(operateKey)){
             if(CollectionUtils.isNotEmpty(dateList) && !dateList.stream().allMatch(Objects::isNull)) {
-                Date beginYear = dataEntity.getDate(nckd.jxccl.opmc.pm.common.PerfManagerFormConstant.BEGIN_YEAR_KEY);
-                Date endYear = dataEntity.getDate(nckd.jxccl.opmc.pm.common.PerfManagerFormConstant.END_YEAR_KEY);
+                Date beginYear = dataEntity.getDate(PerfManagerFormConstant.BEGIN_YEAR_KEY);
+                Date endYear = dataEntity.getDate(PerfManagerFormConstant.END_YEAR_KEY);
                 if(beginYear != null && endYear != null){
                     //校验考核年份是否在周期内
                     boolean hasOutOfRangeEntry = entrys != null ? entrys.stream()
-                            .map(entry -> entry.getDate(nckd.jxccl.opmc.pm.common.PerfManagerFormConstant.APPRAISAL_YEAR_KEY))
+                            .map(entry -> entry.getDate(PerfManagerFormConstant.APPRAISAL_YEAR_KEY))
                             .filter(Objects::nonNull)
                             .anyMatch(date -> !DateUtil.isInRange(DateUtil.toLocalDateTime(date), DateUtil.toLocalDateTime(beginYear), DateUtil.toLocalDateTime(endYear))) : false;
                     if(hasOutOfRangeEntry){
@@ -92,26 +92,26 @@ public class PerfManagerBillPlugin extends AbstractBillPlugIn implements Plugin
                 }
 
                 //判断人员是否有重叠的考核周期
-                DynamicObject person = (DynamicObject) this.getModel().getValue(nckd.jxccl.opmc.pm.common.PerfManagerFormConstant.PERSON_ID_KEY);
-                QFilter finalFilter = new QFilter(nckd.jxccl.opmc.pm.common.PerfManagerFormConstant.PERSON_ID_KEY, QCP.equals, person.getLong(nckd.jxccl.opmc.pm.common.PerfManagerFormConstant.ID_KEY));
-                Object id = this.getModel().getValue(nckd.jxccl.opmc.pm.common.PerfManagerFormConstant.ID_KEY);
+                DynamicObject person = (DynamicObject) this.getModel().getValue(PerfManagerFormConstant.NCKD_PERSON);
+                QFilter finalFilter = new QFilter(PerfManagerFormConstant.NCKD_PERSON, QCP.equals, person.getLong(PerfManagerFormConstant.ID_KEY));
+                Object id = this.getModel().getValue(PerfManagerFormConstant.ID_KEY);
                 if (id != null) {
-                    QFilter filter = new QFilter(nckd.jxccl.opmc.pm.common.PerfManagerFormConstant.ID_KEY, QCP.not_equals, id);
+                    QFilter filter = new QFilter(PerfManagerFormConstant.ID_KEY, QCP.not_equals, id);
                     finalFilter.and(filter);
                 }
-                QFilter filter = new QFilter(nckd.jxccl.opmc.pm.common.PerfManagerFormConstant.ENTRY_ENTITY_KEY + "." + nckd.jxccl.opmc.pm.common.PerfManagerFormConstant.APPRAISAL_YEAR_KEY, QCP.in, dateList);
+                QFilter filter = new QFilter(PerfManagerFormConstant.PERFMANAGER_ENTRY_ENTITYID + "." + PerfManagerFormConstant.APPRAISAL_YEAR_KEY, QCP.in, dateList);
                 finalFilter.and(filter);
-                DynamicObjectCollection query = QueryServiceHelper.query(nckd.jxccl.opmc.pm.common.PerfManagerFormConstant.ENTITY_NAME_KEY, nckd.jxccl.opmc.pm.common.PerfManagerFormConstant.ENTRY_ENTITY_KEY + "." + nckd.jxccl.opmc.pm.common.PerfManagerFormConstant.APPRAISAL_YEAR_KEY, new QFilter[]{finalFilter}, null);
+                DynamicObjectCollection query = QueryServiceHelper.query(PerfManagerFormConstant.ENTITY_NAME_KEY, PerfManagerFormConstant.PERFMANAGER_ENTRY_ENTITYID + "." + PerfManagerFormConstant.APPRAISAL_YEAR_KEY, new QFilter[]{finalFilter}, null);
                 if(!query.isEmpty()){
                     List<String> yearList = query.stream()
                             .map(dynamicObject -> {
-                                Date date = dynamicObject.getDate(nckd.jxccl.opmc.pm.common.PerfManagerFormConstant.ENTRY_ENTITY_KEY + "." + nckd.jxccl.opmc.pm.common.PerfManagerFormConstant.APPRAISAL_YEAR_KEY);
+                                Date date = dynamicObject.getDate(PerfManagerFormConstant.PERFMANAGER_ENTRY_ENTITYID + "." + PerfManagerFormConstant.APPRAISAL_YEAR_KEY);
                                 return DateUtil.format(date, "yyyy");
                             })
                             .collect(Collectors.toList());
                     String yearJoin = String.join("、", yearList);
                     e.setCancel(true);
-                    this.getView().showErrorNotification(StrFormatter.format("人员【{}】已存在【{}】年的考核结果,不能重复添加", person.getString(nckd.jxccl.opmc.pm.common.PerfManagerFormConstant.NAME_KEY), yearJoin));
+                    this.getView().showErrorNotification(StrFormatter.format("人员【{}】已存在【{}】年的考核结果,不能重复添加", person.getString(PerfManagerFormConstant.NAME_KEY), yearJoin));
                 }
             }
         }
@@ -125,14 +125,14 @@ public class PerfManagerBillPlugin extends AbstractBillPlugIn implements Plugin
      * @version 1.0
      */
     private void setEntryYear() {
-        EntryGrid entryGrid = this.getControl(FormConstant.ENTRY_ENTITY_KEY);
+        EntryGrid entryGrid = this.getControl(PerfManagerFormConstant.PERFMANAGER_ENTRY_ENTITYID);
         List<FieldEdit> fieldEdits = entryGrid.getFieldEdits();
         for (FieldEdit fieldEdit : fieldEdits) {
             //选择周期结束年份后设置分录考核年份只能在周期开始和结束范围内
-            if(nckd.jxccl.opmc.pm.common.PerfManagerFormConstant.APPRAISAL_YEAR_KEY.equalsIgnoreCase(fieldEdit.getProperty().getName())) {
+            if(PerfManagerFormConstant.APPRAISAL_YEAR_KEY.equalsIgnoreCase(fieldEdit.getProperty().getName())) {
                 DateEdit appraisalYearEdit = (DateEdit) fieldEdit;
-                Date beginYear = ConvertUtil.toDate(this.getModel().getValue(nckd.jxccl.opmc.pm.common.PerfManagerFormConstant.BEGIN_YEAR_KEY));
-                Date endYear = ConvertUtil.toDate(this.getModel().getValue(nckd.jxccl.opmc.pm.common.PerfManagerFormConstant.END_YEAR_KEY));
+                Date beginYear = ConvertUtil.toDate(this.getModel().getValue(PerfManagerFormConstant.BEGIN_YEAR_KEY));
+                Date endYear = ConvertUtil.toDate(this.getModel().getValue(PerfManagerFormConstant.END_YEAR_KEY));
                 appraisalYearEdit.setMinDate(beginYear);
                 appraisalYearEdit.setMaxDate(endYear);
             }