Explorar el Código

feat(hr): 实现年度调整功能模块

- 新增年度调整表单插件 AnnualAdjustmentFormPlugin
- 新增已生成年度调整查询列表插件 AnnualAdjustQueryListPlugin
- 新增年度调整生效表单插件 AnnualEffectiveFormPlugin- 新增批量新建年度调整表单插件 NewAnnualAdjustFormPlugin
- 新增未生成年度调整列表插件 UnAnnualAdjustListPlugin- 新增年度调整操作插件 AnnualAdjustmentOperationPlugin
-重命名动态调整查询插件类名
-优化调整日期比较逻辑
- 完善职位等级结果空值判断
- 更新动态调整状态字段引用
- 添加创建时间和修改人信息设置
- 修复日志记录类引用错误
- 调整职位档案最新标记逻辑
wyc hace 2 semanas
padre
commit
e56e12ad86
Se han modificado 30 ficheros con 2030 adiciones y 159 borrados
  1. 4 0
      code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/constant/FormConstant.java
  2. 72 0
      code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/enums/psms/AdjustTypeEnum.java
  3. 1 1
      code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/enums/psms/JobSeqEnum.java
  4. 66 0
      code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/enums/psms/TypeStateEnum.java
  5. 15 6
      code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/utils/QueryFieldBuilder.java
  6. 1 0
      code/base/nckd-jxccl-base-helper/src/main/java/nckd/jxccl/base/org/helper/AdminOrgHelper.java
  7. 2 1
      code/base/nckd-jxccl-base-helper/src/main/java/nckd/jxccl/base/orm/helper/QFilterCommonHelper.java
  8. 148 51
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/business/AnnualAdjustmentService.java
  9. 69 21
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/business/JobLevelCalculatorService.java
  10. 57 4
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/common/PositionStructureConstant.java
  11. 67 14
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/helper/PositionStructureHelper.java
  12. 1 1
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/adjust/DynamicAdjustQueryListPlugin.java
  13. 0 15
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/adjust/NewDynamicAdjustmentBatchDiaLogFormPlugin.java
  14. 24 16
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/adjust/NewDynamicAdjustmentDiaLogFormPlugin.java
  15. 128 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/annualadjust/AnnualAdjustQueryListPlugin.java
  16. 87 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/annualadjust/AnnualAdjustmentFormPlugin.java
  17. 65 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/annualadjust/AnnualEffectiveFormPlugin.java
  18. 64 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/annualadjust/NewAnnualAdjustFormPlugin.java
  19. 90 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/annualadjust/UnAnnualAdjustListPlugin.java
  20. 26 14
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/adjust/NewDynamicAdjustmentOperationPlugIn.java
  21. 225 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/annualadjust/AnnualAdjustmentOperationPlugin.java
  22. 175 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/annualadjust/AnnualEffectiveOpPlugin.java
  23. 274 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/annualadjust/AnnualLockOrUnLockedOpPlugin.java
  24. 155 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/annualadjust/AnnualSetinActiveOpPlugin.java
  25. 65 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/annualadjust/PosFileUnlockBillOpPlugin.java
  26. 127 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/file/PersonPosFileDeleteOpPlugin.java
  27. 3 3
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/initial/BaseInitialOperationPlugIn.java
  28. 1 1
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/initial/NewHireInitialOperationPlugIn.java
  29. 8 5
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/report/adjust/UnAdjustedReportFormPlugin.java
  30. 10 6
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/report/adjust/UnAdjustedReportReportListDataPlugin.java

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

@@ -81,6 +81,8 @@ public class FormConstant {
     public static final String REPORTLISTAP = "reportlistap";
     /** 多选基础资料*/
     public static final String BASEDATAID_KEY = "fbasedataid";
+    /** 多选基础资料的ID*/
+    public static final String FBASEDATAID_ID_KEY = "fbasedataid_id";
     /**工具栏*/
     public static final String TBMAIN = "tbmain";
     /** 左树右表-是否包含子部门*/
@@ -173,6 +175,8 @@ public class FormConstant {
     public static final String FIRSTJOINCOMDATE_KEY = "FIRSTJOINCOMDATE";
     /** 毕业时间 */
     public static final String GRADUTIONDATE = "gradutiondate";
+    /** 最高学历 */
+    public static final String ISHIGHESTDEGREE = "ishighestdegree";
     /** 当前user id*/
     public static final String KEY_CURR_USER_ID = "KEY_CURR_USER_ID";
     /** 当前org id*/

+ 72 - 0
code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/enums/psms/AdjustTypeEnum.java

@@ -0,0 +1,72 @@
+package nckd.jxccl.base.common.enums.psms;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 职位体系-调整类别
+ * @author W.Y.C
+ * @date 2025/10/14 10:35
+ * @version 1.0
+ */
+public enum AdjustTypeEnum {
+
+    //升级2;保级1;降级0;首次聘任3;序列变化4;聘任下调5;总分不足6;无聘任7;无考核结果8
+    /**降级*/
+    DOWN_LEVEL("0", "降级"),
+    /**升级*/
+    UP_LEVEL("2", "升级"),
+    /**保级*/
+    KEEP_LEVEL("1", "保级"),
+    /**首次聘任*/
+    FIRST_EMPLOYMENT("3", "首次聘任"),
+    /**序列变化*/
+    SEQUENCE_CHANGE("4", "序列变化"),
+    /**聘任下调*/
+    EMPLOYMENT_DOWN("5", "聘任下调"),
+    /**总分不足*/
+    TOTAL_SCORE_NOT_ENOUGH("6", "总分不足"),
+    /**无聘任*/
+    NO_EMPLOYMENT("7", "无聘任"),
+    /**无考核结果*/
+    NO_ASSESSMENT_RESULT("8", "无考核结果");
+
+    private static final Map<String, AdjustTypeEnum> CODE_MAP = new HashMap<>();
+
+    static {
+        for (AdjustTypeEnum value : AdjustTypeEnum.values()) {
+            CODE_MAP.put(value.code, value);
+        }
+    }
+
+    private final String code;
+    private final String name;
+
+    AdjustTypeEnum(String code, String name) {
+        this.code = code;
+        this.name = name;
+    }
+
+    /**
+     * 根据编码获取枚举
+     * @param code
+     * @return: AdjustTypeEnum
+     * @author W.Y.C
+     * @date: 2025/09/12 14:33
+     */
+    public static AdjustTypeEnum getByCode(String code) {
+        if (code == null) {
+            return null;
+        }
+        return CODE_MAP.get(code);
+    }
+
+
+    public String getCode() {
+        return code;
+    }
+
+    public String getName() {
+        return name;
+    }
+}

+ 1 - 1
code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/enums/JobSeqEnum.java → code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/enums/psms/JobSeqEnum.java

@@ -1,4 +1,4 @@
-package nckd.jxccl.base.common.enums;
+package nckd.jxccl.base.common.enums.psms;
 
 import java.util.HashMap;
 import java.util.Map;

+ 66 - 0
code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/enums/psms/TypeStateEnum.java

@@ -0,0 +1,66 @@
+package nckd.jxccl.base.common.enums.psms;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 职位体系-类型状态
+ * @author W.Y.C
+ * @date 2025/10/14 10:35
+ * @version 1.0
+ */
+public enum TypeStateEnum {
+
+    // 新入职人员初定1;在职人员初定2;年度调整3;职位调动4;管理序列的聘任5;高级职称的聘任6
+    NEW_ENTRY("1", "新入职人员初定"),
+    /**在职人员定级*/
+    IN_SERVICE_LEVEL("2", "在职人员初定"),
+    /**年度调整*/
+    ANNUAL_ADJUSTMENT("3", "年度调整"),
+    /**职位调动*/
+    POSITION_TRANSFER("4", "动态调整"),
+    /**管理序列的聘任*/
+    MANAGEMENT_SEQUENCE_EMPLOYMENT("5", "管理序列的聘任"),
+    /**高级职称的聘任*/
+    HIGH_PROFESSIONAL_EMPLOYMENT("6", "高级职称的聘任");
+
+
+    private static final Map<String, TypeStateEnum> CODE_MAP = new HashMap<>();
+
+    static {
+        for (TypeStateEnum value : TypeStateEnum.values()) {
+            CODE_MAP.put(value.code, value);
+        }
+    }
+
+    private final String code;
+    private final String name;
+
+    TypeStateEnum(String code, String name) {
+        this.code = code;
+        this.name = name;
+    }
+
+    /**
+     * 根据编码获取枚举
+     * @param code
+     * @return: AdjustTypeEnum
+     * @author W.Y.C
+     * @date: 2025/09/12 14:33
+     */
+    public static TypeStateEnum getByCode(String code) {
+        if (code == null) {
+            return null;
+        }
+        return CODE_MAP.get(code);
+    }
+
+
+    public String getCode() {
+        return code;
+    }
+
+    public String getName() {
+        return name;
+    }
+}

+ 15 - 6
code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/utils/QueryFieldBuilder.java

@@ -134,7 +134,8 @@ public final class QueryFieldBuilder {
      * @date: 2025/09/27
      */
     public QueryFieldBuilder orderAsc(String[] prefixParts, String... fieldParts) {
-        orderJoiner.add(joinParts(concat(prefixParts, fieldParts)) + " asc");
+        Arrays.stream(fieldParts)
+                .forEach(f -> orderJoiner.add(joinParts(concat(prefixParts, f))+" asc"));
         return this;
     }
 
@@ -150,7 +151,9 @@ public final class QueryFieldBuilder {
      * @date: 2025/09/27
      */
     public QueryFieldBuilder orderAsc(String... parts) {
-        orderJoiner.add(joinParts(parts) + " asc");
+        for (String part : parts) {
+            orderJoiner.add(joinParts(part) + " asc");
+        }
         return this;
     }
 
@@ -167,7 +170,8 @@ public final class QueryFieldBuilder {
      * @date: 2025/09/27
      */
     public QueryFieldBuilder orderDesc(String[] prefixParts, String... fieldParts) {
-        orderJoiner.add(joinParts(concat(prefixParts, fieldParts)) + " desc");
+        Arrays.stream(fieldParts)
+                .forEach(f -> orderJoiner.add(joinParts(concat(prefixParts, f))+ " desc"));
         return this;
     }
 
@@ -183,7 +187,9 @@ public final class QueryFieldBuilder {
      * @date: 2025/09/27
      */
     public QueryFieldBuilder orderDesc(String... parts) {
-        orderJoiner.add(joinParts(parts) + " desc");
+        for (String part : parts) {
+            orderJoiner.add(joinParts(part) + " desc");
+        }
         return this;
     }
 
@@ -200,7 +206,8 @@ public final class QueryFieldBuilder {
      * @date: 2025/09/27
      */
     public QueryFieldBuilder orderBy(String[] prefixParts, String... fieldParts) {
-        orderJoiner.add(joinParts(concat(prefixParts, fieldParts)));
+        Arrays.stream(fieldParts)
+                .forEach(f -> orderJoiner.add(joinParts(concat(prefixParts, f))));
         return this;
     }
 
@@ -216,7 +223,9 @@ public final class QueryFieldBuilder {
      * @date: 2025/09/27
      */
     public QueryFieldBuilder orderBy(String... parts) {
-        orderJoiner.add(joinParts(parts));
+        for (String part : parts) {
+            orderJoiner.add(joinParts(part));
+        }
         return this;
     }
 

+ 1 - 0
code/base/nckd-jxccl-base-helper/src/main/java/nckd/jxccl/base/org/helper/AdminOrgHelper.java

@@ -6,6 +6,7 @@ import kd.bos.orm.query.QCP;
 import kd.bos.orm.query.QFilter;
 import kd.bos.servicehelper.QueryServiceHelper;
 import kd.hr.hbp.business.dao.factory.HRBaseDaoFactory;
+import kd.tdc.oatr.bussiness.queryservice.AdminOrgQueryService;
 import nckd.jxccl.base.common.constant.FormConstant;
 import nckd.jxccl.base.common.utils.ConvertUtil;
 import nckd.jxccl.base.orm.helper.QFilterCommonHelper;

+ 2 - 1
code/base/nckd-jxccl-base-helper/src/main/java/nckd/jxccl/base/orm/helper/QFilterCommonHelper.java

@@ -10,6 +10,7 @@ import kd.bos.orm.query.QFilter;
 import nckd.jxccl.base.common.constant.FormConstant;
 import nckd.jxccl.base.common.utils.ConvertUtil;
 
+import java.util.Collection;
 import java.util.List;
 
 /**
@@ -140,7 +141,7 @@ public final class QFilterCommonHelper {
      * @author W.Y.C
      * @date: 2025/07/07 09:20
      */
-    public static QFilter getIdInFilter(List<Long> idList) {
+    public static QFilter getIdInFilter(Collection<Long> idList) {
         return new QFilter(FormConstant.ID_KEY, QCP.in, idList);
     }
 }

+ 148 - 51
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/business/AnnualAdjustmentService.java

@@ -8,26 +8,33 @@ import kd.bos.orm.query.QFilter;
 import kd.bos.servicehelper.BusinessDataServiceHelper;
 import nckd.jxccl.base.common.constant.FormConstant;
 import nckd.jxccl.base.common.enums.AppraisalResultEnum;
+import nckd.jxccl.base.common.enums.psms.TypeStateEnum;
 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.QueryFieldBuilder;
 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.common.bo.PositionAppointmentBO;
 import nckd.jxccl.hr.psms.helper.PositionStructureHelper;
 import org.apache.commons.lang3.StringUtils;
 
 import java.math.BigDecimal;
+import java.time.LocalDate;
 import java.time.LocalDateTime;
+import java.time.ZoneId;
 import java.util.Date;
 import java.util.LinkedHashMap;
 import java.util.Map;
+import java.util.Optional;
+import java.util.StringJoiner;
 
 /**
- * 年度调整服务类。
- * <p>重构目标:逻辑清晰、职责单一、条件判断明确、可维护可测试;保持原始业务逻辑完整性。</p>
- */
+* 年度调整服务类
+* @author W.Y.C
+* @date 2025/10/08 22:12
+* @version 1.0
+*/
 public class AnnualAdjustmentService {
 
 
@@ -35,22 +42,34 @@ public class AnnualAdjustmentService {
      * 生成年度调整记录
      * @param person    员工
      * @param beginDate   生效日期
-     * @return 成功返回 null;失败返回错误信息
+     * @return 成功返回 新的职位档案
      * @note 对应SHR:PersonpositionfilecreateViewListHandler#addNewYear_PersonpositionfileInfo(277~815行)
      */
-    public DynamicObject addNewYearPersonPositionFileInfo(DynamicObject person, Date beginDate,String remark) {
+    public static DynamicObject addNewYearPersonPositionFileInfo(DynamicObject person, Date beginDate,String remark,PositionAppointmentBO positionAppointment,DynamicObject performanceResult,DynamicObject firstPersonPosFile) {
         int executeYear = beginDate != null ? DateUtil.getYear(beginDate) : DateUtil.getYear(new Date());
-        //判断当前年是否已执行过年度调整
-        DynamicObject[] personPosFileByYear = PositionStructureHelper.getPersonPosFileByPersonAndState(person.getLong(FormConstant.ID_KEY), new String[]{"3"}, null, new QFilter(PositionStructureConstant.NCKD_EXECUTEYEAR, QCP.equals, executeYear));
-        if(personPosFileByYear != null && personPosFileByYear.length > 0){
-            throw new ValidationException(StrFormatter.format("人员【{}】已存在【{}】年的年度调整!", person.getString(FormConstant.NAME_KEY), executeYear));
-        }
 
         // 1、 初始化上下文并加载基础数据。
         // 对应SHR:291~339行
-        AdjustmentContext ac = initAndLoad(executeYear, person, beginDate);
+        Date adjustDate = beginDate;
+        if (adjustDate != null) {
+            // 如果adjustDate不为空且为年初日期(xxxx-01-01),则转换为当年7月1日
+            LocalDateTime adjustDateTime = DateUtil.toLocalDateTime(adjustDate);
+            if (adjustDateTime.getMonthValue() == 1 && adjustDateTime.getDayOfMonth() == 1) {
+                //前端只选年份,这里默认为XXX-07-01
+                adjustDate = DateUtil.toDate(adjustDateTime.withMonth(7).withDayOfMonth(1));
+            }
+        }else{
+            //获取当年07-01的日期
+            /*LocalDate julyFirst = LocalDate.of(DateUtil.getYear(new Date()), 7, 1);
+            adjustDate = Date.from(julyFirst.atStartOfDay(ZoneId.systemDefault()).toInstant());*/
+            adjustDate = new Date();
+        }
+        String adjustDateStr = DateUtil.format(adjustDate,DateUtil.NORM_DATE_PATTERN);
+        AdjustmentContext ac = initAndLoad(executeYear, person, beginDate,adjustDate,positionAppointment,performanceResult,firstPersonPosFile);
         ac.remark = remark;
 
+
+
         //2、 判断是否为本年首次调整、考核是否已被使用,并根据规则决定是否使用R排名。
         //对应SHR:341~391行
         evaluateFirstAndAppraisalUsage(ac);
@@ -68,7 +87,7 @@ public class AnnualAdjustmentService {
 
         // 5、获取技能/职称分
         //对应SHR:451~481行
-        JobLevelCalculatorService.JobScoreInfo jobScoreInfo = JobLevelCalculatorService.handleJobScores(convertJobSeq, ac.positionAppointment);
+        JobLevelCalculatorService.JobScoreInfo jobScoreInfo = JobLevelCalculatorService.handleJobScores(ac.lastRecordInfo,convertJobSeq, ac.positionAppointment);
         ac.data.setJobScoreInfo(jobScoreInfo);
 
         // 6、计算学历得分并生成说明
@@ -76,6 +95,17 @@ public class AnnualAdjustmentService {
         ac.diplomaScore = JobLevelCalculatorService.handleDiplomaScore(ac.lastRecordInfo, ac.positionAppointment, jobScoreInfo);
         ac.whyDiplomaScore.putAll(jobScoreInfo.whyDiplomaScore);
 
+        //---------------------------------- 调整日志 begin ----------------------------------
+        ac.whyAdjust1.add(StrFormatter.format("毕业时间小于等于【{}】的最高学历【{}】,学历分【{}】{}",
+                adjustDateStr, ConvertUtil.toStr(jobScoreInfo.diplomaName,"无"), ConvertUtil.toStr(jobScoreInfo.diplomaScore,"无"),jobScoreInfo.isDiplomaChange ? "(变化)":""));
+        ac.whyAdjust1.add(StrFormatter.format("聘任时间小于等于【{}】的职称【{}】,职称等级【{}】,职称等级分【{}】{}",
+                adjustDateStr,ConvertUtil.toStr(jobScoreInfo.rankName,"无"),ConvertUtil.toStr(jobScoreInfo.perProTitleName,"无"),ConvertUtil.toStr(jobScoreInfo.perProTitleScore,"无"),
+                jobScoreInfo.isPerProTitleChange ? "(变化)":""));
+        ac.whyAdjust1.add(StrFormatter.format("聘任时间小于等于【{}】的技能【{}】,技能等级【{}】,技能等级分【{}】{}",
+                adjustDateStr,ConvertUtil.toStr(jobScoreInfo.jobStatusName,"无"),ConvertUtil.toStr(jobScoreInfo.quaLevelName,"无"),ConvertUtil.toStr(jobScoreInfo.quaLevelScore,"无"),
+                jobScoreInfo.isQuaLeveleChange ? "(变化)":""));
+        //---------------------------------- 调整日志 end ----------------------------------
+
         // 7、汇总年度积分池与综合分数。
         //对应SHR:483~519行
         aggregateScores(ac);
@@ -103,9 +133,11 @@ public class AnnualAdjustmentService {
                     ac.data.getAppraisalResultName()));
         }
 
-        JobLevelCalculatorService.JobLevelResult jobLevelResult = JobLevelCalculatorService.calculateJobLevel(person, beginDate, ac.positionAppointment);
-        ac.data.getRankingResultInfo().allowanceRankMark = jobLevelResult.rankingResultInfo.allowanceRankMark;
-        ac.data.getRankingResultInfo().allowanceRankSel = jobLevelResult.rankingResultInfo.allowanceRankSel;
+        JobLevelCalculatorService.JobLevelResult jobLevelResult = JobLevelCalculatorService.calculateJobLevel(person, beginDate == null ? new Date() : beginDate, ac.positionAppointment);
+        if(jobLevelResult.rankingResultInfo != null) {
+            ac.data.getRankingResultInfo().allowanceRankMark = jobLevelResult.rankingResultInfo.allowanceRankMark;
+            ac.data.getRankingResultInfo().allowanceRankSel = jobLevelResult.rankingResultInfo.allowanceRankSel;
+        }
 
         //10、构建职位档案
         return buildPersonPositionFile(ac, jobLevel);
@@ -116,12 +148,15 @@ public class AnnualAdjustmentService {
      * 初始化上下文并加载基础数据。
      * @param executeYear 执行年份
      * @param person 员工
-     * @param beginDate 生效日期
+     * @param beginDate 生效日期(可以为空,为空默认为不生效状态)
+     * @param positionAppointment 员工最新任职信息
+     * @param performanceResult 上年度考核结果
+     * @param firstPersonPosFile 初定档案
      * @return: nckd.jxccl.hr.psms.business.AnnualAdjustmentService.AdjustmentContext
      * @author W.Y.C
      * @date: 2025/10/08 21:15
      */
-    private AdjustmentContext initAndLoad(Integer executeYear, DynamicObject person, Date beginDate){
+    private static AdjustmentContext initAndLoad(Integer executeYear, DynamicObject person, Date beginDate,Date adjustDate,PositionAppointmentBO positionAppointment,DynamicObject performanceResult,DynamicObject firstPersonPosFile){
         AdjustmentContext ac = new AdjustmentContext();
         ac.executeYear = executeYear;
         ac.nowYear = executeYear;
@@ -133,19 +168,21 @@ public class AnnualAdjustmentService {
         int lastYear = executeYear - 1;
         LocalDateTime lastYearDateTime = LocalDateTime.of(lastYear, 1, 1, 0, 0);
 
+        ac.whyAdjust1.add(StrFormatter.format("选择的调整时间:【{}】", DateUtil.format(adjustDate,DateUtil.NORM_DATE_PATTERN)));
+
 
         //对应SHR:291行;utils.YearContributeScoreBillEntryScoreSumBypersonidAndYear
         //TODO 1、获取年度贡献积分
         //yearscoresuma、yearscoresumb、yearscoresumc、yearscoresumd、yearscoresume、yearscoresumf、yearscoresumg、yearscoresumh、yearscoresumi、allyearscoresum、yearscoresuma
 
         //2、获取上年度考核结果(对应SHR:PersonpositionfileUtils:884~914行)
-        DynamicObject performanceResult = PerformanceManagerHelper.getPerformanceResult(ac.personId, lastYearDateTime);
+        // DynamicObject performanceResult = PerformanceManagerHelper.getPerformanceResult(ac.personId, lastYearDateTime);
         if(performanceResult == null){
             throw new ValidationException(StrFormatter.format("人员【{}】缺少【{}】年考核结果", ac.personName,lastYear));
         }
         AnnualAdjustmentData data = new AnnualAdjustmentData();
         data.setAppraisalResult(performanceResult);
-        data.setAppraisalResultNumber(performanceResult.getString(FormConstant.ID_KEY));
+        data.setAppraisalResultNumber(performanceResult.getString(FormConstant.NUMBER_KEY));
         data.setAppraisalResultName(performanceResult.getString(FormConstant.NAME_KEY));
         data.setAppraisalResultId(performanceResult.getLong(FormConstant.ID_KEY));
         data.setAppraisalResultScore(performanceResult.getBigDecimal(FormConstant.NCKD_SCORE));
@@ -153,7 +190,7 @@ public class AnnualAdjustmentService {
 
         //3.获取员工上年度年度R排名(对应SHR:PersonpositionfileUtils:915~945行)
         DynamicObject lastPersonPosFile = null;
-        JobLevelCalculatorService.RankingResultInfo rankingInfo = JobLevelCalculatorService.getRankingInfo(ac.personId, ac.personName, beginDate);
+        JobLevelCalculatorService.RankingResultInfo rankingInfo = JobLevelCalculatorService.getRankingInfo(ac.personId, ac.personName, beginDate == null ? new Date() : beginDate);
         data.setRankingResultInfo(rankingInfo);
 
         //4.查询上一条有效年度调整记录(对应SHR:PersonpositionfileUtils:946~997行)
@@ -164,20 +201,23 @@ public class AnnualAdjustmentService {
                         .orderDesc(PositionStructureConstant.NCKD_EXECUTEYEAR,PositionStructureConstant.MODIFY_TIME_KEY,PositionStructureConstant.NCKD_BEGINDATE).buildOrderArray());
         if(personPosFileByPersonAndState == null || personPosFileByPersonAndState.length < 1){
             //没有年度调整记录取初定记录
-            lastPersonPosFile = PositionStructureHelper.getFirstRank(ac.personId);
-            if(lastPersonPosFile == null){
+            if(firstPersonPosFile == null){
                 throw new ValidationException(StrFormatter.format("当前无法为【{}】进行调整,因为他/她尚未建立职位档案。请前往“职位及积分初定” -> 进行初定!", ac.personName));
             }
+            lastPersonPosFile = firstPersonPosFile;
             data.setFirstId(lastPersonPosFile.getLong(FormConstant.ID_KEY));
+            String typeState = lastPersonPosFile.getString(PositionStructureConstant.NCKD_TYPESTATE);
+            ac.whyAdjust1.add(StrFormatter.format("上一条有效职位档案为【{}】档案,初定时间:【{}】;", TypeStateEnum.getByCode(typeState).getName(), DateUtil.format(lastPersonPosFile.getDate(PositionStructureConstant.NCKD_BEGINDATE),DateUtil.NORM_DATE_PATTERN)));
         }else{
             lastPersonPosFile = personPosFileByPersonAndState[0];
             data.setLastId(lastPersonPosFile.getLong(FormConstant.ID_KEY));
+            String typeState = lastPersonPosFile.getString(PositionStructureConstant.NCKD_TYPESTATE);
+            ac.whyAdjust1.add(StrFormatter.format("上一条有效职位档案为【{}】档案,调整时间:【{}】", TypeStateEnum.getByCode(typeState).getName(),DateUtil.format(lastPersonPosFile.getDate(PositionStructureConstant.NCKD_BEGINDATE),DateUtil.NORM_DATE_PATTERN)));
         }
         if(lastPersonPosFile != null) {
             //(firstid、lastsumscore、lastdiplomascore、lastrankscore、lastjobstatusscore、lasthrjobfamilynumber、lastjobgradefid、lastjobgradeindex)
-
-
             data.setLastSumScore(lastPersonPosFile.getBigDecimal(PositionStructureConstant.NCKD_SUMSCORE));
+
             //lastdiplomascore
             //lastrankscore
             //lastjobstatusscore
@@ -187,11 +227,43 @@ public class AnnualAdjustmentService {
             DynamicObject jobLevel = lastPersonPosFile.getDynamicObject(PositionStructureConstant.NCKD_JOBLEVELHR);
             data.setLastJobLevel(jobLevel);
             data.setLastJobGradeIndex(jobLevel.getInt(FormConstant.JOBLEVELSEQ));
-            data.setLastJobGradeIndex(jobLevel.getInt(FormConstant.JOBLEVELSEQ));
+
+            //---------------------------------- 调整日志 begin ----------------------------------
+            ac.whyAdjust1.add(StrFormatter.format("上一职位序列【{}】", jobSeq.getString(FormConstant.NAME_KEY)));
+            ac.whyAdjust1.add(StrFormatter.format("上一职位档案总积分【{}】", lastPersonPosFile.getBigDecimal(PositionStructureConstant.NCKD_ALLSUMSCORE)));
+            ac.whyAdjust1.add(StrFormatter.format("上一职位档案积分池积分【{}】", data.getLastSumScore()));
+
+            DynamicObject diploma = lastPersonPosFile.getDynamicObject(PositionStructureConstant.NCKD_DIPLOMA);
+            BigDecimal diplomaScore = lastPersonPosFile.getBigDecimal(PositionStructureConstant.NCKD_DIPLOMASCORE);
+            ac.whyAdjust1.add(StrFormatter.format("上一职位档案学历【{}】,学历分【{}】", diploma == null ? "无" : diploma.getString(FormConstant.NAME_KEY), diplomaScore == null ? "无" : diplomaScore));
+
+            String rankName = lastPersonPosFile.getString(PositionStructureConstant.NCKD_RANKNAME);
+            rankName = StringUtils.isEmpty(rankName) ? "无" : rankName;
+            DynamicObject proTitleLevel = lastPersonPosFile.getDynamicObject(PositionStructureConstant.NCKD_PROTITLELEVEL);
+            String ranksScore = lastPersonPosFile.getString(PositionStructureConstant.NCKD_RANKSCORE);
+            ac.whyAdjust1.add(StrFormatter.format("上一职位档案职称【{}】,职称等级【{}】,职称等级分【{}】", rankName,proTitleLevel == null ? "无" : proTitleLevel.getString(FormConstant.NAME_KEY), ranksScore == null ? "无" : ranksScore));
+
+            String jobStatusName = lastPersonPosFile.getString(PositionStructureConstant.NCKD_JOBSTATUSNAME);
+            jobStatusName = StringUtils.isEmpty(jobStatusName) ? "无" : jobStatusName;
+            DynamicObject ocpQualLevel = lastPersonPosFile.getDynamicObject(PositionStructureConstant.NCKD_OCPQUALLEVEL);
+            String jobStatusScore = lastPersonPosFile.getString(PositionStructureConstant.NCKD_JOBSTATUSSCORE);
+            ac.whyAdjust1.add(StrFormatter.format("上一职位档案技能【{}】,技能等级【{}】,技能等级分【{}】", jobStatusName,ocpQualLevel == null ? "无" : ocpQualLevel.getString(FormConstant.NAME_KEY), jobStatusScore == null ? "无" : jobStatusScore));
+            ac.whyAdjust1.add(StrFormatter.format("上一职位档案职级【{}({})】", jobLevel.getString(FormConstant.NAME_KEY),data.getLastJobGradeIndex()));
+            ac.whyAdjust1.add("----------------------------------------------------------");
+            //---------------------------------- 调整日志 end ----------------------------------
+        }
+
+        ac.whyAdjust1.add(StrFormatter.format("上年度【{}】考核结果为【{}】;", lastYear, performanceResult.getString(FormConstant.NAME_KEY)));
+        if(rankingInfo == null) {
+            ac.whyAdjust1.add("无全排名");
+        }else{
+            ac.whyAdjust1.add(StrFormatter.format("上年度【{}】排名单元【{}】,全排名名次【{}】,全排名总人数【{}】,R排名名次【{}】,R排名总人数【{}】;",
+                    lastYear, rankingInfo.groupName,rankingInfo.topRank,rankingInfo.allowanceRank,rankingInfo.count,rankingInfo.countR));
         }
 
+
         //5.获取员工任职信息(对应SHR:PersonpositionfileUtils:998~1009行)
-        PositionAppointmentBO positionAppointment = PositionStructureHelper.positionAppointmentQuery(ac.personId, beginDate == null ? new Date() : beginDate);
+        // PositionAppointmentBO positionAppointment = PositionStructureHelper.positionAppointmentQuery(ac.personId, beginDate == null ? new Date() : beginDate);
         DynamicObject perEduExp = positionAppointment.getPerEduExp();
         if(perEduExp != null){
             data.setDiplomaId(perEduExp.getLong(String.join(".", FormConstant.EDUCATION_KEY, FormConstant.ID_KEY)));
@@ -202,8 +274,8 @@ public class AnnualAdjustmentService {
             data.setPersonDepId(empPosOrgRel.getLong(String.join(".",FormConstant.ADMINORG,FormConstant.ID_KEY)));
             data.setHrJobFamilyId(empPosOrgRel.getLong(String.join(".", FormConstant.HBPM_POSITIONHR, FormConstant.NCKD_JOBSEQ, FormConstant.ID_KEY)));
             data.setHrJobFamilyNumber(empPosOrgRel.getString(String.join(".", FormConstant.HBPM_POSITIONHR, FormConstant.NCKD_JOBSEQ, FormConstant.NUMBER_KEY)));
-            data.setPositionId(empPosOrgRel.getLong(String.join(".", FormConstant.HBPM_POSITIONHR,  FormConstant.ID_KEY)));
-            data.setPositionName(empPosOrgRel.getString(String.join(".", FormConstant.HBPM_POSITIONHR,  FormConstant.NAME_KEY)));
+            data.setPositionId(empPosOrgRel.getLong(String.join(".", FormConstant.POSITION_KEY,  FormConstant.ID_KEY)));
+            data.setPositionName(empPosOrgRel.getString(String.join(".", FormConstant.POSITION_KEY,  FormConstant.NAME_KEY)));
             data.setPersonName(empPosOrgRel.getString(String.join(".", FormConstant.EMPLOYEE_KEY,  FormConstant.NAME_KEY)));
             // data.setHrOrgUnitId();
         }else{
@@ -232,10 +304,12 @@ public class AnnualAdjustmentService {
         long jobSeq = positionAppointment.getEmpPosOrgRel().getLong(String.join(".", FormConstant.HBPM_POSITIONHR, FormConstant.NCKD_JOBSEQ, FormConstant.ID_KEY));
         ac.jobSeq = BusinessDataServiceHelper.loadSingle(jobSeq,FormConstant.HBJM_JOBSEQHR);
 
+        ac.data = data;
 
         // 对应SHR:307~322行
         // 判断R位次存在性
         if (ac.data.getRankingResultInfo() == null || ac.data.getRankingResultInfo().allowanceRankPercent == null || ac.data.getRankingResultInfo().topRank == null) {
+            //如果没有排名与R排名,则按保级处理
             ac.keep = true;
         }
         if (ac.data.getRankingResultInfo() == null || ac.data.getRankingResultInfo().allowanceRankPercent == null || ac.data.getRankingResultInfo().allowanceRankPercent <= 0) {
@@ -254,7 +328,6 @@ public class AnnualAdjustmentService {
             ac.minusByAppraisal = -2;
             ac.whyAdjust.append("【考核结果不合格】降2级");
         }
-        ac.data = data;
         return ac;
     }
 
@@ -265,7 +338,7 @@ public class AnnualAdjustmentService {
      * @author W.Y.C
      * @date: 2025/10/08 22:12
      */
-    private void evaluateFirstAndAppraisalUsage(AdjustmentContext ac) {
+    private static void evaluateFirstAndAppraisalUsage(AdjustmentContext ac) {
 
         DynamicObject nowYearPersonPosFile = PositionStructureHelper.getLatsPersonPosFileByPerson(ac.personId, new QFilter(PositionStructureConstant.NCKD_EXECUTEYEAR, QCP.equals, ac.nowYear));
 
@@ -278,15 +351,19 @@ public class AnnualAdjustmentService {
                 throw new ValidationException(StrFormatter.format("人员【{}】,上年考度核结果存在变更,需删除【{}】年度创建的【员工职位档案调整】记录才能继续操作 !", ac.personName,ac.nowYear));
             }
             LocalDateTime nowYearDateTime = LocalDateTime.of(ac.nowYear, 1, 1, 0, 0);
-            ac.useAppraisalresult = JobLevelCalculatorService.useAppraisalResult(ac.personId, DateUtil.toDate(nowYearDateTime));
+            StringBuilder reasons = new StringBuilder();
+            ac.useAppraisalresult = JobLevelCalculatorService.useAppraisalResult(ac.personId, DateUtil.toDate(nowYearDateTime),reasons);
+            ac.useAppraisalResultReasons = reasons;
             if (ac.useAppraisalresult && ac.minusByAppraisal != 0) {
                 ac.whyAdjust.append(" 非").append(ac.nowYear)
                         .append("年度首次调整,且升降【考核结果】已被使用,不需要考虑R排名,保级处理");
                 ac.haveRp = false;
                 ac.keep = true;
             }
+            ac.whyAdjust1.add("【非】本年度首次调整,"+reasons.toString());
         } else {
             ac.whyAdjust.append(" ").append(ac.nowYear).append("年度首次调整,【考核结果】未被使用");
+            ac.whyAdjust1.add("本年度首次调整");
             isyearfirstdo = true;
         }
 
@@ -300,7 +377,9 @@ public class AnnualAdjustmentService {
             isyearfirstdo = true;
             if (ac.minusByAppraisal == 0) {
                 ac.whyAdjust.append("【考核结果】为保级,");
-                Double p = ac.data.getRankingResultInfo().allowanceRankPercent;
+                Double p = Optional.ofNullable(ac.data.getRankingResultInfo())
+                        .map(info -> info.allowanceRankPercent)
+                        .orElse(null);
                 if (p == null || p <= 0) {
                     ac.whyAdjust.append("无R排名");
                     ac.haveRp = false;
@@ -321,7 +400,7 @@ public class AnnualAdjustmentService {
      * @author W.Y.C
      * @date: 2025/10/08 23:13
      */
-    private void loadLastRecordAndValidateBeginDate(AdjustmentContext ac) {
+    private static void loadLastRecordAndValidateBeginDate(AdjustmentContext ac) {
         ac.lastRecordId = ac.data.getLastId() != null && ac.data.getLastId() != 0 ? ac.data.getLastId() : ac.data.getFirstId();
         if (ac.lastRecordId == null || ac.lastRecordId == 0) {
             throw new ValidationException(StrFormatter.format("人员【{}】,缺少职位及积分初定信息,请先完成初定,第二年再操作年度调整!", ac.personName));
@@ -332,7 +411,7 @@ public class AnnualAdjustmentService {
                 && ac.lastRecordInfo != null
                 && ac.lastRecordInfo.getDate(PositionStructureConstant.NCKD_BEGINDATE) != null) {
             Date lastBegin = ac.lastRecordInfo.getDate(PositionStructureConstant.NCKD_BEGINDATE);
-            if (ac.beginDate.before(lastBegin)) {
+            if (ac.beginDate.compareTo(lastBegin) <= 0) {
                 throw new ValidationException(StrFormatter.format("当前无法为【{}】进行调整,因为最近调整时间为【{}】,不能早于最近一次职位调整时间。", ac.personName,DateUtil.format(lastBegin,DateUtil.NORM_DATE_PATTERN)));
             }
         }
@@ -346,7 +425,7 @@ public class AnnualAdjustmentService {
      * @author W.Y.C
      * @date: 2025/10/08 23:46
      */
-    private void aggregateScores(AdjustmentContext ac) {
+    private static void aggregateScores(AdjustmentContext ac) {
         BigDecimal allyearscoresum =
                 ac.data.getAllYearScoreSum() == null ? BigDecimal.ZERO : ac.data.getAllYearScoreSum();
         System.out.println("上年所有贡献单据分数之和" + allyearscoresum);
@@ -383,7 +462,7 @@ public class AnnualAdjustmentService {
      * @author W.Y.C
      * @date: 2025/10/10 14:17
      */
-    private DynamicObject decideTargetJobGrade(AdjustmentContext ac) {
+    private static DynamicObject decideTargetJobGrade(AdjustmentContext ac) {
 
         //上一条职位档案的职位序列(如果是管理序列,则转换为职能序列)
         String newJobSeqNumber = ac.convertJobSeq.getString(FormConstant.NUMBER_KEY);
@@ -407,7 +486,8 @@ public class AnnualAdjustmentService {
      * @author W.Y.C
      * @date: 2025/10/10 14:18
      */
-    private DynamicObject decideWithR(AdjustmentContext ac) {
+    private static DynamicObject decideWithR(AdjustmentContext ac) {
+        //根据[职位序列][积分]和[职称等级/技能等级]获取职位序列最高职级
         DynamicObject jobLevel = JobLevelCalculatorService.getJobLevel(ac.convertJobSeq, ac.allSumScore, ac.data.getZgjbNumber(), ac.data.getZyjndjNumber(), 0, Boolean.FALSE,Boolean.FALSE);
         if(jobLevel == null){
             throw new ValidationException(StrFormatter.format("人员【{}】,职位序列【{}】总积分【{}】职称等级【{}】技能等级【{}】考核结果【{}】没有匹配到符合的职级",
@@ -432,12 +512,15 @@ public class AnnualAdjustmentService {
             }
 
             if (newjobgradeindex > jobGradeindex) {
+                //超过职位序列最高职级则退回职位序列最高职级
                 newjobgradeindex = jobGradeindex;
+                //但超出『积分』和『职称等级、技能等级』的所能定级的最高职级,按
             }
         } else {
             // 考核结果需要升降级则直接执行
             newjobgradeindex = ac.data.getLastJobGradeIndex() + ac.minusByAppraisal;
             if (jobGradeindex <= newjobgradeindex) {
+                //超过职位序列最高职级则退回职位序列最高职级
                 newjobgradeindex = jobGradeindex;
             }
         }
@@ -502,7 +585,7 @@ public class AnnualAdjustmentService {
      * @author W.Y.C
      * @date: 2025/10/10 14:18
      */
-    private DynamicObject decideWithoutR(AdjustmentContext ac) {
+    private static DynamicObject decideWithoutR(AdjustmentContext ac) {
         // String JobGrade = "";
         DynamicObject jobLevel = null;
 
@@ -589,7 +672,7 @@ public class AnnualAdjustmentService {
      * @author W.Y.C
      * @date: 2025/10/10 15:44
      */
-    private DynamicObject buildPersonPositionFile(AdjustmentContext ac, DynamicObject jobLevel) {
+    private static DynamicObject buildPersonPositionFile(AdjustmentContext ac, DynamicObject jobLevel) {
 
         DynamicObject newPersonPosFile = BusinessDataServiceHelper.newDynamicObject(
                 PositionStructureConstant.PERSONPOSFILE_ENTITYID);
@@ -628,27 +711,35 @@ public class AnnualAdjustmentService {
         newPersonPosFile.set(PositionStructureConstant.NCKD_LASTPERSONPOSFILE, ac.lastRecordInfo);
         newPersonPosFile.set(PositionStructureConstant.NCKD_DIPLOMASCORE, ac.diplomaScore);
         newPersonPosFile.set(PositionStructureConstant.NCKD_WHYDIPLOMASCORE, ac.whyDiplomaScore.toString());
-        newPersonPosFile.set(PositionStructureConstant.NCKD_RANKSCORE, ac.data.getJobScoreInfo().perProTitleScore);
-        newPersonPosFile.set(PositionStructureConstant.NCKD_JOBSTATUSSCORE, ac.data.getJobScoreInfo().quaLevelScore);
+        if(ac.data.getJobScoreInfo() != null) {
+            newPersonPosFile.set(PositionStructureConstant.NCKD_RANKSCORE, ac.data.getJobScoreInfo().perProTitleScore);
+            newPersonPosFile.set(PositionStructureConstant.NCKD_JOBSTATUSSCORE, ac.data.getJobScoreInfo().quaLevelScore);
+        }
         newPersonPosFile.set(PositionStructureConstant.NCKD_ALLSUMSCORE, ac.allSumScore);
         newPersonPosFile.set(PositionStructureConstant.NCKD_SUMSCORE,ac.sumScore);
-        newPersonPosFile.set(PositionStructureConstant.NCKD_BEGINDATE, ac.beginDate);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_ADJUSTTYPE,ac.adjustType);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_ADJUSSTATUS, EnableEnum.NO.getCode());
+        if(ac.beginDate != null){
+            newPersonPosFile.set(PositionStructureConstant.NCKD_BEGINDATE, ac.beginDate);
+            newPersonPosFile.set(PositionStructureConstant.NCKD_ADJUSSTATUS, EnableEnum.YES.getCode());
+        }
         newPersonPosFile.set(PositionStructureConstant.NCKD_ADJUSTINT, ac.adjustInt);
         newPersonPosFile.set(PositionStructureConstant.NCKD_FIRSTRANK, EnableEnum.NO.getCode());
 
-        newPersonPosFile.set(PositionStructureConstant.NCKD_TOPRANK, ac.data.getRankingResultInfo().topRank);
-        newPersonPosFile.set(PositionStructureConstant.NCKD_ALLOWANCERANK, ac.data.getRankingResultInfo().allowanceRank);
-        newPersonPosFile.set(PositionStructureConstant.NCKD_TOPRANKPERCENT, ac.data.getRankingResultInfo().topRankPercent);
-        newPersonPosFile.set(PositionStructureConstant.NCKD_ALLOWANCERANKMARK, ac.data.getRankingResultInfo().allowanceRankMark);
-        newPersonPosFile.set(PositionStructureConstant.NCKD_ALLOWANCERANKSEL, ac.data.getRankingResultInfo().allowanceRankSel);
-        newPersonPosFile.set(PositionStructureConstant.NCKD_ALLOWANCERANKPCT, ac.data.getRankingResultInfo().allowanceRankPercent);
+        if(ac.data.getRankingResultInfo() != null) {
+            newPersonPosFile.set(PositionStructureConstant.NCKD_TOPRANK, ac.data.getRankingResultInfo().topRank);
+            newPersonPosFile.set(PositionStructureConstant.NCKD_ALLOWANCERANK, ac.data.getRankingResultInfo().allowanceRank);
+            newPersonPosFile.set(PositionStructureConstant.NCKD_TOPRANKPERCENT, ac.data.getRankingResultInfo().topRankPercent);
+            newPersonPosFile.set(PositionStructureConstant.NCKD_ALLOWANCERANKMARK, ac.data.getRankingResultInfo().allowanceRankMark);
+            newPersonPosFile.set(PositionStructureConstant.NCKD_ALLOWANCERANKSEL, ac.data.getRankingResultInfo().allowanceRankSel);
+            newPersonPosFile.set(PositionStructureConstant.NCKD_ALLOWANCERANKPCT, ac.data.getRankingResultInfo().allowanceRankPercent);
+        }
         //上年度考核结果
         newPersonPosFile.set(PositionStructureConstant.NCKD_APPRAISALRESULT, ac.data.getAppraisalResult());
         newPersonPosFile.set(PositionStructureConstant.NCKD_RESULTSCORE, ac.data.getAppraisalResultScore());
-        newPersonPosFile.set(PositionStructureConstant.NCKD_ADJUSTTYPE, ac.adjustType);
 
         newPersonPosFile.set(PositionStructureConstant.NCKD_DISABLE, EnableEnum.NO.getCode());
-        newPersonPosFile.set(PositionStructureConstant.NCKD_ISCURRENTNEWEST, EnableEnum.YES.getCode());
+        // newPersonPosFile.set(PositionStructureConstant.NCKD_ISCURRENTNEWEST, EnableEnum.YES.getCode());
         newPersonPosFile.set(PositionStructureConstant.STATUS, StatusEnum.C.toString());
         newPersonPosFile.set(PositionStructureConstant.ENABLE, EnableEnum.YES.getCode());
         newPersonPosFile.set(PositionStructureConstant.NCKD_YEARSCORESUMA, ac.data.getYearscoresuma());
@@ -702,6 +793,9 @@ public class AnnualAdjustmentService {
         /** 本年考核是否已使用 */
         boolean useAppraisalresult = false;
 
+        /** 本年考核被使用的原因*/
+        StringBuilder useAppraisalResultReasons;
+
         /** 是否本年首次调整 */
         boolean isYearFirstDo = false;
 
@@ -751,5 +845,8 @@ public class AnnualAdjustmentService {
         DynamicObject convertJobSeq;
 
         String remark;
+
+        /** 调整说明 */
+        StringJoiner whyAdjust1 = new StringJoiner(StrFormatter.LINE_SEPARATOR);
     }
 }

+ 69 - 21
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/business/JobLevelCalculatorService.java

@@ -16,7 +16,7 @@ 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.enums.psms.JobSeqEnum;
 import nckd.jxccl.base.common.exception.ValidationException;
 import nckd.jxccl.base.common.utils.ConvertUtil;
 import nckd.jxccl.base.common.utils.DateUtil;
@@ -155,7 +155,7 @@ public class JobLevelCalculatorService {
 
         // 10. 处理职称/技能等级积分
         // 对应SHR:1015~1036
-        JobScoreInfo jobScoreInfo = handleJobScores(jobSeq, positionAppointment);
+        JobScoreInfo jobScoreInfo = handleJobScores(currentPersonPosFile,jobSeq, positionAppointment);
 
         // 8. 处理学历积分
         //对应SHR:DiplomaScore;947~978行
@@ -353,6 +353,9 @@ public class JobLevelCalculatorService {
 
 
 
+    public static boolean useAppraisalResult(Long personId, Date date) {
+        return useAppraisalResult(personId, date,null);
+    }
     /**
      * 检查某个人在特定年份的考核结果是否被使用
      * 对应SHR:useAppraisalresult方法
@@ -362,7 +365,7 @@ public class JobLevelCalculatorService {
      * @author W.Y.C
      * @date: 2025/09/20 15:10
      */
-    public static boolean useAppraisalResult(Long personId, Date date) {
+    public static boolean useAppraisalResult(Long personId, Date date,StringBuilder reasons) {
         //对应SHR代码:com.kingdee.shr.customer.web.handler.ContributeScore.PersonpositionfileFluctuationListHandler#useAppraisalresult
 
         //查询条件:查询某个人员职位档案非失效状态 并且 (初定 或者 类型状态为:年度调整3;职位调动4) 并且 职称分数或技能分数>0的数据
@@ -398,6 +401,9 @@ public class JobLevelCalculatorService {
             if(dataSet.copy()
                     .filter(PositionStructureConstant.NCKD_FIRSTRANK + " = true and " + PositionStructureConstant.NCKD_EXECUTEYEAR + "=" + year)
                     .count(PositionStructureConstant.NCKD_EXECUTEYEAR, false) == 1){
+                if(reasons != null) {
+                    reasons.append(StrFormatter.format("调整年【{}】与初定年度【】相同", DateUtil.getYear(date), DateUtil.getYear(date)));
+                }
                 return Boolean.TRUE;
             }
 
@@ -413,6 +419,9 @@ public class JobLevelCalculatorService {
             if(dataSet.copy()
                     .filter(PositionStructureConstant.NCKD_EXECUTEYEAR + "=" + year +" and "+PositionStructureConstant.NCKD_ADJUSTTYPE + " in ('0','2')")
                     .count(PositionStructureConstant.NCKD_EXECUTEYEAR, false) > 0){
+                if(reasons != null) {
+                    reasons.append(StrFormatter.format("调整年【{}】存在职级升降", DateUtil.getYear(date)));
+                }
                 return Boolean.TRUE;
             }
 
@@ -456,6 +465,9 @@ public class JobLevelCalculatorService {
             }
             Integer differ = maxSeq - minSeq;
             if (differ != 0) {
+                if(reasons != null) {
+                    reasons.append(StrFormatter.format("调整年【{}】存在职级变化,职级变化数【{}】", DateUtil.getYear(date), differ));
+                }
                 return Boolean.TRUE;
             }
 
@@ -485,6 +497,9 @@ public class JobLevelCalculatorService {
                 level = row.getInteger("level");
             }
             if(maxLevel - level != 0){
+                if(reasons != null) {
+                    reasons.append(StrFormatter.format("调整年【{}】相比往年存在职级变化,职级变化数【{}】", DateUtil.getYear(date), maxLevel - level));
+                }
                 return Boolean.TRUE;
             }
 
@@ -614,7 +629,9 @@ public class JobLevelCalculatorService {
         //获取上一笔职位档案变动记录学历信息
         //对应SHR:lastDiplomaInfo
         DynamicObject diploma = currentPersonPosFileByPerson.getDynamicObject(PositionStructureConstant.NCKD_DIPLOMA);
-
+        if(diploma == null){
+            return BigDecimal.ZERO;
+        }
         //对应SHR:lastDiplomaInfo.getId()
         long diplomaId = diploma.getLong(FormConstant.ID_KEY);
         //对应SHR:lastDiplomaInfo.getName()
@@ -624,6 +641,7 @@ public class JobLevelCalculatorService {
         scoreInfo.whyDiplomaScore.put("上一笔记录学历", diplomaName);
         scoreInfo.whyDiplomaScore.put("上一笔记录学历分数", ConvertUtil.toStr(diplomaScore));
         scoreInfo.whyDiplomaScore.put("学历" + diplomaName + "最新配置分数", ConvertUtil.toStr(diplomaScore));
+        scoreInfo.diplomaName = diplomaName;
         scoreInfo.diplomaId = diplomaId;
         scoreInfo.diplomaScore = diplomaScore;
         //获取上一笔职位档案变动记录学历积分
@@ -663,7 +681,9 @@ public class JobLevelCalculatorService {
             BigDecimal diff = resultScore.subtract(diplomaScore);
             resultScore = lastDiplomaScore.add(diff);
             scoreInfo.diplomaId = currentDiplomaId;
+            scoreInfo.diplomaName = currentDiplomaName;
             scoreInfo.diplomaScore = currentDiplomaScore;
+            scoreInfo.isDiplomaChange = Boolean.TRUE;
             scoreInfo.whyDiplomaScore.put("学历变化",
                     "学历分 = 上一笔记录学历分数(" + lastDiplomaScore + ") + (最新配置分("
                             + ConvertUtil.toStr(currentDiplomaScore) + ") - 上一学历最新配置分(" + ConvertUtil.toStr(diplomaScore) + "))");
@@ -695,7 +715,7 @@ public class JobLevelCalculatorService {
 
         // 判断是否为跨单位调动
         jobSeqInfo.isCrossUnitTransfer = false;
-        // TODO【待修改】 这里需要根据具体业务逻辑判断是否为跨单位调动
+        // TODO【待修改】-职位体系-这里需要根据具体业务逻辑判断是否为跨单位调动
 
         return jobSeqInfo;
     }
@@ -704,16 +724,30 @@ public class JobLevelCalculatorService {
     /**
      * 处理员工职称和技能等级分数
      * 根据职位序列类型(技能序列/非技能序列)确定采用职称等级分还是技能等级分
+     * @param currentPersonPosFileByPerson 最近一次职位档案
      * @param newJobSeq 最新职位序列
      * @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,PositionAppointmentBO positionAppointment) {
+    public static JobScoreInfo handleJobScores(DynamicObject currentPersonPosFileByPerson,DynamicObject newJobSeq,PositionAppointmentBO positionAppointment) {
         //对应SHR:hrjobfamilynumber
         String newJobSeqNumber = newJobSeq.getString(FormConstant.NUMBER_KEY);
 
+
+        DynamicObject lastPerProTitle = currentPersonPosFileByPerson.getDynamicObject(PositionStructureConstant.NCKD_PROTITLELEVEL);
+        Long lastPerProTitleId = null;
+        if(lastPerProTitle != null){
+            lastPerProTitleId = lastPerProTitle.getLong(FormConstant.ID_KEY);
+        }
+        DynamicObject lastQuaLevel = currentPersonPosFileByPerson.getDynamicObject(PositionStructureConstant.NCKD_OCPQUALLEVEL);
+        Long lastQuaLevelId = null;
+        if(lastQuaLevel != null){
+            lastQuaLevelId = lastQuaLevel.getLong(FormConstant.ID_KEY);
+        }
+
+
         // 当前人员最新职称等级
         Long newPerProTitleId = null;
         BigDecimal newPerProTitleScore = BigDecimal.ZERO;
@@ -769,6 +803,7 @@ public class JobLevelCalculatorService {
             jobScoreInfo.perProTitleName = StringUtils.isNotBlank(newPerProTitleName) ? newPerProTitleName : StringUtils.EMPTY;
             jobScoreInfo.rankName = rankName;
             jobScoreInfo.isEndGainJobGrade = false;
+            jobScoreInfo.isPerProTitleChange = !Objects.equals(newPerProTitleId,lastPerProTitleId);
         } else if (JobSeqEnum.SKILL.getCode().equals(newJobSeqNumber)) {
             if (newQuaLevelScore != null && newQuaLevelScore.compareTo(BigDecimal.ZERO) > 0) {
                 //获取当前最新技能等级分
@@ -777,6 +812,7 @@ public class JobLevelCalculatorService {
                 jobScoreInfo.quaLevelNumber = StringUtils.isNotBlank(newQuaLevelNumber) ? newQuaLevelNumber: "";
                 jobScoreInfo.quaLevelName = StringUtils.isNotBlank(newQuaLevelName) ? newQuaLevelName: "";
                 jobScoreInfo.jobStatusName = jobStatusName;
+                jobScoreInfo.isPerProTitleChange = !Objects.equals(newQuaLevelId,lastQuaLevelId);
                 jobScoreInfo.isEndGainJobGrade = false;
             }
         }
@@ -876,8 +912,8 @@ public class JobLevelCalculatorService {
             String selectFields = selectBuilder.buildSelect();
 
             QueryFieldBuilder orderByBuilder = QueryFieldBuilder.create()
-                    .orderBy(PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY, PerfRankMgmtConstant.NCKD_TOPRANK)
-                    .orderBy(PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY, PerfRankMgmtConstant.NCKD_ALLOWANCERANK);
+                    .orderAsc(new String[]{PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY},
+                            PerfRankMgmtConstant.NCKD_TOPRANK,PerfRankMgmtConstant.NCKD_ALLOWANCERANK);
 
             String orderBy = orderByBuilder.buildOrder();
             //对应SHR:allowancerankpercentSQL()方法
@@ -1084,6 +1120,7 @@ public class JobLevelCalculatorService {
         }
 
         if (currentJobLevelIndex > 1 && jobLevelIndex - currentJobLevelIndex > 1) {
+            //限制只能升一级
             jobLevelIndex = currentJobLevelIndex + 1;
         }
 
@@ -1753,32 +1790,43 @@ public class JobLevelCalculatorService {
     public static class JobScoreInfo {
         /** 职称等级分 */
         public BigDecimal perProTitleScore;
-        /** 技能等级分 */
-        public BigDecimal quaLevelScore;
         /** 职称级别ID */
         public Long perProTitleId;
-        /** 技能等级ID */
-        public Long quaLevelId;
         /** 职称级别编码 */
         public String perProTitleNumber;
-        /** 技能等级编码 */
-        public String quaLevelNumber;
         /** 职称等级名称 */
         public String perProTitleName;
-        /** 技能等级名称 */
-        public String quaLevelName;
-        /** 是否缺少聘任 */
-        public boolean isEndGainJobGrade;
         /** 职称名称*/
         public String rankName;
+        /** 职称是否变化 */
+        public boolean isPerProTitleChange;
+
+        /** 技能等级分 */
+        public BigDecimal quaLevelScore;
+        /** 技能等级ID */
+        public Long quaLevelId;
+        /** 技能等级编码 */
+        public String quaLevelNumber;
+        /** 技能等级名称 */
+        public String quaLevelName;
         /** 技能名称*/
         public String jobStatusName;
+        /** 技能是否变化 */
+        public boolean isQuaLeveleChange;
+
+        /** 是否缺少聘任 */
+        public boolean isEndGainJobGrade;
+
+        /** 学历名次 */
+        public String diplomaName;
         /** 学历ID */
         public Long diplomaId;
         /** 学历分 */
         public BigDecimal diplomaScore;
         /** 计算后的得分 */
         public BigDecimal allSumScore;
+        /** 学历是否变化 */
+        public boolean isDiplomaChange;
 
         /** 学历得分说明 */
         Map<String, String> whyDiplomaScore = new LinkedHashMap<>();
@@ -1822,10 +1870,10 @@ public class JobLevelCalculatorService {
 
 
         /** 序列信息 */
-        public JobSeqInfo jobSeqInfo;
+        public JobSeqInfo jobSeqInfo = new JobSeqInfo();
         /** 积分信息 */
-        public JobScoreInfo jobScoreInfo;
+        public JobScoreInfo jobScoreInfo = new JobScoreInfo();
         /** 排名信息 */
-        public RankingResultInfo rankingResultInfo;
+        public RankingResultInfo rankingResultInfo = new RankingResultInfo();
     }
 }

+ 57 - 4
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/common/PositionStructureConstant.java

@@ -62,7 +62,7 @@ public class PositionStructureConstant extends FormConstant {
     public static final String NCKD_APPOINTSTATUS = "NCKD_APPOINTSTATUS";
     /** 年度 */
     public static final String NCKD_EXECUTEYEAR = "NCKD_EXECUTEYEAR";
-    /** 调整类别(已生效1;未生效0) */
+    /** 调整类别(升级2;保级1;降级0;首次聘任3;序列变化4;聘任下调5;总分不足6;无聘任7;无考核结果8) */
     public static final String NCKD_ADJUSTTYPE = "NCKD_ADJUSTTYPE";
     /** 年度调整状态 */
     public static final String NCKD_ADJUSSTATUS = "NCKD_ADJUSSTATUS";
@@ -112,10 +112,14 @@ public class PositionStructureConstant extends FormConstant {
     public static final String NCKD_ALLOWANCERANKSEL = "NCKD_ALLOWANCERANKSEL";
     /** 是否当前最新记录 */
     public static final String NCKD_ISCURRENTNEWEST = "NCKD_ISCURRENTNEWEST";
-    /** 查询人员任职信息及聘任信息*/
+    /** 锁定状态 */
+    public static final String NCKD_LOCKSTATUS = "NCKD_LOCKSTATUS";
+    /** 锁定状态 */
+    public static final String KEY_NCKD_LOCKUSER = "nckd_lockuser";
+    /** 锁定人 */
+    public static final String NCKD_LOCKDATETIME = "nckd_lockdatetime";
+    /** 锁定时间*/
     public static final String POSITIONAPPOINTMENTQUERY = "positionappointmentquery";
-    /** 动态调整状态*/
-    public static final String NCKD_DYADJUSSTATUS = "nckd_dyadjusstatus";
     /*-------------------------------------- 员工职位档案 end --------------------------------------*/
 
 
@@ -191,6 +195,25 @@ public class PositionStructureConstant extends FormConstant {
 
     /*-------------------------------------- 新建动态调整(弹窗) end --------------------------------------*/
 
+    /*-------------------------------------- 年度动态调整(弹窗) begin --------------------------------------*/
+    /** 新建动态调整(弹窗)-实体标识 */
+    public static final String NEWANNUALADJUST_ENTITYID = "nckd_newannualadjust";
+    /** 动态调整-锁定 */
+    public static final String ISLOCKED_OP = "islocked";
+    /** 动态调整-解锁 */
+    public static final String UNLOCKED_OP = "unlocked";
+    /** 动态调整-按年度批量锁定 */
+    public static final String BATCHLOCKYEAR_OP = "batchlockyear";
+    /** 动态调整-按年度批量解锁 */
+    public static final String BATCHUNLOCKYEAR_OP = "batchunlockyear";
+    /*-------------------------------------- 年度动态调整(弹窗) end --------------------------------------*/
+
+    /*-------------------------------------- 年度动态调整-生效(弹窗) begin --------------------------------------*/
+    /** 新建动态调整(弹窗)-实体标识 */
+    public static final String ANNUALEFFECTIVE_ENTITYID = "nckd_annualeffective";
+    public static final String NCKD_MULSELEPERSONPOSFILE = "nckd_mulselepersonposfile";
+    /*-------------------------------------- 年度动态调整-生效(弹窗) end --------------------------------------*/
+
 
     /*-------------------------------------- 职位及积分初定 begin --------------------------------------*/
     /** 职位及积分初定-实体标识 */
@@ -203,6 +226,28 @@ public class PositionStructureConstant extends FormConstant {
     public static final String NCKD_UNADJUSTREPORT = "nckd_unadjustreport";
     /*-------------------------------------- 职位及积分初定 end --------------------------------------*/
 
+    /*-------------------------------------- 年度调整 begin --------------------------------------*/
+    /** 职位及积分初定-实体标识 */
+    public static final String ANNUALADJUSTMENT_ENTITYID = "nckd_annualadjustment";
+    /** 已生成年度调整-实体标识 */
+    public static final String ANNUALADJUST_QUERY = "annualadjustquery";
+    /** 未生成年度调整-实体标识 */
+    public static final String UNANNUALADJUST_QUERY = "unannualadjustquery";
+    /*-------------------------------------- 年度调整 begin --------------------------------------*/
+
+
+    /*-------------------------------------- 年度调整解锁单据 begin --------------------------------------*/
+    /** 年度调整解锁单据-实体标识 */
+    public static final String POSFILEUNLOCK_ENTITYID = "nckd_posfileunlock";
+    /** 单据体 */
+    public static final String NCKD_POSFILEUNLOCKENTRY = "NCKD_POSFILEUNLOCKENTRY";
+    /** 职位档案 */
+    public static final String NCKD_PERSONPOSFILE = "NCKD_PERSONPOSFILE";
+    /** 解锁原因 */
+    public static final String NCKD_REASON = "nckd_reason";
+
+    /*-------------------------------------- 年度调整解锁单据 end --------------------------------------*/
+
 
     /**职位序列对应职级查询*/
     public static final String JOBSEQTOJOBLEVEL_QUERY = "jobseqtojoblevelquery";
@@ -210,4 +255,12 @@ public class PositionStructureConstant extends FormConstant {
     public static final String GETLEVELBYJOBSEQ_QUERY = "getlevelbyjobseqquery";
     /**根据职位序列和资格级别查询职级*/
     public static final String GETLEVELBYJOBSEQQUERYANDQUAL_QUERY = "getlevelbyjobseqqueryandqualquery";
+
+
+    /** 人员考评实体名称 */
+    public static final String PERFMANAGER_ENTITYID = "nckd_perfmanager";
+    /** 人员考评管理分录实体名称 */
+    public static final String PERFMANAGER_ENTRY_ENTITYID = "nckd_perfmanagerentry";
+    /** 分录-考核年份 */
+    public static final String APPRAISAL_YEAR_KEY = "nckd_appraisalyear";
 }

+ 67 - 14
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/helper/PositionStructureHelper.java

@@ -12,9 +12,12 @@ import nckd.jxccl.base.common.constant.FormConstant;
 import nckd.jxccl.base.common.utils.QueryFieldBuilder;
 import nckd.jxccl.hr.psms.common.PositionStructureConstant;
 import nckd.jxccl.hr.psms.common.bo.PositionAppointmentBO;
-import org.apache.commons.lang3.StringUtils;
 
+import java.util.ArrayList;
 import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 /**
  * 职位体系帮助类
@@ -126,31 +129,44 @@ public class PositionStructureHelper {
     }
 
     /**
-     * 获取人员最新的职位档案
+     * 获取人员最新的职位档案(初定 或者 (年度调整 并且 已生效) 或者 (动态调整) 生效时间为最新的那一条)
      * @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);
+        List<DynamicObject> latsPersonPosFileByPerson = getLatsPersonPosFileByPerson(null, personId);
+        return !latsPersonPosFileByPerson.isEmpty() ? latsPersonPosFileByPerson.get(0) : null;
     }
 
     /**
-     * 获取人员最新的职位档案
+     * 获取人员最新的职位档案(初定 或者 (年度调整 并且 已生效) 或者 (动态调整) 生效时间为最新的那一条)
      * @param personId 人员ID
      * @param otherFilter 其他条件
+     * @return: kd.bos.dataentity.entity.DynamicObject
+     * @author W.Y.C
+     * @date: 2025/10/13 11:10
+     */
+    public static DynamicObject getLatsPersonPosFileByPerson(Long personId,QFilter otherFilter) {
+        List<DynamicObject> latsPersonPosFileByPerson = getLatsPersonPosFileByPerson(otherFilter,personId);
+        return !latsPersonPosFileByPerson.isEmpty() ? latsPersonPosFileByPerson.get(0) : null;
+    }
+
+    /**
+     * 获取该人员最新的职位档案(初定 或者 (年度调整 并且 已生效) 或者 (动态调整) 生效时间为最新的那一条)
+     * @param personIds 人员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)
+    public static List<DynamicObject> getLatsPersonPosFileByPerson(QFilter otherFilter,Long... personIds) {
+        QFilter filer = new QFilter(String.join(".", FormConstant.NCKD_PERSON, FormConstant.ID_KEY), QCP.in, personIds)
                 // .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(
@@ -158,13 +174,16 @@ public class PositionStructureHelper {
                                                 .and(new QFilter(PositionStructureConstant.NCKD_ADJUSSTATUS, QCP.in, new String[]{"1", "2"})
                                                 )
                                 )
-                                .or(new QFilter(PositionStructureConstant.NCKD_TYPESTATE, QCP.equals, "4"))
+                                .or(
+                                        new QFilter(PositionStructureConstant.NCKD_TYPESTATE, QCP.equals, "4")
+                                )
                 );
         if(otherFilter != null){
             filer.and(otherFilter);
         }
         QueryFieldBuilder queryFieldBuilder = QueryFieldBuilder.create()
                 .add(FormConstant.ID_KEY)
+                .addIdNumberName(FormConstant.NCKD_PERSON)
                 .add(PositionStructureConstant.NCKD_BEGINDATE)
                 .add(PositionStructureConstant.NCKD_EXECUTEYEAR)
                 .add(PositionStructureConstant.NCKD_TYPESTATE)
@@ -237,7 +256,20 @@ public class PositionStructureHelper {
                 new QFilter[]{filer},
                 queryFieldBuilder.buildOrder()
         );
-        return load.length > 0 ? load[0] : null;
+
+        //只取每个人员最新记录(根据NCKD_BEGINDATE)
+        Map<Long, DynamicObject> latestRecordMap = new HashMap<>();
+        for (DynamicObject dynamicObject : load) {
+            DynamicObject person = dynamicObject.getDynamicObject(FormConstant.NCKD_PERSON);
+            Date beginDate = dynamicObject.getDate(PositionStructureConstant.NCKD_BEGINDATE);
+            long personId = person.getLong(FormConstant.ID_KEY);
+            // 如果map中没有该personId的记录,或者当前记录的beginDate更晚,则更新记录
+            if (!latestRecordMap.containsKey(personId) ||
+                    beginDate.after(latestRecordMap.get(personId).getDate(PositionStructureConstant.NCKD_BEGINDATE))) {
+                latestRecordMap.put(personId, dynamicObject);
+            }
+        }
+        return new ArrayList<>(latestRecordMap.values());
 
     }
 
@@ -341,7 +373,8 @@ public class PositionStructureHelper {
                 .orderDesc(FormConstant.GRADUTIONDATE);
         QFilter perEduExpFilter = new QFilter(FormConstant.EMPLOYEE_KEY,QCP.equals,personId)
                 .and(new QFilter(FormConstant.GRADUTIONDATE, QCP.less_equals,date))
-                .and(new QFilter(FormConstant.GRADUTIONDATE, QCP.is_notnull,null));
+                .and(new QFilter(FormConstant.GRADUTIONDATE, QCP.is_notnull,null))
+                .and(new QFilter(FormConstant.ISHIGHESTDEGREE, QCP.equals,EnableEnum.YES.getCode()));
         DynamicObjectCollection perEduExpDynamicObject = QueryServiceHelper.query(
                 FormConstant.HRPI_PEREDUEXP, perEduExpQueryFieldBuilder.buildSelect(), new QFilter[]{perEduExpFilter}, perEduExpQueryFieldBuilder.buildOrder(), 1);
 
@@ -357,7 +390,8 @@ public class PositionStructureHelper {
                 .orderDesc("awardtime");
         QFilter perProTitleFilter = new QFilter(FormConstant.EMPLOYEE_KEY,QCP.equals,personId)
                 .and(new QFilter("awardtime", QCP.less_equals,date))
-                .and(new QFilter("awardtime", QCP.is_notnull,null));
+                .and(new QFilter("awardtime", QCP.is_notnull,null))
+                .and(new QFilter("iscompany", QCP.equals,EnableEnum.YES.getCode()));
         DynamicObjectCollection perProTitleDynamicObject = QueryServiceHelper.query(
                 FormConstant.HRPI_PERPROTITLE, perProTitleQueryFieldBuilder.buildSelect(), new QFilter[]{perProTitleFilter}, perProTitleQueryFieldBuilder.buildOrder(), 1);
 
@@ -371,7 +405,8 @@ public class PositionStructureHelper {
             .orderDesc("registratedate");
         QFilter perOcpQualFilter = new QFilter(FormConstant.EMPLOYEE_KEY,QCP.equals,personId)
                 .and(new QFilter("registratedate", QCP.less_equals,date))
-                .and(new QFilter("registratedate", QCP.is_notnull,null));
+                .and(new QFilter("registratedate", QCP.is_notnull,null))
+                .and(new QFilter("ismajor", QCP.equals, EnableEnum.YES.getCode()));
         DynamicObjectCollection perOcpQualDynamicObject = QueryServiceHelper.query(
                 FormConstant.HRPI_PEROCPQUAL, perOcpQualQueryFieldBuilder.buildSelect(), new QFilter[]{perOcpQualFilter}, perOcpQualQueryFieldBuilder.buildOrder(), 1);
 
@@ -414,4 +449,22 @@ public class PositionStructureHelper {
         }
         SaveServiceHelper.update(personPosFileColl);
     }
+
+    /**
+     * 将当前人员最新档案标记为最新的
+     * @param personId 人员ID(可传多个)
+     * @return: void
+     * @author W.Y.C
+     * @date: 2025/09/28 21:18
+     */
+    public static void markAsCurrentNewest(QFilter otherFilter,Long... personId) {
+        List<DynamicObject> personPosFileColl = getLatsPersonPosFileByPerson(otherFilter, personId);
+        DynamicObject[] updatePersonPosFileColl = new DynamicObject[personPosFileColl.size()];
+        for (int i = 0; i < personPosFileColl.size(); i++) {
+            DynamicObject dynamicObject = personPosFileColl.get(i);
+            dynamicObject.set(PositionStructureConstant.NCKD_ISCURRENTNEWEST, Boolean.TRUE);
+            updatePersonPosFileColl[i] = dynamicObject;
+        }
+        SaveServiceHelper.update(updatePersonPosFileColl);
+    }
 }

+ 1 - 1
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/adjust/AdjustQueryListPlugin.java → code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/adjust/DynamicAdjustQueryListPlugin.java

@@ -14,7 +14,7 @@ import nckd.jxccl.hr.psms.common.PositionStructureConstant;
 * @date 2025/10/12 17:51
 * @version 1.0
 */
-public class AdjustQueryListPlugin extends AbstractListPlugin implements Plugin {
+public class DynamicAdjustQueryListPlugin extends AbstractListPlugin implements Plugin {
 
     @Override
     public void setFilter(SetFilterEvent e) {

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

@@ -1,31 +1,16 @@
 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.MessageBoxOptions;
 import kd.bos.form.events.AfterDoOperationEventArgs;
 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.common.bo.PositionAppointmentBO;
-import nckd.jxccl.hr.psms.helper.PositionStructureHelper;
 
-import java.time.LocalDateTime;
-import java.util.Date;
 import java.util.EventObject;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
-import java.util.StringJoiner;
 
 /**
 * 批量新建动态调整(弹窗)-插件

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

@@ -106,7 +106,7 @@ public class NewDynamicAdjustmentDiaLogFormPlugin extends AbstractFormPlugin {
         }
         // 2、验证调整日期是否在上一次调整日期之后
         Date lastDate = currentPersonPosFile.getDate(PositionStructureConstant.NCKD_BEGINDATE);
-        if (date.before(lastDate)) {
+        if (date.compareTo(lastDate) <=0) {
             throw new ValidationException(StrFormatter.format("当前无法为【{}】进行调整,因为最近调整时间为【{}】,不能早于最近一次职位调整时间。", personName,DateUtil.format(lastDate,DateUtil.NORM_DATE_PATTERN)));
         }
 
@@ -183,25 +183,33 @@ public class NewDynamicAdjustmentDiaLogFormPlugin extends AbstractFormPlugin {
         }
     }
 
-    private void updateFormInfo(DynamicObject person, Date adjustDate, DynamicObject personPosFile,JobLevelCalculatorService.JobLevelResult jobLevelResult,PositionAppointmentBO positionAppointment) {
+    private void updateFormInfo(DynamicObject person, Date adjustDate, DynamicObject personPosFile, JobLevelCalculatorService.JobLevelResult jobLevelResult, PositionAppointmentBO positionAppointment) {
         LocalDateTime endDay = DateUtil.endOfDay(DateUtil.toLocalDateTime(adjustDate));
         this.getModel().setValue(PositionStructureConstant.NCKD_POSITIONHR,
                 positionAppointment.getEmpPosOrgRel().getLong(String.join(".", FormConstant.POSITION_KEY, FormConstant.ID_KEY)));
-        this.getModel().setValue(PositionStructureConstant.NCKD_ALLOWANCERANKMARK,
-                jobLevelResult.rankingResultInfo.allowanceRankMark);
-        this.getModel().setValue(PositionStructureConstant.NCKD_ALLOWANCERANKPERCENT,
-                jobLevelResult.rankingResultInfo.allowanceRankPercent);
-        this.getModel().setValue(PositionStructureConstant.NCKD_RANKNAME,
-                jobLevelResult.jobScoreInfo.rankName);
-        this.getModel().setValue(PositionStructureConstant.NCKD_PERPROTITLENAME,
-                jobLevelResult.jobScoreInfo.perProTitleName);
-        this.getModel().setValue(PositionStructureConstant.NCKD_QUALEVELNAME,
-                jobLevelResult.jobScoreInfo.quaLevelName);
-        this.getModel().setValue(PositionStructureConstant.NCKD_JOBSTATUSNAME,
-                jobLevelResult.jobScoreInfo.jobStatusName);
+        if (jobLevelResult != null) {
+            if (jobLevelResult.rankingResultInfo != null) {
+                this.getModel().setValue(PositionStructureConstant.NCKD_ALLOWANCERANKMARK,
+                        jobLevelResult.rankingResultInfo.allowanceRankMark);
+                this.getModel().setValue(PositionStructureConstant.NCKD_ALLOWANCERANKPERCENT,
+                        jobLevelResult.rankingResultInfo.allowanceRankPercent);
+            }
+            if (jobLevelResult.jobScoreInfo != null) {
+                this.getModel().setValue(PositionStructureConstant.NCKD_RANKNAME,
+                        jobLevelResult.jobScoreInfo.rankName);
+                this.getModel().setValue(PositionStructureConstant.NCKD_PERPROTITLENAME,
+                        jobLevelResult.jobScoreInfo.perProTitleName);
+                this.getModel().setValue(PositionStructureConstant.NCKD_QUALEVELNAME,
+                        jobLevelResult.jobScoreInfo.quaLevelName);
+                this.getModel().setValue(PositionStructureConstant.NCKD_JOBSTATUSNAME,
+                        jobLevelResult.jobScoreInfo.jobStatusName);
+                this.getModel().setValue(PositionStructureConstant.NCKD_DIPLOMA, jobLevelResult.jobScoreInfo.diplomaId);
+            }
+
+            this.getModel().setValue(PositionStructureConstant.NCKD_JOBLEVEL, jobLevelResult.jobLevel);
+        }
         this.getModel().setValue(PositionStructureConstant.PERSONPOSFILE_ENTITYID, personPosFile);
-        this.getModel().setValue(PositionStructureConstant.NCKD_JOBLEVEL, jobLevelResult.jobLevel);
-        this.getModel().setValue(PositionStructureConstant.NCKD_DIPLOMA, jobLevelResult.jobScoreInfo.diplomaId);
+
         this.getView().setEnable(true, FormConstant.BTN_OK_OP);
 
     }

+ 128 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/annualadjust/AnnualAdjustQueryListPlugin.java

@@ -0,0 +1,128 @@
+package nckd.jxccl.hr.psms.plugin.form.annualadjust;
+
+import kd.bos.common.enums.EnableEnum;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.entity.datamodel.ListSelectedRow;
+import kd.bos.entity.datamodel.ListSelectedRowCollection;
+import kd.bos.form.CloseCallBack;
+import kd.bos.form.FormShowParameter;
+import kd.bos.form.MessageBoxOptions;
+import kd.bos.form.ShowType;
+import kd.bos.form.control.events.ItemClickEvent;
+import kd.bos.form.control.events.ItemClickListener;
+import kd.bos.form.events.AfterDoOperationEventArgs;
+import kd.bos.form.events.ClosedCallBackEvent;
+import kd.bos.form.events.SetFilterEvent;
+import kd.bos.list.plugin.AbstractListPlugin;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.sdk.plugin.Plugin;
+import nckd.jxccl.base.common.constant.FormConstant;
+import nckd.jxccl.base.common.utils.ConvertUtil;
+import nckd.jxccl.hr.psms.common.PositionStructureConstant;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+* 已生成年度调整-查询列表插件
+* @author W.Y.C
+* @date 2025/10/14 17:03
+* @version 1.0
+*/
+public class AnnualAdjustQueryListPlugin extends AbstractListPlugin implements Plugin, ItemClickListener {
+
+    @Override
+    public void setFilter(SetFilterEvent e) {
+        e.addCustomQFilter(new QFilter(PositionStructureConstant.NCKD_DISABLE, QCP.equals, EnableEnum.NO.getCode()));
+        //只查询年度调整的数据
+        e.addCustomQFilter(new QFilter(PositionStructureConstant.NCKD_TYPESTATE, QCP.equals, "3"));
+    }
+
+    @Override
+    public void itemClick(ItemClickEvent evt) {
+        String itemKey = evt.getItemKey();
+        String operationKey = evt.getOperationKey();
+        if("nckd_effective".equals(itemKey) ) {
+            ListSelectedRowCollection selectedRows = this.getSelectedRows();
+            if (selectedRows.isEmpty()) {
+                this.getView().showTipNotification("请至少选择一条数据");
+                return;
+            }
+            List<Long> personIds = new ArrayList<Long>();
+            for (ListSelectedRow selectedRow : selectedRows) {
+                personIds.add(ConvertUtil.toLong(selectedRow.getPrimaryKeyValue()));
+            }
+
+            if ("nckd_effective".equals(itemKey)) {
+                //弹出【批量】在职人员初定窗口
+                FormShowParameter showParameter = new FormShowParameter();
+                showParameter.setFormId(PositionStructureConstant.ANNUALEFFECTIVE_ENTITYID);
+                showParameter.getOpenStyle().setShowType(ShowType.Modal);
+                showParameter.setCaption("年度调整生效");
+                showParameter.setSendToClient(true);
+                showParameter.setCustomParam(FormConstant.NCKD_PERSON, personIds);
+                showParameter.setCloseCallBack(new CloseCallBack(this, PositionStructureConstant.ANNUALEFFECTIVE_ENTITYID));
+                this.getView().showForm(showParameter);
+
+            }
+        }
+    }
+
+    @Override
+    public void afterDoOperation(AfterDoOperationEventArgs afterDoOperationEventArgs) {
+        String operateKey = afterDoOperationEventArgs.getOperateKey();
+        boolean success = afterDoOperationEventArgs.getOperationResult() != null && afterDoOperationEventArgs.getOperationResult().isSuccess();
+        if(success){
+            if("setinactive".equalsIgnoreCase(operateKey)){
+                Map<String, String> customData = afterDoOperationEventArgs.getOperationResult().getCustomData();
+                String message = customData.get("message");
+                if(StringUtils.isNotBlank( message)){
+                    this.getView().showConfirm("提示","置回未生效成功!",message, MessageBoxOptions.OK,null,null,null,null);
+                }else{
+                    this.getView().showSuccessNotification("置回未生效成功!");
+                }
+                //刷新列表
+                this.getView().invokeOperation(FormConstant.REFRESH_OP);
+            }else if(PositionStructureConstant.ISLOCKED_OP.equalsIgnoreCase(operateKey)){
+                Map<String, String> customData = afterDoOperationEventArgs.getOperationResult().getCustomData();
+                String message = customData.get("message");
+                if(StringUtils.isNotBlank( message)){
+                    this.getView().showConfirm("提示","锁定成功!",message, MessageBoxOptions.OK,null,null,null,null);
+                }else{
+                    this.getView().showSuccessNotification("锁定成功!");
+                }
+                this.getView().invokeOperation(FormConstant.REFRESH_OP);
+            }else if(PositionStructureConstant.UNLOCKED_OP.equalsIgnoreCase(operateKey)){
+                Map<String, String> customData = afterDoOperationEventArgs.getOperationResult().getCustomData();
+                if(customData != null && customData.containsKey("message")){
+                    String message = customData.get("message");
+                    if(StringUtils.isNotBlank( message)){
+                        this.getView().showConfirm("提示","解锁成功!",message, MessageBoxOptions.OK,null,null,null,null);
+                    }else{
+                        this.getView().showSuccessNotification("解锁成功!");
+                    }
+                    this.getView().invokeOperation(FormConstant.REFRESH_OP);
+                }else{
+                    this.getView().showSuccessNotification("发起流程成功,待审批通过后自动解锁!");
+                }
+            }
+        }
+
+    }
+
+    @Override
+    public void closedCallBack(ClosedCallBackEvent closedCallBackEvent) {
+        String actionId = closedCallBackEvent.getActionId();
+        if(StringUtils.equalsAny(actionId,PositionStructureConstant.ANNUALEFFECTIVE_ENTITYID)){
+            Object returnData = closedCallBackEvent.getReturnData();
+            if(returnData != null) {
+                //刷新列表
+                this.getView().invokeOperation(FormConstant.REFRESH_OP);
+            }
+        }
+    }
+}

+ 87 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/annualadjust/AnnualAdjustmentFormPlugin.java

@@ -0,0 +1,87 @@
+package nckd.jxccl.hr.psms.plugin.form.annualadjust;
+
+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.list.ListShowParameter;
+import kd.bos.list.plugin.AbstractListPlugin;
+import kd.bos.logging.Log;
+import kd.bos.logging.LogFactory;
+import kd.bos.report.ReportShowParameter;
+import kd.sdk.plugin.Plugin;
+import nckd.jxccl.hr.psms.common.PositionStructureConstant;
+import nckd.jxccl.hr.psms.plugin.form.adjust.DynamicAdjustmentFormPlugin;
+
+import java.util.EventObject;
+
+
+/**
+* 年度调整表单插件
+* @author W.Y.C
+* @date 2025/10/16 19:45
+* @version 1.0
+*/
+public class AnnualAdjustmentFormPlugin extends AbstractListPlugin implements TabSelectListener {
+
+
+    private final static Log logger = LogFactory.getLog(AnnualAdjustmentFormPlugin.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_ADJUSTED.equalsIgnoreCase(currentTab)) {
+            //打开“已生成动态调整”列表
+            openTargetPage(PositionStructureConstant.ANNUALADJUST_QUERY, PositionStructureConstant.NCKD_ADJUSTED,"已生成年度调整");
+        } else if (PositionStructureConstant.NCKD_UNADJUSTED.equalsIgnoreCase(currentTab)) {
+            //打开“未生成年度调整”列表
+            openTargetPage(PositionStructureConstant.UNANNUALADJUST_QUERY,PositionStructureConstant.NCKD_UNADJUSTED,"未生成年度调整");
+        }
+    }
+
+    /**
+     * 在页签中打开表单
+     * @param formId 需要打开的表单标识
+     * @param targetKey 目标容器标识
+     * @return: void
+     * @author W.Y.C
+     * @date: 2025/09/10 17:15
+     */
+    private void openTargetPage(String formId, String targetKey,String caption) {
+        ListShowParameter parameter = new ListShowParameter();
+        parameter.setBillFormId(formId);
+        // 在容器内打开
+        parameter.getOpenStyle().setShowType(ShowType.InContainer);
+        // 指定页签容器标识
+        parameter.getOpenStyle().setTargetKey(targetKey);
+        parameter.setCaption(caption);
+        this.getView().showForm(parameter);
+    }
+}

+ 65 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/annualadjust/AnnualEffectiveFormPlugin.java

@@ -0,0 +1,65 @@
+package nckd.jxccl.hr.psms.plugin.form.annualadjust;
+
+import kd.bos.form.FormShowParameter;
+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.base.common.constant.FormConstant;
+import nckd.jxccl.base.common.utils.ConvertUtil;
+import nckd.jxccl.hr.hstu.common.HonorStudentConstant;
+import nckd.jxccl.hr.psms.common.PositionStructureConstant;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.EventObject;
+import java.util.List;
+import java.util.Map;
+
+/**
+* 年度调整生效表单插件
+* @author W.Y.C
+* @date 2025/10/15 10:14
+* @version 1.0
+*/
+public class AnnualEffectiveFormPlugin extends AbstractFormPlugin implements Plugin {
+
+    @Override
+    public void afterCreateNewData(EventObject e) {
+        // 获取当前页面的FormShowParameter对象
+        FormShowParameter showParameter = this.getView().getFormShowParameter();
+        //获取列表选择的人员
+        Object personIds = showParameter.getCustomParam(FormConstant.NCKD_PERSON);
+        if(personIds != null) {
+            initValue(ConvertUtil.toList(personIds));
+        }
+    }
+
+    /**
+     * 初始化表单信息
+     * @param personIds 人员ID
+     * @return: void
+     * @author W.Y.C
+     * @date: 2025/09/25 21:40
+     */
+    private void initValue(List<Long> personIds){
+        //多选基础资料赋值
+        this.getModel().setValue("nckd_mulselepersonposfile", personIds.toArray());
+        this.getView().updateView(FormConstant.NCKD_ENTRYENTITY);
+    }
+
+    @Override
+    public void afterDoOperation(AfterDoOperationEventArgs afterDoOperationEventArgs) {
+        String operateKey = afterDoOperationEventArgs.getOperateKey();
+        boolean success = afterDoOperationEventArgs.getOperationResult() != null && afterDoOperationEventArgs.getOperationResult().isSuccess();
+        if(success && FormConstant.AFFIRM_OP.equalsIgnoreCase(operateKey)){
+            Map<String, String> customData = afterDoOperationEventArgs.getOperationResult().getCustomData();
+            String message = customData.get("message");
+            if(StringUtils.isNotBlank( message)){
+                this.getView().showConfirm("提示","生效成功!",message, MessageBoxOptions.OK,null,null,null,null);
+            }else{
+                this.getView().getParentView().showSuccessNotification("生效成功!");
+            }
+            this.getView().returnDataToParent("true");
+        }
+    }
+}

+ 64 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/annualadjust/NewAnnualAdjustFormPlugin.java

@@ -0,0 +1,64 @@
+package nckd.jxccl.hr.psms.plugin.form.annualadjust;
+
+import kd.bos.form.FormShowParameter;
+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.base.common.constant.FormConstant;
+import nckd.jxccl.base.common.utils.ConvertUtil;
+import nckd.jxccl.hr.psms.common.PositionStructureConstant;
+
+import java.util.EventObject;
+import java.util.List;
+import java.util.Map;
+
+/**
+* 批量新建年度调整
+* @author W.Y.C
+* @date 2025/10/13 20:33
+* @version 1.0
+*/
+public class NewAnnualAdjustFormPlugin extends AbstractFormPlugin implements Plugin {
+
+    @Override
+    public void afterCreateNewData(EventObject e) {
+        // 获取当前页面的FormShowParameter对象
+        FormShowParameter showParameter = this.getView().getFormShowParameter();
+        //获取列表选择的人员
+        Object personId = showParameter.getCustomParam(FormConstant.NCKD_PERSON);
+        if(personId != null) {
+            initValue(ConvertUtil.toList(personId));
+        }
+    }
+
+    /**
+     * 初始化表单信息
+     * @param personIds 人员ID
+     * @return: void
+     * @author W.Y.C
+     * @date: 2025/09/25 21:40
+     */
+    private void initValue(List<Long> personIds){
+        if(personIds.size() >1) {
+            this.getModel().batchCreateNewEntryRow(FormConstant.NCKD_ENTRYENTITY, personIds.size() - 1);
+        }
+        for (int i = 0; i < personIds.size(); i++) {
+            this.getModel().setValue(FormConstant.NCKD_PERSON,personIds.get(i), i);
+        }
+
+        this.getView().updateView(FormConstant.NCKD_ENTRYENTITY);
+    }
+
+    @Override
+    public void afterDoOperation(AfterDoOperationEventArgs afterDoOperationEventArgs) {
+        String operateKey = afterDoOperationEventArgs.getOperateKey();
+        boolean success = afterDoOperationEventArgs.getOperationResult() != null && afterDoOperationEventArgs.getOperationResult().isSuccess();
+        if(success && PositionStructureConstant.OP_CONFIRMADJUST.equalsIgnoreCase(operateKey)){
+            Map<String, String> customData = afterDoOperationEventArgs.getOperationResult().getCustomData();
+            String jobLeveStr = customData.get(PositionStructureConstant.NCKD_JOBLEVELHR);
+            this.getView().returnDataToParent("true");
+            this.getView().showConfirm("提示","新建调整成功,职位档案已生成。",jobLeveStr, MessageBoxOptions.OK,null,null,null,null);
+        }
+    }
+}

+ 90 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/annualadjust/UnAnnualAdjustListPlugin.java

@@ -0,0 +1,90 @@
+package nckd.jxccl.hr.psms.plugin.form.annualadjust;
+
+import kd.bos.common.enums.EnableEnum;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.entity.datamodel.ListSelectedRow;
+import kd.bos.entity.datamodel.ListSelectedRowCollection;
+import kd.bos.form.CloseCallBack;
+import kd.bos.form.FormShowParameter;
+import kd.bos.form.ShowType;
+import kd.bos.form.control.events.ItemClickEvent;
+import kd.bos.form.events.SetFilterEvent;
+import kd.bos.list.plugin.AbstractListPlugin;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.sdk.plugin.Plugin;
+import nckd.jxccl.base.common.constant.FormConstant;
+import nckd.jxccl.base.common.utils.ConvertUtil;
+import nckd.jxccl.base.common.utils.DateUtil;
+import nckd.jxccl.hr.psms.common.PositionStructureConstant;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+* 未生成年度调整列表
+* @author W.Y.C
+* @date 2025/10/13 12:40
+* @version 1.0
+*/
+public class UnAnnualAdjustListPlugin extends AbstractListPlugin implements Plugin {
+
+    @Override
+    public void setFilter(SetFilterEvent e) {
+        //TODO 【待修改】-职位体系-默认过滤掉副科级及以上
+        //TODO 【待修改】-职位体系-本年度7月前退休、辞职,则列表中删除该人员
+        //过滤掉未初定或初定为当前年或当前年之前或本年度已做过调整的员工
+        LocalDateTime localDateTime = DateUtil.beginOfYear(DateUtil.now());
+        LocalDateTime lastYear = DateUtil.minusYears(localDateTime, 1);
+        QFilter qFilter = new QFilter(String.join(".", PositionStructureConstant.PERSONPOSFILE_ENTITYID, PositionStructureConstant.NCKD_DISABLE), QCP.equals, EnableEnum.NO.getCode())
+                .and(new QFilter(String.join(".", PositionStructureConstant.PERSONPOSFILE_ENTITYID, PositionStructureConstant.NCKD_FIRSTRANK), QCP.equals, EnableEnum.YES.getCode()))
+                .and(new QFilter(String.join(".", PositionStructureConstant.PERSONPOSFILE_ENTITYID, PositionStructureConstant.NCKD_BEGINDATE), QCP.less_than, DateUtil.toDate(localDateTime))
+                .and(new QFilter(String.join(".", PositionStructureConstant.PERFMANAGER_ENTITYID, PositionStructureConstant.PERFMANAGER_ENTRY_ENTITYID,PositionStructureConstant.APPRAISAL_YEAR_KEY),QCP.equals,DateUtil.toDate(lastYear)))
+                );
+        e.addCustomQFilter(qFilter);
+    }
+
+    @Override
+    public void itemClick(ItemClickEvent evt) {
+        String itemKey = evt.getItemKey();
+
+        if("nckd_new".equals(itemKey) ) {
+            ListSelectedRowCollection selectedRows = this.getSelectedRows();
+            if (selectedRows.isEmpty()) {
+                this.getView().showTipNotification("请至少选择一条数据");
+                return;
+            }
+            //任职经历ID
+            List<Long> empPosOrgRelIds = new ArrayList<>(selectedRows.size());
+            for (ListSelectedRow selectedRow : selectedRows) {
+                empPosOrgRelIds.add(ConvertUtil.toLong(selectedRow.getPrimaryKeyValue()));
+            }
+            QFilter qFilter = new QFilter(FormConstant.ID_KEY, QCP.in, empPosOrgRelIds);
+            DynamicObject[] load = BusinessDataServiceHelper.load(FormConstant.HRPI_EMPPOSORGREL, String.join(".", FormConstant.EMPLOYEE_KEY, FormConstant.ID_KEY), new QFilter[]{qFilter});
+            List<Long> personIds = new ArrayList<>(selectedRows.size());
+            for (DynamicObject dynamicObject : load) {
+                DynamicObject employee = dynamicObject.getDynamicObject(FormConstant.EMPLOYEE_KEY);
+                personIds.add(employee.getLong(FormConstant.ID_KEY));
+            }
+
+            if (personIds.isEmpty()) {
+                this.getView().showErrorNotification("请选择正确的数据");
+                return;
+            }
+            if ("nckd_new".equals(itemKey)) {
+                //弹出【批量】在职人员初定窗口
+                FormShowParameter showParameter = new FormShowParameter();
+                showParameter.setFormId(PositionStructureConstant.NEWANNUALADJUST_ENTITYID);
+                showParameter.getOpenStyle().setShowType(ShowType.Modal);
+                showParameter.setCaption("生成年度调整");
+                showParameter.setSendToClient(true);
+                showParameter.setCustomParam(FormConstant.NCKD_PERSON, personIds);
+                showParameter.setCloseCallBack(new CloseCallBack(this, PositionStructureConstant.NEWANNUALADJUST_ENTITYID));
+                this.getView().showForm(showParameter);
+
+            }
+        }
+    }
+}

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

@@ -1,6 +1,7 @@
 package nckd.jxccl.hr.psms.plugin.operate.adjust;
 
 import kd.bos.common.enums.EnableEnum;
+import kd.bos.context.RequestContext;
 import kd.bos.dataentity.entity.CloneUtils;
 import kd.bos.dataentity.entity.DynamicObject;
 import kd.bos.dataentity.entity.DynamicObjectCollection;
@@ -47,7 +48,7 @@ import java.util.StringJoiner;
 */
 public class NewDynamicAdjustmentOperationPlugIn extends AbstractOperationServicePlugIn{
 
-    protected final static Log logger = LogFactory.getLog(BaseInitialOperationPlugIn.class);
+    protected final static Log logger = LogFactory.getLog(NewDynamicAdjustmentOperationPlugIn.class);
 
     /** 记录上一次职位档案(key:人员ID) */
     Map<Long,DynamicObject> latsPersonPosFileMap;
@@ -115,7 +116,7 @@ public class NewDynamicAdjustmentOperationPlugIn extends AbstractOperationServic
                 //判断当前年是否已执行过年度调整
                 DynamicObject[] personPosFileByAdjust = PositionStructureHelper.getPersonPosFileByPersonAndState(person.getLong(FormConstant.ID_KEY), new String[]{"4"},
                         null,
-                        new QFilter(PositionStructureConstant.NCKD_DYADJUSSTATUS, QCP.equals, EnableEnum.NO.getCode()));
+                        new QFilter(PositionStructureConstant.NCKD_ADJUSSTATUS, QCP.equals, EnableEnum.NO.getCode()));
                 if(personPosFileByAdjust != null && personPosFileByAdjust.length > 0){
                     StringJoiner executeYear = new StringJoiner(",");
                     for (DynamicObject dynamicObject : personPosFileByAdjust) {
@@ -127,7 +128,7 @@ public class NewDynamicAdjustmentOperationPlugIn extends AbstractOperationServic
                 if(latsPersonPosFileByPerson != null) {
                     Date date = latsPersonPosFileByPerson.getDate(PositionStructureConstant.NCKD_BEGINDATE);
                     //校验【本次调整时间】不得早于上一笔时间
-                    if (adjustDate != null && adjustDate.before(date)) {
+                    if (adjustDate != null && adjustDate.compareTo(date) <= 0) {
                         addFatalErrorMessage(rowDataEntity, StrFormatter.format("人员【{}】最近一次的职位档案变动时间是【{}】,请勿选择早于该时间段的调整时间!", person.getString(FormConstant.NAME_KEY), DateUtil.format(date, DateUtil.NORM_DATE_PATTERN)));
                     }
                 }
@@ -136,7 +137,7 @@ public class NewDynamicAdjustmentOperationPlugIn extends AbstractOperationServic
                     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)));
+                        addFatalErrorMessage(rowDataEntity, StrFormatter.format("人员【{}】没有【{}】年度考核结果,无法新建调整", person.getString(FormConstant.NAME_KEY),DateUtil.getYear(lastYearDateTime)));
                     }
                     lastYearPerformanceResultMap.put(person.getLong(FormConstant.ID_KEY),lastYearPerformanceResult);
                 }
@@ -168,8 +169,9 @@ public class NewDynamicAdjustmentOperationPlugIn extends AbstractOperationServic
 
         }
 
-        PositionStructureHelper.markAsNotCurrentNewest(personIds.toArray(new Long[0]));
         SaveServiceHelper.save(newPersonPosFiles.toArray(new DynamicObject[0]));
+        PositionStructureHelper.markAsNotCurrentNewest(personIds.toArray(new Long[0]));
+        PositionStructureHelper.markAsCurrentNewest(null,personIds.toArray(new Long[0]));
     }
 
     private void execute(DynamicObject data, List<Long> personIds, List<DynamicObject> newPersonPosFiles) {
@@ -273,12 +275,19 @@ public class NewDynamicAdjustmentOperationPlugIn extends AbstractOperationServic
         newPersonPosFile.set(PositionStructureConstant.NCKD_ADJUSTINT, null);
         newPersonPosFile.set(PositionStructureConstant.NCKD_FIRSTRANK, null);
 
-        newPersonPosFile.set(PositionStructureConstant.NCKD_TOPRANK, jobLevelResult.rankingResultInfo.topRank);
-        newPersonPosFile.set(PositionStructureConstant.NCKD_ALLOWANCERANK, jobLevelResult.rankingResultInfo.allowanceRank);
-        newPersonPosFile.set(PositionStructureConstant.NCKD_TOPRANKPERCENT, jobLevelResult.rankingResultInfo.topRankPercent);
-        newPersonPosFile.set(PositionStructureConstant.NCKD_ALLOWANCERANKMARK, jobLevelResult.rankingResultInfo.allowanceRankMark);
-        newPersonPosFile.set(PositionStructureConstant.NCKD_ALLOWANCERANKSEL, jobLevelResult.rankingResultInfo.allowanceRankSel);
-        newPersonPosFile.set(PositionStructureConstant.NCKD_ALLOWANCERANKPCT, jobLevelResult.rankingResultInfo.allowanceRankPercent);
+
+        newPersonPosFile.set(PositionStructureConstant.NCKD_TOPRANK,
+                jobLevelResult.rankingResultInfo != null ? jobLevelResult.rankingResultInfo.topRank : null);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_ALLOWANCERANK,
+                jobLevelResult.rankingResultInfo != null ? jobLevelResult.rankingResultInfo.allowanceRank : null);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_TOPRANKPERCENT,
+                jobLevelResult.rankingResultInfo != null ? jobLevelResult.rankingResultInfo.topRankPercent : null);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_ALLOWANCERANKMARK,
+                jobLevelResult.rankingResultInfo != null ? jobLevelResult.rankingResultInfo.allowanceRankMark : null);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_ALLOWANCERANKSEL,
+                jobLevelResult.rankingResultInfo != null ? jobLevelResult.rankingResultInfo.allowanceRankSel : null);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_ALLOWANCERANKPCT,
+                jobLevelResult.rankingResultInfo != null ? jobLevelResult.rankingResultInfo.allowanceRankPercent : null);
         //上年度考核结果
         DynamicObject performanceResult = lastYearPerformanceResultMap.get(personId);
         if(performanceResult != null){
@@ -298,13 +307,16 @@ public class NewDynamicAdjustmentOperationPlugIn extends AbstractOperationServic
         }
 
         newPersonPosFile.set(PositionStructureConstant.NCKD_DISABLE, EnableEnum.NO.getCode());
-        newPersonPosFile.set(PositionStructureConstant.NCKD_ISCURRENTNEWEST, EnableEnum.YES.getCode());
+        // 这里不能直接定义为最新的,需要点击生效才是最新的
+        // newPersonPosFile.set(PositionStructureConstant.NCKD_ISCURRENTNEWEST, EnableEnum.YES.getCode());
         newPersonPosFile.set(PositionStructureConstant.STATUS, StatusEnum.C.toString());
         newPersonPosFile.set(PositionStructureConstant.ENABLE, EnableEnum.YES.getCode());
         // 备注
         newPersonPosFile.set(PositionStructureConstant.KEY_NCKD_CAUSEREMARK, remark);
-        //动态调整状态;默认为否
-        newPersonPosFile.set(PositionStructureConstant.NCKD_DYADJUSSTATUS, EnableEnum.NO.getCode());
+        newPersonPosFile.set(FormConstant.CREATE_TIME_KEY, null);
+        newPersonPosFile.set(FormConstant.CREATOR_KEY, RequestContext.get().getCurrUserId());
+        newPersonPosFile.set(FormConstant.MODIFY_TIME_KEY, null);
+        newPersonPosFile.set(FormConstant.MODIFIER_KEY, null);
 
         // 聘任状态
         /*String employmentStatus = latsPersonPosFile.getString(PositionStructureConstant.NCKD_EMPLOYMENTSTATUS);

+ 225 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/annualadjust/AnnualAdjustmentOperationPlugin.java

@@ -0,0 +1,225 @@
+package nckd.jxccl.hr.psms.plugin.operate.annualadjust;
+
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+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.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.operation.SaveServiceHelper;
+import kd.sdk.plugin.Plugin;
+import nckd.jxccl.base.common.constant.FormConstant;
+import nckd.jxccl.base.common.enums.psms.AdjustTypeEnum;
+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.business.AnnualAdjustmentService;
+import nckd.jxccl.hr.psms.common.PositionStructureConstant;
+import nckd.jxccl.hr.psms.common.bo.PositionAppointmentBO;
+import nckd.jxccl.hr.psms.helper.PositionStructureHelper;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+* 新建年度调整记录操作插件
+* @author W.Y.C
+* @date 2025/10/13 20:40
+* @version 1.0
+*/
+public class AnnualAdjustmentOperationPlugin extends AbstractOperationServicePlugIn implements Plugin {
+
+    protected final static Log logger = LogFactory.getLog(AnnualAdjustmentOperationPlugin.class);
+
+    /** 初定职位档案(key:人员ID,value:最新职位档案) */
+    Map<Long,DynamicObject> firstPersonPosFileMap;
+
+    /** 记录上年度考核结果(key:人员ID)*/
+    Map<Long,DynamicObject> lastYearPerformanceResultMap;
+
+    /** 记录上年度考核结果(key:人员ID)*/
+    Map<Long,PositionAppointmentBO> positionAppointmentMap;
+
+    @Override
+    public void onAddValidators(AddValidatorsEventArgs e){
+        e.addValidator(new AbstractValidator() {
+            @Override
+            public void validate() {
+                firstPersonPosFileMap = new HashMap<>();
+                lastYearPerformanceResultMap = new HashMap<>();
+                positionAppointmentMap = new HashMap<>();
+                for (ExtendedDataEntity rowDataEntity : getDataEntities()) {
+                    DynamicObject data = rowDataEntity.getDataEntity();
+                    if(data.containsProperty(FormConstant.NCKD_ENTRYENTITY)){
+                        //批量调整
+                        DynamicObjectCollection dynamicObjectCollection = data.getDynamicObjectCollection(FormConstant.NCKD_ENTRYENTITY);
+                        // 检查人员ID重复
+                        Set<Long> personIds = new HashSet<>();
+                        for (DynamicObject dynamicObject : dynamicObjectCollection) {
+                            if (dynamicObject.containsProperty(FormConstant.NCKD_PERSON)) {
+                                DynamicObject person = dynamicObject.getDynamicObject(FormConstant.NCKD_PERSON);
+                                Long personId = person.getLong(FormConstant.ID_KEY);
+                                if (!personIds.add(personId)) {
+                                    addFatalErrorMessage(rowDataEntity,
+                                            StrFormatter.format("人员【{}】在列表中重复,请保留一条信息",
+                                                    person.getString(FormConstant.NAME_KEY)));
+                                }
+                            }
+                        }
+                        if(!this.getValidateResult().isSuccess()){
+                            return;
+                        }
+
+                        for (DynamicObject dynamicObject : dynamicObjectCollection) {
+                            validator(rowDataEntity, dynamicObject);
+                        }
+                    }else{
+                        //单人调整
+                        validator(rowDataEntity, data);
+                    }
+                }
+            }
+
+            private void validator(ExtendedDataEntity rowDataEntity, DynamicObject data) {
+                Date adjustDate = data.getDate(PositionStructureConstant.NCKD_ADJUSTDATE);
+                if (adjustDate != null) {
+                    // 如果adjustDate不为空且为年初日期(xxxx-01-01),则转换为当年7月1日
+                    LocalDateTime adjustDateTime = DateUtil.toLocalDateTime(adjustDate);
+                    if (adjustDateTime.getMonthValue() == 1 && adjustDateTime.getDayOfMonth() == 1) {
+                        //前端只选年份,这里默认为XXX-07-01
+                        adjustDate = DateUtil.toDate(adjustDateTime.withMonth(7).withDayOfMonth(1));
+                    }
+                }else{
+                    //获取当年07-01的日期
+                    /*LocalDate julyFirst = LocalDate.of(DateUtil.getYear(new Date()), 7, 1);
+                    adjustDate = Date.from(julyFirst.atStartOfDay(ZoneId.systemDefault()).toInstant());*/
+                    adjustDate = new Date();
+                }
+
+                int year = DateUtil.getYear(adjustDate);
+                DynamicObject person = data.getDynamicObject(FormConstant.NCKD_PERSON);
+                long personId = person.getLong(FormConstant.ID_KEY);
+                String personName = person.getString(FormConstant.NAME_KEY);
+                LocalDateTime lastYear = DateUtil.minusYears(DateUtil.toLocalDateTime(adjustDate), 1);
+                DynamicObject performanceResult = PerformanceManagerHelper.getPerformanceResult(personId, lastYear);
+                if(performanceResult == null){
+                    addFatalErrorMessage(rowDataEntity,StrFormatter.format("人员【{}】缺少【{}】年考核结果", personName,lastYear));
+                }
+
+                PositionAppointmentBO positionAppointment = PositionStructureHelper.positionAppointmentQuery(personId, adjustDate);
+                if(positionAppointment.getEmpPosOrgRel() == null){
+                    addFatalErrorMessage(rowDataEntity,StrFormatter.format("当前无法为【{}】进行调整,因为根据时间【{}】未获取到人员任职和聘任信息!", personName, DateUtil.format(adjustDate,DateUtil.NORM_DATE_PATTERN)));
+                }
+
+                DynamicObject firstRank = PositionStructureHelper.getFirstRank(personId);
+                if(firstRank == null){
+                    addFatalErrorMessage(rowDataEntity,StrFormatter.format("当前无法为【{}】进行调整,因为他/她尚未建立职位档案。请前往“职位及积分初定” -> 进行初定!", personName));
+                }else{
+                    if(firstRank.getInt(PositionStructureConstant.NCKD_EXECUTEYEAR) == year){
+                        addFatalErrorMessage(rowDataEntity,StrFormatter.format("当前无法为【{}】进行调整,因为初定当年不能执行年度调整。初定时间【{}】", personName,DateUtil.format(firstRank.getDate(PositionStructureConstant.NCKD_BEGINDATE),DateUtil.NORM_DATE_PATTERN)));
+                    }
+                }
+                int executeYear = DateUtil.getYear(adjustDate);
+                //判断当前年是否已执行过年度调整
+                DynamicObject[] personPosFileByYear = PositionStructureHelper.getPersonPosFileByPersonAndState(person.getLong(FormConstant.ID_KEY), new String[]{"3"}, null, new QFilter(PositionStructureConstant.NCKD_EXECUTEYEAR, QCP.equals, executeYear));
+                if(personPosFileByYear != null && personPosFileByYear.length > 0){
+                    addFatalErrorMessage(rowDataEntity,StrFormatter.format("人员【{}】已存在【{}】年的年度调整", person.getString(FormConstant.NAME_KEY), executeYear));
+                }
+                if(positionAppointment.getEmpPosOrgRel() != null) {
+                    long jobSeqId = positionAppointment.getEmpPosOrgRel().getLong(String.join(".", FormConstant.HBPM_POSITIONHR, FormConstant.NCKD_JOBSEQ, FormConstant.ID_KEY));
+                    if (jobSeqId == 0) {
+                        String positionName = positionAppointment.getEmpPosOrgRel().getString(String.join(".", FormConstant.HBPM_POSITIONHR, FormConstant.NAME_KEY));
+                        addFatalErrorMessage(rowDataEntity, StrFormatter.format("无职位序列,请检查当前人员【{}】任职的岗位【{}】是否有职位序列", personName, positionName));
+                    }
+                }
+
+                if(this.getValidateResult().isSuccess()){
+                    //局部缓存,给下面其他逻辑使用;避免重复查询
+                    firstPersonPosFileMap.put(personId,firstRank);
+                    lastYearPerformanceResultMap.put(personId,performanceResult);
+                    positionAppointmentMap.put(personId,positionAppointment);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void beginOperationTransaction(BeginOperationTransactionArgs e) {
+        logger.info("【职位体系】-年度调整-开始");
+        List<Long> personIds = new ArrayList<>(e.getDataEntities().length);
+        List<DynamicObject> newPersonPosFiles = new ArrayList<>(e.getDataEntities().length);
+        for (DynamicObject data : e.getDataEntities()) {
+            if(data.containsProperty(FormConstant.NCKD_ENTRYENTITY)){
+                //批量调整
+                DynamicObjectCollection dynamicObjectCollection = data.getDynamicObjectCollection(FormConstant.NCKD_ENTRYENTITY);
+                for (DynamicObject dynamicObject : dynamicObjectCollection) {
+                    extracted(dynamicObject, newPersonPosFiles, personIds);
+                }
+            }else{
+                //单次调整
+                extracted(data, newPersonPosFiles, personIds);
+            }
+        }
+
+        SaveServiceHelper.save(newPersonPosFiles.toArray(new DynamicObject[0]));
+        PositionStructureHelper.markAsNotCurrentNewest(personIds.toArray(new Long[0]));
+        PositionStructureHelper.markAsCurrentNewest(null,personIds.toArray(new Long[0]));
+        for (DynamicObject newPersonPosFile : newPersonPosFiles) {
+            String adjustType = newPersonPosFile.getString(PositionStructureConstant.NCKD_ADJUSTTYPE);
+            setJobLevelResult(newPersonPosFile.getDynamicObject(PositionStructureConstant.NCKD_PERSON),newPersonPosFile.getDynamicObject(PositionStructureConstant.NCKD_JOBLEVELHR),AdjustTypeEnum.getByCode(adjustType));
+        }
+    }
+
+    private void extracted(DynamicObject data, List<DynamicObject> newPersonPosFiles, List<Long> personIds) {
+        DynamicObject person = data.getDynamicObject(FormConstant.NCKD_PERSON);
+        long personId = person.getLong(FormConstant.ID_KEY);
+        Date adjustDate = data.getDate(PositionStructureConstant.NCKD_ADJUSTDATE);
+        if (adjustDate != null) {
+            // 如果adjustDate不为空且为年初日期(xxxx-01-01),则转换为当年7月1日
+            LocalDateTime adjustDateTime = DateUtil.toLocalDateTime(adjustDate);
+            if (adjustDateTime.getMonthValue() == 1 && adjustDateTime.getDayOfMonth() == 1) {
+                //前端只选年份,这里默认为XXX-07-01
+                adjustDate = DateUtil.toDate(adjustDateTime.withMonth(7).withDayOfMonth(1));
+            }
+        }
+        String remark = data.getString(FormConstant.NCKD_REMARK);
+        DynamicObject newPersonPosFile = AnnualAdjustmentService.addNewYearPersonPositionFileInfo(person, adjustDate, remark, positionAppointmentMap.get(personId), lastYearPerformanceResultMap.get(personId), firstPersonPosFileMap.get(personId));
+        newPersonPosFiles.add(newPersonPosFile);
+        personIds.add(personId);
+    }
+
+    /**
+     * 设置操作结果中的职级信息
+     */
+    protected void setJobLevelResult(DynamicObject person, DynamicObject jobLeve, AdjustTypeEnum adjustTypeEnum) {
+        String jobLeveStr = StrFormatter.format("{}:{}({}级);{}",
+                person.getString(FormConstant.NAME_KEY),
+                jobLeve.getString(FormConstant.NAME_KEY),
+                jobLeve.getString(FormConstant.JOBLEVELSEQ),
+                adjustTypeEnum.getName());
+
+        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);
+        }
+    }
+}

+ 175 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/annualadjust/AnnualEffectiveOpPlugin.java

@@ -0,0 +1,175 @@
+package nckd.jxccl.hr.psms.plugin.operate.annualadjust;
+
+import kd.bos.common.enums.EnableEnum;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.entity.ExtendedDataEntity;
+import kd.bos.entity.plugin.AbstractOperationServicePlugIn;
+import kd.bos.entity.plugin.AddValidatorsEventArgs;
+import kd.bos.entity.plugin.PreparePropertysEventArgs;
+import kd.bos.entity.plugin.args.BeginOperationTransactionArgs;
+import kd.bos.entity.validate.AbstractValidator;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.servicehelper.operation.SaveServiceHelper;
+import kd.sdk.plugin.Plugin;
+import nckd.jxccl.base.common.constant.FormConstant;
+import nckd.jxccl.base.common.enums.psms.TypeStateEnum;
+import nckd.jxccl.base.common.utils.DateUtil;
+import nckd.jxccl.base.common.utils.QueryFieldBuilder;
+import nckd.jxccl.base.common.utils.StrFormatter;
+import nckd.jxccl.base.orm.helper.QFilterCommonHelper;
+import nckd.jxccl.hr.psms.common.PositionStructureConstant;
+import nckd.jxccl.hr.psms.helper.PositionStructureHelper;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringJoiner;
+
+/**
+* 年度调整【生效】OP
+* @author W.Y.C
+* @date 2025/10/14 20:45
+* @version 1.0
+*/
+public class AnnualEffectiveOpPlugin extends AbstractOperationServicePlugIn implements Plugin {
+
+    /** 需要生效的数据(key:id,value:生效日期)*/
+    private Map<Long,Date> dataMap;
+    private StringJoiner message = new StringJoiner(StrFormatter.LINE_SEPARATOR);
+
+    @Override
+    public void onPreparePropertys(PreparePropertysEventArgs e) {
+        super.onPreparePropertys(e);
+    }
+
+    @Override
+    public void onAddValidators(AddValidatorsEventArgs e){
+        dataMap = new HashMap<>();
+        e.addValidator(new AbstractValidator() {
+            @Override
+            public void validate() {
+                if(!getOperationResult().isSuccess()){
+                    return;
+                }
+                for (ExtendedDataEntity rowDataEntity : getDataEntities()) {
+                    int dataEntityIndex = rowDataEntity.getDataEntityIndex();
+                    DynamicObject data = rowDataEntity.getDataEntity();
+                    List<Long> personIds = new ArrayList<Long>();
+                    Date adjustDate = data.getDate(PositionStructureConstant.NCKD_BEGINDATE);
+                    if(adjustDate == null){
+                        addFatalErrorMessage(rowDataEntity,"生效日期不能为空");
+                    }else {
+                        LocalDateTime adjustDateTime = DateUtil.toLocalDateTime(adjustDate);
+                        if (adjustDateTime.getMonthValue() == 1 && adjustDateTime.getDayOfMonth() == 1) {
+                            //前端只选年份,这里默认为XXX-07-01
+                            adjustDate = DateUtil.toDate(adjustDateTime.withMonth(7).withDayOfMonth(1));
+                        }
+
+                        DynamicObjectCollection mulSelectPersonPosFile = data.getDynamicObjectCollection(PositionStructureConstant.NCKD_MULSELEPERSONPOSFILE);
+                        if(mulSelectPersonPosFile.isEmpty()){
+                            addFatalErrorMessage(rowDataEntity,"请选择要“生效”的职位档案");
+                        }
+                        //获取多选基础资料
+                        for (DynamicObject item : mulSelectPersonPosFile) {
+                            // 获取基础资料的动态对象(包含基础资料的详细信息,如名称、编码等)
+                            DynamicObject mulData = item.getDynamicObject(FormConstant.BASEDATAID_KEY);
+                            // 获取基础资料的ID(即基础资料本身的主键)
+                            long id = item.getLong(FormConstant.FBASEDATAID_ID_KEY);
+                            String adjustStatus = mulData.getString(PositionStructureConstant.NCKD_ADJUSSTATUS);
+                            String typeState = mulData.getString(PositionStructureConstant.NCKD_TYPESTATE);
+                            TypeStateEnum typeStateEnum = TypeStateEnum.getByCode(typeState);
+                            DynamicObject person = mulData.getDynamicObject(FormConstant.NCKD_PERSON);
+
+                            //校验是否是“年度调整”
+                            if (TypeStateEnum.ANNUAL_ADJUSTMENT.getCode().equalsIgnoreCase(typeState)) {
+                                //校验是否是未生效的
+                                if (!EnableEnum.YES.getCode().equals(adjustStatus)) {
+                                    //只有未生效的才需要执行生效
+                                    personIds.add(person.getLong(FormConstant.ID_KEY));
+                                    dataMap.put(id, adjustDate);
+                                }else{
+                                    message.add(StrFormatter.format("【{}】的年度调整已是“生效”状态,忽略此条处理。", person.getString("name")));
+                                }
+                            }else{
+                                //其他类型不能生效
+                                addFatalErrorMessage(rowDataEntity, StrFormatter.format("【{}】不能生效,只有年度调整才能操作生效!", typeStateEnum.getName()));
+                            }
+
+                        }
+                        //TODO 【待修改】-职位体系-测试完打开注释
+                        /*if(!personIds.isEmpty()) {
+                            List<DynamicObject> latsPersonPosFileByPerson = PositionStructureHelper.getLatsPersonPosFileByPerson(null, personIds.toArray(new Long[0]));
+                            for (DynamicObject personPosFileByPerson : latsPersonPosFileByPerson) {
+                                //校验生效日期是否在上一条档案之后
+                                Date lastBeginDate = personPosFileByPerson.getDate(PositionStructureConstant.NCKD_BEGINDATE);
+                                DynamicObject person = personPosFileByPerson.getDynamicObject(FormConstant.NCKD_PERSON);
+                                String typeState = personPosFileByPerson.getString(PositionStructureConstant.NCKD_TYPESTATE);
+                                TypeStateEnum typeStateEnum = TypeStateEnum.getByCode(typeState);
+                                //判断当前日期是否早于或等于上一条档案的生效日期
+                                if (adjustDate.compareTo(lastBeginDate) <= 0) {
+                                    addFatalErrorMessage(rowDataEntity, StrFormatter.format("员工【{}】的生效日期必须在上一档案【{}】之后,上一档案生效日期【{}】;", person.getString(FormConstant.NAME_KEY),typeStateEnum.getName(),DateUtil.format(lastBeginDate, DateUtil.NORM_DATE_PATTERN)));
+                                }
+                            }
+                        }*/
+                    }
+                }
+                //所有情况校验完再校验是否被锁定,比较友好。不然先校验锁等用户解锁之后又发现其他情况而不能继续处理
+                if(this.getValidateResult().isSuccess()) {
+                    for (ExtendedDataEntity rowDataEntity : getDataEntities()) {
+                        DynamicObject data = rowDataEntity.getDataEntity();
+                        DynamicObjectCollection mulSelectPersonPosFile = data.getDynamicObjectCollection(PositionStructureConstant.NCKD_MULSELEPERSONPOSFILE);
+                        for (DynamicObject item : mulSelectPersonPosFile) {
+                            DynamicObject mulData = item.getDynamicObject(FormConstant.BASEDATAID_KEY);
+                            String typeState = mulData.getString(PositionStructureConstant.NCKD_TYPESTATE);
+                            TypeStateEnum typeStateEnum = TypeStateEnum.getByCode(typeState);
+                            DynamicObject person = mulData.getDynamicObject(FormConstant.NCKD_PERSON);
+                            //校验是否已被“锁定”
+                            boolean lockStatus = mulData.getBoolean(PositionStructureConstant.NCKD_LOCKSTATUS);
+                            if (lockStatus) {
+                                addFatalErrorMessage(rowDataEntity, StrFormatter.format("【{}】的{}已“锁定”,不能生效。请先“解锁”。", person.getString(FormConstant.NAME_KEY), typeStateEnum.getName()));
+                            }
+                        }
+                    }
+                }
+            }
+        });
+    }
+
+    @Override
+    public void beginOperationTransaction(BeginOperationTransactionArgs e) {
+        QueryFieldBuilder queryFieldBuilder = QueryFieldBuilder.create()
+                .add(FormConstant.ID_KEY)
+                .add(PositionStructureConstant.NCKD_BEGINDATE)
+                .add(PositionStructureConstant.NCKD_ADJUSSTATUS)
+                .addGroup(new String[]{FormConstant.NCKD_PERSON},FormConstant.ID_KEY);
+        //本次需要生效的id
+        Set<Long> ids = dataMap.keySet();
+        DynamicObject[] load = BusinessDataServiceHelper.load(PositionStructureConstant.PERSONPOSFILE_ENTITYID, queryFieldBuilder.buildSelect(), new QFilter[]{QFilterCommonHelper.getIdInFilter(ids)});
+        List<Long> personIds = new ArrayList<>();
+        for (DynamicObject dynamicObject : load) {
+            long id = dynamicObject.getLong(FormConstant.ID_KEY);
+            Date date = dataMap.get(id);
+            dynamicObject.set(PositionStructureConstant.NCKD_BEGINDATE, date);
+            dynamicObject.set(PositionStructureConstant.NCKD_ADJUSSTATUS, EnableEnum.YES.getCode());
+            personIds.add(dynamicObject.getDynamicObject(FormConstant.NCKD_PERSON).getLong(FormConstant.ID_KEY));
+        }
+        SaveServiceHelper.update(load);
+        if(message.length() > 0){
+            if (this.getOperationResult().getCustomData() == null) {
+                this.getOperationResult().setCustomData(new HashMap<>());
+            }
+            this.getOperationResult().getCustomData().put("message", message.toString());
+        }
+
+        //将人员所有职位档案标记为非最新
+        PositionStructureHelper.markAsNotCurrentNewest(personIds.toArray(new Long[0]));
+        //将当前人员最新档案(初定 或者 (年度调整 并且 已生效) 或者 (动态调整) 生效时间为最新的那一条)标记为最新的
+        PositionStructureHelper.markAsCurrentNewest(null,personIds.toArray(new Long[0]));
+    }
+}

+ 274 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/annualadjust/AnnualLockOrUnLockedOpPlugin.java

@@ -0,0 +1,274 @@
+package nckd.jxccl.hr.psms.plugin.operate.annualadjust;
+
+import kd.bos.common.enums.EnableEnum;
+import kd.bos.context.RequestContext;
+import kd.bos.dataentity.OperateOption;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.dataentity.metadata.dynamicobject.DynamicObjectType;
+import kd.bos.entity.ExtendedDataEntity;
+import kd.bos.entity.operate.OperateOptionConst;
+import kd.bos.entity.operate.interaction.InteractionConfirmResult;
+import kd.bos.entity.operate.interaction.InteractionContext;
+import kd.bos.entity.operate.interaction.KDInteractionException;
+import kd.bos.entity.operate.result.IOperateInfo;
+import kd.bos.entity.operate.result.OperateErrorInfo;
+import kd.bos.entity.operate.result.OperationResult;
+import kd.bos.entity.plugin.AbstractOperationServicePlugIn;
+import kd.bos.entity.plugin.AddValidatorsEventArgs;
+import kd.bos.entity.plugin.PreparePropertysEventArgs;
+import kd.bos.entity.plugin.args.BeforeOperationArgs;
+import kd.bos.entity.plugin.args.BeginOperationTransactionArgs;
+import kd.bos.entity.validate.AbstractValidator;
+import kd.bos.entity.validate.BillStatus;
+import kd.bos.entity.validate.ErrorLevel;
+import kd.bos.form.MessageBoxResult;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.servicehelper.QueryServiceHelper;
+import kd.bos.servicehelper.operation.OperationServiceHelper;
+import kd.bos.servicehelper.operation.SaveServiceHelper;
+import kd.bos.servicehelper.user.UserServiceHelper;
+import kd.sdk.plugin.Plugin;
+import nckd.jxccl.base.common.constant.FormConstant;
+import nckd.jxccl.base.common.exception.ValidationException;
+import nckd.jxccl.base.common.utils.QueryFieldBuilder;
+import nckd.jxccl.base.common.utils.StrFormatter;
+import nckd.jxccl.base.orm.helper.QFilterCommonHelper;
+import nckd.jxccl.hr.psms.common.PositionStructureConstant;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.StringJoiner;
+
+/**
+ * 单据操作插件
+ */
+/**
+* 年度调整锁定解锁
+* @author W.Y.C
+* @date 2025/10/16 9:36
+* @version 1.0
+*/
+public class AnnualLockOrUnLockedOpPlugin extends AbstractOperationServicePlugIn implements Plugin {
+
+    private final static String INTERACTION_SPONORE = "nckd.jxccl.hr.psms.plugin.operate.annualadjust.AnnualLockOrUnLockedOpPlugin";
+    private final static String INTERACTION_SPONORE1 = "nckd.jxccl.hr.psms.plugin.operate.annualadjust.AnnualLockOrUnLockedOpPlugin#1";
+
+    private List<Long> ids;
+
+    StringJoiner pendingMsgJoiner = new StringJoiner(StrFormatter.LINE_SEPARATOR);
+
+    private List<String> pendingPerson = new ArrayList<>();
+
+    private StringJoiner message = new StringJoiner(StrFormatter.LINE_SEPARATOR);
+
+    @Override
+    public void onPreparePropertys(PreparePropertysEventArgs e) {
+        e.getFieldKeys().add(FormConstant.NCKD_PERSON);
+        e.getFieldKeys().add(PositionStructureConstant.NCKD_LASTPERSONPOSFILE);
+        e.getFieldKeys().add(PositionStructureConstant.NCKD_TYPESTATE);
+        e.getFieldKeys().add(PositionStructureConstant.NCKD_ADJUSSTATUS);
+        e.getFieldKeys().add(PositionStructureConstant.NCKD_LOCKSTATUS);
+    }
+
+
+    @Override
+    public void onAddValidators(AddValidatorsEventArgs e){
+        ids = new ArrayList<>();
+        e.addValidator(new AbstractValidator() {
+            @Override
+            public void validate() {
+                String operateKey = this.getOperateKey();
+                //islocked:锁定
+                //unlocked:解锁
+                for (ExtendedDataEntity rowDataEntity : getDataEntities()) {
+                    DynamicObject data = rowDataEntity.getDataEntity();
+                    long id = data.getLong(FormConstant.ID_KEY);
+                    String adjustStatus = data.getString(PositionStructureConstant.NCKD_ADJUSSTATUS);
+                    boolean lockStatus = data.getBoolean(PositionStructureConstant.NCKD_LOCKSTATUS);
+                    DynamicObject person = data.getDynamicObject(FormConstant.NCKD_PERSON);
+                    String personName = person.getString(FormConstant.NAME_KEY);
+                    if(PositionStructureConstant.ISLOCKED_OP.equals(operateKey)) {
+                        if(!lockStatus) {
+                            if (!EnableEnum.YES.getCode().equals(adjustStatus)) {
+                                //锁定的数据不是“未生效”状态
+                                pendingPerson.add(personName);
+                            }
+                            ids.add(id);
+                        }else{
+                            message.add(StrFormatter.format("【{}】的年度调整已是“锁定”状态,忽略此条处理。", person.getString("name")));
+                        }
+                    } else if(PositionStructureConstant.UNLOCKED_OP.equals(operateKey)) {
+                        if(!lockStatus) {
+                            message.add(StrFormatter.format("【{}】的年度调整已是“解锁”状态,忽略此条处理。", person.getString("name")));
+                        }
+                        ids.add(id);
+                    }
+                }
+            }
+        });
+    }
+
+    @Override
+    public void beginOperationTransaction(BeginOperationTransactionArgs e) {
+        String operateKey = e.getOperationKey();
+
+        QueryFieldBuilder queryFieldBuilder = QueryFieldBuilder.create()
+                .add(FormConstant.ID_KEY)
+                .add(PositionStructureConstant.NCKD_LOCKSTATUS)
+                .add(PositionStructureConstant.KEY_NCKD_LOCKUSER)
+                .add(PositionStructureConstant.NCKD_LOCKDATETIME);
+        DynamicObject[] load = BusinessDataServiceHelper.load(PositionStructureConstant.PERSONPOSFILE_ENTITYID, queryFieldBuilder.buildSelect(), new QFilter[]{QFilterCommonHelper.getIdInFilter(ids)});
+        boolean isProcessFlag = false;
+        for (DynamicObject dynamicObject : load) {
+            long id = dynamicObject.getLong(FormConstant.ID_KEY);
+            if(PositionStructureConstant.ISLOCKED_OP.equals(operateKey)) {
+                dynamicObject.set(PositionStructureConstant.NCKD_LOCKSTATUS, EnableEnum.YES.getCode());
+                dynamicObject.set(PositionStructureConstant.KEY_NCKD_LOCKUSER, RequestContext.get().getCurrUserId());
+                dynamicObject.set(PositionStructureConstant.NCKD_LOCKDATETIME, new Date());
+            }else{
+                //TODO 【待修改】-职位体系-解锁需要校验是否总部人员,非总部人员需要走审批
+                if(true) {
+                    //非总部人员解锁,发起解锁申请流程
+                    isProcessFlag = true;
+                }else{
+                    dynamicObject.set(PositionStructureConstant.NCKD_LOCKSTATUS, EnableEnum.NO.getCode());
+                    dynamicObject.set(PositionStructureConstant.KEY_NCKD_LOCKUSER, null);
+                    dynamicObject.set(PositionStructureConstant.NCKD_LOCKDATETIME, null);
+
+                }
+            }
+        }
+        if(isProcessFlag) {
+            //操作原因
+            String reason = this.getOption().getVariableValue("reason", "");
+            //发起解锁单据流程
+            DynamicObject billObj = BusinessDataServiceHelper.newDynamicObject(PositionStructureConstant.POSFILEUNLOCK_ENTITYID);
+            billObj.set(FormConstant.CREATOR_KEY, UserServiceHelper.getCurrentUserId());
+            billObj.set(FormConstant.CREATE_TIME_KEY, System.currentTimeMillis());
+            billObj.set(FormConstant.BILL_STATUS_KEY,BillStatus.A.toString());
+            DynamicObject org = BusinessDataServiceHelper.newDynamicObject(FormConstant.ADMINORGHR_ENTITYID);
+            org.set(FormConstant.ID_KEY, UserServiceHelper.getUserMainOrgId(UserServiceHelper.getCurrentUserId()));
+            billObj.set(FormConstant.ORG_KEY, org);
+            billObj.set(PositionStructureConstant.NCKD_REASON,reason);
+
+            DynamicObjectCollection entryColl = billObj.getDynamicObjectCollection(PositionStructureConstant.NCKD_POSFILEUNLOCKENTRY);
+            DynamicObjectType entryType = entryColl.getDynamicObjectType();
+            for (DynamicObject dynamicObject : load) {
+                DynamicObject entryObj = new DynamicObject(entryType);
+                entryObj.set(PositionStructureConstant.NCKD_PERSONPOSFILE, dynamicObject);
+                entryColl.add(entryObj);
+            }
+            //提交单据
+            OperateOption option = OperateOption.create();
+            OperationResult result = OperationServiceHelper.executeOperate(
+                    "submit",
+                    PositionStructureConstant.POSFILEUNLOCK_ENTITYID,
+                    new DynamicObject[]{billObj},
+                    option
+            );
+            if(!result.isSuccess()) {
+                StringJoiner joiner = new StringJoiner(StrFormatter.LINE_SEPARATOR);
+                for (IOperateInfo iOperateInfo : result.getAllErrorOrValidateInfo()) {
+                    joiner.add(iOperateInfo.getMessage());
+                }
+                throw new ValidationException(StrFormatter.format("提交解锁申请单失败,原因:{}", joiner.toString()));
+
+            }else{
+                if (this.getOperationResult().getCustomData() == null) {
+                    this.getOperationResult().setCustomData(new HashMap<>());
+                }
+                this.getOperationResult().getCustomData().put("bill", "true");
+            }
+        }else{
+            SaveServiceHelper.update(load);
+        }
+
+
+        if(message.length() > 0) {
+            if (this.getOperationResult().getCustomData() == null) {
+                this.getOperationResult().setCustomData(new HashMap<>());
+            }
+            this.getOperationResult().getCustomData().put("message", message.toString());
+        }
+    }
+
+    @Override
+    public void beforeExecuteOperationTransaction(BeforeOperationArgs e) {
+        String operationKey = e.getOperationKey();
+        if(PositionStructureConstant.ISLOCKED_OP.equals(operationKey)) {
+            if (!pendingPerson.isEmpty()) {
+                e.cancel = !this.showInteractionMessage();
+            }
+        }else if(PositionStructureConstant.UNLOCKED_OP.equals(operationKey)){
+            //TODO 【待修改】-职位体系-解锁需要校验是否总部人员,非总部人员需要走审批
+            if(true){
+                //校验选中的档案是否已经有申请单据
+                QueryFieldBuilder queryFieldBuilder = QueryFieldBuilder.create()
+                        .addGroup(new String[]{PositionStructureConstant.NCKD_POSFILEUNLOCKENTRY, PositionStructureConstant.NCKD_PERSONPOSFILE,FormConstant.NCKD_PERSON},
+                                FormConstant.NAME_KEY);
+                QFilter filter = new QFilter(FormConstant.BILL_STATUS_KEY, QCP.not_equals, BillStatus.C.toString())
+                        .and(new QFilter(String.join(".", PositionStructureConstant.NCKD_POSFILEUNLOCKENTRY, PositionStructureConstant.NCKD_PERSONPOSFILE, FormConstant.ID_KEY), QCP.in, ids));
+                DynamicObjectCollection query = QueryServiceHelper.query(PositionStructureConstant.POSFILEUNLOCK_ENTITYID, queryFieldBuilder.buildSelect(), new QFilter[]{filter});
+                if(!query.isEmpty()){
+                    List<String> personNames = new ArrayList<>(query.size());
+                    for (DynamicObject dynamicObject : query) {
+                        String personName = dynamicObject.getString(String.join(".", PositionStructureConstant.NCKD_POSFILEUNLOCKENTRY, PositionStructureConstant.NCKD_PERSONPOSFILE, FormConstant.NCKD_PERSON, FormConstant.NAME_KEY));
+                        personNames.add(personName);
+                    }
+                    throw new ValidationException(StrFormatter.format("人员【{}】的职位档案已存在待解锁的申请,请勿重复申请。", String.join(",", personNames)));
+                }else{
+                    e.cancel = !this.unlockNeedApprovalInteractionMessage();
+                }
+            }
+        }
+    }
+
+
+    private boolean showInteractionMessage() {
+        //交互式操作提示
+        // 检查是否为回调:避免死循环
+        String confirmResultString = this.getOption().getVariableValue(OperateOptionConst.INTERACTIONCONFIRMRESULT, "");
+        InteractionConfirmResult confirmResult = InteractionConfirmResult.fromJsonString(confirmResultString);
+        if (confirmResult.getResults().containsKey(INTERACTION_SPONORE)) {
+            // 已是回调,直接返回结果
+            MessageBoxResult result = MessageBoxResult.valueOf(confirmResult.getResults().get(INTERACTION_SPONORE));
+            return result == MessageBoxResult.Yes;
+        }
+
+        // 首次执行:抛出交互异常
+        InteractionContext interactionContext = new InteractionContext();
+        interactionContext.setSimpleMessage("存在“未生效的”档案");
+        OperateErrorInfo errorInfo = new OperateErrorInfo();
+        errorInfo.setMessage(StrFormatter.format("人员【{}】的档案还“未生效”,锁定后无法后续操作", String.join(",", pendingPerson)));
+        errorInfo.setLevel(ErrorLevel.Warning);
+        interactionContext.addOperateInfo(errorInfo);
+        throw new KDInteractionException(INTERACTION_SPONORE, interactionContext);
+    }
+
+    private boolean unlockNeedApprovalInteractionMessage() {
+        //交互式操作提示
+        // 检查是否为回调:避免死循环
+        String confirmResultString = this.getOption().getVariableValue(OperateOptionConst.INTERACTIONCONFIRMRESULT, "");
+        InteractionConfirmResult confirmResult = InteractionConfirmResult.fromJsonString(confirmResultString);
+        if (confirmResult.getResults().containsKey(INTERACTION_SPONORE1)) {
+            // 已是回调,直接返回结果
+            MessageBoxResult result = MessageBoxResult.valueOf(confirmResult.getResults().get(INTERACTION_SPONORE1));
+            return result == MessageBoxResult.Yes;
+        }
+
+        // 首次执行:抛出交互异常
+        InteractionContext interactionContext = new InteractionContext();
+        interactionContext.setSimpleMessage("存在“未生效的”档案");
+        OperateErrorInfo errorInfo = new OperateErrorInfo();
+        errorInfo.setMessage("解锁需要经过总部审批,审批通过之后自动解锁;点击“是”系统自动发起解锁审批流程");
+        errorInfo.setLevel(ErrorLevel.Warning);
+        interactionContext.addOperateInfo(errorInfo);
+        throw new KDInteractionException(INTERACTION_SPONORE1, interactionContext);
+    }
+
+}

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

@@ -0,0 +1,155 @@
+package nckd.jxccl.hr.psms.plugin.operate.annualadjust;
+
+import kd.bos.common.enums.EnableEnum;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.entity.ExtendedDataEntity;
+import kd.bos.entity.plugin.AbstractOperationServicePlugIn;
+import kd.bos.entity.plugin.AddValidatorsEventArgs;
+import kd.bos.entity.plugin.PreparePropertysEventArgs;
+import kd.bos.entity.plugin.args.BeginOperationTransactionArgs;
+import kd.bos.entity.validate.AbstractValidator;
+import kd.bos.form.events.ClosedCallBackEvent;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.servicehelper.QueryServiceHelper;
+import kd.bos.servicehelper.operation.SaveServiceHelper;
+import kd.sdk.plugin.Plugin;
+import nckd.jxccl.base.common.constant.FormConstant;
+import nckd.jxccl.base.common.enums.psms.TypeStateEnum;
+import nckd.jxccl.base.common.utils.QueryFieldBuilder;
+import nckd.jxccl.base.common.utils.StrFormatter;
+import nckd.jxccl.base.orm.helper.QFilterCommonHelper;
+import nckd.jxccl.hr.psms.common.PositionStructureConstant;
+import nckd.jxccl.hr.psms.helper.PositionStructureHelper;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringJoiner;
+
+/**
+* 年度调整【置回未生效】OP
+* @author W.Y.C
+* @date 2025/10/14 20:47
+* @version 1.0
+*/
+public class AnnualSetinActiveOpPlugin extends AbstractOperationServicePlugIn implements Plugin {
+
+    /** 需要生效的数据(key:id,value:生效日期)*/
+    private Map<Long,Integer> idMaps = new HashMap<>();
+    private StringJoiner message = new StringJoiner(StrFormatter.LINE_SEPARATOR);
+    @Override
+    public void onPreparePropertys(PreparePropertysEventArgs e) {
+        e.getFieldKeys().add(FormConstant.NCKD_PERSON);
+        e.getFieldKeys().add(PositionStructureConstant.NCKD_LASTPERSONPOSFILE);
+        e.getFieldKeys().add(PositionStructureConstant.NCKD_TYPESTATE);
+        e.getFieldKeys().add(PositionStructureConstant.NCKD_ADJUSSTATUS);
+        e.getFieldKeys().add(PositionStructureConstant.NCKD_LOCKSTATUS);
+    }
+
+    @Override
+    public void onAddValidators(AddValidatorsEventArgs e){
+        e.addValidator(new AbstractValidator() {
+            @Override
+            public void validate() {
+                for (ExtendedDataEntity rowDataEntity : getDataEntities()) {
+                    DynamicObject data = rowDataEntity.getDataEntity();
+                    long id = data.getLong(FormConstant.ID_KEY);
+                    String typeState = data.getString(PositionStructureConstant.NCKD_TYPESTATE);
+                    String adjustStatus = data.getString(PositionStructureConstant.NCKD_ADJUSSTATUS);
+                    DynamicObject person = data.getDynamicObject(FormConstant.NCKD_PERSON);
+                    TypeStateEnum typeStateEnum = TypeStateEnum.getByCode(typeState);
+                    List<Long> personIds = new ArrayList<Long>();
+
+                    //校验是否是“年度调整”
+                    if(TypeStateEnum.ANNUAL_ADJUSTMENT.getCode().equalsIgnoreCase(typeState)){
+                        if (EnableEnum.YES.getCode().equals(adjustStatus)) {
+                            //只有已生效的才需要置回未生效
+                            personIds.add(person.getLong(FormConstant.ID_KEY));
+                            idMaps.put(id,rowDataEntity.getDataEntityIndex());
+                        }else{
+                            message.add(StrFormatter.format("【{}】的年度调整已是“未生效”状态,忽略此条处理。", person.getString("name")));
+                        }
+                    }else{
+                        //其他类型不能置回未生效
+                        addFatalErrorMessage(rowDataEntity, StrFormatter.format("【{}】不能置回未生效,只有年度调整才能操作", typeStateEnum.getName()));
+                    }
+                }
+                if(!idMaps.isEmpty()){
+                    //校验是否已被使用
+                    QFilter qFilter = new QFilter(PositionStructureConstant.NCKD_DISABLE, QCP.equals, EnableEnum.NO.getCode())
+                            .and(new QFilter(String.join(".", PositionStructureConstant.NCKD_LASTPERSONPOSFILE, FormConstant.ID_KEY), QCP.in, idMaps.keySet()));
+                    QueryFieldBuilder queryFieldBuilder = QueryFieldBuilder.create()
+                            .add(PositionStructureConstant.ID_KEY)
+                            .addGroup(new String[]{FormConstant.NCKD_PERSON}, FormConstant.NAME_KEY)
+                            .add(PositionStructureConstant.NCKD_EXECUTEYEAR)
+                            .add(PositionStructureConstant.NCKD_TYPESTATE);
+                    DynamicObjectCollection query = QueryServiceHelper.query(PositionStructureConstant.PERSONPOSFILE_ENTITYID, queryFieldBuilder.buildSelect(), new QFilter[]{qFilter});
+                    if(!query.isEmpty()){
+                        for (DynamicObject dynamicObject : query) {
+                            long id = dynamicObject.getLong(FormConstant.ID_KEY);
+                            String executeYear = dynamicObject.getString(PositionStructureConstant.NCKD_EXECUTEYEAR);
+                            String typeState = dynamicObject.getString(PositionStructureConstant.NCKD_TYPESTATE);
+                            TypeStateEnum typeStateEnum = TypeStateEnum.getByCode(typeState);
+                            String personName = dynamicObject.getString(String.join(".", FormConstant.NCKD_PERSON, FormConstant.NAME_KEY));
+                            addFatalErrorMessage(getDataEntities()[idMaps.get(id)], StrFormatter.format("【{}】的档案已被【{}】年度的“{}”档案使用,不能置回未生效。",personName, executeYear,typeStateEnum.getName()));
+                        }
+                    }
+                }
+
+                //所有情况校验完再校验是否被锁定,比较友好。不然先校验锁等用户解锁之后又发现其他情况而不能继续处理
+                if(this.getValidateResult().isSuccess()) {
+                    for (ExtendedDataEntity rowDataEntity : getDataEntities()) {
+                        DynamicObject data = rowDataEntity.getDataEntity();
+                        String typeState = data.getString(PositionStructureConstant.NCKD_TYPESTATE);
+                        TypeStateEnum typeStateEnum = TypeStateEnum.getByCode(typeState);
+                        DynamicObject person = data.getDynamicObject(FormConstant.NCKD_PERSON);
+                        //校验是否已被“锁定”
+                        boolean lockStatus = data.getBoolean(PositionStructureConstant.NCKD_LOCKSTATUS);
+                        if (lockStatus) {
+                            addFatalErrorMessage(rowDataEntity, StrFormatter.format("【{}】的{}已“锁定”,不能生效。请先“解锁”。", person.getString(FormConstant.NAME_KEY), typeStateEnum.getName()));
+                        }
+                    }
+                }
+            }
+        });
+    }
+
+    @Override
+    public void beginOperationTransaction(BeginOperationTransactionArgs e) {
+        QueryFieldBuilder queryFieldBuilder = QueryFieldBuilder.create()
+                .add(FormConstant.ID_KEY)
+                .add(PositionStructureConstant.NCKD_BEGINDATE)
+                .add(PositionStructureConstant.NCKD_ADJUSSTATUS)
+                .addGroup(new String[]{FormConstant.NCKD_PERSON},FormConstant.ID_KEY);
+        //本次需要生效的id
+        Set<Long> ids = idMaps.keySet();
+        DynamicObject[] load = BusinessDataServiceHelper.load(PositionStructureConstant.PERSONPOSFILE_ENTITYID, queryFieldBuilder.buildSelect(), new QFilter[]{QFilterCommonHelper.getIdInFilter(ids)});
+        List<Long> personIds = new ArrayList<>();
+        for (DynamicObject dynamicObject : load) {
+            long id = dynamicObject.getLong(FormConstant.ID_KEY);
+            dynamicObject.set(PositionStructureConstant.NCKD_BEGINDATE, null);
+            dynamicObject.set(PositionStructureConstant.NCKD_ADJUSSTATUS, EnableEnum.NO.getCode());
+            personIds.add(dynamicObject.getDynamicObject(FormConstant.NCKD_PERSON).getLong(FormConstant.ID_KEY));
+        }
+        SaveServiceHelper.update(load);
+        if(message.length() > 0){
+            if (this.getOperationResult().getCustomData() == null) {
+                this.getOperationResult().setCustomData(new HashMap<>());
+            }
+            this.getOperationResult().getCustomData().put("message", message.toString());
+        }
+
+        //将人员所有职位档案标记为非最新
+        PositionStructureHelper.markAsNotCurrentNewest(personIds.toArray(new Long[0]));
+        //将当前人员最新档案(初定 或者 (年度调整 并且 已生效) 或者 (动态调整) 生效时间为最新的那一条)标记为最新的
+        PositionStructureHelper.markAsCurrentNewest(null,personIds.toArray(new Long[0]));
+    }
+}

+ 65 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/annualadjust/PosFileUnlockBillOpPlugin.java

@@ -0,0 +1,65 @@
+package nckd.jxccl.hr.psms.plugin.operate.annualadjust;
+
+import kd.bos.common.enums.EnableEnum;
+import kd.bos.context.RequestContext;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.entity.plugin.AbstractOperationServicePlugIn;
+import kd.bos.entity.plugin.args.BeginOperationTransactionArgs;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.servicehelper.operation.SaveServiceHelper;
+import kd.sdk.plugin.Plugin;
+import nckd.jxccl.base.common.constant.FormConstant;
+import nckd.jxccl.base.common.utils.QueryFieldBuilder;
+import nckd.jxccl.base.orm.helper.QFilterCommonHelper;
+import nckd.jxccl.hr.psms.common.PositionStructureConstant;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+* 年度调整解锁单据-解锁/反解锁
+* @author W.Y.C
+* @date 2025/10/16 16:33
+* @version 1.0
+*/
+public class PosFileUnlockBillOpPlugin extends AbstractOperationServicePlugIn implements Plugin {
+
+
+    @Override
+    public void beginOperationTransaction(BeginOperationTransactionArgs e) {
+        String operateKey = e.getOperationKey();
+        List<Long> ids = new ArrayList<>();
+        for (DynamicObject data : e.getDataEntities()) {
+            DynamicObjectCollection entrys = data.getDynamicObjectCollection(PositionStructureConstant.NCKD_POSFILEUNLOCKENTRY);
+
+            for (DynamicObject entry : entrys) {
+                DynamicObject personPosFile = entry.getDynamicObject(PositionStructureConstant.NCKD_PERSONPOSFILE);
+                long id = personPosFile.getLong(FormConstant.ID_KEY);
+                ids.add(id);
+            }
+        }
+        QueryFieldBuilder queryFieldBuilder = QueryFieldBuilder.create()
+                .add(FormConstant.ID_KEY)
+                .add(PositionStructureConstant.NCKD_LOCKSTATUS)
+                .add(PositionStructureConstant.KEY_NCKD_LOCKUSER)
+                .add(PositionStructureConstant.NCKD_LOCKDATETIME);
+        DynamicObject[] load = BusinessDataServiceHelper.load(PositionStructureConstant.PERSONPOSFILE_ENTITYID, queryFieldBuilder.buildSelect(), new QFilter[]{QFilterCommonHelper.getIdInFilter(ids)});
+        for (DynamicObject dynamicObject : load) {
+            if(PositionStructureConstant.UNLOCKED_OP.equals(operateKey)) {
+                //审批通过-解锁
+                dynamicObject.set(PositionStructureConstant.NCKD_LOCKSTATUS, EnableEnum.NO.getCode());
+                dynamicObject.set(PositionStructureConstant.KEY_NCKD_LOCKUSER, null);
+                dynamicObject.set(PositionStructureConstant.NCKD_LOCKDATETIME, null);
+            }else{
+                //反向撤回-反解锁
+                dynamicObject.set(PositionStructureConstant.NCKD_LOCKSTATUS, EnableEnum.YES.getCode());
+                dynamicObject.set(PositionStructureConstant.KEY_NCKD_LOCKUSER, RequestContext.get().getCurrUserId());
+                dynamicObject.set(PositionStructureConstant.NCKD_LOCKDATETIME, new Date());
+            }
+        }
+        SaveServiceHelper.update(load);
+    }
+}

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

@@ -0,0 +1,127 @@
+package nckd.jxccl.hr.psms.plugin.operate.file;
+
+import kd.bos.common.enums.EnableEnum;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.entity.ExtendedDataEntity;
+import kd.bos.entity.plugin.AbstractOperationServicePlugIn;
+import kd.bos.entity.plugin.AddValidatorsEventArgs;
+import kd.bos.entity.plugin.PreparePropertysEventArgs;
+import kd.bos.entity.plugin.args.BeginOperationTransactionArgs;
+import kd.bos.entity.validate.AbstractValidator;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.QueryServiceHelper;
+import kd.sdk.plugin.Plugin;
+import nckd.jxccl.base.common.constant.FormConstant;
+import nckd.jxccl.base.common.enums.psms.TypeStateEnum;
+import nckd.jxccl.base.common.utils.QueryFieldBuilder;
+import nckd.jxccl.base.common.utils.StrFormatter;
+import nckd.jxccl.hr.psms.common.PositionStructureConstant;
+import nckd.jxccl.hr.psms.helper.PositionStructureHelper;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+* 职位档案删除OP
+* @author W.Y.C
+* @date 2025/10/13 10:06
+* @version 1.0
+*/
+public class PersonPosFileDeleteOpPlugin extends AbstractOperationServicePlugIn implements Plugin {
+
+    /** 需要生效的数据(key:id,value:生效日期)*/
+    private Map<Long,Integer> idMaps = new HashMap<>();
+
+    @Override
+    public void onPreparePropertys(PreparePropertysEventArgs e) {
+        e.getFieldKeys().add(FormConstant.NCKD_PERSON);
+        e.getFieldKeys().add(PositionStructureConstant.NCKD_LASTPERSONPOSFILE);
+        e.getFieldKeys().add(PositionStructureConstant.NCKD_TYPESTATE);
+        e.getFieldKeys().add(PositionStructureConstant.NCKD_ADJUSSTATUS);
+        e.getFieldKeys().add(PositionStructureConstant.NCKD_LOCKSTATUS);
+    }
+
+    @Override
+    public void onAddValidators(AddValidatorsEventArgs e){
+        e.addValidator(new AbstractValidator() {
+            @Override
+            public void validate() {
+                for (ExtendedDataEntity rowDataEntity : getDataEntities()) {
+                    DynamicObject data = rowDataEntity.getDataEntity();
+                    long id = data.getLong(FormConstant.ID_KEY);
+                    String adjustStatus = data.getString(PositionStructureConstant.NCKD_ADJUSSTATUS);
+                    String typeState = data.getString(PositionStructureConstant.NCKD_TYPESTATE);
+                    DynamicObject person = data.getDynamicObject(FormConstant.NCKD_PERSON);
+                    TypeStateEnum typeStateEnum = TypeStateEnum.getByCode(typeState);
+
+                    if (TypeStateEnum.ANNUAL_ADJUSTMENT.getCode().equalsIgnoreCase(typeState)) {
+                        if (EnableEnum.YES.getCode().equals(adjustStatus)) {
+                            addFatalErrorMessage(rowDataEntity, StrFormatter.format("【{}】的{}已“生效”,不能删除。请先“置回未生效”状态。", person.getString("name"), typeStateEnum.getName()));
+                        }else{
+                            idMaps.put(id,rowDataEntity.getDataEntityIndex());
+                        }
+                    }else {
+                        //其他类型不能置回未生效
+                        addFatalErrorMessage(rowDataEntity, StrFormatter.format("【{}】不能删除", typeStateEnum.getName()));
+                    }
+
+                }
+                if(!idMaps.isEmpty()){
+                    //校验是否已被使用
+                    QFilter qFilter = new QFilter(PositionStructureConstant.NCKD_DISABLE, QCP.equals, EnableEnum.NO.getCode())
+                            .and(new QFilter(String.join(".", PositionStructureConstant.NCKD_LASTPERSONPOSFILE, FormConstant.ID_KEY), QCP.in, idMaps.keySet()));
+                    QueryFieldBuilder queryFieldBuilder = QueryFieldBuilder.create()
+                            .add(PositionStructureConstant.ID_KEY)
+                            .addGroup(new String[]{FormConstant.NCKD_PERSON}, FormConstant.NAME_KEY)
+                            .add(PositionStructureConstant.NCKD_EXECUTEYEAR)
+                            .add(PositionStructureConstant.NCKD_TYPESTATE);
+                    DynamicObjectCollection query = QueryServiceHelper.query(PositionStructureConstant.PERSONPOSFILE_ENTITYID, queryFieldBuilder.buildSelect(), new QFilter[]{qFilter});
+                    if(!query.isEmpty()){
+                        for (DynamicObject dynamicObject : query) {
+                            long id = dynamicObject.getLong(FormConstant.ID_KEY);
+                            String executeYear = dynamicObject.getString(PositionStructureConstant.NCKD_EXECUTEYEAR);
+                            String typeState = dynamicObject.getString(PositionStructureConstant.NCKD_TYPESTATE);
+                            TypeStateEnum typeStateEnum = TypeStateEnum.getByCode(typeState);
+                            String personName = dynamicObject.getString(String.join(".", FormConstant.NCKD_PERSON, FormConstant.NAME_KEY));
+                            addFatalErrorMessage(getDataEntities()[idMaps.get(id)], StrFormatter.format("【{}】的档案已被【{}】年度的“{}”档案使用,不能删除",personName, executeYear,typeStateEnum.getName()));
+                        }
+                    }
+                }
+                //TODO 【待修改】-职位体系-校验有没有被下游使用(例如:薪酬)
+
+                //所有情况校验完再校验是否被锁定,比较友好。不然先校验锁等用户解锁之后又发现其他情况而不能继续处理
+                if(this.getValidateResult().isSuccess()) {
+                    for (ExtendedDataEntity rowDataEntity : getDataEntities()) {
+                        DynamicObject data = rowDataEntity.getDataEntity();
+                        String typeState = data.getString(PositionStructureConstant.NCKD_TYPESTATE);
+                        DynamicObject person = data.getDynamicObject(FormConstant.NCKD_PERSON);
+                        TypeStateEnum typeStateEnum = TypeStateEnum.getByCode(typeState);
+                        boolean lockStatus = data.getBoolean(PositionStructureConstant.NCKD_LOCKSTATUS);
+                        if (lockStatus) {
+                            addFatalErrorMessage(rowDataEntity, StrFormatter.format("【{}】的{}已“锁定”,不能删除。请先“解锁”。", person.getString("name"), typeStateEnum.getName()));
+                        }
+                    }
+                }
+            }
+        });
+    }
+
+    @Override
+    public void beginOperationTransaction(BeginOperationTransactionArgs e) {
+        Long[] personIds = new Long[e.getDataEntities().length];
+        Long[] ids = new Long[e.getDataEntities().length];
+        for (int i = 0; i < e.getDataEntities().length; i++) {
+            DynamicObject dataEntity = e.getDataEntities()[i];
+            long id = dataEntity.getLong(FormConstant.ID_KEY);
+            ids[i] = id;
+            DynamicObject person = dataEntity.getDynamicObject(FormConstant.NCKD_PERSON);
+            personIds[i] = person.getLong(FormConstant.ID_KEY);
+        }
+        //将人员所有职位档案标记为非最新
+        PositionStructureHelper.markAsNotCurrentNewest(personIds);
+        //将当前人员最新档案(初定 或者 (年度调整 并且 已生效) 或者 (动态调整) 生效时间为最新的那一条)标记为最新的
+        PositionStructureHelper.markAsCurrentNewest(new QFilter(FormConstant.ID_KEY, QCP.not_in,ids),personIds);
+    }
+}

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

@@ -1,6 +1,7 @@
 package nckd.jxccl.hr.psms.plugin.operate.initial;
 
 import kd.bos.common.enums.EnableEnum;
+import kd.bos.context.RequestContext;
 import kd.bos.dataentity.entity.DynamicObject;
 import kd.bos.dataentity.entity.DynamicObjectCollection;
 import kd.bos.entity.constant.StatusEnum;
@@ -9,11 +10,10 @@ 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.enums.psms.JobSeqEnum;
 import nckd.jxccl.base.common.utils.DateUtil;
 import nckd.jxccl.base.common.utils.QueryFieldBuilder;
 import nckd.jxccl.base.common.utils.StrFormatter;
@@ -31,7 +31,6 @@ import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.StringJoiner;
 
 /**
  * 人员初定操作插件基类
@@ -391,6 +390,7 @@ public abstract class BaseInitialOperationPlugIn extends AbstractOperationServic
         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.CREATOR_KEY, RequestContext.get().getCurrUserId());
 
 
         SaveServiceHelper.save(new DynamicObject[]{personPosFile});

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

@@ -174,7 +174,7 @@ public class NewHireInitialOperationPlugIn extends BaseInitialOperationPlugIn {
                 //返回定级后的职级
                 setJobLevelResult(data.person, jobLeve);
 
-                //TODO 【待修改】 这里可能有协同,初定完成后需要将待办任务置为已完成
+                //TODO 【待修改】-职位体系-这里可能有协同,初定完成后需要将待办任务置为已完成
             }
         }
 

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

@@ -38,6 +38,8 @@ public class UnAdjustedReportFormPlugin extends AbstractReportFormPlugin impleme
         super.initialize();
     }
 
+
+
     @Override
     public void afterBindData(EventObject e) {
         /*super.afterBindData(e);
@@ -73,18 +75,19 @@ public class UnAdjustedReportFormPlugin extends AbstractReportFormPlugin impleme
                 showParameter.setCustomParam(FormConstant.NCKD_PERSON,personIds);
                 showParameter.setCloseCallBack(new CloseCallBack(this, PositionStructureConstant.NEWDYNAMICADJUBATCH_ENTITYID));
                 this.getView().showForm(showParameter);
-            }else if(selectedRowIndexes.length == 1){{
-                DynamicObject rowData = reportList.getReportModel().getRowData(selectedRowIndexes[0]);
+            }else {
+
                 //单人调整
                 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));
                 showParameter.setCloseCallBack(new CloseCallBack(this, PositionStructureConstant.NEWDYNAMICADJUDIALOG_ENTITYID));
+                if(selectedRowIndexes.length == 1){
+                    DynamicObject rowData = reportList.getReportModel().getRowData(selectedRowIndexes[0]);
+                    showParameter.setCustomParam(FormConstant.NCKD_PERSON,rowData.getDynamicObject(FormConstant.NCKD_PERSON).getLong(FormConstant.ID_KEY));
+                }
                 this.getView().showForm(showParameter);
-            }}else{
-                this.getView().showErrorNotification("请选择要调整的员工");
             }
         }
     }

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

@@ -6,6 +6,7 @@ import kd.bos.entity.report.AbstractReportListDataPlugin;
 import kd.bos.entity.report.FastFilter;
 import kd.bos.entity.report.FilterItemInfo;
 import kd.bos.entity.report.ReportQueryParam;
+import kd.bos.orm.ORMHint;
 import kd.bos.orm.query.QCP;
 import kd.bos.orm.query.QFilter;
 import kd.bos.servicehelper.QueryServiceHelper;
@@ -98,13 +99,16 @@ public class UnAdjustedReportReportListDataPlugin extends AbstractReportListData
                         PositionStructureConstant.NCKD_DISABLE), QCP.equals, EnableEnum.NO.getCode()))
                 .and(new QFilter(String.join(".", FormConstant.ASSIGNMENT_ENTITYID, FormConstant.IS_PRIMARY),
                         QCP.equals, EnableEnum.YES.getCode()))
-                .and(new QFilter(String.join(".", FormConstant.HRPI_PERPROTITLE, "iscompany"),
-                        QCP.equals, EnableEnum.YES.getCode()))
-                .and(new QFilter(String.join(".", FormConstant.HRPI_PEROCPQUAL, "ismajor"),
-                        QCP.equals, EnableEnum.YES.getCode()))
                 .and(new QFilter(FormConstant.IS_PRIMARY, QCP.equals, EnableEnum.YES.getCode()))
-                .and(new QFilter(String.join(".", FormConstant.HRPI_PEREDUEXP, "ishighestdegree"),
-                        QCP.equals, EnableEnum.YES.getCode()));
+                .and(QFilter.join(FormConstant.EMPLOYEE_KEY, String.join(".", FormConstant.HRPI_PERPROTITLE, FormConstant.EMPLOYEE_KEY), new QFilter(String.join(".", FormConstant.HRPI_PERPROTITLE, "iscompany"),
+                                QCP.equals, EnableEnum.YES.getCode()),
+                        ORMHint.JoinHint.LEFT, Boolean.FALSE))
+                .and(QFilter.join(FormConstant.EMPLOYEE_KEY, String.join(".", FormConstant.HRPI_PEROCPQUAL, FormConstant.EMPLOYEE_KEY), new QFilter(String.join(".", FormConstant.HRPI_PEROCPQUAL, "ismajor"),
+                                QCP.equals, EnableEnum.YES.getCode()),
+                        ORMHint.JoinHint.LEFT, Boolean.FALSE))
+                .and(QFilter.join(FormConstant.EMPLOYEE_KEY, String.join(".", FormConstant.HRPI_PEREDUEXP, FormConstant.EMPLOYEE_KEY), new QFilter(String.join(".", FormConstant.HRPI_PEREDUEXP, "ishighestdegree"),
+                                QCP.equals, EnableEnum.YES.getCode()),
+                        ORMHint.JoinHint.LEFT, Boolean.FALSE));
     }
 
     /**