Parcourir la source

Merge remote-tracking branch 'origin/master'

Tyx il y a 1 semaine
Parent
commit
0a40b00530
54 fichiers modifiés avec 5877 ajouts et 815 suppressions
  1. 37 0
      build.gradle
  2. 25 4
      code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/constant/FormConstant.java
  3. 72 0
      code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/enums/psms/AdjustTypeEnum.java
  4. 4 2
      code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/enums/psms/JobSeqEnum.java
  5. 66 0
      code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/enums/psms/TypeStateEnum.java
  6. 10 0
      code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/utils/DateUtil.java
  7. 345 0
      code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/utils/QueryFieldBuilder.java
  8. 1 1
      code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/utils/StrFormatter.java
  9. 0 1
      code/base/nckd-jxccl-base-helper/src/main/java/nckd/jxccl/base/org/helper/AdminOrgHelper.java
  10. 2 1
      code/base/nckd-jxccl-base-helper/src/main/java/nckd/jxccl/base/orm/helper/QFilterCommonHelper.java
  11. 6 5
      code/base/nckd-jxccl-base-helper/src/main/java/nckd/jxccl/base/pm/helper/PerformanceManagerHelper.java
  12. 1 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/hstu/business/algo/RankMapFunction.java
  13. 0 1
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/hstu/business/helper/HonorStudentHelper.java
  14. 1 2
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/hstu/common/HonorStudentConstant.java
  15. 0 2
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/hstu/plugin/form/EvalQuestTreeListPlugin.java
  16. 0 3
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/hstu/plugin/operate/CreateQvalQuestAffirmOperationPlugin.java
  17. 0 3
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/hstu/plugin/operate/EvalQuestBeginEvalOperationPlugin.java
  18. 0 3
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/hstu/plugin/operate/EvalQuestEndEvalOperationPlugin.java
  19. 291 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/business/AnnualAdjustmentData.java
  20. 925 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/business/AnnualAdjustmentService.java
  21. 289 305
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/business/JobLevelCalculatorService.java
  22. 95 3
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/common/PositionStructureConstant.java
  23. 71 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/common/bo/PositionAppointmentBO.java
  24. 300 168
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/helper/PositionStructureHelper.java
  25. 25 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/adjust/DynamicAdjustQueryListPlugin.java
  26. 88 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/adjust/DynamicAdjustmentFormPlugin.java
  27. 67 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/adjust/NewDynamicAdjustmentBatchDiaLogFormPlugin.java
  28. 80 9
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/adjust/NewDynamicAdjustmentDiaLogFormPlugin.java
  29. 127 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/annualadjust/AnnualAdjustQueryListPlugin.java
  30. 84 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/annualadjust/AnnualAdjustmentFormPlugin.java
  31. 63 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/annualadjust/AnnualEffectiveFormPlugin.java
  32. 64 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/annualadjust/NewAnnualAdjustFormPlugin.java
  33. 131 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/annualadjust/UnAnnualAdjustListPlugin.java
  34. 26 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/initial/GradedPersonQueryListPlugin.java
  35. 124 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/initial/NewHireInitialBatchFormPlugin.java
  36. 137 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/initial/NewHireInitialFormPlugin.java
  37. 64 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/initial/ServingInitialBatchFormPlugin.java
  38. 114 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/initial/ServingInitialFormPlugin.java
  39. 116 8
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/initial/UngradedPersonQueryListPlugin.java
  40. 325 50
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/adjust/NewDynamicAdjustmentOperationPlugIn.java
  41. 225 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/annualadjust/AnnualAdjustmentOperationPlugin.java
  42. 175 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/annualadjust/AnnualEffectiveOpPlugin.java
  43. 274 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/annualadjust/AnnualLockOrUnLockedOpPlugin.java
  44. 151 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/annualadjust/AnnualSetinActiveOpPlugin.java
  45. 65 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/annualadjust/PosFileUnlockBillOpPlugin.java
  46. 135 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/file/PersonPosFileDeleteOpPlugin.java
  47. 140 41
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/initial/BaseInitialOperationPlugIn.java
  48. 108 77
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/initial/NewHireInitialOperationPlugIn.java
  49. 105 84
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/initial/ServingInitialOperationPlugIn.java
  50. 45 14
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/report/adjust/UnAdjustedReportFormPlugin.java
  51. 269 26
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/report/adjust/UnAdjustedReportReportListDataPlugin.java
  52. 5 0
      code/opmc/nckd-jxccl-opmc/src/main/java/nckd/jxccl/opmc/pm/plugin/form/PerfManagerWizardFormPlugin.java
  53. 2 1
      config.gradle
  54. 2 1
      gradle.properties

+ 37 - 0
build.gradle

@@ -24,6 +24,7 @@ def bos = ext.path.bos
 def trd = ext.path.trd
 def cus = ext.path.cus
 def biz = ext.path.biz
+def zip = ext.path.zip
 def outputdir = ext.path.outputdir
 
 //所有工程共用的配置
@@ -140,6 +141,42 @@ subprojects {
 		into cus
 		exclude '*sources.jar','*javadoc.jar','*cosmic-debug*.jar'
 	 }
+    
+    task hrzip(type: Zip) {
+      group 'build'
+      description '生成hr zip包'
+      from outputdir
+      include 'nckd-jxccl-hr*.jar'
+      destinationDirectory = file(zip)
+      archiveFileName = "nckd-hr.zip"
+    }
+  
+    task basezip(type: Zip) {
+      group 'build'
+      description '生成base zip包'
+      from outputdir
+      include 'nckd-jxccl-base*.jar'
+      destinationDirectory = file(zip)
+      archiveFileName = "nckd-base.zip"
+    }
+    
+    task swczip(type: Zip) {
+      group 'build'
+      description '生成swc zip包'
+      from outputdir
+      include 'nckd-jxccl-swc*.jar'
+      destinationDirectory = file(zip)
+      archiveFileName = "nckd-swc.zip"
+    }
+  
+    task rptzip(type: Zip) {
+      group 'build'
+      description '生成swc zip包'
+      from outputdir
+      include 'nckd-jxccl-rpt*.jar'
+      destinationDirectory = file(zip)
+      archiveFileName = "nckd-rpt.zip"
+    }
 	 
 	test.ignoreFailures true
 	

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

@@ -8,7 +8,7 @@ package nckd.jxccl.base.common.constant;
  */
 public class FormConstant {
 
-    //====================================== 标品实体标识 ======================================
+    //====================================== 标品实体标识(需要小写) ======================================
     /**学历-实体标识*/
     public static final String HBSS_DIPLOMA = "hbss_diploma";
     /**教育经历-实体标识*/
@@ -38,9 +38,11 @@ public class FormConstant {
     /** 组织分配-实体标识 */
     public static final String ASSIGNMENT_ENTITYID = "hrpi_assignment";
     /** 岗位-实体标识*/
-    public static final String HBPM_POSITIONHR = "HBPM_POSITIONHR";
+    public static final String HBPM_POSITIONHR = "hbpm_positionhr";
     /** 服务年限-实体标识*/
-    public static final String HRPI_PERSERLEN = "HRPI_PERSERLEN";
+    public static final String HRPI_PERSERLEN = "hrpi_perserlen";
+    /** 雇佣信息-实体标识*/
+    public static final String HRPI_EMPENTREL = "hrpi_empentrel";
 
 
     //====================================== 标品op ======================================
@@ -81,11 +83,14 @@ 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";
     /** 左树右表-是否包含子部门*/
     public static final String CHKINCLUDECHILD = "chkincludechild";
-
+    /** 单据体 */
+    public static final String NCKD_ENTRYENTITY = "NCKD_ENTRYENTITY";
 
     //====================================== 通用字段 ======================================
     /** ID标识 */
@@ -146,14 +151,20 @@ public class FormConstant {
     public static final String POSITION_KEY = "position";
     /** 学历*/
     public static final String EDUCATION_KEY = "education";
+    /** 职称*/
+    public static final String PROFESSIONAL_KEY = "professional";
     /** 职称级别*/
     public static final String PROLEVEL_KEY = "prolevel";
+    /** 技能*/
+    public static final String QUALIFICATION_KEY = "qualification";
     /** 职业资格等级*/
     public static final String QUALEVEL_KEY = "qualevel";
     /** 是否主要 */
     public static final String IS_PRIMARY = "isprimary";
     /** 是否删除 */
     public static final String IS_DELETED = "isdeleted";
+    /** 最新任职记录 */
+    public static final String IS_SEQLATESTRECORD = "isseqlatestrecord";
     /** 组织分配 */
     public static final String ASSIGNMENT = "assignment";
     /** 开始时间 */
@@ -166,6 +177,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*/
@@ -178,9 +191,15 @@ public class FormConstant {
     public static final String EMPLOYEE_KEY = "employee";
     /**职级序列*/
     public static final String JOBLEVELSEQ = "joblevelseq";
+    /**职级序列*/
+    public static final String ENTRYDATE = "entrydate";
+    /**当前数据*/
+    public static final String ISCURRENTDATA = "iscurrentdata";
 
     /** 工具栏标识(二开)*/
     public static final String NCKD_TOOLBARAP = "nckd_toolbarap";
+    /** 组织分配*/
+    public static final String NCKD_ASSIGNMENT = "NCKD_ASSIGNMENT";
     /** 人员标识*/
     public static final String NCKD_PERSON = "nckd_person";
     /** 职位序列*/
@@ -191,5 +210,7 @@ public class FormConstant {
     public static final String NCKD_COEFFICIENT = "NCKD_COEFFICIENT";
     /** 职层-资格级别*/
     public static final String NCKD_JOBLEVELNUMBER = "NCKD_JOBLEVELNUMBER";
+    /** 备注*/
+    public static final String NCKD_REMARK = "NCKD_REMARK";
 
 }

+ 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;
+    }
+}

+ 4 - 2
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;
@@ -17,7 +17,9 @@ public enum JobSeqEnum {
     /** 职能序列 */
     FUNCTIONAL("02", "职能序列"),
     /** 技能序列 */
-    SKILL("03", "技能序列");
+    SKILL("03", "技能序列"),
+    /** 管理序列 */
+    MANAGE("04", "管理序列");
 
     private static final Map<String, JobSeqEnum> CODE_MAP = new HashMap<>();
 

+ 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;
+    }
+}

+ 10 - 0
code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/utils/DateUtil.java

@@ -607,6 +607,16 @@ public class DateUtil {
 
     // ==================== 日期比较 ====================
 
+    /**
+     * 比较两个日期时间
+     *
+     * @param dt1 日期时间1
+     * @param dt2 日期时间2
+     * @return 比较结果(负数:dt1 < dt2,0:相等,正数:dt1 > dt2)
+     */
+    public static int compare(Date dt1, Date dt2) {
+        return toLocalDateTime(dt1).compareTo(toLocalDateTime(dt2));
+    }
     /**
      * 比较两个日期时间
      *

+ 345 - 0
code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/utils/QueryFieldBuilder.java

@@ -0,0 +1,345 @@
+package nckd.jxccl.base.common.utils;
+
+import nckd.jxccl.base.common.constant.FormConstant;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.StringJoiner;
+
+/**
+* 查询字段构建器
+* @author W.Y.C
+* @date 2025/9/27 16:34
+* @version 1.0
+*/
+public final class QueryFieldBuilder {
+
+    private final StringJoiner selectJoiner;
+    private final StringJoiner orderJoiner;
+
+
+
+    private QueryFieldBuilder() {
+        this.selectJoiner = new StringJoiner(",");
+        this.orderJoiner = new StringJoiner(",");
+    }
+
+    public static QueryFieldBuilder create() {
+        return new QueryFieldBuilder();
+    }
+
+    // ---------------- SELECT ----------------
+
+    /**
+     * 添加单个字段
+     * 使用方式:
+     * QueryFieldBuilder.create().add(FormConstant.ID_KEY).buildSelect();
+     * 输出结果:
+     * "ID_KEY"
+     * @param parts 字段路径,可单级或多级
+     * @return QueryFieldBuilder
+     * @author W.Y.C
+     * @date: 2025/09/27
+     */
+    public QueryFieldBuilder add(String... parts) {
+        selectJoiner.add(joinParts(parts));
+        return this;
+    }
+
+    /**
+     * 添加多级路径 + 多个子字段
+     * 使用方式:
+     * builder.addGroup(new String[]{"HRPI_PEREDUEXP","EDUCATION_KEY"}, FormConstant.ID_KEY, FormConstant.NAME_KEY);
+     * 输出结果:
+     * "HRPI_PEREDUEXP.EDUCATION_KEY.ID_KEY,HRPI_PEREDUEXP.EDUCATION_KEY.NAME_KEY"
+     * @param prefixParts 多级前缀数组
+     * @param subFields 子字段
+     * @return QueryFieldBuilder
+     * @author W.Y.C
+     * @date: 2025/09/27
+     */
+    public QueryFieldBuilder addGroup(String[] prefixParts, String... subFields) {
+        Arrays.stream(subFields)
+                .forEach(f -> selectJoiner.add(joinParts(concat(prefixParts, f))));
+        return this;
+    }
+
+    /**
+     * 添加固定三件套 ID + NUMBER + NAME
+     * 使用方式:
+     * builder.addIdNumberName(FormConstant.COMPANY_KEY);
+     * 输出结果:
+     * "COMPANY_KEY.ID,COMPANY_KEY.NUMBER,COMPANY_KEY.NAME"
+     * @param prefixParts 字段前缀
+     * @return QueryFieldBuilder
+     * @author W.Y.C
+     * @date: 2025/09/27
+     */
+    public QueryFieldBuilder addIdNumberName(String... prefixParts) {
+        return addGroup(prefixParts,
+                FormConstant.ID_KEY,
+                FormConstant.NUMBER_KEY,
+                FormConstant.NAME_KEY);
+    }
+
+    /**
+     * 添加三件套 ID+NUMBER+NAME 并可扩展其他字段
+     * 使用方式:
+     * builder.addIdNumberNameWithExtras(new String[]{FormConstant.EDUCATION_KEY}, FormConstant.NCKD_SCORE, FormConstant.STARTDATE);
+     * 输出结果:
+     * "EDUCATION_KEY.ID,EDUCATION_KEY.NUMBER,EDUCATION_KEY.NAME,EDUCATION_KEY.NCKD_SCORE,EDUCATION_KEY.STARTDATE"
+     * @param prefixParts 字段前缀
+     * @param extraFields 扩展字段
+     * @return QueryFieldBuilder
+     * @author W.Y.C
+     * @date: 2025/09/27
+     */
+    public QueryFieldBuilder addIdNumberNameWithExtras(String[] prefixParts, String... extraFields) {
+        addIdNumberName(prefixParts);
+        if (extraFields != null && extraFields.length > 0) {
+            addGroup(prefixParts, extraFields);
+        }
+        return this;
+    }
+
+    /**
+     * 添加三件套 ID+NUMBER+NAME 并可扩展其他字段
+     * 使用方式:
+     * builder.addIdNumberNameWithExtras(FormConstant.NCKD_SCORE, FormConstant.STARTDATE);
+     * 输出结果:
+     * "EDUCATION_KEY.ID,EDUCATION_KEY.NUMBER,EDUCATION_KEY.NAME,EDUCATION_KEY.NCKD_SCORE,EDUCATION_KEY.STARTDATE"
+     * @param extraFields 扩展字段
+     * @return QueryFieldBuilder
+     * @author W.Y.C
+     * @date: 2025/09/27
+     */
+    public QueryFieldBuilder addIdNumberNameWithExtras(String... extraFields) {
+        return addIdNumberNameWithExtras(null, extraFields);
+    }
+
+
+    // ---------------- ORDER ----------------
+
+    /**
+     * 升序排序,支持多级路径
+     * 使用方式:
+     * builder.orderAsc(new String[]{HonorStudentConstant.NCKD_EVALUATIONRULE, FormConstant.RULE_DETAIL}, FormConstant.ID_KEY);
+     * 输出结果:
+     * "NCKD_EVALUATIONRULE.RULE_DETAIL.ID asc"
+     * @param prefixParts 多级前缀数组
+     * @param fieldParts 排序字段
+     * @return QueryFieldBuilder
+     * @author W.Y.C
+     * @date: 2025/09/27
+     */
+    public QueryFieldBuilder orderAsc(String[] prefixParts, String... fieldParts) {
+        Arrays.stream(fieldParts)
+                .forEach(f -> orderJoiner.add(joinParts(concat(prefixParts, f))+" asc"));
+        return this;
+    }
+
+    /**
+     * 升序排序,单级路径
+     * 使用方式:
+     * builder.orderAsc(HonorStudentConstant.NCKD_SUMSCORE);
+     * 输出结果:
+     * "NCKD_SUMSCORE asc"
+     * @param parts 排序字段
+     * @return QueryFieldBuilder
+     * @author W.Y.C
+     * @date: 2025/09/27
+     */
+    public QueryFieldBuilder orderAsc(String... parts) {
+        for (String part : parts) {
+            orderJoiner.add(joinParts(part) + " asc");
+        }
+        return this;
+    }
+
+    /**
+     * 降序排序,支持多级路径
+     * 使用方式:
+     * builder.orderDesc(new String[]{HonorStudentConstant.NCKD_EVALUATIONRULE, FormConstant.RULE_DETAIL}, FormConstant.ID_KEY);
+     * 输出结果:
+     * "NCKD_EVALUATIONRULE.RULE_DETAIL.ID desc"
+     * @param prefixParts 多级前缀
+     * @param fieldParts 排序字段
+     * @return QueryFieldBuilder
+     * @author W.Y.C
+     * @date: 2025/09/27
+     */
+    public QueryFieldBuilder orderDesc(String[] prefixParts, String... fieldParts) {
+        Arrays.stream(fieldParts)
+                .forEach(f -> orderJoiner.add(joinParts(concat(prefixParts, f))+ " desc"));
+        return this;
+    }
+
+    /**
+     * 降序排序,单级路径
+     * 使用方式:
+     * builder.orderDesc(HonorStudentConstant.NCKD_SUMSCORE);
+     * 输出结果:
+     * "NCKD_SUMSCORE desc"
+     * @param parts 排序字段
+     * @return QueryFieldBuilder
+     * @author W.Y.C
+     * @date: 2025/09/27
+     */
+    public QueryFieldBuilder orderDesc(String... parts) {
+        for (String part : parts) {
+            orderJoiner.add(joinParts(part) + " desc");
+        }
+        return this;
+    }
+
+    /**
+     * 默认排序,不指定 asc/desc(不指定数据库默认未asc)
+     * 使用方式:
+     * builder.orderBy(new String[]{HonorStudentConstant.USEORG_KEY}, FormConstant.ID_KEY);
+     * 输出结果:
+     * "USEORG_KEY.ID_KEY"
+     * @param prefixParts 多级前缀
+     * @param fieldParts 排序字段
+     * @return QueryFieldBuilder
+     * @author W.Y.C
+     * @date: 2025/09/27
+     */
+    public QueryFieldBuilder orderBy(String[] prefixParts, String... fieldParts) {
+        Arrays.stream(fieldParts)
+                .forEach(f -> orderJoiner.add(joinParts(concat(prefixParts, f))));
+        return this;
+    }
+
+    /**
+     * 默认排序,不指定 asc/desc(不指定数据库默认未asc),单级路径
+     * 使用方式:
+     * builder.orderBy(HonorStudentConstant.USEORG_KEY);
+     * 输出结果:
+     * "USEORG_KEY"
+     * @param parts 排序字段
+     * @return QueryFieldBuilder
+     * @author W.Y.C
+     * @date: 2025/09/27
+     */
+    public QueryFieldBuilder orderBy(String... parts) {
+        for (String part : parts) {
+            orderJoiner.add(joinParts(part));
+        }
+        return this;
+    }
+
+    // ---------------- build ----------------
+
+    /**
+     * 构建 SELECT 字段字符串
+     * 使用方式:
+     * builder.buildSelect();
+     * 输出结果:
+     * "ID_KEY,COMPANY_KEY.ID,COMPANY_KEY.NUMBER,COMPANY_KEY.NAME,..."
+     * @return String
+     * @author W.Y.C
+     * @date: 2025/09/27
+     */
+    public String buildSelect() {
+        return selectJoiner.toString();
+    }
+    /**
+     * 构建并返回 SELECT 字段数组
+     * 使用方式:
+     * String[] fields = builder.buildSelectArray();
+     * 输出结果:
+     * ["ID_KEY", "COMPANY_KEY.ID", "COMPANY_KEY.NUMBER", "COMPANY_KEY.NAME", ...]
+     * @return String[]
+     * @author W.Y.C
+     * @date: 2025/09/27
+     */
+    public String[] buildSelectArray() {
+        return selectJoiner.toString().split(",");
+    }
+
+
+    /**
+     * 构建 SELECT 字段数组,并排除指定字段
+     * 使用方式:
+     * String[] fields = builder.buildSelectArrayExclude(FormConstant.ID_KEY, "COMPANY_KEY.NUMBER");
+     * 输出结果:
+     * ["COMPANY_KEY.ID", "COMPANY_KEY.NAME", ...] (排除了ID_KEY和COMPANY_KEY.NUMBER)
+     * @param excludeFields 需要排除的字段数组
+     * @return String[]
+     * @author W.Y.C
+     * @date: 2025/09/27
+     */
+    public String[] buildSelectArrayExclude(String... excludeFields) {
+        if (excludeFields == null || excludeFields.length == 0) {
+            return buildSelectArray();
+        }
+
+        Set<String> excludeSet = new HashSet<>(Arrays.asList(excludeFields));
+        return Arrays.stream(buildSelectArray())
+                .filter(field -> !excludeSet.contains(field))
+                .toArray(String[]::new);
+    }
+
+    /**
+     * 构建 SELECT 字段数组,并排除指定前缀的字段
+     * 使用方式:
+     * String[] fields = builder.buildSelectArrayExcludePrefix("COMPANY_KEY", "HRPI_PEREDUEXP");
+     * 输出结果:
+     * ["ID_KEY", "EDUCATION_KEY.ID_KEY", "EDUCATION_KEY.NAME_KEY", ...] (排除了所有以COMPANY_KEY和HRPI_PEREDUEXP开头的字段)
+     * @param excludePrefixes 需要排除的字段前缀数组
+     * @return String[]
+     * @author W.Y.C
+     * @date: 2025/09/27
+     */
+    public String[] buildSelectArrayExcludePrefix(String... excludePrefixes) {
+        if (excludePrefixes == null || excludePrefixes.length == 0) {
+            return buildSelectArray();
+        }
+
+        Set<String> excludePrefixSet = new HashSet<>(Arrays.asList(excludePrefixes));
+        return Arrays.stream(buildSelectArray())
+                .filter(field -> excludePrefixSet.stream().noneMatch(prefix -> field.startsWith(prefix + ".")))
+                .toArray(String[]::new);
+    }
+
+    /**
+     * 构建 ORDER BY 字段字符串
+     * 使用方式:
+     * builder.buildOrder();
+     * 输出结果:
+     * "NCKD_EVALUATIONRULE.ID asc,USEORG_KEY.ID,NCKD_SUMSCORE desc"
+     * @return String
+     * @author W.Y.C
+     * @date: 2025/09/27
+     */
+    public String buildOrder() {
+        return orderJoiner.toString();
+    }
+
+    public String[] buildOrderArray() {
+        return orderJoiner.toString().split(",");
+    }
+
+    @Override
+    public String toString() {
+        return "SELECT: " + buildSelect() + "\nORDER: " + buildOrder();
+    }
+
+    // ---------------- 工具方法 ----------------
+
+    /** 拼接多级路径 */
+    private String joinParts(String... parts) {
+        return String.join(".", parts);
+    }
+
+
+    private String[] concat(String[] prefixParts, String... subFields) {
+        if (prefixParts == null || prefixParts.length == 0) {
+            return subFields;
+        }
+        String[] result = Arrays.copyOf(prefixParts, prefixParts.length + subFields.length);
+        System.arraycopy(subFields, 0, result, prefixParts.length, subFields.length);
+        return result;
+    }
+}

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

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

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

@@ -7,7 +7,6 @@ import kd.bos.orm.query.QFilter;
 import kd.bos.servicehelper.QueryServiceHelper;
 import kd.hr.hbp.business.dao.factory.HRBaseDaoFactory;
 import nckd.jxccl.base.common.constant.FormConstant;
-import nckd.jxccl.base.common.utils.ConvertUtil;
 import nckd.jxccl.base.orm.helper.QFilterCommonHelper;
 
 import java.util.HashSet;

+ 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);
     }
 }

+ 6 - 5
code/base/nckd-jxccl-base-helper/src/main/java/nckd/jxccl/base/pm/helper/PerformanceManagerHelper.java

@@ -7,6 +7,7 @@ import kd.bos.orm.query.QFilter;
 import kd.bos.servicehelper.BusinessDataServiceHelper;
 import nckd.jxccl.base.common.constant.FormConstant;
 import nckd.jxccl.base.common.utils.DateUtil;
+import nckd.jxccl.base.common.utils.QueryFieldBuilder;
 
 import java.time.LocalDateTime;
 import java.util.Date;
@@ -43,11 +44,11 @@ public class PerformanceManagerHelper {
         QFilter filter = new QFilter(String.join(".",FormConstant.NCKD_PERSON, FormConstant.ID_KEY), QCP.equals,personId)
                 .and(new QFilter(String.join(".",PERFMANAGER_ENTRY_ENTITYID, NCKD_APPRAISALYEAR),QCP.equals,beginOfYear));
 
-        String selectField = String.join(",",
-                String.join(".",PERFMANAGER_ENTRY_ENTITYID, NCKD_APPRAISALYEAR),
-                String.join(".",PERFMANAGER_ENTRY_ENTITYID, NCKD_APPRAISALRESULT)
-        );
-        DynamicObject[] load = BusinessDataServiceHelper.load(PERFMANAGER_ENTITYID, selectField, new QFilter[]{filter}, FormConstant.CREATE_TIME_KEY + " desc");
+        QueryFieldBuilder queryFieldBuilder = QueryFieldBuilder.create()
+                .addGroup(new String[]{PERFMANAGER_ENTRY_ENTITYID}, NCKD_APPRAISALYEAR,NCKD_APPRAISALRESULT,FormConstant.NCKD_SCORE)
+                .addIdNumberNameWithExtras(new String[]{PERFMANAGER_ENTRY_ENTITYID, NCKD_APPRAISALRESULT}, FormConstant.NCKD_SCORE)
+                .orderDesc(FormConstant.CREATE_TIME_KEY);
+        DynamicObject[] load = BusinessDataServiceHelper.load(PERFMANAGER_ENTITYID, queryFieldBuilder.buildSelect(), new QFilter[]{filter},queryFieldBuilder.buildOrder() );
         if(load != null && load.length > 0){
             DynamicObject perfManager = load[0];
             DynamicObjectCollection perfManagerEntryColl = perfManager.getDynamicObjectCollection(PERFMANAGER_ENTRY_ENTITYID);

+ 1 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/hstu/business/algo/RankMapFunction.java

@@ -7,6 +7,7 @@ import kd.bos.algo.Row;
 import kd.bos.algo.RowMeta;
 import nckd.jxccl.base.common.constant.FormConstant;
 import nckd.jxccl.hr.hstu.common.HonorStudentConstant;
+
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;

+ 0 - 1
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/hstu/business/helper/HonorStudentHelper.java

@@ -28,7 +28,6 @@ import nckd.jxccl.hr.hstu.business.algo.RankMapFunction;
 import nckd.jxccl.hr.hstu.business.algo.UnitRankMapFunction;
 import nckd.jxccl.hr.hstu.common.HonorStudentConstant;
 import nckd.jxccl.hr.hstu.common.bo.EvalQuestResultBO;
-import nckd.jxccl.hr.hstu.plugin.form.EvalQuestListPlugin;
 import org.apache.commons.lang3.StringUtils;
 import org.jetbrains.annotations.NotNull;
 

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

@@ -190,8 +190,7 @@ public class HonorStudentConstant extends FormConstant {
 
     /** 综合测评答卷-实体标识 */
     public static final String NCKD_EVALRESULT_ENTITYID = "nckd_evalresult";
-    /** 单据体 */
-    public static final String NCKD_ENTRYENTITY = "NCKD_ENTRYENTITY";
+
     /** 缺省id字段 */
     public static final String ID = "ID";
     /** 分录行号 */

+ 0 - 2
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/hstu/plugin/form/EvalQuestTreeListPlugin.java

@@ -2,7 +2,6 @@ package nckd.jxccl.hr.hstu.plugin.form;
 
 import kd.bos.dataentity.entity.DynamicObject;
 import kd.bos.dataentity.entity.DynamicObjectCollection;
-import kd.bos.form.plugin.AbstractFormPlugin;
 import kd.bos.list.events.BuildTreeListFilterEvent;
 import kd.bos.orm.query.QCP;
 import kd.bos.orm.query.QFilter;
@@ -11,7 +10,6 @@ import kd.hr.hbp.common.constants.org.OrgTreeDynEnum;
 import kd.hr.hbp.common.util.org.model.OrgTreeModel;
 import kd.hr.hbp.formplugin.web.org.template.AdminOrgTreeListTemplate;
 import kd.sdk.hr.hspm.common.utils.PropertyHelper;
-import kd.sdk.plugin.Plugin;
 import nckd.jxccl.base.common.constant.FormConstant;
 import nckd.jxccl.base.common.utils.ConvertUtil;
 import nckd.jxccl.base.orm.helper.QFilterCommonHelper;

+ 0 - 3
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/hstu/plugin/operate/CreateQvalQuestAffirmOperationPlugin.java

@@ -2,12 +2,10 @@ package nckd.jxccl.hr.hstu.plugin.operate;
 
 import kd.bos.dataentity.entity.DynamicObject;
 import kd.bos.dataentity.entity.DynamicObjectCollection;
-import kd.bos.entity.operate.result.OperateErrorInfo;
 import kd.bos.entity.plugin.AbstractOperationServicePlugIn;
 import kd.bos.entity.plugin.AddValidatorsEventArgs;
 import kd.bos.entity.plugin.args.BeforeOperationArgs;
 import kd.bos.entity.plugin.args.BeginOperationTransactionArgs;
-import kd.bos.entity.validate.ErrorLevel;
 import kd.bos.logging.Log;
 import kd.bos.logging.LogFactory;
 import kd.bos.orm.query.QCP;
@@ -15,7 +13,6 @@ 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.exception.ValidationException;
 import nckd.jxccl.base.common.utils.StrFormatter;
 import nckd.jxccl.hr.hstu.business.CreateEvalQuestService;
 import nckd.jxccl.hr.hstu.common.HonorStudentConstant;

+ 0 - 3
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/hstu/plugin/operate/EvalQuestBeginEvalOperationPlugin.java

@@ -1,8 +1,6 @@
 package nckd.jxccl.hr.hstu.plugin.operate;
 
 import kd.bos.dataentity.entity.DynamicObject;
-import kd.bos.db.tx.TX;
-import kd.bos.db.tx.TXHandle;
 import kd.bos.entity.plugin.AbstractOperationServicePlugIn;
 import kd.bos.entity.plugin.args.BeginOperationTransactionArgs;
 import kd.bos.logging.Log;
@@ -18,7 +16,6 @@ import nckd.jxccl.base.common.utils.StrFormatter;
 import nckd.jxccl.base.orm.helper.QFilterCommonHelper;
 import nckd.jxccl.hr.hstu.business.helper.HonorStudentHelper;
 import nckd.jxccl.hr.hstu.common.HonorStudentConstant;
-import nckd.jxccl.hr.hstu.plugin.form.EvalQuestListPlugin;
 
 import java.util.ArrayList;
 import java.util.Arrays;

+ 0 - 3
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/hstu/plugin/operate/EvalQuestEndEvalOperationPlugin.java

@@ -1,8 +1,6 @@
 package nckd.jxccl.hr.hstu.plugin.operate;
 
 import kd.bos.dataentity.entity.DynamicObject;
-import kd.bos.db.tx.TX;
-import kd.bos.db.tx.TXHandle;
 import kd.bos.entity.plugin.AbstractOperationServicePlugIn;
 import kd.bos.entity.plugin.args.BeginOperationTransactionArgs;
 import kd.bos.logging.Log;
@@ -15,7 +13,6 @@ 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.StrFormatter;
-import nckd.jxccl.base.orm.helper.QFilterCommonHelper;
 import nckd.jxccl.hr.hstu.business.helper.HonorStudentHelper;
 import nckd.jxccl.hr.hstu.common.HonorStudentConstant;
 import org.apache.commons.lang3.StringUtils;

+ 291 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/business/AnnualAdjustmentData.java

@@ -0,0 +1,291 @@
+package nckd.jxccl.hr.psms.business;
+
+import kd.bos.dataentity.entity.DynamicObject;
+import org.apache.commons.lang3.StringUtils;
+
+import java.math.BigDecimal;
+
+/**
+ * 强类型数据承载类(替代原 selMap)。
+ * <p>来源:utils.YearContributeScoreBillEntryScoreSumBypersonidAndYear(...) 的查询结果。</p>
+ * <p>所有字段与原 addNewYear_PersonpositionfileInfo(277~815) 所用键一一对应。</p>
+ */
+public class AnnualAdjustmentData {
+
+    /** 员工姓名(personname) */
+    private String personName;
+
+    /** 主任职位名称(positonname) */
+    private String positionName;
+
+    /** 职位序列FID(hrjobfamilyid) */
+    private Long hrJobFamilyId;
+
+    /** 职位序列编码(hrjobfamilynumber),例如:01技术、02职能、03技能、04管理 */
+    private String hrJobFamilyNumber;
+
+    /** 职位FID(positonid) */
+    private Long positionId;
+
+    /** 行政组织FID(persondepid) */
+    private Long personDepId;
+
+    /** HR组织FID(hrorgunitid) */
+    private String hrOrgUnitId;
+
+    /** 考核结果 */
+    private DynamicObject appraisalResult;
+
+    /** 考核结果ID(appraisalresultid) */
+    private Long appraisalResultId;
+
+    /** 考核结果编号(appraisalresultnumber) */
+    private String appraisalResultNumber;
+
+    /** 考核结果名称 */
+    private String appraisalResultName;
+
+    /**R排名*/
+    private JobLevelCalculatorService.RankingResultInfo rankingResultInfo;
+    /**积分数据*/
+    public JobLevelCalculatorService.JobScoreInfo jobScoreInfo;
+
+    /** 上一条年度记录ID(lastid) */
+    private Long lastId;
+
+    /** 首条年度记录ID(firstid) */
+    private Long firstId;
+
+    /** 学历ID(diplomaid) */
+    private Long diplomaId;
+
+    /** 学历配置分(diplomascore) */
+    private BigDecimal diplomaScore;
+
+    /** 职称名称(rankname) */
+    private String rankName;
+
+    /** 职称等级ID(zgjbid) */
+    private Long zgjbId;
+
+    /** 职称等级编码(zgjbnumber) */
+    private String zgjbNumber;
+
+    /** 职称等级名称 */
+    private String zgjbName;
+
+    /** 职称等级配置分(zgjbscore) */
+    private BigDecimal zgjbScore;
+
+    /** 技能岗位名称(jobstatusname) */
+    private String jobStatusName;
+
+
+    /** 技能等级ID(zyjndjid) */
+    private Long zyjndjId;
+
+    /** 技能等级编码(zyjndjnumber) */
+    private String zyjndjNumber;
+
+    /** 技能等级名称(zyjndjnumber) */
+    private String zyjndjName;
+
+    /** 技能等级配置分(zyjndjscore) */
+    private BigDecimal zyjndjScore;
+
+    /** 上年所有贡献单据分之和(allyearscoresum) */
+    private BigDecimal allYearScoreSum;
+
+    /** 上年度考核得分(appraisalresultscore) */
+    private BigDecimal appraisalResultScore;
+
+    /** 上年度累计积分池(lastsumscore) */
+    private BigDecimal lastSumScore;
+
+    /** 上一条记录的职位序列编码(lasthrjobfamilynumber) */
+    private String lastHrJobFamilyNumber;
+
+    /** 上一条记录的职位序列 */
+    private DynamicObject lastJobSeq;
+
+    /** 上一条记录的职位序列(转换后,这里转换指的是:如果是管理序列,则按职能序列进行调整) */
+    private DynamicObject convertLastJobSeq;
+
+    /** 上一条记录的职级顺序号(lastjobgradeindex) */
+    private Integer lastJobGradeIndex;
+
+    /** 上一条记录的职级(lastjobgradefid) */
+    private DynamicObject lastJobLevel;
+
+    /** 年度分项得分A(yearscoresuma) */
+    private Double yearscoresuma;
+
+    /** 年度分项得分B(yearscoresumb) */
+    private Double yearscoresumb;
+
+    /** 年度分项得分C(yearscoresumc) */
+    private Double yearscoresumc;
+
+    /** 年度分项得分D(yearscoresumd) */
+    private Double yearscoresumd;
+
+    /** 年度分项得分E(yearscoresume) */
+    private Double yearscoresume;
+
+    /** 年度分项得分F(yearscoresumf) */
+    private Double yearscoresumf;
+
+    /** 年度分项得分G(yearscoresumg) */
+    private Double yearscoresumg;
+
+    /** 年度分项得分H(yearscoresumh) */
+    private Double yearscoresumh;
+
+    /** 年度分项得分I(yearscoresumi) */
+    private Double yearscoresumi;
+
+    public String getPersonName() { return personName; }
+    public void setPersonName(String personName) { this.personName = personName; }
+    public String getPositionName() { return positionName; }
+    public void setPositionName(String positionName) { this.positionName = positionName; }
+    public Long getHrJobFamilyId() { return hrJobFamilyId; }
+    public void setHrJobFamilyId(Long hrJobFamilyId) { this.hrJobFamilyId = hrJobFamilyId; }
+    public String getHrJobFamilyNumber() { return hrJobFamilyNumber; }
+    public void setHrJobFamilyNumber(String hrJobFamilyNumber) { this.hrJobFamilyNumber = hrJobFamilyNumber; }
+    public Long getPositionId() { return positionId; }
+    public void setPositionId(Long positionId) { this.positionId = positionId; }
+    public Long getPersonDepId() { return personDepId; }
+    public void setPersonDepId(Long personDepId) { this.personDepId = personDepId; }
+    public String getHrOrgUnitId() { return hrOrgUnitId; }
+    public void setHrOrgUnitId(String hrOrgUnitId) { this.hrOrgUnitId = hrOrgUnitId; }
+
+    public DynamicObject getAppraisalResult() {
+        return appraisalResult;
+    }
+
+    public void setAppraisalResult(DynamicObject appraisalResult) {
+        this.appraisalResult = appraisalResult;
+    }
+
+    public Long getAppraisalResultId() { return appraisalResultId; }
+    public void setAppraisalResultId(Long appraisalResultId) { this.appraisalResultId = appraisalResultId; }
+    public String getAppraisalResultNumber() { return appraisalResultNumber; }
+    public void setAppraisalResultNumber(String appraisalResultNumber) { this.appraisalResultNumber = appraisalResultNumber; }
+    public Long getLastId() { return lastId; }
+    public void setLastId(Long lastId) { this.lastId = lastId; }
+    public Long getFirstId() { return firstId; }
+    public void setFirstId(Long firstId) { this.firstId = firstId; }
+    public Long getDiplomaId() { return diplomaId; }
+    public void setDiplomaId(Long diplomaId) { this.diplomaId = diplomaId; }
+    public BigDecimal getDiplomaScore() { return diplomaScore; }
+    public void setDiplomaScore(BigDecimal diplomaScore) { this.diplomaScore = diplomaScore; }
+    public String getRankName() { return rankName; }
+    public void setRankName(String rankName) { this.rankName = rankName; }
+    public Long getZgjbId() { return zgjbId; }
+    public void setZgjbId(Long zgjbId) { this.zgjbId = zgjbId; }
+    public String getZgjbNumber() { return zgjbNumber; }
+    public void setZgjbNumber(String zgjbNumber) { this.zgjbNumber = zgjbNumber; }
+    public BigDecimal getZgjbScore() { return zgjbScore; }
+    public void setZgjbScore(BigDecimal zgjbScore) { this.zgjbScore = zgjbScore; }
+    public String getJobStatusName() { return jobStatusName; }
+    public void setJobStatusName(String jobStatusName) { this.jobStatusName = jobStatusName; }
+    public Long getZyjndjId() { return zyjndjId; }
+    public void setZyjndjId(Long zyjndjId) { this.zyjndjId = zyjndjId; }
+    public String getZyjndjNumber() { return zyjndjNumber; }
+    public void setZyjndjNumber(String zyjndjNumber) { this.zyjndjNumber = zyjndjNumber; }
+    public BigDecimal getZyjndjScore() { return zyjndjScore; }
+    public void setZyjndjScore(BigDecimal zyjndjScore) { this.zyjndjScore = zyjndjScore; }
+    public BigDecimal getAllYearScoreSum() { return allYearScoreSum; }
+    public void setAllYearScoreSum(BigDecimal allYearScoreSum) { this.allYearScoreSum = allYearScoreSum; }
+    public BigDecimal getAppraisalResultScore() { return appraisalResultScore; }
+    public void setAppraisalResultScore(BigDecimal appraisalResultScore) { this.appraisalResultScore = appraisalResultScore; }
+    public BigDecimal getLastSumScore() { return lastSumScore; }
+    public void setLastSumScore(BigDecimal lastSumScore) { this.lastSumScore = lastSumScore; }
+    public String getLastHrJobFamilyNumber() { return lastHrJobFamilyNumber; }
+    public void setLastHrJobFamilyNumber(String lastHrJobFamilyNumber) { this.lastHrJobFamilyNumber = lastHrJobFamilyNumber; }
+    public Integer getLastJobGradeIndex() { return lastJobGradeIndex; }
+    public void setLastJobGradeIndex(Integer lastJobGradeIndex) { this.lastJobGradeIndex = lastJobGradeIndex; }
+
+    public DynamicObject getLastJobLevel() {
+        return lastJobLevel;
+    }
+
+    public void setLastJobLevel(DynamicObject lastJobLevel) {
+        this.lastJobLevel = lastJobLevel;
+    }
+
+    public Double getYearscoresuma() { return yearscoresuma; }
+    public void setYearscoresuma(Double yearscoresuma) { this.yearscoresuma = yearscoresuma; }
+    public Double getYearscoresumb() { return yearscoresumb; }
+    public void setYearscoresumb(Double yearscoresumb) { this.yearscoresumb = yearscoresumb; }
+    public Double getYearscoresumc() { return yearscoresumc; }
+    public void setYearscoresumc(Double yearscoresumc) { this.yearscoresumc = yearscoresumc; }
+    public Double getYearscoresumd() { return yearscoresumd; }
+    public void setYearscoresumd(Double yearscoresumd) { this.yearscoresumd = yearscoresumd; }
+    public Double getYearscoresume() { return yearscoresume; }
+    public void setYearscoresume(Double yearscoresume) { this.yearscoresume = yearscoresume; }
+    public Double getYearscoresumf() { return yearscoresumf; }
+    public void setYearscoresumf(Double yearscoresumf) { this.yearscoresumf = yearscoresumf; }
+    public Double getYearscoresumg() { return yearscoresumg; }
+    public void setYearscoresumg(Double yearscoresumg) { this.yearscoresumg = yearscoresumg; }
+    public Double getYearscoresumh() { return yearscoresumh; }
+    public void setYearscoresumh(Double yearscoresumh) { this.yearscoresumh = yearscoresumh; }
+    public Double getYearscoresumi() { return yearscoresumi; }
+    public void setYearscoresumi(Double yearscoresumi) { this.yearscoresumi = yearscoresumi; }
+
+    public DynamicObject getLastJobSeq() {
+        return lastJobSeq;
+    }
+
+    public void setLastJobSeq(DynamicObject lastJobSeq) {
+        this.lastJobSeq = lastJobSeq;
+    }
+
+    public JobLevelCalculatorService.RankingResultInfo getRankingResultInfo() {
+        return rankingResultInfo;
+    }
+
+    public void setRankingResultInfo(JobLevelCalculatorService.RankingResultInfo rankingResultInfo) {
+        this.rankingResultInfo = rankingResultInfo;
+    }
+
+    public JobLevelCalculatorService.JobScoreInfo getJobScoreInfo() {
+        return jobScoreInfo;
+    }
+
+    public void setJobScoreInfo(JobLevelCalculatorService.JobScoreInfo jobScoreInfo) {
+        this.jobScoreInfo = jobScoreInfo;
+    }
+
+    public String getZgjbName() {
+        return StringUtils.isNotBlank(zgjbName) ? zgjbName : "无";
+    }
+
+    public void setZgjbName(String zgjbName) {
+        this.zgjbName = zgjbName;
+    }
+
+    public String getZyjndjName() {
+        return StringUtils.isNotBlank(zyjndjName) ? zyjndjName : "无";
+    }
+
+    public void setZyjndjName(String zyjndjName) {
+        this.zyjndjName = zyjndjName;
+    }
+
+    public String getAppraisalResultName() {
+        return appraisalResultName = StringUtils.isNotBlank(appraisalResultName) ? appraisalResultName : "无";
+    }
+
+    public void setAppraisalResultName(String appraisalResultName) {
+        this.appraisalResultName = appraisalResultName;
+    }
+
+    public DynamicObject getConvertLastJobSeq() {
+        return convertLastJobSeq;
+    }
+
+    public void setConvertLastJobSeq(DynamicObject convertLastJobSeq) {
+        this.convertLastJobSeq = convertLastJobSeq;
+    }
+}

+ 925 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/business/AnnualAdjustmentService.java

@@ -0,0 +1,925 @@
+package nckd.jxccl.hr.psms.business;
+
+import kd.bos.common.enums.EnableEnum;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.entity.constant.StatusEnum;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import nckd.jxccl.base.common.constant.FormConstant;
+import nckd.jxccl.base.common.enums.AppraisalResultEnum;
+import nckd.jxccl.base.common.enums.psms.AdjustTypeEnum;
+import nckd.jxccl.base.common.enums.psms.JobSeqEnum;
+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.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.LocalDateTime;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.StringJoiner;
+
+/**
+* 年度调整服务类
+* @author W.Y.C
+* @date 2025/10/08 22:12
+* @version 1.0
+*/
+public class AnnualAdjustmentService {
+
+
+    /**
+     * 生成年度调整记录
+     * @param person    员工
+     * @param beginDate   生效日期
+     * @return 成功返回 新的职位档案
+     * @note 对应SHR:PersonpositionfilecreateViewListHandler#addNewYear_PersonpositionfileInfo(277~815行)
+     */
+    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());
+
+        // 1、 初始化上下文并加载基础数据。
+        // 对应SHR:291~339行
+        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);
+
+        // 3、加载上一条记录并校验生效日期。
+        //对应SHR:393~421行
+        loadLastRecordAndValidateBeginDate(ac);
+
+        // 4、处理职位序列(如果是管理序列,则按职能序列进行调整)
+        //对应SHR:521~527行
+        DynamicObject convertJobSeq = JobLevelCalculatorService.handleJobSeq(ac.jobSeq);
+        ac.convertJobSeq = convertJobSeq;
+        DynamicObject convertLastJobSeq = JobLevelCalculatorService.handleJobSeq(ac.data.getLastJobSeq());
+        ac.data.setConvertLastJobSeq(convertLastJobSeq);
+
+        // 5、获取技能/职称分
+        //对应SHR:451~481行
+        JobLevelCalculatorService.JobScoreInfo jobScoreInfo = JobLevelCalculatorService.handleJobScores(ac.lastRecordInfo,convertJobSeq, ac.positionAppointment);
+        ac.data.setJobScoreInfo(jobScoreInfo);
+
+        // 6、计算学历得分并生成说明
+        //对应SHR:423~449行
+        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);
+
+
+        //8.确定目标职级(含有R排名和无R排名两条路径)。
+        //对应SHR:540~702行
+        DynamicObject jobLevel = decideTargetJobGrade(ac);
+
+        // 9、上年度考核结果为“无”时,取最低职级
+        //对应SHR:703~707行
+        if (AppraisalResultEnum.NONE.getCode().equals(ac.data.getAppraisalResultNumber())) {
+            // 考核结果为无时取最低职级
+            ac.adjustType = "8";
+            ac.adjustInt = 0;
+            jobLevel = JobLevelCalculatorService.getLowestJobLevel(ac.convertJobSeq);
+        }
+        if(jobLevel == null){
+            throw new ValidationException(StrFormatter.format("人员【{}】,职位序列【{}】总积分【{}】职称等级【{}】技能等级【{}】考核结果【{}】没有匹配到符合的职级",
+                    ac.personName,
+                    ac.jobSeq.getString(FormConstant.NAME_KEY),
+                    ac.allSumScore.toString(),
+                    ac.data.getZgjbName(),
+                    ac.data.getZyjndjName(),
+                    ac.data.getAppraisalResultName()));
+        }
+
+        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;
+        }
+
+        //【三期需求】:计算保级原因
+        judgeLevelKeepReason(ac, jobLevel);
+
+        //10、构建职位档案
+        return buildPersonPositionFile(ac, jobLevel);
+
+
+
+    }
+
+    private static void judgeLevelKeepReason(AdjustmentContext ac, DynamicObject jobLevel) {
+        if(AdjustTypeEnum.KEEP_LEVEL.getCode().equals(ac.adjustType)){
+            if(ac.useAppraisalresult){
+                ac.levelKeepReason = 1;
+            }else{
+                BigDecimal allSumScore = ac.allSumScore;
+                int currentSeq = jobLevel.getInt(PositionStructureConstant.JOBLEVELSEQ);
+                //下一级所需的积分
+                DynamicObject nextJobLevel = ac.jobLevelByJobSeqMap.get(currentSeq + 1);
+                if(nextJobLevel != null) {
+                    BigDecimal nextScore = nextJobLevel.getBigDecimal(FormConstant.NCKD_SCORE);
+                    //如果未达到下一级积分则认定:总积分没有达到下一级最低分数要求
+                    if (allSumScore.compareTo(nextScore) < 0) {
+                        //allSumScore小于nextScore
+                        if (ac.rankNoUpGrad) {
+                            //总积分及排名均未达到
+                            ac.levelKeepReason = 6;
+                        } else {
+                            //总积分没有达到下一级最低分数要求
+                            ac.levelKeepReason = 2;
+                        }
+                    } else {
+                        if(ac.rankNoUpGrad) {
+                            //R排名与全员绩效考核等级均未达到升级标准
+                            ac.levelKeepReason = 5;
+                        }else{
+                            //判断职称等级或技能等级是否达到
+                            //1、获取当前职称或技能等级序号
+                            //2、获取>当前职级的所有职级的资格条件的序号,取最小序号
+                            //3、判断当前职称或技能等级序号是否大于最小序号
+                            //4、如果当前职级序号小于最小序号则认为未达到
+                            //这里先不判断,因为其他所有保级情况都已涵盖。剩下的就只有职称或技能达不到了
+                            if (JobSeqEnum.SKILL.getCode().equals(ac.jobSeq.getString(FormConstant.NUMBER_KEY))) {
+                                ac.levelKeepReason = 4;
+                            }else{
+                                ac.levelKeepReason = 3;
+                            }
+                        }
+                    }
+                }else {
+                    //达到职级上限
+                    ac.levelKeepReason = 7;
+                }
+            }
+        }
+    }
+
+
+    /**
+     * 初始化上下文并加载基础数据。
+     * @param executeYear 执行年份
+     * @param person 员工
+     * @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 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;
+        ac.personId = person.getLong(FormConstant.ID_KEY);
+        ac.personName = person.getString(FormConstant.NAME_KEY);
+        ac.beginDate = beginDate;
+        ac.personInfo = person;
+
+        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);
+        if(performanceResult == null){
+            throw new ValidationException(StrFormatter.format("人员【{}】缺少【{}】年考核结果", ac.personName,lastYear));
+        }
+        AnnualAdjustmentData data = new AnnualAdjustmentData();
+        data.setAppraisalResult(performanceResult);
+        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));
+
+
+        //3.获取员工上年度年度R排名(对应SHR:PersonpositionfileUtils:915~945行)
+        DynamicObject lastPersonPosFile = null;
+        JobLevelCalculatorService.RankingResultInfo rankingInfo = JobLevelCalculatorService.getRankingInfo(ac.personId, ac.personName, beginDate == null ? new Date() : beginDate);
+        data.setRankingResultInfo(rankingInfo);
+
+        //4.查询上一条有效年度调整记录(对应SHR:PersonpositionfileUtils:946~997行)
+        DynamicObject[] personPosFileByPersonAndState = PositionStructureHelper.getPersonPosFileByPersonAndState(ac.personId,
+                new String[]{"3","4"},
+                null,null,
+                QueryFieldBuilder.create()
+                        .orderDesc(PositionStructureConstant.NCKD_EXECUTEYEAR,PositionStructureConstant.MODIFY_TIME_KEY,PositionStructureConstant.NCKD_BEGINDATE).buildOrderArray());
+        if(personPosFileByPersonAndState == null || personPosFileByPersonAndState.length < 1){
+            //没有年度调整记录取初定记录
+            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
+            DynamicObject jobSeq = lastPersonPosFile.getDynamicObject(PositionStructureConstant.NCKD_JOBSEQHR);
+            data.setLastHrJobFamilyNumber(jobSeq.getString(FormConstant.NUMBER_KEY));
+            data.setLastJobSeq(jobSeq);
+            DynamicObject jobLevel = lastPersonPosFile.getDynamicObject(PositionStructureConstant.NCKD_JOBLEVELHR);
+            data.setLastJobLevel(jobLevel);
+            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);
+        DynamicObject perEduExp = positionAppointment.getPerEduExp();
+        if(perEduExp != null){
+            data.setDiplomaId(perEduExp.getLong(String.join(".", FormConstant.EDUCATION_KEY, FormConstant.ID_KEY)));
+            data.setDiplomaScore(perEduExp.getBigDecimal(String.join(".", FormConstant.EDUCATION_KEY, FormConstant.NCKD_SCORE)));
+        }
+        DynamicObject empPosOrgRel = positionAppointment.getEmpPosOrgRel();
+        if(empPosOrgRel != null){
+            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.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{
+            throw new ValidationException(StrFormatter.format("当前无法为【{}】进行调整,因为根据时间【{}】未获取到人员任职和聘任信息!", ac.personName, DateUtil.format(beginDate == null ? new Date() : beginDate,DateUtil.NORM_DATE_PATTERN)));
+        }
+        DynamicObject perProTitle = positionAppointment.getPerProTitle();
+        if(perProTitle != null){
+            data.setRankName(perProTitle.getString(String.join(".", FormConstant.PROLEVEL_KEY, FormConstant.NAME_KEY)));
+            data.setZgjbId(perProTitle.getLong(String.join(".", FormConstant.PROLEVEL_KEY, FormConstant.ID_KEY)));
+            data.setZgjbNumber(perProTitle.getString(String.join(".", FormConstant.PROLEVEL_KEY, FormConstant.NUMBER_KEY)));
+            data.setZgjbName(perProTitle.getString(String.join(".", FormConstant.PROLEVEL_KEY, FormConstant.NAME_KEY)));
+            data.setZgjbScore(perProTitle.getBigDecimal(String.join(".", FormConstant.PROLEVEL_KEY, FormConstant.NCKD_SCORE)));
+        }
+        DynamicObject perOcpQual = positionAppointment.getPerOcpQual();
+        if(perOcpQual != null){
+            data.setJobStatusName(perOcpQual.getString(String.join(".", FormConstant.QUALEVEL_KEY, FormConstant.NAME_KEY)));
+            data.setZyjndjId(perOcpQual.getLong(String.join(".", FormConstant.QUALEVEL_KEY, FormConstant.ID_KEY)));
+            data.setZyjndjNumber(perOcpQual.getString(String.join(".", FormConstant.QUALEVEL_KEY, FormConstant.NUMBER_KEY)));
+            data.setZyjndjName(perOcpQual.getString(String.join(".", FormConstant.QUALEVEL_KEY, FormConstant.NAME_KEY)));
+            data.setZyjndjScore(perOcpQual.getBigDecimal(String.join(".", FormConstant.QUALEVEL_KEY, FormConstant.NCKD_SCORE)));
+        }
+        if (data.getHrJobFamilyId() == null || data.getHrJobFamilyId() == 0) {
+            throw new ValidationException(StrFormatter.format("无职位序列,请检查当前人员【{}】任职的岗位【{}】是否有职位序列",ac.personName,data.getPositionName()));
+        }
+        ac.positionAppointment = positionAppointment;
+        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) {
+            ac.haveRp = false;
+        }
+
+        // 对应SHR:323~339行
+        // 考核结果对应升降级逻辑
+        if (AppraisalResultEnum.EXCELLENT.getCode().equals(ac.data.getAppraisalResultNumber())) {
+            ac.minusByAppraisal = 1;
+            ac.whyAdjust.append("【考核结果优秀】升1级");
+        } else if (AppraisalResultEnum.BASICALLY_QUALIFIED.getCode().equals(ac.data.getAppraisalResultNumber())) {
+            ac.minusByAppraisal = -1;
+            ac.whyAdjust.append("【考核结果基本合格】降1级");
+        } else if (AppraisalResultEnum.UN_QUALIFIED.getCode().equals(ac.data.getAppraisalResultNumber())) {
+            ac.minusByAppraisal = -2;
+            ac.whyAdjust.append("【考核结果不合格】降2级");
+        }
+        return ac;
+    }
+
+    /**
+     * 判断是否为本年首次调整、考核是否已被使用,并根据规则决定是否使用R排名。
+     * @param ac 上下文对象
+     * @return: void
+     * @author W.Y.C
+     * @date: 2025/10/08 22:12
+     */
+    private static void evaluateFirstAndAppraisalUsage(AdjustmentContext ac) {
+
+        DynamicObject nowYearPersonPosFile = PositionStructureHelper.getLatsPersonPosFileByPerson(ac.personId, new QFilter(PositionStructureConstant.NCKD_EXECUTEYEAR, QCP.equals, ac.nowYear));
+
+        boolean isyearfirstdo = false;
+        if (nowYearPersonPosFile != null) {
+            DynamicObject nowYearAppraisalResult = nowYearPersonPosFile.getDynamicObject(PositionStructureConstant.NCKD_APPRAISALRESULT);
+            String nowYearAppraisalResultNumber = nowYearAppraisalResult.getString(FormConstant.NUMBER_KEY);
+            String appraisalResultNumber = ac.data.getAppraisalResultNumber();
+            if (!StringUtils.equals(nowYearAppraisalResultNumber, appraisalResultNumber)) {
+                throw new ValidationException(StrFormatter.format("人员【{}】,上年考度核结果存在变更,需删除【{}】年度创建的【员工职位档案调整】记录才能继续操作 !", ac.personName,ac.nowYear));
+            }
+            LocalDateTime nowYearDateTime = LocalDateTime.of(ac.nowYear, 1, 1, 0, 0);
+            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;
+        }
+
+        if (ac.useAppraisalresult) {
+            ac.whyAdjust.append(" ").append(ac.nowYear).append("年度【考核结果】已被使用,保级处理");
+            isyearfirstdo = false;
+            ac.keep = true;
+        } else {
+            ac.whyAdjust.append(" ").append(ac.nowYear).append("年度【考核结果】未被使用");
+            ac.keep = false;
+            isyearfirstdo = true;
+            if (ac.minusByAppraisal == 0) {
+                ac.whyAdjust.append("【考核结果】为保级,");
+                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;
+                } else {
+                    ac.whyAdjust.append("使用R排名进行计算 ");
+                    ac.haveRp = true;
+                }
+            }
+        }
+
+        ac.isYearFirstDo = isyearfirstdo;
+    }
+
+    /**
+     * 加载上一条记录并校验生效日期。
+     * @param ac 上下文
+     * @return: void
+     * @author W.Y.C
+     * @date: 2025/10/08 23:13
+     */
+    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));
+        }
+        ac.lastRecordInfo = BusinessDataServiceHelper.loadSingle(ac.lastRecordId, PositionStructureConstant.PERSONPOSFILE_ENTITYID);
+
+        if (ac.beginDate != null
+                && ac.lastRecordInfo != null
+                && ac.lastRecordInfo.getDate(PositionStructureConstant.NCKD_BEGINDATE) != null) {
+            Date lastBegin = ac.lastRecordInfo.getDate(PositionStructureConstant.NCKD_BEGINDATE);
+            if (ac.beginDate.compareTo(lastBegin) <= 0) {
+                throw new ValidationException(StrFormatter.format("当前无法为【{}】进行调整,因为最近调整时间为【{}】,不能早于最近一次职位调整时间。", ac.personName,DateUtil.format(lastBegin,DateUtil.NORM_DATE_PATTERN)));
+            }
+        }
+    }
+
+
+    /**
+     * 汇总年度积分池与综合分数。
+     * @param ac 上下文对象
+     * @return: void
+     * @author W.Y.C
+     * @date: 2025/10/08 23:46
+     */
+    private static void aggregateScores(AdjustmentContext ac) {
+        BigDecimal allyearscoresum =
+                ac.data.getAllYearScoreSum() == null ? BigDecimal.ZERO : ac.data.getAllYearScoreSum();
+        System.out.println("上年所有贡献单据分数之和" + allyearscoresum);
+
+        BigDecimal appraisalresultscore =
+                ac.data.getAppraisalResultScore() == null ? BigDecimal.ZERO : ac.data.getAppraisalResultScore();
+        System.out.println("上年年度考核得分" + appraisalresultscore);
+
+        ac.lastYearContributeScore = allyearscoresum.add(appraisalresultscore);
+        System.out.println("上年度贡献综合评价分" + ac.lastYearContributeScore);
+
+        ac.addYearContributeScore = ac.lastYearContributeScore.multiply(new BigDecimal("0.1"));
+        System.out.println("年度新增的贡献积分" + ac.addYearContributeScore);
+
+        BigDecimal lastsum = ac.data.getLastSumScore() == null ? BigDecimal.ZERO : ac.data.getLastSumScore();
+        System.out.println("上年累计积分池的分" + lastsum);
+
+        ac.sumScore = lastsum.add(ac.addYearContributeScore);
+        System.out.println("累计积分池的分" + ac.sumScore);
+
+
+        ac.allSumScore = ac.sumScore
+                .add(ac.diplomaScore)
+                .add(ac.data.getJobScoreInfo().perProTitleScore)
+                .add(ac.data.getJobScoreInfo().quaLevelScore);
+        System.out.println("累计总积分" + ac.allSumScore);
+    }
+
+    /**
+     * 确定目标职级(含有R排名和无R排名两条路径)。
+     * @param ac 上下文对象
+     * @return: kd.bos.dataentity.entity.DynamicObject
+     * @note 对应原 PersonpositionfilecreateViewListHandler:540~707 行
+     * @author W.Y.C
+     * @date: 2025/10/10 14:17
+     */
+    private static DynamicObject decideTargetJobGrade(AdjustmentContext ac) {
+
+        //上一条职位档案的职位序列(如果是管理序列,则转换为职能序列)
+        String newJobSeqNumber = ac.convertJobSeq.getString(FormConstant.NUMBER_KEY);
+        String newLastJobSeqNumber = ac.data.getConvertLastJobSeq().getString(FormConstant.NUMBER_KEY);
+        if (!newJobSeqNumber.equalsIgnoreCase(newLastJobSeqNumber)) {
+            //序列发生变化
+            throw new ValidationException(StrFormatter.format("人员【{}】,序列发生变化!请先到【员工职位档案调整】中进行操作", ac.personName));
+        }
+
+        if (ac.haveRp) {
+            return decideWithR(ac);
+        } else {
+            return decideWithoutR(ac);
+        }
+    }
+
+    /**
+     * 有 R 排名时的定位路径。
+     * @param ac 上下文对象
+     * @return: kd.bos.dataentity.entity.DynamicObject
+     * @author W.Y.C
+     * @date: 2025/10/10 14:18
+     */
+    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("人员【{}】,职位序列【{}】总积分【{}】职称等级【{}】技能等级【{}】考核结果【{}】没有匹配到符合的职级",
+                    ac.personName,
+                    ac.jobSeq.getString(FormConstant.NAME_KEY),
+                    ac.allSumScore.toString(),
+                    ac.data.getZgjbName(),
+                    ac.data.getZyjndjName(),
+                    ac.data.getAppraisalResultName()));
+        }
+        int jobGradeindex = jobLevel.getInt(FormConstant.JOBLEVELSEQ);
+        System.out.println("当前积分所得职级顺序号:::" + jobGradeindex);
+
+        Map<Integer, DynamicObject> jobLevelByJobSeqMap = JobLevelCalculatorService.getJobLevelByJobSeqMap(ac.convertJobSeq);
+        ac.jobLevelByJobSeqMap = jobLevelByJobSeqMap;
+        int newjobgradeindex = ac.data.getLastJobGradeIndex();
+
+        // 补充根据R排名确定是否升级
+        if (ac.minusByAppraisal == 0) {
+            // 如果考评结果不需要升降级则验证R排名
+            if (!ac.keep) {
+                newjobgradeindex = JobLevelCalculatorService.getnewjobgradeindexByrank(ac.data.getLastJobGradeIndex(), newjobgradeindex, ac.data.getRankingResultInfo(), ac.convertJobSeq);
+                if(ac.data.getLastJobGradeIndex() == newjobgradeindex){
+                    ac.rankNoUpGrad = true;
+                }
+            }
+
+            if (newjobgradeindex > jobGradeindex) {
+                //超过职位序列最高职级则退回职位序列最高职级
+                newjobgradeindex = jobGradeindex;
+                //但超出『积分』和『职称等级、技能等级』的所能定级的最高职级,按
+            }
+        } else {
+            // 考核结果需要升降级则直接执行
+            newjobgradeindex = ac.data.getLastJobGradeIndex() + ac.minusByAppraisal;
+            if (jobGradeindex <= newjobgradeindex) {
+                //超过职位序列最高职级则退回职位序列最高职级
+                newjobgradeindex = jobGradeindex;
+            }
+        }
+
+        // 确定是否超出总分最高任命职级
+        DynamicObject maxJobLevel = JobLevelCalculatorService.getMaxJobLevel(ac.convertJobSeq, ac.allSumScore);
+        int personMaxjobgradeindex = maxJobLevel.getInt(FormConstant.JOBLEVELSEQ);
+        // 超出总分能任命最高职职级则该最高职级就是要任命的职级
+        if (newjobgradeindex > personMaxjobgradeindex) {
+            newjobgradeindex = personMaxjobgradeindex;
+        }
+
+        // 确定是否超出该职称等级或技能等级最高任命职级
+        DynamicObject maxJobLevel1 = JobLevelCalculatorService.getMaxJobLevel(ac.convertJobSeq, ac.allSumScore, null, ac.data.getZgjbNumber(), ac.data.getZyjndjNumber());
+        int maxJobGradeIndex = maxJobLevel1.getInt(FormConstant.JOBLEVELSEQ);
+        // 职级超出职称等级或技能等级能任命最高职职级则该最高职级就是要任命的职级
+        if (newjobgradeindex > maxJobGradeIndex) {
+            newjobgradeindex = maxJobGradeIndex;
+        }
+
+        if((ac.data.getZgjbId() == null || ac.data.getZyjndjId() == 0) && (ac.data.getZyjndjId() == null || ac.data.getZyjndjId() == 0)) {
+            //如果没有聘任则取最低职级
+            jobLevel = JobLevelCalculatorService.getLowestJobLevel(ac.convertJobSeq);
+            if(jobLevel != null) {
+                newjobgradeindex = jobLevel.getInt(FormConstant.JOBLEVELSEQ);
+            }
+        }else if(jobLevelByJobSeqMap.get(newjobgradeindex) != null){
+            jobLevel = jobLevelByJobSeqMap.get(newjobgradeindex);
+        }else{
+            //兜底;如果没有获取到合适的职级则取最低级
+            jobLevel = JobLevelCalculatorService.getLowestJobLevel(ac.convertJobSeq);
+            if(jobLevel != null) {
+                newjobgradeindex = jobLevel.getInt(FormConstant.JOBLEVELSEQ);
+            }
+        }
+
+
+        if((ac.data.getZgjbId() == null || ac.data.getZyjndjId() == 0) && (ac.data.getZyjndjId() == null || ac.data.getZyjndjId() == 0)) {
+            //无聘任
+            ac.adjustType = "7";
+        }else if (newjobgradeindex == ac.data.getLastJobGradeIndex()) {
+            //保级
+            ac.adjustType = "1";
+        }else if (newjobgradeindex > ac.data.getLastJobGradeIndex()) {
+            //升级
+            ac.adjustType = "2";
+        } else {
+            //降级
+            ac.adjustType = "0";
+        }
+        // 升降级数
+        ac.adjustInt = newjobgradeindex - ac.data.getLastJobGradeIndex();
+
+        return jobLevel;
+
+    }
+
+    /**
+     * 无 R 排名时的定位路径。
+     * @param ac 上下文对象
+     * @return: kd.bos.dataentity.entity.DynamicObject
+     * @author W.Y.C
+     * @date: 2025/10/10 14:18
+     */
+    private static DynamicObject decideWithoutR(AdjustmentContext ac) {
+        // String JobGrade = "";
+        DynamicObject jobLevel = null;
+
+        if (ac.minusByAppraisal == 0 && ac.keep) {
+            System.out.println("绩效分组排名,没有勾选享受职位津贴,保持原职级不变,没有则为最低档");
+            ac.whyAdjust.append("绩效分组排名,没有勾选享受职位津贴,保持原职级不变");
+            jobLevel = ac.data.getLastJobLevel();
+            ac.adjustType = "1";
+            if(ac.useAppraisalresult){
+                ac.levelKeepReason = 1;
+            }
+
+            if (jobLevel == null) {
+                jobLevel = JobLevelCalculatorService.getLowestJobLevel(ac.convertJobSeq);
+                ac.adjustType = "0";
+            }
+
+        } else {
+            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("人员【{}】,职位序列【{}】总积分【{}】职称等级【{}】技能等级【{}】考核结果【{}】没有匹配到符合的职级",
+                        ac.personName,
+                        ac.jobSeq.getString(FormConstant.NAME_KEY),
+                        ac.allSumScore.toString(),
+                        ac.data.getZgjbName(),
+                        ac.data.getZyjndjName(),
+                        ac.data.getAppraisalResultName()));
+            }
+
+            int jobGradeindex = jobLevel.getInt(FormConstant.JOBLEVELSEQ);
+            System.out.println("当前积分所得职级顺序号:::" + jobGradeindex);
+
+            Map<Integer, DynamicObject> jobLevelByJobSeqMap = JobLevelCalculatorService.getJobLevelByJobSeqMap(ac.data.getConvertLastJobSeq());
+            ac.jobLevelByJobSeqMap = jobLevelByJobSeqMap;
+            int newjobgradeindex = ac.data.getLastJobGradeIndex();
+
+            if (ac.isYearFirstDo) {
+                newjobgradeindex += ac.minusByAppraisal;
+            }
+
+            if (jobGradeindex <= newjobgradeindex) {
+                newjobgradeindex = jobGradeindex;
+            }
+
+            // 确定是否超出该职称等级或技能等级最高任命职级
+            DynamicObject maxJobLevel1 = JobLevelCalculatorService.getMaxJobLevel(ac.convertJobSeq, ac.allSumScore, null, ac.data.getZgjbNumber(), ac.data.getZyjndjNumber());
+            int maxJobGradeIndex = maxJobLevel1.getInt(FormConstant.JOBLEVELSEQ);
+            // 职级超出职称等级或技能等级能任命最高职职级则该最高职级就是要任命的职级
+            if (newjobgradeindex > maxJobGradeIndex) {
+                newjobgradeindex = maxJobGradeIndex;
+            }
+
+            // 如果最后超出最低排名则按最低排名
+            if (jobLevelByJobSeqMap.get(newjobgradeindex) != null) {
+                jobLevel = jobLevelByJobSeqMap.get(newjobgradeindex);
+            } else {
+                jobLevel = JobLevelCalculatorService.getLowestJobLevel(ac.convertJobSeq);
+                if(jobLevel != null) {
+                    newjobgradeindex = jobLevel.getInt(FormConstant.JOBLEVELSEQ);
+                }
+            }
+
+            // 设置调整类型和调整级别数
+            if (newjobgradeindex == ac.data.getLastJobGradeIndex()) {
+                //保级
+                ac.adjustType = "1";
+            } else if (newjobgradeindex > ac.data.getLastJobGradeIndex()) {
+                //升级
+                ac.adjustType = "2";
+            } else {
+                //降级
+                ac.adjustType = "0";
+            }
+            // 升降级数
+            ac.adjustInt = newjobgradeindex - ac.data.getLastJobGradeIndex();
+        }
+
+        return jobLevel;
+    }
+
+
+    /**
+     * 构建职位档案
+     * @param ac 上下文对象
+     * @param jobLevel 目标职级对象
+     * @return: kd.bos.dataentity.entity.DynamicObject
+     * @note 对应原 710~803行
+     * @author W.Y.C
+     * @date: 2025/10/10 15:44
+     */
+    private static DynamicObject buildPersonPositionFile(AdjustmentContext ac, DynamicObject jobLevel) {
+
+        DynamicObject newPersonPosFile = BusinessDataServiceHelper.newDynamicObject(
+                PositionStructureConstant.PERSONPOSFILE_ENTITYID);
+
+        DynamicObject empPosOrgRel = ac.positionAppointment.getEmpPosOrgRel();
+        newPersonPosFile.set(PositionStructureConstant.NCKD_PERSON, ac.personInfo);
+        DynamicObject company = BusinessDataServiceHelper.newDynamicObject(FormConstant.ADMINORGHR_ENTITYID);
+        company.set(FormConstant.ID_KEY, empPosOrgRel.getLong(String.join(".",FormConstant.COMPANY_KEY,FormConstant.ID_KEY)));
+        newPersonPosFile.set(PositionStructureConstant.USEORG_KEY, company);
+        DynamicObject dep = BusinessDataServiceHelper.newDynamicObject(FormConstant.ADMINORGHR_ENTITYID);
+        dep.set(FormConstant.ID_KEY, empPosOrgRel.getLong(String.join(".",FormConstant.ADMINORG,FormConstant.ID_KEY)));
+        newPersonPosFile.set(PositionStructureConstant.ORG_KEY, dep);
+        newPersonPosFile.set(PositionStructureConstant.CREATEORG_KEY, dep);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_TYPESTATE, "3");
+        newPersonPosFile.set(PositionStructureConstant.NCKD_EXECUTEYEAR, ac.executeYear);
+        Long positionId = empPosOrgRel.getLong(String.join(".",FormConstant.POSITION_KEY,FormConstant.ID_KEY));
+        DynamicObject position = BusinessDataServiceHelper.newDynamicObject(FormConstant.HBPM_POSITIONHR);
+        position.set(FormConstant.ID_KEY, positionId);
+        Long jobSeqId = empPosOrgRel.getLong(String.join(".", FormConstant.HBPM_POSITIONHR, FormConstant.NCKD_JOBSEQ, FormConstant.ID_KEY));
+        DynamicObject jobSeq = BusinessDataServiceHelper.newDynamicObject(FormConstant.HBJM_JOBSEQHR);
+        jobSeq.set(FormConstant.ID_KEY, jobSeqId);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_JOBSEQHR, jobSeq);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_POSITIONHR, position);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_RANKNAME, "无".equalsIgnoreCase(ac.data.getRankName()) ? null : ac.data.getRankName());
+        DynamicObject proTitleLevel = BusinessDataServiceHelper.newDynamicObject(PositionStructureConstant.HBSS_PROTITLELEVEL);
+        proTitleLevel.set(FormConstant.ID_KEY, ac.data.getZgjbId());
+        newPersonPosFile.set(PositionStructureConstant.NCKD_PROTITLELEVEL, proTitleLevel);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_JOBSTATUSNAME, "无".equalsIgnoreCase(ac.data.getZyjndjName()) ? null : ac.data.getZyjndjName());
+        DynamicObject ocpQualLevel = BusinessDataServiceHelper.newDynamicObject(PositionStructureConstant.HBSS_OCPQUALLEVEL);
+        ocpQualLevel.set(FormConstant.ID_KEY, ac.data.getZyjndjId());
+        newPersonPosFile.set(PositionStructureConstant.NCKD_OCPQUALLEVEL, ocpQualLevel);
+        DynamicObject diploma = BusinessDataServiceHelper.newDynamicObject(PositionStructureConstant.HBSS_DIPLOMA);
+        diploma.set(FormConstant.ID_KEY, ac.data.getDiplomaId());
+        newPersonPosFile.set(PositionStructureConstant.NCKD_DIPLOMA, diploma);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_JOBLEVELHR, jobLevel);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_LASTPERSONPOSFILE, ac.lastRecordInfo);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_DIPLOMASCORE, ac.diplomaScore);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_WHYDIPLOMASCORE, ac.whyDiplomaScore.toString());
+        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_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());
+
+        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_DISABLE, EnableEnum.NO.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());
+        newPersonPosFile.set(PositionStructureConstant.NCKD_YEARSCORESUMB, ac.data.getYearscoresumb());
+        newPersonPosFile.set(PositionStructureConstant.NCKD_YEARSCORESUMC, ac.data.getYearscoresumc());
+        newPersonPosFile.set(PositionStructureConstant.NCKD_YEARSCORESUMD, ac.data.getYearscoresumd());
+        newPersonPosFile.set(PositionStructureConstant.NCKD_YEARSCORESUME, ac.data.getYearscoresume());
+        newPersonPosFile.set(PositionStructureConstant.NCKD_YEARSCORESUMF, ac.data.getYearscoresumf());
+        newPersonPosFile.set(PositionStructureConstant.NCKD_YEARSCORESUMG, ac.data.getYearscoresumg());
+        newPersonPosFile.set(PositionStructureConstant.NCKD_YEARSCORESUMH, ac.data.getYearscoresumh());
+        newPersonPosFile.set(PositionStructureConstant.NCKD_YEARSCORESUMI, ac.data.getYearscoresumi());
+        newPersonPosFile.set(PositionStructureConstant.NCKD_LYRCONTRIBSCORE, ac.lastYearContributeScore);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_ALLYEARSCORESUM, ac.data.getAllYearScoreSum());
+        newPersonPosFile.set(PositionStructureConstant.NCKD_ADDYCONTRIBSCORE, ac.addYearContributeScore);
+        // 备注
+        newPersonPosFile.set(PositionStructureConstant.KEY_NCKD_CAUSEREMARK, ac.remark);
+        if(AdjustTypeEnum.KEEP_LEVEL.getCode().equals(ac.adjustType)){
+            //保级原因
+            newPersonPosFile.set(PositionStructureConstant.NCKD_LEVELKEEPREASON, ac.levelKeepReason+"");
+        }
+
+        return newPersonPosFile;
+    }
+    /**
+     * 内部运行时上下文。
+     */
+    private static class AdjustmentContext {
+        /** 执行年份 */
+        Integer executeYear;
+
+        /** 当前年份 */
+        Integer nowYear;
+
+        /** 调整生效日期 */
+        Date beginDate;
+
+        /** 员工ID */
+        Long personId;
+
+        /** 员工Name */
+        String personName;
+
+        /** 员工信息对象 */
+        DynamicObject personInfo;
+
+        /** 强类型数据对象(替代原 selMap) */
+        AnnualAdjustmentData data;
+
+        /** 是否有R位次(allowancerankpercent≤0则为false) */
+        boolean haveRp = true;
+
+        /** 是否保级(缺R位次时为true) */
+        boolean keep = false;
+
+        /** 本年考核是否已使用 */
+        boolean useAppraisalresult = false;
+
+        /** 本年考核被使用的原因*/
+        StringBuilder useAppraisalResultReasons;
+
+        /** 是否本年首次调整 */
+        boolean isYearFirstDo = false;
+
+        /** 考核升降级标记(+1升/-1降/-2降两级) */
+        int minusByAppraisal = 0;
+
+        /** 固定类型(默认3) */
+        String typestate = "3";
+
+        /** 上一条档案记录ID */
+        Long lastRecordId;
+
+        /** 上一条记录对象 */
+        DynamicObject lastRecordInfo;
+
+        /** 上年度贡献综合评价分 */
+        BigDecimal lastYearContributeScore = BigDecimal.ZERO;
+        /** 年度新增贡献积分 */
+        BigDecimal addYearContributeScore = BigDecimal.ZERO;
+        /** 当前累计积分池 */
+        BigDecimal sumScore = BigDecimal.ZERO;
+        /** 总分(池+学历+职称/技能) */
+        BigDecimal allSumScore = BigDecimal.ZERO;
+
+        /** 学历得分 */
+        BigDecimal diplomaScore = BigDecimal.ZERO;
+        /** 学历得分说明 */
+        Map<String, String> whyDiplomaScore = new LinkedHashMap<>();
+
+        /** 职称得分 */
+        BigDecimal rankScore = BigDecimal.ZERO;
+        /** 技能得分 */
+        BigDecimal jobStatusScore = BigDecimal.ZERO;
+
+        /** 调整说明 */
+        StringBuffer whyAdjust = new StringBuffer();
+
+        /** 升降类型:0降/1保/2升/7固定/8无考核结果 */
+        String adjustType;
+        /** 升降数量:正数升、负数降、0保 */
+        Integer adjustInt;
+
+        PositionAppointmentBO positionAppointment;
+        /**当前任职的序列(未转换前,这里转换指的是:如果是管理序列,则按职能序列进行调整)*/
+        DynamicObject jobSeq;
+        /**当前任职的序列(转换后,这里转换指的是:如果是管理序列,则按职能序列进行调整)*/
+        DynamicObject convertJobSeq;
+
+        String remark;
+
+        /** 调整说明 */
+        StringJoiner whyAdjust1 = new StringJoiner(StrFormatter.LINE_SEPARATOR);
+
+        /**标记排名未升级*/
+        boolean rankNoUpGrad = false;
+
+        /***1:本自然年度内已升过级,达到升级限制;2:总积分没有达到下一级最低分数要求;3:未聘任高一级的职称;4:未聘任高一级的技能,5:R排名与全员绩效考核等级均未达到升级标准;6:总积分及排名均未达到;7:达到职级上限*/
+        Integer levelKeepReason;
+
+        Map<Integer, DynamicObject> jobLevelByJobSeqMap;
+    }
+}

Fichier diff supprimé car celui-ci est trop grand
+ 289 - 305
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/business/JobLevelCalculatorService.java


+ 95 - 3
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,7 +112,15 @@ 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 NCKD_LEVELKEEPREASON = "nckd_levelkeepreason";
+    /** 锁定时间*/
     public static final String POSITIONAPPOINTMENTQUERY = "positionappointmentquery";
     /*-------------------------------------- 员工职位档案 end --------------------------------------*/
 
@@ -147,6 +155,8 @@ public class PositionStructureConstant extends FormConstant {
     /*-------------------------------------- 在职人员初定(弹窗) begin --------------------------------------*/
     /** 在职人员初定(弹窗)-实体标识 */
     public static final String SERVINGINITIAL_ENTITYID = "nckd_servinginitial";
+    /** 批量在职人员初定(弹窗)-实体标识 */
+    public static final String SERVINGINITIALBATH_ENTITYID = "nckd_servinginitialbatch";
     /** 职位序列(根据岗位需要自动带出) */
     public static final String NCKD_JOBSEQ = "nckd_jobseq";
     /** 优秀生 */
@@ -159,6 +169,8 @@ public class PositionStructureConstant extends FormConstant {
     /*-------------------------------------- 新入职人员初定(弹窗) begin --------------------------------------*/
     /** 新入职人员初定(弹窗)-实体标识 */
     public static final String NEWHIREINITIAL_ENTITYID = "nckd_newhireinitial";
+    /** 批量新入职人员初定(弹窗)-实体标识 */
+    public static final String NEWHIREINITIALBATCH_ENTITYID = "nckd_newhireinitialbatch";
     /** 本次加入集团日期 */
     public static final String NCKD_JOINCOMDATE = "nckd_joincomdate";
     /** 确认定级 */
@@ -168,9 +180,89 @@ public class PositionStructureConstant extends FormConstant {
     /*-------------------------------------- 新建动态调整(弹窗) begin --------------------------------------*/
     /** 新建动态调整(弹窗)-实体标识 */
     public static final String NEWDYNAMICADJUDIALOG_ENTITYID = "nckd_newdynamicadjudialog";
+    /** 批量新建动态调整(弹窗)-实体标识 */
+    public static final String NEWDYNAMICADJUBATCH_ENTITYID = "nckd_newdynamicadjubatch";
     /** 本次加入集团日期 */
     public static final String NCKD_ADJUSTDATE = "nckd_adjustdate";
+    //调动后职级
+    public static final String NCKD_JOBLEVEL = "nckd_joblevel";
+    /** 确认定级 */
+    public static final String OP_CONFIRMADJUST = "confirmadjust";
+    /** R排名名次/R排名总人数 */
+    public static final String NCKD_ALLOWANCERANKPERCENT = "NCKD_ALLOWANCERANKPERCENT";
+    /** 职称等级 */
+    public static final String NCKD_PERPROTITLENAME = "NCKD_PERPROTITLENAME";
+    /** 技能等级 */
+    public static final String NCKD_QUALEVELNAME = "NCKD_QUALEVELNAME";
+
     /*-------------------------------------- 新建动态调整(弹窗) 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 --------------------------------------*/
+    /** 职位及积分初定-实体标识 */
+    public static final String DYNAMICADJUSTMENT_ENTITYID = "nckd_dynamicadjustment";
+    /** 页签控件-已生成动态调整 */
+    public static final String NCKD_ADJUSTED = "nckd_adjusted";
+    /** 页签控件-未生成动态调整 */
+    public static final String NCKD_UNADJUSTED = "nckd_unadjusted";
+    public static final String ADJUST_QUERY = "adjustquery";
+    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 JOBSEQTOJOBLEVELQUERY = "jobseqtojoblevelquery";
+    public static final String JOBSEQTOJOBLEVEL_QUERY = "jobseqtojoblevelquery";
+    /**根据职位序列查询职级*/
+    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";
 }

+ 71 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/common/bo/PositionAppointmentBO.java

@@ -0,0 +1,71 @@
+package nckd.jxccl.hr.psms.common.bo;
+
+import kd.bos.dataentity.entity.DynamicObject;
+
+/**
+ * 员工任职
+ * @author W.Y.C
+ * @date 2025/10/5 20:13
+ * @version 1.0
+ */
+public  class PositionAppointmentBO {
+
+    /**
+     * 员工任职关系
+     */
+    private DynamicObject empPosOrgRel;
+    /**
+     * 员工学历信息
+     */
+    private DynamicObject perEduExp;
+    /**
+     * 员工职称信息
+     */
+    private DynamicObject perProTitle;
+    /**
+     * 员工职业资格信息
+     */
+    private DynamicObject perOcpQual;
+
+    public PositionAppointmentBO() {
+    }
+
+    public PositionAppointmentBO(DynamicObject empPosOrgRel, DynamicObject perEduExp, DynamicObject perProTitle, DynamicObject perOcpQual) {
+        this.empPosOrgRel = empPosOrgRel;
+        this.perEduExp = perEduExp;
+        this.perProTitle = perProTitle;
+        this.perOcpQual = perOcpQual;
+    }
+
+    public DynamicObject getEmpPosOrgRel() {
+        return empPosOrgRel;
+    }
+
+    public void setEmpPosOrgRel(DynamicObject empPosOrgRel) {
+        this.empPosOrgRel = empPosOrgRel;
+    }
+
+    public DynamicObject getPerEduExp() {
+        return perEduExp;
+    }
+
+    public void setPerEduExp(DynamicObject perEduExp) {
+        this.perEduExp = perEduExp;
+    }
+
+    public DynamicObject getPerOcpQual() {
+        return perOcpQual;
+    }
+
+    public void setPerOcpQual(DynamicObject perOcpQual) {
+        this.perOcpQual = perOcpQual;
+    }
+
+    public DynamicObject getPerProTitle() {
+        return perProTitle;
+    }
+
+    public void setPerProTitle(DynamicObject perProTitle) {
+        this.perProTitle = perProTitle;
+    }
+}

+ 300 - 168
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/helper/PositionStructureHelper.java

@@ -7,13 +7,17 @@ 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 nckd.jxccl.base.common.constant.FormConstant;
-import nckd.jxccl.base.common.enums.JobSeqEnum;
+import nckd.jxccl.base.common.utils.QueryFieldBuilder;
 import nckd.jxccl.hr.psms.common.PositionStructureConstant;
+import nckd.jxccl.hr.psms.common.bo.PositionAppointmentBO;
 
-import java.math.BigDecimal;
+import java.util.ArrayList;
 import java.util.Date;
-import java.util.StringJoiner;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 /**
  * 职位体系帮助类
@@ -24,51 +28,6 @@ import java.util.StringJoiner;
  */
 public class PositionStructureHelper {
 
-    /**
-     * 计算职级
-     * 对应SHR:com.kingdee.shr.customer.web.handler.PersonpositionfileUtils#GainJobGrade 和 com.kingdee.shr.customer.web.handler.PersonpositionfileUtils#endGainJobGrade
-     * TODO [待补充完成]
-     * @param jobSeq 职位序列
-     * @param score 积分数
-     * @param proTitleLevel 职称等级
-     * @param ocpQualLevel 技能等级
-     * @param appraisalResult 绩效结果
-     * @param downgradeNum 降级数(根据绩效计算:基本合格:降1级、不合格:降2级)
-     * @param useMinLevel 是否强制按最低职级定级
-     * @param useNonLevel 是否强制按无职级定级(当为true时不满足三要素【聘任职称/技能、考核结果、积分】任意一条件时按“无职级”定级)
-     * @return: kd.bos.dataentity.entity.DynamicObject 返回:匹配的职级
-     * @author W.Y.C
-     * @date: 2025/09/13 14:21
-     */
-    public static DynamicObject calculateLevel(JobSeqEnum jobSeq, BigDecimal score, DynamicObject proTitleLevel,DynamicObject ocpQualLevel,
-                                               DynamicObject appraisalResult,int downgradeNum,boolean useMinLevel,boolean useNonLevel) {
-        //三要素说明:
-        //1、【技术序列、职能序列】:有聘任的职称等级;【技能序列】:有聘任的技能等级
-        //2、有考核结果
-        //3、积分数能够达到职级
-        if(useNonLevel) {
-            boolean nonLevel = Boolean.FALSE;
-            if ((jobSeq == JobSeqEnum.TECHNICALS || jobSeq == JobSeqEnum.FUNCTIONAL) && proTitleLevel == null) {
-                //【技术序列或职能序列】无职称等级
-                nonLevel = Boolean.TRUE;
-            } else if (jobSeq == JobSeqEnum.SKILL && ocpQualLevel == null) {
-                //【技能序列】无技能等级
-                nonLevel = Boolean.TRUE;
-            }
-            if (appraisalResult == null) {
-                //无考核结果
-                nonLevel = Boolean.TRUE;
-            }
-            if(score.compareTo(new BigDecimal(18)) < 0){
-                //积分数小于18
-                nonLevel = Boolean.TRUE;
-            }
-            //查询该序列“无职级”的数据并返回
-        }
-        DynamicObject dynamicObject = BusinessDataServiceHelper.loadSingle("2294386972933752832", "hbjm_joblevelhr");
-        return dynamicObject;
-    }
-
 
     /**
      * 判断是否已经初定
@@ -94,7 +53,24 @@ public class PositionStructureHelper {
      * @author W.Y.C
      * @date: 2025/09/23 21:08
      */
+
     public static DynamicObject[] getPersonPosFileByPersonAndState(Long personId,String[] typeState,String[] adjustType,QFilter otherFilter) {
+        return getPersonPosFileByPersonAndState(personId,typeState,adjustType,otherFilter, new String[0]);
+    }
+
+    /**
+     * 根据人员和状态过滤职位档案
+     * @param personId 人员ID
+     * @param typeState 类型状态(新入职人员初定1;在职人员初定2;年度调整3;职位调动4;管理序列的聘任5;高级职称的聘任6)
+     * @param adjustType 调整类别(升级2;保级1;降级0;首次聘任3;序列变化4;聘任下调5;总分不足6;无聘任7;无考核结果8)
+     * @param otherFilter
+     * @return: kd.bos.dataentity.entity.DynamicObject[]
+     * @author W.Y.C
+     * @date: 2025/09/23 21:08
+     */
+
+    public static DynamicObject[] getPersonPosFileByPersonAndState(Long personId,String[] typeState,String[] adjustType,QFilter otherFilter,String... orderBy) {
+
         QFilter filer = new QFilter(String.join(".", FormConstant.NCKD_PERSON, FormConstant.ID_KEY), QCP.equals, personId)
                 .and(new QFilter(PositionStructureConstant.NCKD_DISABLE, QCP.equals, EnableEnum.NO.getCode()));
         if(typeState != null){
@@ -106,7 +82,7 @@ public class PositionStructureHelper {
         if(otherFilter != null){
             filer.and(otherFilter);
         }
-        StringJoiner selectFields = new StringJoiner(",")
+        QueryFieldBuilder queryFieldBuilder = QueryFieldBuilder.create()
                 .add(FormConstant.ID_KEY)
                 .add(PositionStructureConstant.NCKD_BEGINDATE)
                 .add(PositionStructureConstant.NCKD_EXECUTEYEAR)
@@ -115,9 +91,7 @@ public class PositionStructureHelper {
                 .add(PositionStructureConstant.NCKD_DIPLOMASCORE)
                 .add(PositionStructureConstant.NCKD_RANKSCORE)
                 .add(PositionStructureConstant.NCKD_JOBSTATUSSCORE)
-                .add(String.join(".",PositionStructureConstant.NCKD_APPRAISALRESULT,FormConstant.ID_KEY))
-                .add(String.join(".",PositionStructureConstant.NCKD_APPRAISALRESULT,FormConstant.NAME_KEY))
-                .add(String.join(".",PositionStructureConstant.NCKD_APPRAISALRESULT,FormConstant.NUMBER_KEY))
+                .addIdNumberName(PositionStructureConstant.NCKD_APPRAISALRESULT)
                 .add(PositionStructureConstant.NCKD_ALLOWANCERANKPCT)
                 .add(PositionStructureConstant.NCKD_ALLOWANCERANKSEL)
                 .add(PositionStructureConstant.NCKD_ALLOWANCERANKMARK)
@@ -125,54 +99,74 @@ public class PositionStructureHelper {
                 .add(PositionStructureConstant.NCKD_SUMSCORE)
                 .add(PositionStructureConstant.NCKD_JOBLEVELHR)
                 .add(PositionStructureConstant.NCKD_RANKNAME)
-                .add(String.join(".",PositionStructureConstant.NCKD_PROTITLELEVEL,FormConstant.ID_KEY))
-                .add(String.join(".",PositionStructureConstant.NCKD_PROTITLELEVEL,FormConstant.NAME_KEY))
-                .add(String.join(".",PositionStructureConstant.NCKD_PROTITLELEVEL,FormConstant.NUMBER_KEY))
+                .addIdNumberName(PositionStructureConstant.NCKD_PROTITLELEVEL)
                 .add(PositionStructureConstant.NCKD_JOBSTATUSNAME)
-                .add(String.join(".",PositionStructureConstant.NCKD_OCPQUALLEVEL,FormConstant.ID_KEY))
-                .add(String.join(".",PositionStructureConstant.NCKD_OCPQUALLEVEL,FormConstant.NAME_KEY))
-                .add(String.join(".",PositionStructureConstant.NCKD_OCPQUALLEVEL,FormConstant.NUMBER_KEY))
-
-                .add(String.join(".",PositionStructureConstant.NCKD_JOBLEVELHR,FormConstant.ID_KEY))
-                .add(String.join(".",PositionStructureConstant.NCKD_JOBLEVELHR,FormConstant.NAME_KEY))
-                .add(String.join(".",PositionStructureConstant.NCKD_JOBLEVELHR,FormConstant.NUMBER_KEY))
-                .add(String.join(".",PositionStructureConstant.NCKD_JOBLEVELHR,FormConstant.JOBLEVELSEQ))
-
-                .add(String.join(".",PositionStructureConstant.NCKD_DIPLOMA,FormConstant.ID_KEY))
-                .add(String.join(".",PositionStructureConstant.NCKD_DIPLOMA,FormConstant.NAME_KEY))
-                .add(String.join(".",PositionStructureConstant.NCKD_DIPLOMA,FormConstant.NUMBER_KEY))
-                .add(String.join(".",PositionStructureConstant.NCKD_DIPLOMA,FormConstant.NCKD_SCORE))
-
-                .add(String.join(".",PositionStructureConstant.NCKD_JOBSEQHR,FormConstant.ID_KEY))
-                .add(String.join(".",PositionStructureConstant.NCKD_JOBSEQHR,FormConstant.NAME_KEY))
-                .add(String.join(".",PositionStructureConstant.NCKD_JOBSEQHR,FormConstant.NUMBER_KEY));
-        return BusinessDataServiceHelper.load(PositionStructureConstant.PERSONPOSFILE_ENTITYID, selectFields.toString(), new QFilter[]{filer},PositionStructureConstant.NCKD_BEGINDATE + " desc");
+                .addIdNumberName(PositionStructureConstant.NCKD_OCPQUALLEVEL)
+                .addIdNumberNameWithExtras(
+                        new String[]{PositionStructureConstant.NCKD_JOBLEVELHR},
+                        FormConstant.JOBLEVELSEQ
+                )
+                .addIdNumberNameWithExtras(
+                        new String[]{PositionStructureConstant.NCKD_DIPLOMA},
+                        FormConstant.NCKD_SCORE
+                )
+                .addIdNumberName(PositionStructureConstant.NCKD_JOBSEQHR);
+        if(orderBy != null && orderBy.length > 0){
+            queryFieldBuilder.orderBy(orderBy);
+        }else{
+            queryFieldBuilder.orderDesc(PositionStructureConstant.NCKD_BEGINDATE);
+        }
+
+
+        return BusinessDataServiceHelper.load(
+                PositionStructureConstant.PERSONPOSFILE_ENTITYID,
+                queryFieldBuilder.buildSelect(),
+                new QFilter[]{filer},
+                queryFieldBuilder.buildOrder()
+        );
+
+
     }
 
     /**
-     * 获取该人员最新的职位档案
+     * 获取人员最新的职位档案(初定 或者 (年度调整 并且 已生效) 或者 (动态调整) 生效时间为最新的那一条)
      * @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/09/20 20:09
+     * @date: 2025/10/13 11:10
      */
     public static DynamicObject getLatsPersonPosFileByPerson(Long personId,QFilter otherFilter) {
-        QFilter filer = new QFilter(String.join(".", FormConstant.NCKD_PERSON, FormConstant.ID_KEY), QCP.equals, personId)
+        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 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(
@@ -180,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);
         }
-        StringJoiner selectFields = new StringJoiner(",")
+        QueryFieldBuilder queryFieldBuilder = QueryFieldBuilder.create()
                 .add(FormConstant.ID_KEY)
+                .addIdNumberName(FormConstant.NCKD_PERSON)
                 .add(PositionStructureConstant.NCKD_BEGINDATE)
                 .add(PositionStructureConstant.NCKD_EXECUTEYEAR)
                 .add(PositionStructureConstant.NCKD_TYPESTATE)
@@ -194,38 +191,85 @@ public class PositionStructureHelper {
                 .add(PositionStructureConstant.NCKD_DIPLOMASCORE)
                 .add(PositionStructureConstant.NCKD_RANKSCORE)
                 .add(PositionStructureConstant.NCKD_JOBSTATUSSCORE)
-                .add(String.join(".",PositionStructureConstant.NCKD_APPRAISALRESULT,FormConstant.ID_KEY))
-                .add(String.join(".",PositionStructureConstant.NCKD_APPRAISALRESULT,FormConstant.NAME_KEY))
-                .add(String.join(".",PositionStructureConstant.NCKD_APPRAISALRESULT,FormConstant.NUMBER_KEY))
+                .addIdNumberName(PositionStructureConstant.NCKD_APPRAISALRESULT)
                 .add(PositionStructureConstant.NCKD_ALLOWANCERANKPCT)
                 .add(PositionStructureConstant.NCKD_ALLOWANCERANKSEL)
                 .add(PositionStructureConstant.NCKD_ALLOWANCERANKMARK)
                 .add(PositionStructureConstant.NCKD_ALLSUMSCORE)
+                .add(PositionStructureConstant.NCKD_SUMSCORE)
                 .add(PositionStructureConstant.NCKD_JOBLEVELHR)
                 .add(PositionStructureConstant.NCKD_RANKNAME)
-                .add(String.join(".",PositionStructureConstant.NCKD_PROTITLELEVEL,FormConstant.ID_KEY))
-                .add(String.join(".",PositionStructureConstant.NCKD_PROTITLELEVEL,FormConstant.NAME_KEY))
-                .add(String.join(".",PositionStructureConstant.NCKD_PROTITLELEVEL,FormConstant.NUMBER_KEY))
+                .addIdNumberName(PositionStructureConstant.NCKD_PROTITLELEVEL)
                 .add(PositionStructureConstant.NCKD_JOBSTATUSNAME)
-                .add(String.join(".",PositionStructureConstant.NCKD_OCPQUALLEVEL,FormConstant.ID_KEY))
-                .add(String.join(".",PositionStructureConstant.NCKD_OCPQUALLEVEL,FormConstant.NAME_KEY))
-                .add(String.join(".",PositionStructureConstant.NCKD_OCPQUALLEVEL,FormConstant.NUMBER_KEY))
-
-                .add(String.join(".",PositionStructureConstant.NCKD_JOBLEVELHR,FormConstant.ID_KEY))
-                .add(String.join(".",PositionStructureConstant.NCKD_JOBLEVELHR,FormConstant.NAME_KEY))
-                .add(String.join(".",PositionStructureConstant.NCKD_JOBLEVELHR,FormConstant.NUMBER_KEY))
-                .add(String.join(".",PositionStructureConstant.NCKD_JOBLEVELHR,FormConstant.JOBLEVELSEQ))
-                //学历
-                .add(String.join(".",PositionStructureConstant.NCKD_DIPLOMA,FormConstant.ID_KEY))
-                .add(String.join(".",PositionStructureConstant.NCKD_DIPLOMA,FormConstant.NAME_KEY))
-                .add(String.join(".",PositionStructureConstant.NCKD_DIPLOMA,FormConstant.NUMBER_KEY))
-                .add(String.join(".",PositionStructureConstant.NCKD_DIPLOMA,FormConstant.NCKD_SCORE))
-
-                .add(String.join(".",PositionStructureConstant.NCKD_JOBSEQHR,FormConstant.ID_KEY))
-                .add(String.join(".",PositionStructureConstant.NCKD_JOBSEQHR,FormConstant.NAME_KEY))
-                .add(String.join(".",PositionStructureConstant.NCKD_JOBSEQHR,FormConstant.NUMBER_KEY));
-        DynamicObject[] load = BusinessDataServiceHelper.load(PositionStructureConstant.PERSONPOSFILE_ENTITYID, selectFields.toString(), new QFilter[]{filer},PositionStructureConstant.NCKD_BEGINDATE + " desc");
-        return load.length > 0 ? load[0] : null;
+                .addIdNumberName(PositionStructureConstant.NCKD_OCPQUALLEVEL)
+                .addIdNumberNameWithExtras(
+                        new String[]{PositionStructureConstant.NCKD_JOBLEVELHR},
+                        FormConstant.JOBLEVELSEQ
+                )
+                // 学历
+                .addIdNumberNameWithExtras(
+                        new String[]{PositionStructureConstant.NCKD_DIPLOMA},
+                        FormConstant.NCKD_SCORE
+                )
+                // 职位序列
+                .addIdNumberName(PositionStructureConstant.NCKD_JOBSEQHR)
+                // 岗位
+                .addIdNumberName(PositionStructureConstant.NCKD_POSITIONHR)
+                //其他字段
+                .add(PositionStructureConstant.NCKD_RESULTSCORE)
+                //.add(PositionStructureConstant.NCKD_DISABLE)
+                .add(PositionStructureConstant.NCKD_ADJUSTTYPE)
+                .add(PositionStructureConstant.NCKD_ADJUSTINT)
+                .add(PositionStructureConstant.NCKD_ALLOWANCERANK)
+                .add(PositionStructureConstant.NCKD_TOPRANK)
+                .add(PositionStructureConstant.NCKD_TOPRANKPERCENT)
+
+                /*.add(PositionStructureConstant.KEY_NCKD_CAUSEREMARK)
+                .add(PositionStructureConstant.NCKD_WHYDIPLOMASCORE)*/
+
+                .add(PositionStructureConstant.NCKD_ISCURRENTNEWEST)
+                .add(PositionStructureConstant.NCKD_LASTPERSONPOSFILE)
+                .add(PositionStructureConstant.NCKD_EMPLOYMENTSTATUS)
+                .add(PositionStructureConstant.NCKD_EMPLOYMENTYEARS)
+                .add(PositionStructureConstant.NCKD_ORGINSSCORE)
+                .add(PositionStructureConstant.NCKD_LYRCONTRIBSCORE)
+                .add(PositionStructureConstant.NCKD_ENDDATE)
+                .add(PositionStructureConstant.NCKD_APPOINTSTATUS)
+                .add(PositionStructureConstant.NCKD_ADJUSSTATUS)
+                .add(PositionStructureConstant.NCKD_YEARSCORESUMA)
+                .add(PositionStructureConstant.NCKD_YEARSCORESUMB)
+                .add(PositionStructureConstant.NCKD_YEARSCORESUMC)
+                .add(PositionStructureConstant.NCKD_YEARSCORESUMD)
+                .add(PositionStructureConstant.NCKD_YEARSCORESUME)
+                .add(PositionStructureConstant.NCKD_YEARSCORESUMF)
+                .add(PositionStructureConstant.NCKD_YEARSCORESUMG)
+                .add(PositionStructureConstant.NCKD_YEARSCORESUMH)
+                .add(PositionStructureConstant.NCKD_YEARSCORESUMI)
+                .add(PositionStructureConstant.NCKD_ALLYEARSCORESUM)
+                .add(PositionStructureConstant.NCKD_ADDYCONTRIBSCORE)
+                //排序
+                .orderDesc(PositionStructureConstant.NCKD_BEGINDATE);
+
+        DynamicObject[] load = BusinessDataServiceHelper.load(
+                PositionStructureConstant.PERSONPOSFILE_ENTITYID,
+                queryFieldBuilder.buildSelect(),
+                new QFilter[]{filer},
+                queryFieldBuilder.buildOrder()
+        );
+
+        //只取每个人员最新记录(根据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());
 
     }
 
@@ -236,38 +280,40 @@ public class PositionStructureHelper {
      * @author W.Y.C
      * @date: 2025/09/20 18:57
      */
+
     public static DynamicObject getFirstRank(Long personId) {
         QFilter filer = new QFilter(String.join(".", FormConstant.NCKD_PERSON, FormConstant.ID_KEY), QCP.equals, personId)
                 .and(new QFilter(PositionStructureConstant.NCKD_DISABLE, QCP.equals, EnableEnum.NO.getCode()))
                 .and(new QFilter(PositionStructureConstant.NCKD_FIRSTRANK, QCP.equals, EnableEnum.YES.getCode()));
-        StringJoiner selectFields = new StringJoiner(",")
+        QueryFieldBuilder queryFieldBuilder = QueryFieldBuilder.create()
                 .add(FormConstant.ID_KEY)
                 .add(PositionStructureConstant.NCKD_BEGINDATE)
                 .add(PositionStructureConstant.NCKD_EXECUTEYEAR)
                 .add(PositionStructureConstant.NCKD_TYPESTATE)
                 .add(PositionStructureConstant.NCKD_FIRSTRANK)
-                .add(String.join(".",PositionStructureConstant.NCKD_APPRAISALRESULT,FormConstant.ID_KEY))
-                .add(String.join(".",PositionStructureConstant.NCKD_APPRAISALRESULT,FormConstant.NAME_KEY))
-                .add(String.join(".",PositionStructureConstant.NCKD_APPRAISALRESULT,FormConstant.NUMBER_KEY))
+                .addIdNumberName(PositionStructureConstant.NCKD_APPRAISALRESULT)
                 .add(PositionStructureConstant.NCKD_ALLOWANCERANKPCT)
                 .add(PositionStructureConstant.NCKD_ALLOWANCERANKSEL)
                 .add(PositionStructureConstant.NCKD_ALLOWANCERANKMARK)
                 .add(PositionStructureConstant.NCKD_ALLSUMSCORE)
                 .add(PositionStructureConstant.NCKD_JOBLEVELHR)
                 .add(PositionStructureConstant.NCKD_RANKNAME)
-                .add(String.join(".",PositionStructureConstant.NCKD_PROTITLELEVEL,FormConstant.ID_KEY))
-                .add(String.join(".",PositionStructureConstant.NCKD_PROTITLELEVEL,FormConstant.NAME_KEY))
-                .add(String.join(".",PositionStructureConstant.NCKD_PROTITLELEVEL,FormConstant.NUMBER_KEY))
+                .addIdNumberName(PositionStructureConstant.NCKD_PROTITLELEVEL)
                 .add(PositionStructureConstant.NCKD_JOBSTATUSNAME)
-                .add(String.join(".",PositionStructureConstant.NCKD_OCPQUALLEVEL,FormConstant.ID_KEY))
-                .add(String.join(".",PositionStructureConstant.NCKD_OCPQUALLEVEL,FormConstant.NAME_KEY))
-                .add(String.join(".",PositionStructureConstant.NCKD_OCPQUALLEVEL,FormConstant.NUMBER_KEY))
-
-                .add(String.join(".",PositionStructureConstant.NCKD_JOBLEVELHR,FormConstant.ID_KEY))
-                .add(String.join(".",PositionStructureConstant.NCKD_JOBLEVELHR,FormConstant.NAME_KEY))
-                .add(String.join(".",PositionStructureConstant.NCKD_JOBLEVELHR,FormConstant.NUMBER_KEY))
-                .add(String.join(".",PositionStructureConstant.NCKD_JOBLEVELHR,FormConstant.JOBLEVELSEQ));
-        DynamicObject[] load = BusinessDataServiceHelper.load(PositionStructureConstant.PERSONPOSFILE_ENTITYID, selectFields.toString(), new QFilter[]{filer},PositionStructureConstant.NCKD_BEGINDATE + " desc");
+                .addIdNumberName(PositionStructureConstant.NCKD_OCPQUALLEVEL)
+                .addIdNumberNameWithExtras(
+                        new String[]{PositionStructureConstant.NCKD_JOBLEVELHR},
+                        FormConstant.JOBLEVELSEQ
+                )
+                .addIdNumberName(PositionStructureConstant.NCKD_JOBSEQHR)
+                .orderDesc(PositionStructureConstant.NCKD_BEGINDATE);
+
+        DynamicObject[] load = BusinessDataServiceHelper.load(
+                PositionStructureConstant.PERSONPOSFILE_ENTITYID,
+                queryFieldBuilder.buildSelect(),
+                new QFilter[]{filer},
+                queryFieldBuilder.buildOrder()
+        );
         return load.length > 0 ? load[0] : null;
     }
 
@@ -275,47 +321,31 @@ public class PositionStructureHelper {
      * 根据时间获取员工任职(所属公司、岗位、职位序列)、聘任信息(职称等级、技能等级)和学历
      * @param personId 员工id
      * @param date 需查询的时间(任职经历:查询该时间范围内的任职。职称等级或技能等级:查询该时间聘任的等级。学历:查询毕业时间小于等于该时间的学历)
-     * @return: kd.bos.dataentity.entity.DynamicObject
+     * @return: kd.bos.dataentity.entity.DynamicObject 平铺结构(PlainObject)
      * @author W.Y.C
      * @date: 2025/09/17 10:27
      */
-    public static DynamicObject positionAppointmentQuery(Long personId, Date date) {
-        StringJoiner selectFields = new StringJoiner(",")
-                //所属公司
-                .add(String.join(".", FormConstant.COMPANY_KEY, FormConstant.ID_KEY))
-                .add(String.join(".", FormConstant.COMPANY_KEY, FormConstant.NAME_KEY))
-                .add(String.join(".", FormConstant.COMPANY_KEY, FormConstant.NUMBER_KEY))
-                //行政组织(部门)
-                .add(String.join(".", FormConstant.ADMINORG, FormConstant.ID_KEY))
-                .add(String.join(".", FormConstant.ADMINORG, FormConstant.NAME_KEY))
-                .add(String.join(".", FormConstant.ADMINORG, FormConstant.NUMBER_KEY))
-                //岗位
-                .add(String.join(".", FormConstant.POSITION_KEY, FormConstant.ID_KEY))
-                .add(String.join(".", FormConstant.POSITION_KEY, FormConstant.NAME_KEY))
-                .add(String.join(".", FormConstant.POSITION_KEY, FormConstant.NUMBER_KEY))
-                //职位序列
-                .add(String.join(".", FormConstant.HBPM_POSITIONHR, FormConstant.NCKD_JOBSEQ,FormConstant.ID_KEY))
-                .add(String.join(".", FormConstant.HBPM_POSITIONHR, FormConstant.NCKD_JOBSEQ,FormConstant.NAME_KEY))
-                .add(String.join(".", FormConstant.HBPM_POSITIONHR, FormConstant.NCKD_JOBSEQ,FormConstant.NUMBER_KEY))
-                //学历
-                .add(String.join(".", FormConstant.HRPI_PEREDUEXP, FormConstant.EDUCATION_KEY, FormConstant.ID_KEY))
-                .add(String.join(".", FormConstant.HRPI_PEREDUEXP, FormConstant.EDUCATION_KEY, FormConstant.NAME_KEY))
-                .add(String.join(".", FormConstant.HRPI_PEREDUEXP, FormConstant.EDUCATION_KEY, FormConstant.NUMBER_KEY))
-                .add(String.join(".", FormConstant.HRPI_PEREDUEXP, FormConstant.EDUCATION_KEY, FormConstant.NCKD_SCORE))
-                //职称等级
-                .add(String.join(".", FormConstant.HRPI_PERPROTITLE, FormConstant.PROLEVEL_KEY, FormConstant.ID_KEY))
-                .add(String.join(".", FormConstant.HRPI_PERPROTITLE, FormConstant.PROLEVEL_KEY, FormConstant.NAME_KEY))
-                .add(String.join(".", FormConstant.HRPI_PERPROTITLE, FormConstant.PROLEVEL_KEY, FormConstant.NUMBER_KEY))
-                .add(String.join(".", FormConstant.HRPI_PERPROTITLE, FormConstant.PROLEVEL_KEY, FormConstant.NCKD_SCORE))
-                //技能等级
-                .add(String.join(".", FormConstant.HRPI_PEROCPQUAL, FormConstant.QUALEVEL_KEY, FormConstant.ID_KEY))
-                .add(String.join(".", FormConstant.HRPI_PEROCPQUAL, FormConstant.QUALEVEL_KEY, FormConstant.NAME_KEY))
-                .add(String.join(".", FormConstant.HRPI_PEROCPQUAL, FormConstant.QUALEVEL_KEY, FormConstant.NUMBER_KEY))
-                .add(String.join(".", FormConstant.HRPI_PEROCPQUAL, FormConstant.QUALEVEL_KEY, FormConstant.NCKD_SCORE))
-                //服务年限.本次加入集团日期
-                .add(String.join(".", FormConstant.HRPI_PERSERLEN,FormConstant.JOINCOMDATE_KEY))
-                //服务年限.首次加入集团日期
-                .add(String.join(".", FormConstant.HRPI_PERSERLEN,FormConstant.FIRSTJOINCOMDATE_KEY));
+    public static PositionAppointmentBO positionAppointmentQuery(Long personId, Date date) {
+        QueryFieldBuilder queryFieldBuilder = QueryFieldBuilder.create()
+                .add(FormConstant.ID_KEY)
+                .addIdNumberName(FormConstant.EMPLOYEE_KEY)
+                // 组织分配
+                .addGroup(new String[]{FormConstant.ASSIGNMENT}, FormConstant.ID_KEY)
+                // 所属公司
+                .addIdNumberName(FormConstant.COMPANY_KEY)
+                // 行政组织(部门)
+                .addIdNumberName(FormConstant.ADMINORG)
+                // 岗位
+                .addIdNumberName(FormConstant.POSITION_KEY)
+                // 职位序列
+                .addIdNumberName(new String[]{FormConstant.HBPM_POSITIONHR, FormConstant.NCKD_JOBSEQ})
+                // 服务年限
+                .addGroup(new String[]{FormConstant.HRPI_PERSERLEN},
+                        FormConstant.JOINCOMDATE_KEY,
+                        FormConstant.FIRSTJOINCOMDATE_KEY
+                )
+                .orderDesc(FormConstant.STARTDATE)
+                .orderDesc(FormConstant.ENDDATE);
 
         //获取时间范围内的任职信息
         QFilter filer = new QFilter(String.join(".", FormConstant.EMPLOYEE_KEY,FormConstant.ID_KEY),QCP.equals,personId)
@@ -325,14 +355,116 @@ public class PositionStructureHelper {
                 .and(new QFilter(String.join(".", FormConstant.STARTDATE),QCP.less_equals,date))
                 .and(new QFilter(String.join(".", FormConstant.ENDDATE),QCP.large_equals,date));
 
-        //教育经历毕业时间
-        filer.and(new QFilter(String.join(".", FormConstant.HRPI_PEREDUEXP, FormConstant.GRADUTIONDATE), QCP.less_equals,date)
-                .or(new QFilter(String.join(".", FormConstant.HRPI_PEREDUEXP, FormConstant.ID_KEY), QCP.is_null,null)));
 
-        //TODO 职称聘任时间
-        //TODO 技能聘任时间
+        // 1、查询任职经历(取调整时间范围内的主任职)
+        DynamicObjectCollection empPosOrgRelDynamicObject = QueryServiceHelper.query(PositionStructureConstant.POSITIONAPPOINTMENTQUERY, 
+                queryFieldBuilder.buildSelect(), 
+                new QFilter[]{filer}, 
+                queryFieldBuilder.buildOrder(), 
+                1);
+
+        // 2、查询学历(取毕业时间小于等于调整时间内的学历)
+        QueryFieldBuilder perEduExpQueryFieldBuilder = QueryFieldBuilder.create()
+                .addIdNumberName(FormConstant.EMPLOYEE_KEY)
+                .addIdNumberNameWithExtras(
+                        new String[]{FormConstant.EDUCATION_KEY},
+                        FormConstant.NCKD_SCORE
+                )
+                .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.ISHIGHESTDEGREE, QCP.equals,EnableEnum.YES.getCode()));
+        DynamicObjectCollection perEduExpDynamicObject = QueryServiceHelper.query(
+                FormConstant.HRPI_PEREDUEXP, perEduExpQueryFieldBuilder.buildSelect(), new QFilter[]{perEduExpFilter}, perEduExpQueryFieldBuilder.buildOrder(), 1);
+
 
-        DynamicObjectCollection query = QueryServiceHelper.query(PositionStructureConstant.POSITIONAPPOINTMENTQUERY, selectFields.toString(), new QFilter[]{filer});
-        return !query.isEmpty() ? query.get(0) : null;
+        // 3、查询职称
+        QueryFieldBuilder perProTitleQueryFieldBuilder = QueryFieldBuilder.create()
+                .addIdNumberName(FormConstant.EMPLOYEE_KEY)
+                .addGroup(new String[]{FormConstant.PROFESSIONAL_KEY}, FormConstant.NAME_KEY)
+                .addIdNumberNameWithExtras(
+                        new String[]{FormConstant.PROLEVEL_KEY},
+                        FormConstant.NCKD_SCORE
+                )
+                .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("iscompany", QCP.equals,EnableEnum.YES.getCode()));
+        DynamicObjectCollection perProTitleDynamicObject = QueryServiceHelper.query(
+                FormConstant.HRPI_PERPROTITLE, perProTitleQueryFieldBuilder.buildSelect(), new QFilter[]{perProTitleFilter}, perProTitleQueryFieldBuilder.buildOrder(), 1);
+
+        // 4、查询技能
+        QueryFieldBuilder perOcpQualQueryFieldBuilder = QueryFieldBuilder.create()
+                .addGroup(new String[]{FormConstant.QUALIFICATION_KEY}, FormConstant.NAME_KEY)
+                .addIdNumberNameWithExtras(
+                        new String[]{FormConstant.QUALEVEL_KEY},
+                        FormConstant.NCKD_SCORE
+                )
+            .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("ismajor", QCP.equals, EnableEnum.YES.getCode()));
+        DynamicObjectCollection perOcpQualDynamicObject = QueryServiceHelper.query(
+                FormConstant.HRPI_PEROCPQUAL, perOcpQualQueryFieldBuilder.buildSelect(), new QFilter[]{perOcpQualFilter}, perOcpQualQueryFieldBuilder.buildOrder(), 1);
+
+        PositionAppointmentBO positionAppointmentBO = new PositionAppointmentBO();
+        if (empPosOrgRelDynamicObject != null && !empPosOrgRelDynamicObject.isEmpty()) {
+            positionAppointmentBO.setEmpPosOrgRel(empPosOrgRelDynamicObject.get(0));
+        }
+        if (perEduExpDynamicObject != null && !perEduExpDynamicObject.isEmpty()) {
+            positionAppointmentBO.setPerEduExp(perEduExpDynamicObject.get(0));
+        }
+        if (perProTitleDynamicObject != null && !perProTitleDynamicObject.isEmpty()) {
+            positionAppointmentBO.setPerProTitle(perProTitleDynamicObject.get(0));
+        }
+        if (perOcpQualDynamicObject != null && !perOcpQualDynamicObject.isEmpty()) {
+            positionAppointmentBO.setPerOcpQual(perOcpQualDynamicObject.get(0));
+        }
+
+
+        return positionAppointmentBO;
+    }
+
+
+
+    /**
+     * 根据人员将所有IsCurrentNewest更新为非最新的
+     * @param personId 人员ID(可传多个)
+     * @return: void
+     * @author W.Y.C
+     * @date: 2025/09/28 21:18
+     */
+    public static void markAsNotCurrentNewest(Long... personId) {
+        QFilter filter = new QFilter(String.join(".", FormConstant.NCKD_PERSON, FormConstant.ID_KEY), QCP.in, personId)
+                .and(new QFilter(PositionStructureConstant.NCKD_DISABLE, QCP.equals, EnableEnum.NO.getCode()));
+        QueryFieldBuilder queryField = QueryFieldBuilder.create()
+                .add(FormConstant.ID_KEY)
+                .add(PositionStructureConstant.NCKD_ISCURRENTNEWEST);
+        DynamicObject[] personPosFileColl = BusinessDataServiceHelper.load(PositionStructureConstant.PERSONPOSFILE_ENTITYID,queryField.buildSelect(), new QFilter[]{filter});
+        for (DynamicObject personPosFile : personPosFileColl) {
+            personPosFile.set(PositionStructureConstant.NCKD_ISCURRENTNEWEST, Boolean.FALSE);
+        }
+        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);
     }
 }

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

@@ -0,0 +1,25 @@
+package nckd.jxccl.hr.psms.plugin.form.adjust;
+
+import kd.bos.common.enums.EnableEnum;
+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.sdk.plugin.Plugin;
+import nckd.jxccl.hr.psms.common.PositionStructureConstant;
+
+/**
+* 已生成动态调整-查询列表插件
+* @author W.Y.C
+* @date 2025/10/12 17:51
+* @version 1.0
+*/
+public class DynamicAdjustQueryListPlugin extends AbstractListPlugin implements Plugin {
+
+    @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, "4"));
+    }
+}

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

@@ -0,0 +1,88 @@
+package nckd.jxccl.hr.psms.plugin.form.adjust;
+
+import kd.bos.form.ShowType;
+import kd.bos.form.container.Tab;
+import kd.bos.form.control.events.TabSelectEvent;
+import kd.bos.form.control.events.TabSelectListener;
+import kd.bos.form.plugin.AbstractFormPlugin;
+import kd.bos.list.ListShowParameter;
+import kd.bos.logging.Log;
+import kd.bos.logging.LogFactory;
+import kd.bos.report.ReportShowParameter;
+import nckd.jxccl.hr.psms.common.PositionStructureConstant;
+
+import java.util.EventObject;
+
+/**
+* 职位体系-动态调整
+* @author W.Y.C
+* @date 2025/10/11 18:28
+* @version 1.0
+*/
+public class DynamicAdjustmentFormPlugin extends AbstractFormPlugin implements TabSelectListener {
+
+    private final static Log logger = LogFactory.getLog(DynamicAdjustmentFormPlugin.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.ADJUST_QUERY, PositionStructureConstant.NCKD_ADJUSTED);
+        } else if (PositionStructureConstant.NCKD_UNADJUSTED.equalsIgnoreCase(currentTab)) {
+            //打开“未生成动态调整”列表
+            // openTargetPage(PositionStructureConstant.NCKD_UNADJUSTREPORT,PositionStructureConstant.NCKD_UNADJUSTED);
+            ReportShowParameter reportShowParameter = new ReportShowParameter();
+            reportShowParameter.setFormId(PositionStructureConstant.NCKD_UNADJUSTREPORT);
+            reportShowParameter.getOpenStyle().setShowType(ShowType.InContainer);
+            reportShowParameter.getOpenStyle().setTargetKey(PositionStructureConstant.NCKD_UNADJUSTED);
+            reportShowParameter.setCaption("未生成动态调整");
+            this.getView().showForm(reportShowParameter);
+        }
+    }
+
+    /**
+     * 在页签中打开表单
+     * @param formId 需要打开的表单标识
+     * @param targetKey 目标容器标识
+     * @return: void
+     * @author W.Y.C
+     * @date: 2025/09/10 17:15
+     */
+    private void openTargetPage(String formId, String targetKey) {
+        ListShowParameter parameter = new ListShowParameter();
+        parameter.setBillFormId(formId);
+        // 在容器内打开
+        parameter.getOpenStyle().setShowType(ShowType.InContainer);
+        // 指定页签容器标识
+        parameter.getOpenStyle().setTargetKey(targetKey);
+        this.getView().showForm(parameter);
+    }
+}

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

@@ -0,0 +1,67 @@
+package nckd.jxccl.hr.psms.plugin.form.adjust;
+
+import kd.bos.form.FormShowParameter;
+import kd.bos.form.MessageBoxOptions;
+import kd.bos.form.events.AfterDoOperationEventArgs;
+import kd.bos.form.plugin.AbstractFormPlugin;
+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/9/17 15:56
+* @version 1.0
+*/
+public class NewDynamicAdjustmentBatchDiaLogFormPlugin extends AbstractFormPlugin {
+
+    @Override
+    public void registerListener(EventObject e) {
+        super.registerListener(e);
+    }
+
+    @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);
+        }
+    }
+}

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

@@ -4,6 +4,8 @@ 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;
@@ -14,11 +16,13 @@ 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.Map;
 import java.util.Objects;
 import java.util.StringJoiner;
 
@@ -37,6 +41,7 @@ public class NewDynamicAdjustmentDiaLogFormPlugin extends AbstractFormPlugin {
 
     @Override
     public void afterCreateNewData(EventObject e) {
+        this.getView().setEnable(false, FormConstant.BTN_OK_OP);
         // 获取当前页面的FormShowParameter对象
         FormShowParameter formShowParameter = this.getView().getFormShowParameter();
         // 获取列表选择的人员
@@ -50,7 +55,6 @@ public class NewDynamicAdjustmentDiaLogFormPlugin extends AbstractFormPlugin {
 
     @Override
     public void propertyChanged(PropertyChangedArgs e) {
-        System.out.println();
         String fieldKey = e.getProperty().getName();
         ChangeData[] changeSet = e.getChangeSet();
 
@@ -68,10 +72,19 @@ public class NewDynamicAdjustmentDiaLogFormPlugin extends AbstractFormPlugin {
                         DynamicObject latsPersonPosFileByPerson = PositionStructureHelper.getLatsPersonPosFileByPerson(person.getLong(FormConstant.ID_KEY));
                         if(latsPersonPosFileByPerson == null){
                             this.getView().showErrorNotification(StrFormatter.format("当前无法为【{}】进行动态调整,因为他/她尚未建立职位档案。请前往“职位及积分初定” -> 进行初定!", person.getString(FormConstant.NAME_KEY)));
-                            this.getModel().setValue(PositionStructureConstant.PERSONPOSFILE_ENTITYID, null);
+                            clearFormInfo();
                         }else{
-                            DynamicObject jobLevel = getJobLevel(person, adjustDate);
-                            this.getModel().setValue(PositionStructureConstant.PERSONPOSFILE_ENTITYID, latsPersonPosFileByPerson);
+                            try{
+                                LocalDateTime endDay = DateUtil.endOfDay(DateUtil.toLocalDateTime(adjustDate));
+                                PositionAppointmentBO positionAppointment = PositionStructureHelper.positionAppointmentQuery(person.getLong(FormConstant.ID_KEY), DateUtil.toDate(endDay));
+                                JobLevelCalculatorService.JobLevelResult jobLevelResult = getJobLevel(person, adjustDate,positionAppointment);
+
+                                updateFormInfo(person, adjustDate, latsPersonPosFileByPerson,jobLevelResult,positionAppointment);
+                                this.getView().showSuccessNotification("信息加载完成");
+                            }catch (ValidationException ex){
+                               clearFormInfo();
+                               this.getView().showErrorNotification(ex.getMessage());
+                            }
                         }
                     }
                 }
@@ -80,7 +93,8 @@ public class NewDynamicAdjustmentDiaLogFormPlugin extends AbstractFormPlugin {
     }
 
 
-    public static DynamicObject getJobLevel(DynamicObject person,Date date){
+
+    public static JobLevelCalculatorService.JobLevelResult getJobLevel(DynamicObject person,Date date,PositionAppointmentBO positionAppointment){
         //移植代码:com.kingdee.shr.customer.web.handler.ContributeScore.PersonpositionfileFluctuationListHandler#selaboutJobGradeAction
         long personId = person.getLong(FormConstant.ID_KEY);
         String personName = person.getString(FormConstant.NAME_KEY);
@@ -92,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)));
         }
 
@@ -101,8 +115,8 @@ public class NewDynamicAdjustmentDiaLogFormPlugin extends AbstractFormPlugin {
         LocalDateTime endDay = DateUtil.endOfDay(DateUtil.toLocalDateTime(date));
         // 3、根据时间获取员工信息(任职经历、聘任、学历)
         //对应SHR:resultMap
-        DynamicObject positionAppointment = PositionStructureHelper.positionAppointmentQuery(personId, DateUtil.toDate(endDay));
-        if(positionAppointment == null || positionAppointment.getDataEntityType() == null){
+        // DynamicObject positionAppointment = PositionStructureHelper.positionAppointmentQuery(personId, DateUtil.toDate(endDay));
+        if(positionAppointment == null || positionAppointment.getEmpPosOrgRel() == null){
             throw new ValidationException(StrFormatter.format("当前无法为【{}】进行调整,因为根据时间【{}】未获取到人员任职和聘任信息!", personName,DateUtil.format(date,DateUtil.NORM_DATE_PATTERN)));
         }
 
@@ -154,6 +168,63 @@ public class NewDynamicAdjustmentDiaLogFormPlugin extends AbstractFormPlugin {
         // 10、处理R排名百分比
         // JobLevelCalculatorService.RankingResult rankingInfo = JobLevelCalculatorService.getRankingInfo(personId, personName, date);
 
-        return jobLevelResult.jobLevel;
+        return jobLevelResult;
+    }
+
+    @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);
+        }
+    }
+
+    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)));
+        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.getView().setEnable(true, FormConstant.BTN_OK_OP);
+
+    }
+
+    private void clearFormInfo() {
+        this.getModel().setValue(PositionStructureConstant.PERSONPOSFILE_ENTITYID, null);
+        this.getModel().setValue(PositionStructureConstant.NCKD_POSITIONHR, null);
+        this.getModel().setValue(PositionStructureConstant.NCKD_ALLOWANCERANKMARK, null);
+        this.getModel().setValue(PositionStructureConstant.NCKD_ALLOWANCERANKPERCENT, null);
+        this.getModel().setValue(PositionStructureConstant.NCKD_RANKNAME, null);
+        this.getModel().setValue(PositionStructureConstant.NCKD_PERPROTITLENAME, null);
+        this.getModel().setValue(PositionStructureConstant.NCKD_QUALEVELNAME, null);
+        this.getModel().setValue(PositionStructureConstant.NCKD_JOBSTATUSNAME, null);
+        this.getModel().setValue(PositionStructureConstant.NCKD_JOBLEVEL, null);
+        this.getModel().setValue(PositionStructureConstant.NCKD_DIPLOMA, null);
+        this.getView().setEnable(false, FormConstant.BTN_OK_OP);
     }
 }

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

@@ -0,0 +1,127 @@
+package nckd.jxccl.hr.psms.plugin.form.annualadjust;
+
+import kd.bos.common.enums.EnableEnum;
+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.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) {
+        QFilter qFilter = new QFilter(PositionStructureConstant.NCKD_DISABLE, QCP.equals, EnableEnum.NO.getCode())
+        //只查询年度调整的数据
+        .and(new QFilter(PositionStructureConstant.NCKD_TYPESTATE, QCP.equals, "3"));
+        e.addCustomQFilter(qFilter);
+    }
+
+    @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);
+            }
+        }
+    }
+}

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

@@ -0,0 +1,84 @@
+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 nckd.jxccl.hr.psms.common.PositionStructureConstant;
+
+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);
+    }
+}

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

@@ -0,0 +1,63 @@
+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 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);
+        }
+    }
+}

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

@@ -0,0 +1,131 @@
+package nckd.jxccl.hr.psms.plugin.form.annualadjust;
+
+import kd.bos.common.enums.EnableEnum;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.entity.datamodel.ListSelectedRow;
+import kd.bos.entity.datamodel.ListSelectedRowCollection;
+import kd.bos.entity.datamodel.events.PackageDataEvent;
+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.FilterContainerInitArgs;
+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.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.ConvertUtil;
+import nckd.jxccl.base.common.utils.DateUtil;
+import nckd.jxccl.base.common.utils.QueryFieldBuilder;
+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 filterContainerInit(FilterContainerInitArgs args) {
+        super.filterContainerInit(args);
+    }
+
+    @Override
+    public void setFilter(SetFilterEvent e) {
+        //TODO 【待修改】-职位体系-默认过滤掉副科级及以上
+        //TODO 【待修改】-职位体系-本年度7月前退休、辞职,则列表中删除该人员
+        //过滤掉未初定或初定为当前年或当前年之前或本年度已做过调整的员工
+        LocalDateTime localDateTime = DateUtil.beginOfYear(DateUtil.now());
+        //查询当年已执行年度调整的人员
+        QueryFieldBuilder queryFieldBuilder = QueryFieldBuilder.create().addIdNumberName(FormConstant.NCKD_PERSON);
+        QFilter yearFilter = new QFilter(PositionStructureConstant.NCKD_EXECUTEYEAR, QCP.equals, localDateTime.getYear())
+                .and(new QFilter(PositionStructureConstant.NCKD_DISABLE, QCP.equals, EnableEnum.NO.getCode()))
+                .and(new QFilter(PositionStructureConstant.NCKD_TYPESTATE, QCP.equals, TypeStateEnum.ANNUAL_ADJUSTMENT.getCode()));
+        DynamicObjectCollection query = QueryServiceHelper.query(PositionStructureConstant.PERSONPOSFILE_ENTITYID, queryFieldBuilder.buildSelect(), new QFilter[]{yearFilter});
+        List<Long> personIds = new ArrayList<>(query.size());
+        for (DynamicObject dynamicObject : query) {
+            long personId = dynamicObject.getLong(String.join(".", FormConstant.NCKD_PERSON, FormConstant.ID_KEY));
+            personIds.add(personId);
+        }
+
+        LocalDateTime lastYear = DateUtil.minusYears(localDateTime, 1);
+        QFilter qFilter = new QFilter(FormConstant.IS_PRIMARY, QCP.equals, EnableEnum.YES.getCode())
+                .and(new QFilter(String.join(".", FormConstant.ASSIGNMENT, FormConstant.IS_PRIMARY), QCP.equals, EnableEnum.YES.getCode()))
+                .and(new QFilter(String.join(".", FormConstant.ASSIGNMENT, FormConstant.IS_DELETED), QCP.equals, EnableEnum.NO.getCode()))
+                .and(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_ISCURRENTNEWEST), QCP.equals, EnableEnum.YES.getCode()))
+                //有初定的人员
+                .and(new QFilter(String.join(".", PositionStructureConstant.PERSONPOSFILE_ENTITYID+"init", PositionStructureConstant.NCKD_FIRSTRANK), QCP.equals, EnableEnum.YES.getCode()))
+                //初定时间不等于当年
+                .and(new QFilter(String.join(".", PositionStructureConstant.PERSONPOSFILE_ENTITYID+"init", PositionStructureConstant.NCKD_EXECUTEYEAR), QCP.not_equals2, localDateTime.getYear()))
+                //上年度有考核结果的人员
+                .and(new QFilter(String.join(".", PositionStructureConstant.PERFMANAGER_ENTITYID, PositionStructureConstant.PERFMANAGER_ENTRY_ENTITYID, PositionStructureConstant.APPRAISAL_YEAR_KEY), QCP.equals, DateUtil.toDate(lastYear)))
+                //当年还未年度调整的人员
+                .and(new QFilter(FormConstant.EMPLOYEE_KEY, QCP.not_in, personIds));
+        e.addCustomQFilter(qFilter);
+    }
+
+    @Override
+    public void packageData(PackageDataEvent e) {
+        super.packageData(e);
+        String colKey = e.getColKey();
+        int year = DateUtil.now().getYear();
+        if ("nckd_currentyear".equals(colKey)) {
+            e.setFormatValue(year);
+        }
+    }
+
+    @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 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/initial/GradedPersonQueryListPlugin.java

@@ -0,0 +1,26 @@
+package nckd.jxccl.hr.psms.plugin.form.initial;
+
+import kd.bos.common.enums.EnableEnum;
+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.sdk.plugin.Plugin;
+import nckd.jxccl.hr.psms.common.PositionStructureConstant;
+
+/**
+* 位体系-已定级人员列表
+* @author W.Y.C
+* @date 2025/10/5 23:34
+* @version 1.0
+*/
+public class GradedPersonQueryListPlugin extends AbstractListPlugin implements Plugin {
+
+    @Override
+    public void setFilter(SetFilterEvent setFilterEvent) {
+        //只查询初定的档案
+        QFilter filter = new QFilter(PositionStructureConstant.NCKD_DISABLE, QCP.equals,EnableEnum.NO.getCode())
+                .and(new QFilter(PositionStructureConstant.NCKD_FIRSTRANK, QCP.equals,EnableEnum.YES.getCode()));
+        setFilterEvent.addCustomQFilter(filter);
+    }
+}

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

@@ -0,0 +1,124 @@
+package nckd.jxccl.hr.psms.plugin.form.initial;
+
+import kd.bos.common.enums.EnableEnum;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+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 kd.bos.servicehelper.QueryServiceHelper;
+import kd.drp.pos.common.util.StringJoin;
+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.base.common.utils.QueryFieldBuilder;
+import nckd.jxccl.base.common.utils.StrFormatter;
+import nckd.jxccl.hr.psms.common.PositionStructureConstant;
+
+import java.time.LocalDateTime;
+import java.util.Date;
+import java.util.EventObject;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.StringJoiner;
+import java.util.stream.Collectors;
+
+/**
+* 新入职人员初定(弹窗)-弹窗页面插件
+* @author W.Y.C
+* @date 2025/9/15 14:17
+* @version 1.0
+*/
+public class NewHireInitialBatchFormPlugin extends AbstractFormPlugin implements Plugin {
+
+    private boolean affirm = false;
+
+    @Override
+    public void afterCreateNewData(EventObject e) {
+        FormShowParameter showParameter = this.getView().getFormShowParameter();
+        //获取列表选择的人员
+        Object personId = showParameter.getCustomParam("personId");
+        affirm = ConvertUtil.toBoolean(showParameter.getCustomParam("affirm"),false);
+        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.isEmpty()) {
+            this.getModel().batchCreateNewEntryRow(FormConstant.NCKD_ENTRYENTITY, personIds.size() - 1);
+        }
+        Map<Long, DynamicObject> personNextYearDateMap = new HashMap<>();
+        if(affirm && !personIds.isEmpty()){
+            //新入职确认操作,获取入职日期
+            QFilter filter = new QFilter(FormConstant.EMPLOYEE_KEY, QCP.in, personIds).and(new QFilter(FormConstant.ISCURRENTDATA, QCP.equals, EnableEnum.YES.getCode()));
+            QueryFieldBuilder queryFieldBuilder = QueryFieldBuilder.create()
+                    .add(FormConstant.ENTRYDATE)
+                    .addIdNumberName(FormConstant.EMPLOYEE_KEY);
+            DynamicObjectCollection query = QueryServiceHelper.query(FormConstant.HRPI_EMPENTREL, queryFieldBuilder.buildSelect(), new QFilter[]{filter});
+            personNextYearDateMap = query.stream()
+                    .collect(Collectors.toMap(
+                            obj -> obj.getLong(String.join(".", FormConstant.EMPLOYEE_KEY, FormConstant.ID_KEY)),
+                            obj -> obj
+                    ));
+
+        }
+        StringJoiner notification = new StringJoiner(StrFormatter.LINE_SEPARATOR);
+        Date current = new Date();
+        for (int i = 0; i < personIds.size(); i++) {
+            this.getModel().setValue(FormConstant.NCKD_PERSON,personIds.get(i), i);
+
+            if(personNextYearDateMap.get(personIds.get(i)) != null) {
+                DynamicObject dynamicObject = personNextYearDateMap.get(personIds.get(i));
+                Date entryDate = dynamicObject.getDate(FormConstant.ENTRYDATE);
+                //获取入职日期次年1月1号
+                LocalDateTime nextYearDate = DateUtil.addYears(DateUtil.toLocalDateTime(entryDate), 1);
+                LocalDateTime beginYear = DateUtil.beginOfYear(nextYearDate);
+
+                this.getModel().setValue(PositionStructureConstant.NCKD_BEGINDATE, beginYear,i);
+
+                //判断beginYear是否大于当前日期
+                if (DateUtil.toLocalDateTime(current).isBefore(beginYear)) {
+                    String personName = dynamicObject.getString(String.join(".", FormConstant.EMPLOYEE_KEY, FormConstant.NAME_KEY));
+                    notification.add(StrFormatter.format("人员【{}】的入职时间为【{}】初定日期为【{}】,初定日期不能大于当前日期;",personName,
+                            DateUtil.format(entryDate,DateUtil.NORM_DATE_PATTERN),
+                            DateUtil.format(beginYear,DateUtil.NORM_DATE_PATTERN)));
+
+                }
+            }
+
+
+        }
+        if(notification.length() > 0){
+            this.getView().showTipNotification(notification.toString());
+        }
+
+
+        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_CONFIRMINITIAL.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);
+        }
+    }
+}

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

@@ -1,12 +1,34 @@
 package nckd.jxccl.hr.psms.plugin.form.initial;
 
+import com.kingdee.cosmic.ctrl.kdf.formatter.StringFormatter;
+import kd.bos.common.enums.EnableEnum;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+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 kd.bos.servicehelper.QueryServiceHelper;
 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.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.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.Map;
+import java.util.Objects;
 
 /**
 * 新入职人员初定(弹窗)-弹窗页面插件
@@ -15,6 +37,120 @@ import java.util.Map;
 * @version 1.0
 */
 public class NewHireInitialFormPlugin extends AbstractFormPlugin implements Plugin {
+
+    private boolean affirm = false;
+    @Override
+    public void afterCreateNewData(EventObject e) {
+        FormShowParameter showParameter = this.getView().getFormShowParameter();
+        //获取列表选择的人员
+        Object personId = showParameter.getCustomParam("personId");
+        affirm = ConvertUtil.toBoolean(showParameter.getCustomParam("affirm"),false);
+        if(personId != null) {
+            this.getModel().setValue(FormConstant.NCKD_PERSON, personId);
+            this.getView().updateView(FormConstant.NCKD_PERSON);
+
+            initValue(ConvertUtil.toLong(personId),null);
+        }
+    }
+
+    @Override
+    public void propertyChanged(PropertyChangedArgs e) {
+        String fieldKey = e.getProperty().getName();
+        ChangeData[] changeSet = e.getChangeSet();
+
+        //【员工】或【调整日期】发生变更则重新获取员工任职信息
+        if (PositionStructureConstant.NCKD_BEGINDATE.equalsIgnoreCase(fieldKey) || FormConstant.NCKD_PERSON.equalsIgnoreCase(fieldKey)) {
+            if (changeSet != null && changeSet.length > 0) {
+                ChangeData changeData = changeSet[0];
+                Object oldValue = changeData.getOldValue();
+                Object newValue = changeData.getNewValue();
+                if (!Objects.equals(oldValue, newValue)) {
+                    Date beginDate = ConvertUtil.toDate(this.getModel().getValue(PositionStructureConstant.NCKD_BEGINDATE));
+                    DynamicObject person = ConvertUtil.toDynamicObject(this.getModel().getValue(PositionStructureConstant.NCKD_PERSON));
+                    if(person.getDataEntityType() != null) {
+                        initValue(person.getLong(FormConstant.ID_KEY), beginDate);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * 初始化表单信息
+     * @param personId 人员ID
+     * @param date 日期
+     * @return: void
+     * @author W.Y.C
+     * @date: 2025/09/25 21:40
+     */
+    private void initValue(Long personId, Date date){
+        if(date == null){
+            date = new Date();
+        }
+        PositionAppointmentBO positionAppointMen = PositionStructureHelper.positionAppointmentQuery(personId, date);
+        if(positionAppointMen != null) {
+            //学历
+            DynamicObject perEduExp = positionAppointMen.getPerEduExp();
+            if(perEduExp != null) {
+                long diplomaId = perEduExp.getLong(String.join(".", FormConstant.EDUCATION_KEY, FormConstant.ID_KEY));
+                this.getModel().setValue(PositionStructureConstant.NCKD_DIPLOMA, diplomaId == 0?null:diplomaId);
+            }
+
+            //岗位、职位序列、组织分配、任职信息、本次加入集团日期
+            DynamicObject empPosOrgRel = positionAppointMen.getEmpPosOrgRel();
+            if(empPosOrgRel != null) {
+                long positionId = empPosOrgRel.getLong(String.join(".", FormConstant.POSITION_KEY, FormConstant.ID_KEY));
+                this.getModel().setValue(PositionStructureConstant.NCKD_POSITIONHR, positionId == 0 ? null : positionId);
+
+                long jobSeqId = empPosOrgRel.getLong(String.join(".", FormConstant.HBPM_POSITIONHR, FormConstant.NCKD_JOBSEQ, FormConstant.ID_KEY));
+                this.getModel().setValue(PositionStructureConstant.NCKD_JOBSEQ, jobSeqId == 0 ? null : jobSeqId);
+            }
+
+            //职称名称和职称等级
+            DynamicObject perProTitle = positionAppointMen.getPerProTitle();
+            if(perProTitle != null) {
+                String rankName = perProTitle.getString(String.join(".", FormConstant.PROFESSIONAL_KEY, FormConstant.NAME_KEY));
+                this.getModel().setValue(PositionStructureConstant.NCKD_RANKNAME, rankName);
+
+                long proLevelId = perProTitle.getLong(String.join(".",  FormConstant.PROLEVEL_KEY, FormConstant.ID_KEY));
+                this.getModel().setValue(PositionStructureConstant.NCKD_PROTITLELEVEL, proLevelId == 0 ? null : proLevelId);
+            }
+
+            //技能名称和技能等级
+            DynamicObject perOcpQual = positionAppointMen.getPerOcpQual();
+            if(perOcpQual != null) {
+                String jobStatusName = perOcpQual.getString(String.join(".",  FormConstant.QUALIFICATION_KEY, FormConstant.NAME_KEY));
+                this.getModel().setValue(PositionStructureConstant.NCKD_JOBSTATUSNAME, jobStatusName);
+
+                long quaLevelId = perOcpQual.getLong(String.join(".", FormConstant.QUALEVEL_KEY, FormConstant.ID_KEY));
+                this.getModel().setValue(PositionStructureConstant.NCKD_OCPQUALLEVEL, quaLevelId == 0 ? null : quaLevelId);
+            }
+
+            if(affirm){
+                String personName = positionAppointMen.getEmpPosOrgRel().getString(String.join(".", FormConstant.EMPLOYEE_KEY, FormConstant.NAME_KEY));
+                //新入职确认操作,获取入职日期
+                QFilter filter = new QFilter(FormConstant.EMPLOYEE_KEY, QCP.equals, personId).and(new QFilter(FormConstant.ISCURRENTDATA, QCP.equals, EnableEnum.YES.getCode()));
+                DynamicObject query = QueryServiceHelper.queryOne(FormConstant.HRPI_EMPENTREL, FormConstant.ENTRYDATE, new QFilter[]{filter});
+                Date entryDate = query.getDate(FormConstant.ENTRYDATE);
+                //获取入职日期次年1月1号
+                LocalDateTime nextYearDate = DateUtil.addYears(DateUtil.toLocalDateTime(entryDate), 1);
+                LocalDateTime beginYear = DateUtil.beginOfYear(nextYearDate);
+                //判断beginYear是否大于当前日期
+                Date current = new Date();
+                if(DateUtil.toLocalDateTime(current).isBefore(beginYear)){
+                    this.getView().showTipNotification(StrFormatter.format("人员【{}】的入职时间为【{}】初定日期为【{}】,初定日期不能大于当前日期。",personName,
+                            DateUtil.format(entryDate,DateUtil.NORM_DATE_PATTERN),
+                            DateUtil.format(beginYear,DateUtil.NORM_DATE_PATTERN)));
+                }
+                this.getModel().setValue(PositionStructureConstant.NCKD_BEGINDATE, beginYear);
+            }
+        }
+        // TODO 【待修改】优秀生
+        // this.getModel().setValue(PositionStructureConstant.NCKD_EXCELLENT, );
+
+
+    }
+
     @Override
     public void afterDoOperation(AfterDoOperationEventArgs afterDoOperationEventArgs) {
         String operateKey = afterDoOperationEventArgs.getOperateKey();
@@ -22,6 +158,7 @@ public class NewHireInitialFormPlugin extends AbstractFormPlugin implements Plug
         if(success && PositionStructureConstant.OP_CONFIRMINITIAL.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);
         }
     }

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

@@ -0,0 +1,64 @@
+package nckd.jxccl.hr.psms.plugin.form.initial;
+
+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/9/15 14:47
+* @version 1.0
+*/
+public class ServingInitialBatchFormPlugin extends AbstractFormPlugin implements Plugin {
+
+    @Override
+    public void afterCreateNewData(EventObject e) {
+        FormShowParameter showParameter = this.getView().getFormShowParameter();
+        //获取列表选择的人员
+        Object personId = showParameter.getCustomParam("personId");
+        if(personId != null) {
+            initValue(ConvertUtil.toList(personId));
+        }
+    }
+
+    /**
+     * 初始化表单数据
+     * @param personIds 人员ID
+     * @return: void
+     * @author W.Y.C
+     * @date: 2025/09/25 21:39
+     */
+    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_CONFIRMINITIAL.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);
+
+        }
+    }
+}

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

@@ -1,12 +1,23 @@
 package nckd.jxccl.hr.psms.plugin.form.initial;
 
+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.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 nckd.jxccl.hr.psms.common.bo.PositionAppointmentBO;
+import nckd.jxccl.hr.psms.helper.PositionStructureHelper;
 
+import java.util.Date;
+import java.util.EventObject;
 import java.util.Map;
+import java.util.Objects;
 
 /**
 * 在职人员初定(弹窗)-弹窗页面插件
@@ -16,6 +27,107 @@ import java.util.Map;
 */
 public class ServingInitialFormPlugin extends AbstractFormPlugin implements Plugin {
 
+    @Override
+    public void afterCreateNewData(EventObject e) {
+        FormShowParameter showParameter = this.getView().getFormShowParameter();
+        //获取列表选择的人员
+        Object personId = showParameter.getCustomParam("personId");
+        if(personId != null) {
+            this.getModel().setValue(FormConstant.NCKD_PERSON, personId);
+            this.getView().updateView(FormConstant.NCKD_PERSON);
+
+            initValue(ConvertUtil.toLong(personId),null);
+        }
+    }
+
+    @Override
+    public void propertyChanged(PropertyChangedArgs e) {
+        String fieldKey = e.getProperty().getName();
+        ChangeData[] changeSet = e.getChangeSet();
+
+        //【员工】或【调整日期】发生变更则重新获取员工任职信息
+        if (PositionStructureConstant.NCKD_BEGINDATE.equalsIgnoreCase(fieldKey) || FormConstant.NCKD_PERSON.equalsIgnoreCase(fieldKey)) {
+            if (changeSet != null && changeSet.length > 0) {
+                ChangeData changeData = changeSet[0];
+                Object oldValue = changeData.getOldValue();
+                Object newValue = changeData.getNewValue();
+                if (!Objects.equals(oldValue, newValue)) {
+                    Date beginDate = ConvertUtil.toDate(this.getModel().getValue(PositionStructureConstant.NCKD_BEGINDATE));
+                    DynamicObject person = ConvertUtil.toDynamicObject(this.getModel().getValue(PositionStructureConstant.NCKD_PERSON));
+                    if(person.getDataEntityType() != null) {
+                        initValue(person.getLong(FormConstant.ID_KEY), beginDate);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * 初始化表单数据
+     * @param personId 人员ID
+     * @param date 日期
+     * @return: void
+     * @author W.Y.C
+     * @date: 2025/09/25 21:39
+     */
+    private void initValue(Long personId, Date date) {
+        if (date == null) {
+            date = new Date();
+        }
+        PositionAppointmentBO positionAppointMen = PositionStructureHelper.positionAppointmentQuery(personId, date);
+        if (positionAppointMen != null) {
+            // 学历
+            DynamicObject perEduExp = positionAppointMen.getPerEduExp();
+            if (perEduExp != null) {
+                long diplomaId = perEduExp.getLong(String.join(".", FormConstant.EDUCATION_KEY, FormConstant.ID_KEY));
+                this.getModel().setValue(PositionStructureConstant.NCKD_DIPLOMA, diplomaId == 0 ? null : diplomaId);
+            }
+
+            // 职称信息
+            DynamicObject perProTitle = positionAppointMen.getPerProTitle();
+            if (perProTitle != null) {
+                // 职称名称
+                this.getModel().setValue(PositionStructureConstant.NCKD_RANKNAME,
+                        perProTitle.getString(String.join(".", FormConstant.PROFESSIONAL_KEY, FormConstant.NAME_KEY)));
+
+                // 职称等级
+                long proLevelId = perProTitle.getLong(String.join(".", FormConstant.PROLEVEL_KEY, FormConstant.ID_KEY));
+                this.getModel().setValue(PositionStructureConstant.NCKD_PROTITLELEVEL, proLevelId == 0 ? null : proLevelId);
+            }
+
+            // 技能信息
+            DynamicObject perOcpQual = positionAppointMen.getPerOcpQual();
+            if (perOcpQual != null) {
+                // 技能名称
+                this.getModel().setValue(PositionStructureConstant.NCKD_JOBSTATUSNAME,
+                        perOcpQual.getString(String.join(".", FormConstant.QUALIFICATION_KEY, FormConstant.NAME_KEY)));
+
+                // 技能等级
+                long quaLevelId = perOcpQual.getLong(String.join(".", FormConstant.QUALEVEL_KEY, FormConstant.ID_KEY));
+                this.getModel().setValue(PositionStructureConstant.NCKD_OCPQUALLEVEL, quaLevelId == 0 ? null : quaLevelId);
+            }
+
+            // 任职信息
+            DynamicObject empPosOrgRel = positionAppointMen.getEmpPosOrgRel();
+            if (empPosOrgRel != null) {
+                // 岗位
+                long positionId = empPosOrgRel.getLong(String.join(".", FormConstant.POSITION_KEY, FormConstant.ID_KEY));
+                this.getModel().setValue(PositionStructureConstant.NCKD_POSITIONHR, positionId == 0 ? null : positionId);
+
+                // 职位序列
+                long jobSeqId = empPosOrgRel.getLong(String.join(".", FormConstant.HBPM_POSITIONHR, FormConstant.NCKD_JOBSEQ, FormConstant.ID_KEY));
+                this.getModel().setValue(PositionStructureConstant.NCKD_JOBSEQ, jobSeqId == 0 ? null : jobSeqId);
+
+                // 本次加入集团日期
+                Date joinComDate = empPosOrgRel.getDate(String.join(".", FormConstant.HRPI_PERSERLEN, FormConstant.JOINCOMDATE_KEY));
+                this.getModel().setValue(PositionStructureConstant.NCKD_JOINCOMDATE, joinComDate);
+
+            }
+            // TODO 【待修改】优秀生
+            // this.getModel().setValue(PositionStructureConstant.NCKD_EXCELLENT, );
+        }
+    }
+
     @Override
     public void afterDoOperation(AfterDoOperationEventArgs afterDoOperationEventArgs) {
         String operateKey = afterDoOperationEventArgs.getOperateKey();
@@ -23,7 +135,9 @@ public class ServingInitialFormPlugin extends AbstractFormPlugin implements Plug
         if(success && PositionStructureConstant.OP_CONFIRMINITIAL.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);
+
         }
     }
 }

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

@@ -1,20 +1,30 @@
 package nckd.jxccl.hr.psms.plugin.form.initial;
 
 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.events.BeforeDoOperationEventArgs;
+import kd.bos.form.control.events.ItemClickEvent;
+import kd.bos.form.control.events.ItemClickListener;
+import kd.bos.form.events.ClosedCallBackEvent;
 import kd.bos.form.events.SetFilterEvent;
-import kd.bos.form.operate.FormOperate;
 import kd.bos.list.plugin.AbstractListPlugin;
 import kd.bos.logging.Log;
 import kd.bos.logging.LogFactory;
 import kd.bos.orm.query.QCP;
 import kd.bos.orm.query.QFilter;
+import kd.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;
 
 /**
 * 职位体系-未定级人员列表
@@ -22,32 +32,117 @@ import nckd.jxccl.hr.psms.common.PositionStructureConstant;
 * @date 2025/9/6 14:05
 * @version 1.0
 */
-public class UngradedPersonQueryListPlugin extends AbstractListPlugin implements Plugin {
+public class UngradedPersonQueryListPlugin extends AbstractListPlugin implements Plugin, ItemClickListener {
 
     private final static Log logger = LogFactory.getLog(UngradedPersonQueryListPlugin.class);
 
     @Override
     public void setFilter(SetFilterEvent setFilterEvent) {
         //只查询没有初定的人员
-        setFilterEvent.addCustomQFilter(new QFilter(String.join(".",PositionStructureConstant.PERSONPOSFILE_ENTITYID, FormConstant.ID_KEY), QCP.is_null,null));
+        QFilter filter = new QFilter(String.join(".", PositionStructureConstant.PERSONPOSFILE_ENTITYID, FormConstant.ID_KEY), QCP.is_null, null)
+                //组织分配为主组织分配的人员
+                .and(new QFilter(String.join(".",FormConstant.ASSIGNMENT_ENTITYID, FormConstant.IS_PRIMARY), QCP.equals,EnableEnum.YES.getCode()))
+                .and(new QFilter(FormConstant.IS_SEQLATESTRECORD, QCP.equals,EnableEnum.YES.getCode()))
+                .and(new QFilter(FormConstant.IS_DELETED, QCP.equals,EnableEnum.NO.getCode()))
+                .and(new QFilter(FormConstant.IS_PRIMARY, QCP.equals,EnableEnum.YES.getCode()));
+        setFilterEvent.addCustomQFilter(filter);
+    }
 
-        //组织分配为主组织分配的人员
-        setFilterEvent.addCustomQFilter(new QFilter(String.join(".",FormConstant.ASSIGNMENT_ENTITYID, FormConstant.IS_PRIMARY), QCP.equals,EnableEnum.YES.getCode()));
+    @Override
+    public void itemClick(ItemClickEvent evt) {
+        String itemKey = evt.getItemKey();
 
-    }
+        if("nckd_settingjobgrade".equals(itemKey) || "nckd_settingjobgradenew".equals(itemKey) || "nckd_settingjobgradeb".equals(itemKey) || "nckd_settingjobgradenewb".equals(itemKey) || "nckd_affirm".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_settingjobgrade".equals(itemKey) || "nckd_settingjobgradeb".equals(itemKey)) {
+                if(selectedRows.size() > 1 || "nckd_settingjobgradeb".equals(itemKey)){
+                    //弹出【批量】在职人员初定窗口
+                    FormShowParameter showParameter = new FormShowParameter();
+                    showParameter.setFormId(PositionStructureConstant.SERVINGINITIALBATH_ENTITYID);
+                    showParameter.getOpenStyle().setShowType(ShowType.Modal);
+                    showParameter.setCaption("【批量】在职人员初定");
+                    showParameter.setSendToClient(true);
+                    showParameter.setCustomParam("personId", personIds);
+                    showParameter.setCloseCallBack(new CloseCallBack(this, PositionStructureConstant.SERVINGINITIALBATH_ENTITYID));
+                    this.getView().showForm(showParameter);
+                }else {
+                    //弹出在职人员初定窗口
+                    FormShowParameter showParameter = new FormShowParameter();
+                    showParameter.setFormId(PositionStructureConstant.SERVINGINITIAL_ENTITYID);
+                    showParameter.getOpenStyle().setShowType(ShowType.Modal);
+                    showParameter.setCaption("在职人员初定-定级信息确认");
+                    showParameter.setSendToClient(true);
+                    showParameter.setCustomParam("personId", personIds.get(0));
+                    showParameter.setCloseCallBack(new CloseCallBack(this, PositionStructureConstant.SERVINGINITIAL_ENTITYID));
+                    this.getView().showForm(showParameter);
+                }
+            } else if ("nckd_settingjobgradenew".equals(itemKey) || "nckd_settingjobgradenewb".equals(itemKey) || "nckd_affirm".equals(itemKey)) {
+                boolean affirm = "nckd_affirm".equals(itemKey);
+                if(selectedRows.size() > 1 || "nckd_settingjobgradenewb".equals(itemKey)){
+                    //弹出【批量】新入职人员初定窗口
+                    FormShowParameter showParameter = new FormShowParameter();
+                    showParameter.setFormId(PositionStructureConstant.NEWHIREINITIALBATCH_ENTITYID);
+                    showParameter.getOpenStyle().setShowType(ShowType.Modal);
+                    showParameter.setCaption("【批量】新入职人员初定");
+                    showParameter.setSendToClient(true);
+                    showParameter.setCustomParam("personId", personIds);
+                    showParameter.setCustomParam("affirm", affirm);
+                    showParameter.setCloseCallBack(new CloseCallBack(this, PositionStructureConstant.NEWHIREINITIALBATCH_ENTITYID));
+                    this.getView().showForm(showParameter);
+                }else {
+                    //弹出【单人】新入职人员初定窗口
+                    FormShowParameter showParameter = new FormShowParameter();
+                    showParameter.setFormId(PositionStructureConstant.NEWHIREINITIAL_ENTITYID);
+                    showParameter.getOpenStyle().setShowType(ShowType.Modal);
+                    showParameter.setCaption("新入职人员初定-定级信息确认");
+                    showParameter.setCustomParam("personId", personIds.get(0));
+                    showParameter.setCustomParam("affirm", affirm);
+                    showParameter.setCloseCallBack(new CloseCallBack(this, PositionStructureConstant.NEWHIREINITIAL_ENTITYID));
+                    this.getView().showForm(showParameter);
+                }
+            }
+        }
+    }
 
-    @Override
+    /*@Override
     public void beforeDoOperation(BeforeDoOperationEventArgs args) {
         FormOperate formOperate = (FormOperate) args.getSource();
         String operateKey = formOperate.getOperateKey();
         ListSelectedRowCollection selectedRows = this.getSelectedRows();
+        if(selectedRows.size() > 1){
+            this.getView().showTipNotification("请选择一条数据,不支持多选!");
+            return;
+        }
         if(PositionStructureConstant.OP_SETTINGJOBGRADE.equals(operateKey)){
             //弹出在职人员初定窗口
             FormShowParameter showParameter = new FormShowParameter();
             showParameter.setFormId(PositionStructureConstant.SERVINGINITIAL_ENTITYID);
             showParameter.getOpenStyle().setShowType(ShowType.Modal);
             showParameter.setCaption("在职人员初定-定级信息确认");
+            showParameter.setSendToClient(true);
+            showParameter.setCustomParam("personId",selectedRows.get(0).getPrimaryKeyValue());
+            showParameter.setCloseCallBack(new CloseCallBack(this, PositionStructureConstant.SERVINGINITIAL_ENTITYID));
             this.getView().showForm(showParameter);
         }else if(PositionStructureConstant.OP_SETTINGJOBGRADENEW.equals(operateKey)){
             //弹出新入职人员初定窗口
@@ -55,7 +150,20 @@ public class UngradedPersonQueryListPlugin extends AbstractListPlugin implements
             showParameter.setFormId(PositionStructureConstant.NEWHIREINITIAL_ENTITYID);
             showParameter.getOpenStyle().setShowType(ShowType.Modal);
             showParameter.setCaption("新入职人员初定-定级信息确认");
+            showParameter.setCustomParam("personId",selectedRows.get(0).getPrimaryKeyValue());
+            showParameter.setCloseCallBack(new CloseCallBack(this, PositionStructureConstant.NEWHIREINITIAL_ENTITYID));
             this.getView().showForm(showParameter);
         }
+    }*/
+    @Override
+    public void closedCallBack(ClosedCallBackEvent closedCallBackEvent) {
+        String actionId = closedCallBackEvent.getActionId();
+        if(StringUtils.equalsAny(actionId,PositionStructureConstant.SERVINGINITIAL_ENTITYID,PositionStructureConstant.NEWHIREINITIAL_ENTITYID)){
+            Object returnData = closedCallBackEvent.getReturnData();
+            if(returnData != null) {
+                //刷新列表
+                this.getView().invokeOperation(FormConstant.REFRESH_OP);
+            }
+        }
     }
 }

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

@@ -1,27 +1,43 @@
 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;
 import kd.bos.entity.ExtendedDataEntity;
+import kd.bos.entity.constant.StatusEnum;
 import kd.bos.entity.plugin.AbstractOperationServicePlugIn;
 import kd.bos.entity.plugin.AddValidatorsEventArgs;
 import kd.bos.entity.plugin.args.BeginOperationTransactionArgs;
 import kd.bos.entity.validate.AbstractValidator;
 import kd.bos.logging.Log;
 import kd.bos.logging.LogFactory;
-import kd.sdk.plugin.Plugin;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.servicehelper.operation.SaveServiceHelper;
 import nckd.jxccl.base.common.constant.FormConstant;
+import nckd.jxccl.base.common.exception.ValidationException;
 import nckd.jxccl.base.common.utils.DateUtil;
 import nckd.jxccl.base.common.utils.StrFormatter;
 import nckd.jxccl.base.pm.helper.PerformanceManagerHelper;
+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 nckd.jxccl.hr.psms.plugin.operate.initial.BaseInitialOperationPlugIn;
 
 import java.math.BigDecimal;
 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.Objects;
+import java.util.Set;
+import java.util.StringJoiner;
 
 /**
 * 新建动态调整OP
@@ -31,12 +47,12 @@ import java.util.Map;
 */
 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;
 
-    /** 记录上年度考核结果*/
+    /** 记录上年度考核结果(key:人员ID)*/
     Map<Long,DynamicObject> lastYearPerformanceResultMap;
 
     @Override
@@ -50,78 +66,337 @@ public class NewDynamicAdjustmentOperationPlugIn extends AbstractOperationServic
                     //前面的校验器失败跳过本校验器
                     return;
                 }
-                Date currentDate = new Date();
                 for (ExtendedDataEntity rowDataEntity : getDataEntities()) {
                     DynamicObject data = rowDataEntity.getDataEntity();
-                    Date adjustDate = data.getDate(PositionStructureConstant.NCKD_ADJUSTDATE);
-                    if (adjustDate == null) {
-                        addFatalErrorMessage(rowDataEntity,"调整时间不能为空");
-                        return;
-                    }
-                    if (adjustDate.after(currentDate)) {
-                        addFatalErrorMessage(rowDataEntity,"初定时间不能晚于当前日期,请检查输入的日期是否正确");
-                        return;
-                    }
-                    DynamicObject person = data.getDynamicObject(FormConstant.NCKD_PERSON);
-                    if(person == null){
-                        addFatalErrorMessage(rowDataEntity,"请选择要调整的员工");
-                        return;
+                    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);
                     }
-                    DynamicObject latsPersonPosFileByPerson = PositionStructureHelper.getLatsPersonPosFileByPerson(person.getLong(FormConstant.ID_KEY));
-                    if(latsPersonPosFileByPerson == null){
-                        addFatalErrorMessage(rowDataEntity, StrFormatter.format("当前无法为【{}】进行动态调整,因为他/她尚未建立职位档案。请前往“职位及积分初定” -> 进行初定!", person.getString(FormConstant.NAME_KEY)));
-                        return;
+                }
+            }
+
+            private void validator(ExtendedDataEntity rowDataEntity, DynamicObject data) {
+                DynamicObject person = data.getDynamicObject(FormConstant.NCKD_PERSON);
+                if(person == null){
+                    addFatalErrorMessage(rowDataEntity,"请选择要调整的员工");
+                    return;
+                }
+                Date adjustDate = data.getDate(PositionStructureConstant.NCKD_ADJUSTDATE);
+                if (adjustDate == null) {
+                    addFatalErrorMessage(rowDataEntity,StrFormatter.format("人员【{}】调整时间不能为空",person.getString(FormConstant.NAME_KEY)));
+                }
+                DynamicObject latsPersonPosFileByPerson = PositionStructureHelper.getLatsPersonPosFileByPerson(person.getLong(FormConstant.ID_KEY));
+                if(latsPersonPosFileByPerson == null){
+                    addFatalErrorMessage(rowDataEntity, StrFormatter.format("当前无法为【{}】进行动态调整,因为他/她尚未建立职位档案。请前往“职位及积分初定” -> 进行初定!", person.getString(FormConstant.NAME_KEY)));
+                }
+                //判断当前年是否已执行过年度调整
+                DynamicObject[] personPosFileByAdjust = PositionStructureHelper.getPersonPosFileByPersonAndState(person.getLong(FormConstant.ID_KEY), new String[]{"4"},
+                        null,
+                        new QFilter(PositionStructureConstant.NCKD_ADJUSSTATUS, QCP.equals, EnableEnum.NO.getCode()));
+                if(personPosFileByAdjust != null && personPosFileByAdjust.length > 0){
+                    StringJoiner executeYear = new StringJoiner(",");
+                    for (DynamicObject dynamicObject : personPosFileByAdjust) {
+                        executeYear.add(dynamicObject.getInt(PositionStructureConstant.NCKD_EXECUTEYEAR)+"");
                     }
-                    //最近一次职位档案开始时间
+                    addFatalErrorMessage(rowDataEntity, StrFormatter.format("当前无法为【{}】进行动态调整,因为存在【{}】年度未生效的动态调整!", person.getString(FormConstant.NAME_KEY), executeYear.toString()));
+                }
+                //最近一次职位档案开始时间
+                if(latsPersonPosFileByPerson != null) {
                     Date date = latsPersonPosFileByPerson.getDate(PositionStructureConstant.NCKD_BEGINDATE);
                     //校验【本次调整时间】不得早于上一笔时间
-                    if (adjustDate.before(date)) {
-                        addFatalErrorMessage(rowDataEntity, StrFormatter.format("【{}】最近一次的职位档案变动时间是【{}】,请勿选择早于该时间段的调整时间!", person.getString(FormConstant.NAME_KEY), DateUtil.format(date,DateUtil.NORM_DATE_PATTERN)));
+                    if (adjustDate != null && adjustDate.compareTo(date) <= 0) {
+                        addFatalErrorMessage(rowDataEntity, StrFormatter.format("人员【{}】最近一次的职位档案变动时间是【{}】,请勿选择早于该时间段的调整时间!", person.getString(FormConstant.NAME_KEY), DateUtil.format(date, DateUtil.NORM_DATE_PATTERN)));
                     }
-                    //获取上年度绩效结果
+                }
+                //获取上年度绩效结果
+                if(adjustDate != null) {
                     LocalDateTime lastYearDateTime = DateUtil.minusYears(DateUtil.toLocalDateTime(adjustDate), 1);
                     DynamicObject lastYearPerformanceResult = PerformanceManagerHelper.getPerformanceResult(person.getLong(FormConstant.ID_KEY), lastYearDateTime);
-                    if(lastYearPerformanceResult == null){
-                        addFatalErrorMessage(rowDataEntity,StrFormatter.format("员工【{}】没有【{}】年度考核结果,无法新建调整",DateUtil.getYear(lastYearDateTime),person.getString(FormConstant.NAME_KEY)));
-                        return;
+                    if (lastYearPerformanceResult == null) {
+                        addFatalErrorMessage(rowDataEntity, StrFormatter.format("人员【{}】没有【{}】年度考核结果,无法新建调整", person.getString(FormConstant.NAME_KEY),DateUtil.getYear(lastYearDateTime)));
                     }
-
-                    latsPersonPosFileMap.put(person.getLong(FormConstant.ID_KEY),latsPersonPosFileByPerson);
-                    lastYearPerformanceResultMap.put(person.getLong(FormConstant.ID_KEY),latsPersonPosFileByPerson);
-
+                    lastYearPerformanceResultMap.put(person.getLong(FormConstant.ID_KEY),lastYearPerformanceResult);
                 }
+                latsPersonPosFileMap.put(person.getLong(FormConstant.ID_KEY),latsPersonPosFileByPerson);
+
             }
         });
     }
 
     @Override
     public void beginOperationTransaction(BeginOperationTransactionArgs e) {
+        if(!this.getOperationResult().isSuccess()){
+            return;
+        }
         logger.info("【职位体系】-动态调整-开始");
+        List<Long> personIds = new ArrayList<>(e.getDataEntities().length);
+        List<DynamicObject> newPersonPosFiles = new ArrayList<>(e.getDataEntities().length);
         for (DynamicObject data : e.getDataEntities()) {
-            DynamicObject person = data.getDynamicObject(FormConstant.NCKD_PERSON);
-            long personId = person.getLong(FormConstant.ID_KEY);
-            //最近一次档案
-            DynamicObject latsPersonPosFile = latsPersonPosFileMap.get(personId);
-            //上一次职级
-            DynamicObject jobLevelHr = latsPersonPosFile.getDynamicObject(PositionStructureConstant.NCKD_JOBLEVELHR);
-            //选择的调整时间
-            Date adjustDate = data.getDate(PositionStructureConstant.NCKD_ADJUSTDATE);
-            LocalDateTime adjustDateEnd = DateUtil.endOfDay(DateUtil.toLocalDateTime(adjustDate));
-            //查询员工在调整日期内的最新信息(职位、部门、序列、学历、职称、技能)
-            DynamicObject positionAppointment = PositionStructureHelper.positionAppointmentQuery(personId, DateUtil.toDate(adjustDateEnd));
-            //最新档案总积分
-            BigDecimal allSumScore = latsPersonPosFile.getBigDecimal(PositionStructureConstant.NCKD_ALLSUMSCORE);
+            if(data.containsProperty(FormConstant.NCKD_ENTRYENTITY)){
+                //批量调整
+                DynamicObjectCollection dynamicObjectCollection = data.getDynamicObjectCollection(FormConstant.NCKD_ENTRYENTITY);
+                for (DynamicObject dynamicObject : dynamicObjectCollection) {
+                    execute(dynamicObject, personIds, newPersonPosFiles);
+                }
+            }else{
+                //单次调整
+                execute(data, personIds, newPersonPosFiles);
+            }
 
+        }
 
+        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) {
+        DynamicObject person = data.getDynamicObject(FormConstant.NCKD_PERSON);
+        long personId = person.getLong(FormConstant.ID_KEY);
+        String personNumber = person.getString(FormConstant.NUMBER_KEY);
+        String personName = person.getString(FormConstant.NAME_KEY);
+        String logPrefix = StrFormatter.format("【职位体系】-动态调整-人员【{}({})】",
+                personName,
+                personNumber);
+        //最近一次档案
+        DynamicObject latsPersonPosFile = latsPersonPosFileMap.get(personId);
+        //上一次职级
+        DynamicObject jobLevelHr = latsPersonPosFile.getDynamicObject(PositionStructureConstant.NCKD_JOBLEVELHR);
+        //选择的调整时间
+        Date adjustDate = data.getDate(PositionStructureConstant.NCKD_ADJUSTDATE);
+        LocalDateTime adjustDateEnd = DateUtil.endOfDay(DateUtil.toLocalDateTime(adjustDate));
+        //查询员工在调整日期内的最新信息(职位、部门、序列、学历、职称、技能)
+        PositionAppointmentBO positionAppointment = PositionStructureHelper.positionAppointmentQuery(personId, DateUtil.toDate(adjustDateEnd));
+        if(positionAppointment.getEmpPosOrgRel() == null){
+            throw new ValidationException(StrFormatter.format("当前无法为【{}】进行调整,因为根据时间【{}】未获取到人员任职和聘任信息!", personName,DateUtil.format(adjustDateEnd,DateUtil.NORM_DATE_PATTERN)));
+        }
+        //最新档案总积分
+        BigDecimal allSumScore = latsPersonPosFile.getBigDecimal(PositionStructureConstant.NCKD_ALLSUMSCORE);
+        //计算本次职级
+        JobLevelCalculatorService.JobLevelResult jobLevelResult = JobLevelCalculatorService.calculateJobLevel(person, adjustDate, positionAppointment);
+        long jobLevelId = jobLevelResult.jobLevel.getLong(FormConstant.ID_KEY);
+
+        Long frontendJobLevelId = null;
+        if(data.containsProperty(PositionStructureConstant.NCKD_JOBLEVEL)){
+            //前端传入的职级
+            DynamicObject frontendJobLevel = data.getDynamicObject(PositionStructureConstant.NCKD_JOBLEVEL);
+            if(frontendJobLevel != null){
+                frontendJobLevelId = frontendJobLevel.getLong(FormConstant.ID_KEY);
+            }
+        }
+        String remark = null;
+        if(data.containsProperty(PositionStructureConstant.NCKD_REMARK)){
+            //前端传入的职级
+            remark = data.getString(PositionStructureConstant.NCKD_REMARK);
+        }
 
-            //上年度考核结果
-            DynamicObject lastYearPerformanceResult = lastYearPerformanceResultMap.get(personId);
+        createPersonPosFile(person, positionAppointment, adjustDate, jobLevelResult, latsPersonPosFile, allSumScore, personId, frontendJobLevelId, jobLevelId, remark, personIds, newPersonPosFiles,lastYearPerformanceResultMap);
+        setJobLevelResult(person,jobLevelHr);
+    }
+
+    private static void createPersonPosFile(DynamicObject person, PositionAppointmentBO positionAppointment, Date adjustDate,
+                                            JobLevelCalculatorService.JobLevelResult jobLevelResult, DynamicObject latsPersonPosFile,
+                                            BigDecimal allSumScore, long personId, Long frontendJobLevelId,
+                                            long jobLevelId, String remark, List<Long> personIds,
+                                            List<DynamicObject> newPersonPosFiles,Map<Long,DynamicObject> lastYearPerformanceResultMap) {
+
+        DynamicObject currentPersonPosFile = BusinessDataServiceHelper.loadSingle(latsPersonPosFile.getLong(FormConstant.ID_KEY), PositionStructureConstant.PERSONPOSFILE_ENTITYID);
+        //创建新对象:克隆整个数据包
+        DynamicObject newPersonPosFile = (DynamicObject) new CloneUtils(false, true).clone(currentPersonPosFile);
+        // 构建职位档案
+        /*DynamicObject newPersonPosFile = BusinessDataServiceHelper.newDynamicObject(
+                PositionStructureConstant.PERSONPOSFILE_ENTITYID);*/
 
+        DynamicObject empPosOrgRel = positionAppointment.getEmpPosOrgRel();
+        newPersonPosFile.set(PositionStructureConstant.NCKD_PERSON, person);
+        DynamicObject company = BusinessDataServiceHelper.newDynamicObject(FormConstant.ADMINORGHR_ENTITYID);
+        company.set(FormConstant.ID_KEY, empPosOrgRel.getLong(String.join(".",FormConstant.COMPANY_KEY,FormConstant.ID_KEY)));
+        newPersonPosFile.set(PositionStructureConstant.USEORG_KEY, company);
+        DynamicObject dep = BusinessDataServiceHelper.newDynamicObject(FormConstant.ADMINORGHR_ENTITYID);
+        dep.set(FormConstant.ID_KEY, empPosOrgRel.getLong(String.join(".",FormConstant.ADMINORG,FormConstant.ID_KEY)));
+        newPersonPosFile.set(PositionStructureConstant.ORG_KEY, dep);
+        newPersonPosFile.set(PositionStructureConstant.CREATEORG_KEY, dep);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_TYPESTATE, "4");
+        newPersonPosFile.set(PositionStructureConstant.NCKD_EXECUTEYEAR, DateUtil.getYear(adjustDate));
+        Long positionId = empPosOrgRel.getLong(String.join(".",FormConstant.POSITION_KEY,FormConstant.ID_KEY));
+        DynamicObject position = BusinessDataServiceHelper.newDynamicObject(FormConstant.HBPM_POSITIONHR);
+        position.set(FormConstant.ID_KEY, positionId);
+        Long jobSeqId = empPosOrgRel.getLong(String.join(".", FormConstant.HBPM_POSITIONHR, FormConstant.NCKD_JOBSEQ, FormConstant.ID_KEY));
+        DynamicObject jobSeq = BusinessDataServiceHelper.newDynamicObject(FormConstant.HBJM_JOBSEQHR);
+        jobSeq.set(FormConstant.ID_KEY, jobSeqId);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_JOBSEQHR, jobSeq);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_POSITIONHR, position);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_RANKNAME, jobLevelResult.jobScoreInfo.rankName);
+        DynamicObject proTitleLevel = BusinessDataServiceHelper.newDynamicObject(PositionStructureConstant.HBSS_PROTITLELEVEL);
+        proTitleLevel.set(FormConstant.ID_KEY, jobLevelResult.jobScoreInfo.perProTitleId);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_PROTITLELEVEL, proTitleLevel);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_JOBSTATUSNAME, jobLevelResult.jobScoreInfo.jobStatusName);
+        DynamicObject ocpQualLevel = BusinessDataServiceHelper.newDynamicObject(PositionStructureConstant.HBSS_OCPQUALLEVEL);
+        ocpQualLevel.set(FormConstant.ID_KEY, jobLevelResult.jobScoreInfo.quaLevelId);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_OCPQUALLEVEL, ocpQualLevel);
+        DynamicObject diploma = BusinessDataServiceHelper.newDynamicObject(PositionStructureConstant.HBSS_DIPLOMA);
+        diploma.set(FormConstant.ID_KEY, jobLevelResult.jobScoreInfo.diplomaId);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_DIPLOMA, diploma);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_JOBLEVELHR, jobLevelResult.jobLevel);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_LASTPERSONPOSFILE, latsPersonPosFile);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_DIPLOMASCORE, jobLevelResult.jobScoreInfo.diplomaScore);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_WHYDIPLOMASCORE, jobLevelResult.adjustMsg);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_RANKSCORE, jobLevelResult.jobScoreInfo.perProTitleScore);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_JOBSTATUSSCORE, jobLevelResult.jobScoreInfo.quaLevelScore);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_ALLSUMSCORE, jobLevelResult.jobScoreInfo.allSumScore);
+        //积分池(生涯积分)使用最后一次职位档案中的,因为动态调整不涉及生涯积分变动
+        BigDecimal sumScore = latsPersonPosFile.containsProperty(PositionStructureConstant.NCKD_SUMSCORE) ? latsPersonPosFile.getBigDecimal(PositionStructureConstant.NCKD_SUMSCORE) : null;
+        newPersonPosFile.set(PositionStructureConstant.NCKD_SUMSCORE,sumScore);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_BEGINDATE, adjustDate);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_ADJUSTINT, null);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_FIRSTRANK, null);
 
-            // 初定当年可以做动态调整,但不能做年度调整
 
+        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){
+            newPersonPosFile.set(PositionStructureConstant.NCKD_APPRAISALRESULT, performanceResult);
+            newPersonPosFile.set(PositionStructureConstant.NCKD_RESULTSCORE, performanceResult.getBigDecimal(FormConstant.NCKD_SCORE));
+        }
+        if(frontendJobLevelId != null && Objects.equals(frontendJobLevelId, jobLevelId)){
+            newPersonPosFile.set(PositionStructureConstant.NCKD_ADJUSTTYPE, jobLevelResult.adjustType);
+        }else{
+            //判断【职称等级】和【技能等级】都为空时设为"7",表示“无聘任”。
+            if((jobLevelResult.jobScoreInfo.perProTitleId == null || jobLevelResult.jobScoreInfo.perProTitleId == 0)
+                    && (jobLevelResult.jobScoreInfo.quaLevelId == null || jobLevelResult.jobScoreInfo.quaLevelId == 0)){
+                newPersonPosFile.set(PositionStructureConstant.NCKD_ADJUSTTYPE, "7");
+            }else{
+                newPersonPosFile.set(PositionStructureConstant.NCKD_ADJUSTTYPE, jobLevelResult.adjustType);
+            }
+        }
+
+        newPersonPosFile.set(PositionStructureConstant.NCKD_DISABLE, EnableEnum.NO.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(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);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_EMPLOYMENTSTATUS, employmentStatus);
+        // 连续聘任年限
+        int employmentYears = latsPersonPosFile.getInt(PositionStructureConstant.NCKD_EMPLOYMENTYEARS);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_EMPLOYMENTYEARS, employmentYears);
+        // 优秀生分
+        BigDecimal orginsScore = latsPersonPosFile.getBigDecimal(PositionStructureConstant.NCKD_ORGINSSCORE);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_ORGINSSCORE, orginsScore);
+        // 上年度贡献综合评价分
+        BigDecimal lyrContribScore = latsPersonPosFile.getBigDecimal(PositionStructureConstant.NCKD_LYRCONTRIBSCORE);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_LYRCONTRIBSCORE, lyrContribScore);
+        // 任命结束日期
+        Date endDate = latsPersonPosFile.getDate(PositionStructureConstant.NCKD_ENDDATE);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_ENDDATE, endDate);
+        // 任命状态
+        String appointStatus = latsPersonPosFile.getString(PositionStructureConstant.NCKD_APPOINTSTATUS);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_APPOINTSTATUS, appointStatus);
+        // 年度调整状态
+        String adjusStatus = latsPersonPosFile.getString(PositionStructureConstant.NCKD_ADJUSSTATUS);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_ADJUSSTATUS, adjusStatus);
+        // 年度科研与创新分
+        BigDecimal yearScoreSumA = latsPersonPosFile.getBigDecimal(PositionStructureConstant.NCKD_YEARSCORESUMA);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_YEARSCORESUMA, yearScoreSumA);
+        // 年度专利申报分
+        BigDecimal yearScoreSumB = latsPersonPosFile.getBigDecimal(PositionStructureConstant.NCKD_YEARSCORESUMB);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_YEARSCORESUMB, yearScoreSumB);
+        // 年度论文发表分
+        BigDecimal yearScoreSumC = latsPersonPosFile.getBigDecimal(PositionStructureConstant.NCKD_YEARSCORESUMC);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_YEARSCORESUMC, yearScoreSumC);
+        // 年度技能竞赛分
+        BigDecimal yearScoreSumD = latsPersonPosFile.getBigDecimal(PositionStructureConstant.NCKD_YEARSCORESUMD);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_YEARSCORESUMD, yearScoreSumD);
+        // 年度培训教材分
+        BigDecimal yearScoreSumE = latsPersonPosFile.getBigDecimal(PositionStructureConstant.NCKD_YEARSCORESUME);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_YEARSCORESUME, yearScoreSumE);
+        // 年度技术标准分
+        BigDecimal yearScoreSumF = latsPersonPosFile.getBigDecimal(PositionStructureConstant.NCKD_YEARSCORESUMF);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_YEARSCORESUMF, yearScoreSumF);
+        // 年度管理规范分
+        BigDecimal yearScoreSumG = latsPersonPosFile.getBigDecimal(PositionStructureConstant.NCKD_YEARSCORESUMG);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_YEARSCORESUMG, yearScoreSumG);
+        // 年度师带徒分
+        BigDecimal yearScoreSumH = latsPersonPosFile.getBigDecimal(PositionStructureConstant.NCKD_YEARSCORESUMH);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_YEARSCORESUMH, yearScoreSumH);
+        // 年度培训授课分
+        BigDecimal yearScoreSumI = latsPersonPosFile.getBigDecimal(PositionStructureConstant.NCKD_YEARSCORESUMI);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_YEARSCORESUMI, yearScoreSumI);
+        // 贡献单据分数之和
+        BigDecimal allYearScoreSum = latsPersonPosFile.getBigDecimal(PositionStructureConstant.NCKD_ALLYEARSCORESUM);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_ALLYEARSCORESUM, allYearScoreSum);
+        // 年度新增的贡献积分
+        BigDecimal addYContribScore = latsPersonPosFile.getBigDecimal(PositionStructureConstant.NCKD_ADDYCONTRIBSCORE);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_ADDYCONTRIBSCORE, addYContribScore);*/
+
+
+        personIds.add(personId);
+        newPersonPosFiles.add(newPersonPosFile);
+    }
+
+    /**
+     * 设置操作结果中的职级信息
+     */
+    protected void setJobLevelResult(DynamicObject person, DynamicObject jobLeve) {
+        String jobLeveStr = StrFormatter.format("{}:{}({}级)",
+                person.getString(FormConstant.NAME_KEY),
+                jobLeve.getString(FormConstant.NAME_KEY),
+                jobLeve.getString(FormConstant.JOBLEVELSEQ));
+
+        Map<String, String> customData = this.getOperationResult().getCustomData();
+        if(customData == null) {
+            this.getOperationResult().setCustomData(new HashMap<>());
+            customData = this.getOperationResult().getCustomData();
+        }
 
+        String oldJobLeveStr = customData.get(PositionStructureConstant.NCKD_JOBLEVELHR);
+        if (oldJobLeveStr == null || oldJobLeveStr.isEmpty()) {
+            customData.put(PositionStructureConstant.NCKD_JOBLEVELHR, jobLeveStr);
+        } else {
+            customData.put(PositionStructureConstant.NCKD_JOBLEVELHR, oldJobLeveStr + StrFormatter.LINE_SEPARATOR + jobLeveStr);
         }
     }
 }

+ 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);
+    }
+
+}

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

@@ -0,0 +1,151 @@
+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.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 java.util.ArrayList;
+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);
+    }
+}

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

@@ -0,0 +1,135 @@
+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 org.apache.commons.lang3.StringUtils;
+
+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 if(StringUtils.equalsAny(typeState, TypeStateEnum.NEW_ENTRY.getCode(), TypeStateEnum.IN_SERVICE_LEVEL.getCode())){
+                        idMaps.put(id,rowDataEntity.getDataEntityIndex());
+                    }else if (TypeStateEnum.POSITION_TRANSFER.getCode().equalsIgnoreCase(typeState)) {
+                        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)
+                            .addGroup(new String[]{PositionStructureConstant.NCKD_LASTPERSONPOSFILE}, FormConstant.ID_KEY,PositionStructureConstant.NCKD_TYPESTATE)
+                            .add(PositionStructureConstant.NCKD_EXECUTEYEAR)
+                            .add(PositionStructureConstant.NCKD_TYPESTATE)
+                            .orderDesc(PositionStructureConstant.NCKD_EXECUTEYEAR);
+                    DynamicObjectCollection query = QueryServiceHelper.query(PositionStructureConstant.PERSONPOSFILE_ENTITYID, queryFieldBuilder.buildSelect(), new QFilter[]{qFilter},queryFieldBuilder.buildOrder());
+                    if(!query.isEmpty()){
+                        for (DynamicObject dynamicObject : query) {
+                            long id = dynamicObject.getLong(String.join(".", PositionStructureConstant.NCKD_LASTPERSONPOSFILE, 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);
+    }
+}

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

@@ -1,28 +1,35 @@
 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;
 import kd.bos.entity.plugin.AbstractOperationServicePlugIn;
 import kd.bos.logging.Log;
 import kd.bos.logging.LogFactory;
 import kd.bos.orm.query.QFilter;
 import kd.bos.servicehelper.BusinessDataServiceHelper;
-import kd.bos.servicehelper.QueryServiceHelper;
 import kd.bos.servicehelper.operation.SaveServiceHelper;
 import nckd.jxccl.base.common.constant.FormConstant;
 import nckd.jxccl.base.common.enums.AppraisalResultEnum;
-import nckd.jxccl.base.common.enums.JobSeqEnum;
+import nckd.jxccl.base.common.enums.psms.JobSeqEnum;
 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.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.jetbrains.annotations.NotNull;
 
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
+import java.util.ArrayList;
 import java.util.Date;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -35,6 +42,8 @@ public abstract class BaseInitialOperationPlugIn extends AbstractOperationServic
     
     protected final static Log logger = LogFactory.getLog(BaseInitialOperationPlugIn.class);
 
+    public List<BaseInitialData> baseInitialData;
+
     /**
      * 基本信息数据对象
      */
@@ -99,49 +108,131 @@ public abstract class BaseInitialOperationPlugIn extends AbstractOperationServic
     }
 
     /**
-     * 提取基本信息
+     * 提取基本信息(支持批量)
+     * @param initialData 页面数据
+     * @return: java.util.List<nckd.jxccl.hr.psms.plugin.operate.initial.BaseInitialOperationPlugIn.BaseInitialData>
+     * @author W.Y.C
+     * @date: 2025/10/12 14:20
      */
-    protected BaseInitialData extractBasicInfo(DynamicObject initialData) {
+    protected List<BaseInitialData> extractBasicInfo(DynamicObject initialData) {
+        if(baseInitialData != null){
+            return baseInitialData;
+        }
+        List<BaseInitialData> dataList = new ArrayList<>();
+        if(initialData.containsProperty(FormConstant.NCKD_ENTRYENTITY)){
+            DynamicObjectCollection dynamicObjectCollection = initialData.getDynamicObjectCollection(FormConstant.NCKD_ENTRYENTITY);
+            for (DynamicObject dynamicObject : dynamicObjectCollection) {
+                //批量初定
+                BaseInitialData data = getBaseInitialData(dynamicObject);
+                dataList.add(data);
+            }
+        }else{
+            BaseInitialData data = getBaseInitialData(initialData);
+            dataList.add(data);
+        }
+
+        baseInitialData = dataList;
+        return baseInitialData;
+    }
+
+    @NotNull
+    private BaseInitialData getBaseInitialData(DynamicObject initialData) {
         BaseInitialData data = new BaseInitialData();
 
         data.beginDate = initialData.getDate(PositionStructureConstant.NCKD_BEGINDATE);
         data.causeRemark = initialData.getString(PositionStructureConstant.KEY_NCKD_CAUSEREMARK);
-        data.rankName = initialData.getString(PositionStructureConstant.NCKD_RANKNAME);
-        data.jobStatusName = initialData.getString(PositionStructureConstant.NCKD_JOBSTATUSNAME);
         data.person = initialData.getDynamicObject(PositionStructureConstant.NCKD_PERSON);
-        data.positionHr = initialData.getDynamicObject(PositionStructureConstant.NCKD_POSITIONHR);
-
-        // 主任职经历(在业务规则中配置)
-        DynamicObject empPosOrgRel = initialData.getDynamicObject(PositionStructureConstant.NCKD_EMPPOSORGREL);
-        data.empPosOrgRel = empPosOrgRel;
-        if(empPosOrgRel != null) {
-            data.company = empPosOrgRel.getDynamicObject(FormConstant.COMPANY_KEY);
-            data.dep = empPosOrgRel.getDynamicObject(FormConstant.ADMINORG);
+
+        PositionAppointmentBO positionAppointment = PositionStructureHelper.positionAppointmentQuery(data.person.getLong(FormConstant.ID_KEY), data.beginDate);
+        // 学历
+        DynamicObject perEduExp = positionAppointment.getPerEduExp();
+        if (perEduExp != null) {
+            // 学历
+            long diplomaId = perEduExp.getLong(String.join(".", FormConstant.EDUCATION_KEY, FormConstant.ID_KEY));
+            if(diplomaId > 0) {
+                DynamicObject diploma = BusinessDataServiceHelper.newDynamicObject(PositionStructureConstant.HBSS_DIPLOMA);
+                diploma.set(FormConstant.ID_KEY, diplomaId);
+                data.diploma = diploma;
+            }
+        }
+        // 职称信息
+        DynamicObject perProTitle = positionAppointment.getPerProTitle();
+        if (perProTitle != null) {
+            data.rankName = perProTitle.getString(String.join(".", FormConstant.PROFESSIONAL_KEY, FormConstant.NAME_KEY));
+            // 职称等级
+            long perProTitleId = perProTitle.getLong(String.join(".", FormConstant.PROLEVEL_KEY, FormConstant.ID_KEY));
+            if(perProTitleId > 0) {
+                DynamicObject proTitleLevel = BusinessDataServiceHelper.newDynamicObject(PositionStructureConstant.HBSS_PROTITLELEVEL);
+                proTitleLevel.set(FormConstant.ID_KEY, perProTitleId);
+                data.proTitleLevel = proTitleLevel;
+            }
         }
 
-        // 优秀生
-        data.isExcellent = initialData.getBoolean(PositionStructureConstant.NCKD_EXCELLENT);
+        // 技能信息
+        DynamicObject perOcpQual = positionAppointment.getPerOcpQual();
+        if (perOcpQual != null) {
+            data.jobStatusName = perOcpQual.getString(String.join(".", FormConstant.QUALIFICATION_KEY, FormConstant.NAME_KEY));
+            // 技能等级
+            long quaLevelId = perOcpQual.getLong(String.join(".", FormConstant.QUALEVEL_KEY, FormConstant.ID_KEY));
+            if(quaLevelId > 0) {
+                DynamicObject ocpQualLevel = BusinessDataServiceHelper.newDynamicObject(PositionStructureConstant.HBSS_OCPQUALLEVEL);
+                ocpQualLevel.set(FormConstant.ID_KEY, quaLevelId);
+                data.ocpQualLevel = ocpQualLevel;
+            }
 
-        // 职位序列
-        data.jobSeq = initialData.getDynamicObject(PositionStructureConstant.NCKD_JOBSEQ);
-        if(data.jobSeq != null) {
-            data.jobSeqNumber = data.jobSeq.getString(FormConstant.NUMBER_KEY);
-            data.jobSeqName = data.jobSeq.getString(FormConstant.NAME_KEY);
-            data.jobSeqEnum = JobSeqEnum.getByCode(data.jobSeqNumber);
         }
+        // 任职信息
+        DynamicObject empPosOrgRel = positionAppointment.getEmpPosOrgRel();
+        if (empPosOrgRel != null) {
+            data.empPosOrgRel = empPosOrgRel;
+            //岗位
+            long positionId = empPosOrgRel.getLong(String.join(".", FormConstant.POSITION_KEY, FormConstant.ID_KEY));
+            if(positionId > 0) {
+                DynamicObject position = BusinessDataServiceHelper.newDynamicObject(FormConstant.HBPM_POSITIONHR);
+                position.set(FormConstant.ID_KEY, positionId);
+                position.set(FormConstant.NAME_KEY, empPosOrgRel.getString(String.join(".", FormConstant.POSITION_KEY, FormConstant.NAME_KEY)));
+                data.positionHr = position;
+            }
 
-        // 职称等级
-        data.proTitleLevel = initialData.getDynamicObject(PositionStructureConstant.NCKD_PROTITLELEVEL);
+            long companyId = empPosOrgRel.getLong(String.join(".", FormConstant.COMPANY_KEY, FormConstant.ID_KEY));
+            if(companyId > 0) {
+                DynamicObject company = BusinessDataServiceHelper.newDynamicObject(FormConstant.ADMINORGHR_ENTITYID);
+                company.set(FormConstant.ID_KEY, companyId);
+                data.company = company;
+            }
+            long adminOrgId = empPosOrgRel.getLong(String.join(".", FormConstant.ADMINORG, FormConstant.ID_KEY));
+            if(adminOrgId > 0) {
+                DynamicObject dep = BusinessDataServiceHelper.newDynamicObject(FormConstant.ADMINORGHR_ENTITYID);
+                dep.set(FormConstant.ID_KEY, adminOrgId);
+                data.dep = dep;
+            }
 
-        // 技能等级
-        data.ocpQualLevel = initialData.getDynamicObject(PositionStructureConstant.NCKD_OCPQUALLEVEL);
+            // 职位序列
+            Long jobSeqId = empPosOrgRel.getLong(String.join(".", FormConstant.HBPM_POSITIONHR, FormConstant.NCKD_JOBSEQ, FormConstant.ID_KEY));
+            if(jobSeqId > 0) {
+                String jobSeqNumber = empPosOrgRel.getString(String.join(".", FormConstant.HBPM_POSITIONHR, FormConstant.NCKD_JOBSEQ, FormConstant.NUMBER_KEY));
+                String jobSeqName = empPosOrgRel.getString(String.join(".", FormConstant.HBPM_POSITIONHR, FormConstant.NCKD_JOBSEQ, FormConstant.NAME_KEY));
+                DynamicObject jobSeq = BusinessDataServiceHelper.newDynamicObject(FormConstant.HBJM_JOBSEQHR);
+                jobSeq.set(FormConstant.ID_KEY, jobSeqId);
+                jobSeq.set(FormConstant.NUMBER_KEY, jobSeqNumber);
+                jobSeq.set(FormConstant.NAME_KEY, jobSeqName);
+                data.jobSeq = jobSeq;
+                data.jobSeqNumber = jobSeqNumber;
+                data.jobSeqName = jobSeqName;
+                data.jobSeqEnum = JobSeqEnum.getByCode(data.jobSeqNumber);
+            }
+
+
+
+            //本次加入集团时间
+            data.joinComDate = empPosOrgRel.getDate(String.join(".", FormConstant.HRPI_PERSERLEN, FormConstant.JOINCOMDATE_KEY));
+        }
+        // TODO 【待修改】优秀生
+        // data.isExcellent = initialData.getBoolean(PositionStructureConstant.NCKD_EXCELLENT);
 
-        // 学历
-        data.diploma = initialData.getDynamicObject(PositionStructureConstant.NCKD_DIPLOMA);
 
         // 特定字段由子类处理
         extractSpecificFields(data, initialData);
-
         return data;
     }
 
@@ -185,15 +276,17 @@ public abstract class BaseInitialOperationPlugIn extends AbstractOperationServic
     protected ScoreData getScoreData(BaseInitialData data, String logPrefix) {
         ScoreData scoreData = new ScoreData();
 
+        QueryFieldBuilder queryFieldBuilder = QueryFieldBuilder.create().addIdNumberNameWithExtras(FormConstant.NCKD_SCORE);
+
         // 学历积分
         scoreData.educationScore = BigDecimal.ZERO;
         if (data.diploma != null) {
-            DynamicObject education = QueryServiceHelper.queryOne(FormConstant.HBSS_DIPLOMA,
-                    "name,nckd_score",
+            DynamicObject education = BusinessDataServiceHelper.loadSingle(FormConstant.HBSS_DIPLOMA,
+                    queryFieldBuilder.buildSelect(),
                     new QFilter[]{QFilterCommonHelper.getIdEqFilter(data.diploma.getLong(FormConstant.ID_KEY))});
             if (education != null) {
-                scoreData.educationScore = education.getBigDecimal("nckd_score");
-                logger.info("{}-学历【{}】-积分【{}】", logPrefix, education.getString("name"), scoreData.educationScore);
+                scoreData.educationScore = education.getBigDecimal(FormConstant.NCKD_SCORE);
+                logger.info("{}-学历【{}】-积分【{}】", logPrefix, education.getString(FormConstant.NAME_KEY), scoreData.educationScore);
             } else {
                 logger.warn("{}-未找到学历信息", logPrefix);
             }
@@ -208,21 +301,25 @@ public abstract class BaseInitialOperationPlugIn extends AbstractOperationServic
         scoreData.dbProTitleLevel = null;
         scoreData.dbOcpQualLevel = null;
 
+
+
         if (data.jobSeqEnum != JobSeqEnum.SKILL && data.proTitleLevel != null) {
             // 非技能序列获取职称积分
-            scoreData.dbProTitleLevel = QueryServiceHelper.queryOne(FormConstant.HBSS_PROTITLELEVEL,
-                    "name,nckd_score",
+            scoreData.dbProTitleLevel = BusinessDataServiceHelper.loadSingle(FormConstant.HBSS_PROTITLELEVEL,
+                    queryFieldBuilder.buildSelect(),
                     new QFilter[]{QFilterCommonHelper.getIdEqFilter(data.proTitleLevel.getLong(FormConstant.ID_KEY))});
             if (scoreData.dbProTitleLevel != null) {
-                scoreData.proTitleScore = scoreData.dbProTitleLevel.getBigDecimal("nckd_score");
+                scoreData.proTitleScore = scoreData.dbProTitleLevel.getBigDecimal(FormConstant.NCKD_SCORE);
+                data.jobStatusName = null;
             }
         } else if (data.jobSeqEnum == JobSeqEnum.SKILL && data.ocpQualLevel != null) {
             // 技能序列获取技能积分
-            scoreData.dbOcpQualLevel = QueryServiceHelper.queryOne(FormConstant.HBSS_OCPQUALLEVEL,
-                    "name,nckd_score",
+            scoreData.dbOcpQualLevel = BusinessDataServiceHelper.loadSingle(FormConstant.HBSS_OCPQUALLEVEL,
+                    queryFieldBuilder.buildSelect(),
                     new QFilter[]{QFilterCommonHelper.getIdEqFilter(data.ocpQualLevel.getLong(FormConstant.ID_KEY))});
             if (scoreData.dbOcpQualLevel != null) {
-                scoreData.perOcpQualScore = scoreData.dbOcpQualLevel.getBigDecimal("nckd_score");
+                scoreData.perOcpQualScore = scoreData.dbOcpQualLevel.getBigDecimal(FormConstant.NCKD_SCORE);
+                data.rankName = null;
             }
         }
 
@@ -287,12 +384,14 @@ public abstract class BaseInitialOperationPlugIn extends AbstractOperationServic
         personPosFile.set(PositionStructureConstant.NCKD_DIPLOMASCORE, scoreData.educationScore);
         personPosFile.set(PositionStructureConstant.NCKD_EXECUTEYEAR, DateUtil.getYear(data.beginDate));
         personPosFile.set(PositionStructureConstant.NCKD_ORGINSSCORE, excellentScore);
+        personPosFile.set(PositionStructureConstant.NCKD_EMPLOYMENTYEARS, data.employmentYears);
         personPosFile.set(PositionStructureConstant.KEY_NCKD_CAUSEREMARK, data.causeRemark);
         personPosFile.set(PositionStructureConstant.NCKD_DISABLE, EnableEnum.NO.getCode());
         personPosFile.set(PositionStructureConstant.NCKD_ISCURRENTNEWEST, EnableEnum.YES.getCode());
         personPosFile.set(PositionStructureConstant.STATUS, StatusEnum.C.toString());
         personPosFile.set(PositionStructureConstant.ENABLE, EnableEnum.YES.getCode());
-        personPosFile.set(PositionStructureConstant.ENABLE, EnableEnum.YES.getCode());
+        personPosFile.set(PositionStructureConstant.CREATOR_KEY, RequestContext.get().getCurrUserId());
+
 
         SaveServiceHelper.save(new DynamicObject[]{personPosFile});
     }
@@ -303,7 +402,7 @@ public abstract class BaseInitialOperationPlugIn extends AbstractOperationServic
     protected void setJobLevelResult(DynamicObject person, DynamicObject jobLeve) {
         String jobLeveStr = StrFormatter.format("{}:{}({}级)", 
                 person.getString(FormConstant.NAME_KEY),
-                jobLeve.getString(FormConstant.NAME_KEY), 
+                jobLeve.getString(FormConstant.NAME_KEY),
                 jobLeve.getString(FormConstant.JOBLEVELSEQ));
         
         Map<String, String> customData = this.getOperationResult().getCustomData();

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

@@ -10,11 +10,14 @@ import kd.bos.entity.validate.AbstractValidator;
 import nckd.jxccl.base.common.constant.FormConstant;
 import nckd.jxccl.base.common.utils.DateUtil;
 import nckd.jxccl.base.common.utils.StrFormatter;
-import nckd.jxccl.hr.psms.common.PositionStructureConstant;
+import nckd.jxccl.hr.psms.business.JobLevelCalculatorService;
 import nckd.jxccl.hr.psms.helper.PositionStructureHelper;
 
 import java.math.BigDecimal;
 import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 
 /**
  * 新入职人员初定-确认定级操作OP
@@ -37,43 +40,61 @@ public class NewHireInitialOperationPlugIn extends BaseInitialOperationPlugIn {
                 Date currentDate = new Date();
                 for (ExtendedDataEntity rowDataEntity : getDataEntities()) {
                     DynamicObject data = rowDataEntity.getDataEntity();
-
                     //提取信息
-                    BaseInitialData newHireInitialData = (BaseInitialData) extractBasicInfo(data);
-                    Date beginDate = newHireInitialData.beginDate;
-                    DynamicObject person = newHireInitialData.person;
-                    //------ 1.非空校验 ------
-                    if(person == null){
-                        addFatalErrorMessage(rowDataEntity,"请选择人员");
-                        return;
-                    }
-                    if(newHireInitialData.empPosOrgRel == null){
-                        addFatalErrorMessage(rowDataEntity,"该人员无任职信息");
-                        return;
-                    }
-                    if (beginDate == null) {
-                        addFatalErrorMessage(rowDataEntity,"初定时间不能为空");
-                        return;
-                    }
-                    DynamicObject positionHr = newHireInitialData.positionHr;
-                    DynamicObject jobSeq = newHireInitialData.jobSeq;
-                    if(positionHr == null){
-                        addFatalErrorMessage(rowDataEntity,"无岗位,请检查当前员工岗位信息");
-                        return;
+                    List<BaseInitialData> newHireInitialDataList =  extractBasicInfo(data);
+
+                    // 检查人员ID重复
+                    Set<Long> personIds = new HashSet<>();
+                    for (BaseInitialData newHireInitialData : newHireInitialDataList) {
+                        if (newHireInitialData.person != null) {
+                            Long personId = newHireInitialData.person.getLong(FormConstant.ID_KEY);
+                            if (!personIds.add(personId)) {
+                                addFatalErrorMessage(rowDataEntity,
+                                        StrFormatter.format("人员【{}】在列表中重复,请保留一条信息",
+                                                newHireInitialData.person.getString(FormConstant.NAME_KEY)));
+                            }
+                        }
                     }
-                    if (jobSeq == null) {
-                        addFatalErrorMessage(rowDataEntity,StrFormatter.format("无职位序列,请检查当前员工岗位【{}】是否有职位序列",positionHr.getString(FormConstant.NAME_KEY)));
+                    if(!this.getValidateResult().isSuccess()){
                         return;
                     }
 
-                    //------ 3、验证初定时间不能超过当前日期 ------
-                    if (newHireInitialData.joinComDate.after(beginDate)) {
-                        addFatalErrorMessage(rowDataEntity,"初定时间不能早于本次加入集团日期,本次加入集团时间:"+DateUtil.format(newHireInitialData.joinComDate, DateUtil.NORM_DATE_PATTERN));
-                        return;
-                    }
-                    if (beginDate.after(currentDate)) {
-                        addFatalErrorMessage(rowDataEntity,"初定时间不能晚于当前日期,请检查输入的日期是否正确");
-                        return;
+                    for (BaseInitialData newHireInitialData : newHireInitialDataList) {
+                        Date beginDate = newHireInitialData.beginDate;
+                        DynamicObject person = newHireInitialData.person;
+                        //------ 1.非空校验 ------
+                        if(person == null){
+                            addFatalErrorMessage(rowDataEntity,"请选择人员");
+                            return;
+                        }
+                        if(PositionStructureHelper.isInitial(person.getLong(FormConstant.ID_KEY))){
+                            addFatalErrorMessage(rowDataEntity,StrFormatter.format("人员【{}】已初定,不能重复初定",person.getString(FormConstant.NAME_KEY)));
+                        }
+                        if(newHireInitialData.empPosOrgRel == null){
+                            addFatalErrorMessage(rowDataEntity,StrFormatter.format("人员【{}】无任职信息",person.getString(FormConstant.NAME_KEY)));
+                        }
+                        if (beginDate == null) {
+                            addFatalErrorMessage(rowDataEntity,StrFormatter.format("人员【{}】初定时间不能为空",person.getString(FormConstant.NAME_KEY)));
+                        }
+                        DynamicObject positionHr = newHireInitialData.positionHr;
+                        DynamicObject jobSeq = newHireInitialData.jobSeq;
+                        if(positionHr == null){
+                            addFatalErrorMessage(rowDataEntity,StrFormatter.format("无岗位,请检查当前人员【{}】任职的岗位信息",person.getString(FormConstant.NAME_KEY)));
+                        }
+                        if (positionHr != null && jobSeq == null) {
+                            addFatalErrorMessage(rowDataEntity,StrFormatter.format("无职位序列,请检查当前人员【{}】任职的岗位【{}】是否有职位序列",person.getString(FormConstant.NAME_KEY),positionHr.getString(FormConstant.NAME_KEY)));
+                        }
+
+                        //------ 3、验证初定时间不能超过当前日期 ------
+                        if(newHireInitialData.joinComDate == null){
+                            addFatalErrorMessage(rowDataEntity,StrFormatter.format("无本次加入集团日期,请检查当前人员【{}】服务年限信息",person.getString(FormConstant.NAME_KEY)));
+                        }
+                        if (newHireInitialData.joinComDate != null && newHireInitialData.joinComDate.after(beginDate)) {
+                            addFatalErrorMessage(rowDataEntity,StrFormatter.format("人员【{}】初定时间不能早于本次加入集团日期,本次加入集团时间:【{}】",person.getString(FormConstant.NAME_KEY),DateUtil.format(newHireInitialData.joinComDate, DateUtil.NORM_DATE_PATTERN)));
+                        }
+                        if (beginDate != null && beginDate.after(currentDate)) {
+                            addFatalErrorMessage(rowDataEntity,StrFormatter.format("人员【{}】初定时间不能晚于当前日期,请检查输入的日期是否正确",person.getString(FormConstant.NAME_KEY)));
+                        }
                     }
                 }
             }
@@ -82,9 +103,12 @@ public class NewHireInitialOperationPlugIn extends BaseInitialOperationPlugIn {
 
     @Override
     public void beginOperationTransaction(BeginOperationTransactionArgs e) {
-        logger.info("【职位体系】-新入职人员初定-开始");
-        for (DynamicObject servingInitial : e.getDataEntities()) {
-            processNewHireInitialData(servingInitial);
+        if(this.getOperationResult().isSuccess()) {
+            //对应SHR:com.kingdee.shr.customer.web.handler.ContributeScore.PersonpositionfileListHandler#addNewPersonpositionfileInfo
+            logger.info("【职位体系】-新入职人员初定-开始");
+            for (DynamicObject servingInitial : e.getDataEntities()) {
+                processNewHireInitialData(servingInitial);
+            }
         }
     }
 
@@ -105,51 +129,58 @@ public class NewHireInitialOperationPlugIn extends BaseInitialOperationPlugIn {
     private void processNewHireInitialData(DynamicObject newHireInitial) {
 
         //提取信息
-        BaseInitialData data = (BaseInitialData) extractBasicInfo(newHireInitial);
-
-        String logPrefix = StrFormatter.format("【职位体系】-新入职人员初定-员工【{}({})】",
-                data.person.getString(FormConstant.NAME_KEY),
-                data.person.getString(FormConstant.EMP_NUMBER_KEY));
-        logger.info("{}-初定日期【{}】-职位序列【{}】-岗位【{}】",
-                logPrefix,
-                DateUtil.format(data.beginDate, DateUtil.NORM_DATE_PATTERN),
-                data.jobSeqName,
-                data.positionHr.getString(FormConstant.NAME_KEY));
-
-        // 获取上年度绩效结果
-        getPreviousYearPerformance(data, logPrefix);
-
-        // 获取学历、职称、技能对应的积分
-        ScoreData scoreData = getScoreData(data, logPrefix);
-
-        // 优秀生得2分
-        BigDecimal excellentScore = data.isExcellent ? new BigDecimal(2) : BigDecimal.ZERO;
-
-        // 累计总积分 = 学历分 + 职称/技能分 + 优秀生分
-        BigDecimal allSumScore = scoreData.educationScore.add(scoreData.proTitleScore).add(scoreData.perOcpQualScore).add(excellentScore);
-        logger.info("{}-学历分 + 职称/技能分 + 优秀生分 = {}", logPrefix, allSumScore);
-        // 计算累计积分池(生涯积分)
-        BigDecimal sumScore = calculateSumScore(allSumScore, scoreData, logPrefix);
-
-        // 计算职级
-        //【三期需求】-不满足三要素(聘任职称/技能、考核结果、积分)按"无职级"初定
-        //当考核结果为无时,职级定为最低级
-        boolean useMinLevel = data.lastYearAppraisalResultEnum == nckd.jxccl.base.common.enums.AppraisalResultEnum.NONE;
-        DynamicObject jobLeve = PositionStructureHelper.calculateLevel(
-                data.jobSeqEnum, sumScore, scoreData.dbProTitleLevel,
-                scoreData.dbOcpQualLevel,data.lastYearAppraisalResult,data.downgradeNum,useMinLevel,Boolean.TRUE);
-        if(jobLeve != null) {
-            // 构建职位档案并存入数据库
-            createAndSavePersonPosFile(data, scoreData, allSumScore, sumScore, jobLeve, logPrefix, "1");
-
-            //返回定级后的职级
-            setJobLevelResult(data.person, jobLeve);
+        List<BaseInitialData> dataList =  extractBasicInfo(newHireInitial);
+        for (BaseInitialData data : dataList) {
+
+            String logPrefix = StrFormatter.format("【职位体系】-新入职人员初定-人员【{}({})】",
+                    data.person.getString(FormConstant.NAME_KEY),
+                    data.person.getString(FormConstant.EMP_NUMBER_KEY));
+            logger.info("{}-初定日期【{}】-职位序列【{}】-岗位【{}】",
+                    logPrefix,
+                    DateUtil.format(data.beginDate, DateUtil.NORM_DATE_PATTERN),
+                    data.jobSeqName,
+                    data.positionHr.getString(FormConstant.NAME_KEY));
+
+            // 获取上年度绩效结果
+            getPreviousYearPerformance(data, logPrefix);
+
+            // 获取学历、职称、技能对应的积分
+            ScoreData scoreData = getScoreData(data, logPrefix);
+
+            // 优秀生得2分
+            BigDecimal excellentScore = data.isExcellent ? new BigDecimal(2) : BigDecimal.ZERO;
+
+            // 累计总积分 = 学历分 + 职称/技能分 + 优秀生分
+            BigDecimal allSumScore = scoreData.educationScore.add(scoreData.proTitleScore).add(scoreData.perOcpQualScore).add(excellentScore);
+            logger.info("{}-学历分 + 职称/技能分 + 优秀生分 = {}", logPrefix, allSumScore);
+            // 计算累计积分池(生涯积分)
+            BigDecimal sumScore = calculateSumScore(allSumScore, scoreData, logPrefix);
+
+            // 计算职级
+            //当考核结果为无时,职级定为最低级
+            boolean useMinLevel = data.lastYearAppraisalResultEnum == nckd.jxccl.base.common.enums.AppraisalResultEnum.NONE;
+            //【三期需求】-不满足三要素(聘任职称/技能、考核结果、积分)按"无职级"初定
+            String perProTitleNumber = scoreData.dbProTitleLevel != null ? scoreData.dbProTitleLevel.getString(FormConstant.NUMBER_KEY) : null;
+            String quaLevelNumber = scoreData.dbOcpQualLevel != null ? scoreData.dbOcpQualLevel.getString(FormConstant.NUMBER_KEY) : null;
+            boolean threeElementMeet = JobLevelCalculatorService.checkThreeElementsRequirement(data.jobSeq, allSumScore, scoreData.dbProTitleLevel,
+                    scoreData.dbOcpQualLevel, data.lastYearAppraisalResultEnum);
+            DynamicObject jobLeve = JobLevelCalculatorService.getJobLevel(
+                    data.jobSeq, allSumScore, perProTitleNumber,
+                    quaLevelNumber,data.downgradeNum,useMinLevel,!threeElementMeet);
+            if(jobLeve != null) {
+                // 构建职位档案并存入数据库
+                createAndSavePersonPosFile(data, scoreData, allSumScore, sumScore, jobLeve, logPrefix, "1");
+
+                //返回定级后的职级
+                setJobLevelResult(data.person, jobLeve);
+
+                //TODO 【待修改】-职位体系-这里可能有协同,初定完成后需要将待办任务置为已完成
+            }
         }
+
     }
 
     @Override
     protected void extractSpecificFields(BaseInitialData data, DynamicObject initialData) {
-        // 本次加入集团时间
-        data.joinComDate = initialData.getDate(PositionStructureConstant.NCKD_JOINCOMDATE);
     }
 }

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

@@ -11,12 +11,16 @@ import nckd.jxccl.base.common.constant.FormConstant;
 import nckd.jxccl.base.common.utils.DateUtil;
 import nckd.jxccl.base.common.utils.StrFormatter;
 import nckd.jxccl.base.pm.helper.PerformanceManagerHelper;
+import nckd.jxccl.hr.psms.business.JobLevelCalculatorService;
 import nckd.jxccl.hr.psms.common.PositionStructureConstant;
 import nckd.jxccl.hr.psms.helper.PositionStructureHelper;
 
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
 import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 
 /**
  * 在职人员初定-确认定级操作OP
@@ -38,59 +42,71 @@ public class ServingInitialOperationPlugIn extends BaseInitialOperationPlugIn {
                 Date currentDate = new Date();
                 for (ExtendedDataEntity rowDataEntity : getDataEntities()) {
                     DynamicObject data = rowDataEntity.getDataEntity();
-
                     //提取信息
-                    BaseInitialData servingInitialData = (BaseInitialData) extractBasicInfo(data);
-                    Date beginDate = servingInitialData.beginDate;
-                    DynamicObject person = servingInitialData.person;
-                    //------ 1.非空校验 ------
-                    if(person == null){
-                        addFatalErrorMessage(rowDataEntity,"请选择人员");
-                        return;
+                    List<BaseInitialData> servingInitialDataList = extractBasicInfo(data);
+
+                    // 检查人员ID重复
+                    Set<Long> personIds = new HashSet<>();
+                    for (BaseInitialData servingInitialData : servingInitialDataList) {
+                        if (servingInitialData.person != null) {
+                            Long personId = servingInitialData.person.getLong(FormConstant.ID_KEY);
+                            if (!personIds.add(personId)) {
+                                addFatalErrorMessage(rowDataEntity,
+                                        StrFormatter.format("人员【{}】在列表中重复,请保留一条信息",
+                                                servingInitialData.person.getString(FormConstant.NAME_KEY)));
+                                return;
+                            }
+                        }
                     }
-                    if(servingInitialData.empPosOrgRel == null){
-                        addFatalErrorMessage(rowDataEntity,"该人员无任职信息");
-                        return;
-                    }
-                    if (beginDate == null) {
-                        addFatalErrorMessage(rowDataEntity,"初定时间不能为空");
-                        return;
-                    }
-                    BigDecimal allSumScore = servingInitialData.allSumScore;
-                    if(allSumScore == null || allSumScore.compareTo(BigDecimal.ZERO) == 0){
-                        addFatalErrorMessage(rowDataEntity,"请填写总积分");
-                        return;
-                    }
-                    DynamicObject positionHr = servingInitialData.positionHr;
-                    DynamicObject jobSeq = servingInitialData.jobSeq;
-                    if(positionHr == null){
-                        addFatalErrorMessage(rowDataEntity,"无岗位,请检查当前员工岗位信息");
-                        return;
-                    }
-                    if (jobSeq == null) {
-                        addFatalErrorMessage(rowDataEntity,StrFormatter.format("无职位序列,请检查当前员工岗位【{}】是否有职位序列",positionHr.getString(FormConstant.NAME_KEY)));
+                    if(!this.getValidateResult().isSuccess()){
                         return;
                     }
 
-                    //------ 2、判断是否已存在初定记录 ------
-                    boolean exists = PositionStructureHelper.isInitial(person.getLong(FormConstant.ID_KEY));
-                    if(exists){
-                        addFatalErrorMessage(rowDataEntity,StrFormatter.format("人员【{}】已初定,不能重复初定",person.getString(FormConstant.NAME_KEY)));
-                        return;
-                    }
-                    //------ 3、验证初定时间不能超过当前日期 ------
-                    if (beginDate.after(currentDate)) {
-                        addFatalErrorMessage(rowDataEntity,"初定时间不能晚于当前日期,请检查输入的日期是否正确");
-                        return;
+                    for (BaseInitialData servingInitialData : servingInitialDataList) {
+                        Date beginDate = servingInitialData.beginDate;
+                        DynamicObject person = servingInitialData.person;
+                        //------ 1.非空校验 ------
+                        if(person == null){
+                            addFatalErrorMessage(rowDataEntity,"请选择人员");
+                            return;
+                        }
+                        if(PositionStructureHelper.isInitial(person.getLong(FormConstant.ID_KEY))){
+                            addFatalErrorMessage(rowDataEntity,StrFormatter.format("人员【{}】已初定,不能重复初定",person.getString(FormConstant.NAME_KEY)));
+                        }
+                        if(servingInitialData.empPosOrgRel == null){
+                            addFatalErrorMessage(rowDataEntity,StrFormatter.format("人员【{}】无任职信息",person.getString(FormConstant.NAME_KEY)));
+                        }
+                        if (beginDate == null) {
+                            addFatalErrorMessage(rowDataEntity,StrFormatter.format("人员【{}】初定时间不能为空",person.getString(FormConstant.NAME_KEY)));
+                        }
+                        BigDecimal allSumScore = servingInitialData.allSumScore;
+                        if(allSumScore == null || allSumScore.compareTo(BigDecimal.ZERO) == 0){
+                            addFatalErrorMessage(rowDataEntity,StrFormatter.format("人员【{}】请填写总积分",person.getString(FormConstant.NAME_KEY)));
+                        }
+                        DynamicObject positionHr = servingInitialData.positionHr;
+                        DynamicObject jobSeq = servingInitialData.jobSeq;
+                        if(positionHr == null){
+                            addFatalErrorMessage(rowDataEntity,StrFormatter.format("无岗位,请检查当前人员【{}】任职的岗位信息",person.getString(FormConstant.NAME_KEY)));
+                        }
+                        if (positionHr != null && jobSeq == null) {
+                            addFatalErrorMessage(rowDataEntity,StrFormatter.format("无职位序列,请检查当前人员【{}】任职的岗位【{}】是否有职位序列",person.getString(FormConstant.NAME_KEY),positionHr.getString(FormConstant.NAME_KEY)));
+                        }
+
+                        //------ 3、验证初定时间不能超过当前日期 ------
+                        if (beginDate != null && beginDate.after(currentDate)) {
+                            addFatalErrorMessage(rowDataEntity,StrFormatter.format("人员【{}】初定时间不能晚于当前日期,请检查输入的日期是否正确",person.getString(FormConstant.NAME_KEY)));
+                        }
+
+                        //------ 4.获取上年度绩效结果 ------
+                        if(beginDate != null) {
+                            LocalDateTime lastYearDateTime = DateUtil.minusYears(DateUtil.toLocalDateTime(beginDate), 1);
+                            DynamicObject lastYearPerformanceResult = PerformanceManagerHelper.getPerformanceResult(person.getLong(FormConstant.ID_KEY), lastYearDateTime);
+                            if (lastYearPerformanceResult == null) {
+                                addFatalErrorMessage(rowDataEntity, StrFormatter.format("人员【{}】没有上年度考核结果,无法初定", person.getString(FormConstant.NAME_KEY)));
+                            }
+                        }
                     }
 
-                    //------ 4.获取上年度绩效结果 ------
-                    LocalDateTime lastYearDateTime = DateUtil.minusYears(DateUtil.toLocalDateTime(beginDate), 1);
-                    DynamicObject lastYearPerformanceResult = PerformanceManagerHelper.getPerformanceResult(person.getLong(FormConstant.ID_KEY), lastYearDateTime);
-                    if(lastYearPerformanceResult == null){
-                        addFatalErrorMessage(rowDataEntity,StrFormatter.format("员工【{}】没有上年度考核结果,无法初定",person.getString(FormConstant.NAME_KEY)));
-                        return;
-                    }
                 }
             }
         });
@@ -98,9 +114,12 @@ public class ServingInitialOperationPlugIn extends BaseInitialOperationPlugIn {
 
     @Override
     public void beginOperationTransaction(BeginOperationTransactionArgs e) {
-        logger.info("【职位体系】-在职人员初定-开始");
-        for (DynamicObject servingInitial : e.getDataEntities()) {
-            processServingInitial(servingInitial);
+        if(this.getOperationResult().isSuccess()) {
+            //对应SHR:com.kingdee.shr.customer.web.handler.ContributeScore.PersonpositionfileListHandler#addNewPersonpositionfileInfo_older
+            logger.info("【职位体系】-在职人员初定-开始");
+            for (DynamicObject servingInitial : e.getDataEntities()) {
+                processServingInitial(servingInitial);
+            }
         }
     }
 
@@ -120,41 +139,43 @@ public class ServingInitialOperationPlugIn extends BaseInitialOperationPlugIn {
      */
     private void processServingInitial(DynamicObject servingInitial) {
         // 提取基本信息
-        BaseInitialData data = extractBasicInfo(servingInitial);
-
-        String logPrefix = StrFormatter.format("【职位体系】-在职人员初定-员工【{}({})】",
-                data.person.getString(FormConstant.NAME_KEY),
-                data.person.getString(FormConstant.EMP_NUMBER_KEY));
-        logger.info("{}-初定日期【{}】-职位序列【{}】-岗位【{}】-总积分【{}】",
-                logPrefix,
-                DateUtil.format(data.beginDate, DateUtil.NORM_DATE_PATTERN),
-                data.jobSeqName,
-                data.positionHr.getString(FormConstant.NAME_KEY),
-                data.allSumScore.toString());
-
-        // 获取上年度绩效结果
-        getPreviousYearPerformance(data, logPrefix);
-
-        // 获取学历、职称、技能对应的积分
-        ScoreData scoreData = getScoreData(data, logPrefix);
-
-        // 计算累计积分池(生涯积分)
-        BigDecimal sumScore = calculateSumScore(data.allSumScore, scoreData, logPrefix);
-
-        // 计算职级
-        //【三期需求】-不满足三要素(聘任职称/技能、考核结果、积分)按"无职级"初定
-        //当考核结果为无时,职级定为最低级
-        boolean useMinLevel = data.lastYearAppraisalResultEnum == nckd.jxccl.base.common.enums.AppraisalResultEnum.NONE;
-        DynamicObject jobLeve = PositionStructureHelper.calculateLevel(
-                data.jobSeqEnum, sumScore, scoreData.dbProTitleLevel,
-                scoreData.dbOcpQualLevel, data.lastYearAppraisalResult, data.downgradeNum,useMinLevel,Boolean.TRUE);
-
-        if(jobLeve != null) {
-            // 构建职位档案并存入数据库
-            createAndSavePersonPosFile(data, scoreData, data.allSumScore, sumScore, jobLeve, logPrefix, "2");
-
-            //返回定级后的职级。张三:职位级为"初级(1)"
-            setJobLevelResult(data.person, jobLeve);
+        List<BaseInitialData> dataList = extractBasicInfo(servingInitial);
+        for (BaseInitialData data : dataList) {
+            String logPrefix = StrFormatter.format("【职位体系】-在职人员初定-人员【{}({})】",
+                    data.person.getString(FormConstant.NAME_KEY),
+                    data.person.getString(FormConstant.EMP_NUMBER_KEY));
+            logger.info("{}-初定日期【{}】-职位序列【{}】-岗位【{}】-总积分【{}】",
+                    logPrefix,
+                    DateUtil.format(data.beginDate, DateUtil.NORM_DATE_PATTERN),
+                    data.jobSeqName,
+                    data.positionHr.getString(FormConstant.NAME_KEY),
+                    data.allSumScore.toString());
+
+            // 获取上年度绩效结果
+            getPreviousYearPerformance(data, logPrefix);
+
+            // 获取学历、职称、技能对应的积分
+            ScoreData scoreData = getScoreData(data, logPrefix);
+
+            // 计算累计积分池(生涯积分)
+            BigDecimal sumScore = calculateSumScore(data.allSumScore, scoreData, logPrefix);
+
+            // 计算职级
+            //当考核结果为无时,职级定为最低级
+            boolean useMinLevel = data.lastYearAppraisalResultEnum == nckd.jxccl.base.common.enums.AppraisalResultEnum.NONE;
+            String perProTitleNumber = scoreData.dbProTitleLevel != null ? scoreData.dbProTitleLevel.getString(FormConstant.NUMBER_KEY) : null;
+            String quaLevelNumber = scoreData.dbOcpQualLevel != null ? scoreData.dbOcpQualLevel.getString(FormConstant.NUMBER_KEY) : null;
+            DynamicObject jobLeve = JobLevelCalculatorService.getJobLevel(
+                    data.jobSeq, data.allSumScore, perProTitleNumber,
+                    quaLevelNumber,  data.downgradeNum,useMinLevel,Boolean.FALSE);
+
+            if(jobLeve != null) {
+                // 构建职位档案并存入数据库
+                createAndSavePersonPosFile(data, scoreData, data.allSumScore, sumScore, jobLeve, logPrefix, "2");
+
+                //返回定级后的职级。张三:职位级为"初级(1)"
+                setJobLevelResult(data.person, jobLeve);
+            }
         }
     }
 

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

@@ -1,16 +1,21 @@
 package nckd.jxccl.hr.psms.plugin.report.adjust;
 
 import kd.bos.dataentity.entity.DynamicObject;
+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.control.events.ItemClickListener;
+import kd.bos.form.events.ClosedCallBackEvent;
 import kd.bos.report.ReportList;
 import kd.bos.report.plugin.AbstractReportFormPlugin;
 import nckd.jxccl.base.common.constant.FormConstant;
 import nckd.jxccl.hr.psms.common.PositionStructureConstant;
+import org.apache.commons.lang3.StringUtils;
 
+import java.util.ArrayList;
 import java.util.EventObject;
+import java.util.List;
 
 /**
 * 未生成动态调整-报表插件
@@ -20,6 +25,13 @@ import java.util.EventObject;
 */
 public class UnAdjustedReportFormPlugin extends AbstractReportFormPlugin implements ItemClickListener {
 
+    @Override
+    public void initialize() {
+        super.initialize();
+    }
+
+
+
     @Override
     public void afterBindData(EventObject e) {
         /*super.afterBindData(e);
@@ -36,32 +48,51 @@ public class UnAdjustedReportFormPlugin extends AbstractReportFormPlugin impleme
     public void itemClick(ItemClickEvent evt) {
         String itemKey = evt.getItemKey();
         String operationKey = evt.getOperationKey();
-        if("nckd_new".equalsIgnoreCase(itemKey)){
+        if("nckd_new".equalsIgnoreCase(itemKey) || "nckd_newbatch".equalsIgnoreCase(itemKey)){
             //创建调整
             ReportList reportList = this.getView().getControl(FormConstant.REPORTLISTAP);
             //获取报表选中的行数据
             int[] selectedRowIndexes = reportList.getEntryState().getSelectedRows();
-            if(selectedRowIndexes.length == 1){
-                DynamicObject rowData = reportList.getReportModel().getRowData(selectedRowIndexes[0]);
-                //单人调整
+            if(selectedRowIndexes.length > 1 || "nckd_newbatch".equals(itemKey)){
+                //多人调整
+                List<Long> personIds = new ArrayList<>();
+                for (int selectedRowIndex : selectedRowIndexes) {
+                    DynamicObject rowData = reportList.getReportModel().getRowData(selectedRowIndex);
+                    personIds.add(rowData.getDynamicObject(FormConstant.NCKD_PERSON).getLong(FormConstant.ID_KEY));
+                }
                 FormShowParameter showParameter = new FormShowParameter();
-                showParameter.setFormId(PositionStructureConstant.NEWDYNAMICADJUDIALOG_ENTITYID);
+                showParameter.setFormId(PositionStructureConstant.NEWDYNAMICADJUBATCH_ENTITYID);
                 showParameter.getOpenStyle().setShowType(ShowType.Modal);
-                showParameter.setCaption("新建调整");
-                showParameter.setCustomParam(FormConstant.NCKD_PERSON,rowData.getDynamicObject(FormConstant.NCKD_PERSON).getLong(FormConstant.ID_KEY));
+                showParameter.setCaption("批量调整");
+                showParameter.setCustomParam(FormConstant.NCKD_PERSON,personIds);
+                showParameter.setCloseCallBack(new CloseCallBack(this, PositionStructureConstant.NEWDYNAMICADJUBATCH_ENTITYID));
                 this.getView().showForm(showParameter);
-            }else if(selectedRowIndexes.length > 1){
-                //多人调整
+            }else {
+
+                //单人调整
                 FormShowParameter showParameter = new FormShowParameter();
-                showParameter.setFormId(PositionStructureConstant.SERVINGINITIAL_ENTITYID);
+                showParameter.setFormId(PositionStructureConstant.NEWDYNAMICADJUDIALOG_ENTITYID);
                 showParameter.getOpenStyle().setShowType(ShowType.Modal);
-                showParameter.setCaption("批量调整");
+                showParameter.setCaption("新建调整");
+                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("请选择要调整的员工");
-                return;
             }
+        }
+    }
 
+    @Override
+    public void closedCallBack(ClosedCallBackEvent closedCallBackEvent) {
+        String actionId = closedCallBackEvent.getActionId();
+        if(StringUtils.equalsAny(actionId,PositionStructureConstant.NEWDYNAMICADJUDIALOG_ENTITYID)){
+            Object returnData = closedCallBackEvent.getReturnData();
+            if(returnData != null) {
+                //刷新列表
+                this.getView().invokeOperation(FormConstant.REFRESH_OP);
+            }
         }
     }
 }

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

@@ -1,44 +1,287 @@
 package nckd.jxccl.hr.psms.plugin.report.adjust;
 
 import kd.bos.algo.DataSet;
-import kd.bos.entity.report.AbstractReportListDataPluginExt;
-import kd.bos.event.AfterQueryEvent;
+import kd.bos.common.enums.EnableEnum;
+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;
+import nckd.jxccl.base.common.constant.FormConstant;
+import nckd.jxccl.base.common.utils.ConvertUtil;
+import nckd.jxccl.base.common.utils.QueryFieldBuilder;
+import nckd.jxccl.hr.psms.common.PositionStructureConstant;
 
+import java.util.List;
+import java.util.Map;
 import java.util.StringJoiner;
 
 /**
-* 未生成动态调整-查询扩展插件
-* @author W.Y.C
-* @date 2025/9/17 17:01
-* @version 1.0
-*/
-public class UnAdjustedReportReportListDataPlugin extends AbstractReportListDataPluginExt {
-
+ * 未生成动态调整-查询插件
+ * @author W.Y.C
+ * @date 2025/10/10 19:51
+ * @version 1.0
+ */
+public class UnAdjustedReportReportListDataPlugin extends AbstractReportListDataPlugin {
     @Override
-    public void afterQuery(AfterQueryEvent event) {
-        //由于普通查询列表无法对两个实体的字段对比过滤,这里使用报表方式实现。查询结果之后使用algo进一步过滤数据
+    public DataSet query(ReportQueryParam reportQueryParam, Object o) throws Throwable {
+        // 构建查询字段
+        QueryFieldBuilder queryFieldBuilder = buildQueryFieldBuilder();
+
+        // 构建基础查询条件
+        QFilter qFilter = buildBaseQueryFilter();
+
+        // 处理快速过滤条件
+        processFastFilter(reportQueryParam, qFilter);
+
+        //其他过滤条件
+        processFilter(reportQueryParam, qFilter);
+
+
+        // 执行基础查询
+        DataSet dataSet = QueryServiceHelper.queryDataSet(PositionStructureConstant.PERSONPOSFILE_ENTITYID, "unadjustquery",
+                queryFieldBuilder.buildSelect(), new QFilter[]{qFilter}, null,10000);
+
+        // 添加扩展过滤条件
+        DataSet filteredDataSet = addExtendedFilters(dataSet);
+
+        // 添加调整类型字段
+        DataSet dataSetWithAdjustTypes = addAdjustTypeFields(filteredDataSet);
+
+        // 处理报表过滤条件
+        String filterCondition = processOtherFilter(reportQueryParam);
+
+        return dataSetWithAdjustTypes.filter(filterCondition);
+    }
+
+    /**
+     * 构建查询字段
+     */
+    private QueryFieldBuilder buildQueryFieldBuilder() {
+        return QueryFieldBuilder.create()
+                .add(FormConstant.ID_KEY)
+                .addGroup(new String[]{FormConstant.ASSIGNMENT_ENTITYID}, FormConstant.IS_PRIMARY)
+                .addGroup(new String[]{FormConstant.EMPLOYEE_KEY}, FormConstant.EMP_NUMBER_KEY, FormConstant.NAME_KEY)
+                .addGroup(new String[]{PositionStructureConstant.PERSONPOSFILE_ENTITYID},
+                        FormConstant.USEORG_KEY,
+                        FormConstant.ORG_KEY,
+                        PositionStructureConstant.NCKD_POSITIONHR,
+                        PositionStructureConstant.NCKD_JOBSEQHR,
+                        PositionStructureConstant.NCKD_PROTITLELEVEL,
+                        PositionStructureConstant.NCKD_OCPQUALLEVEL,
+                        PositionStructureConstant.NCKD_DIPLOMA,
+                        PositionStructureConstant.NCKD_ALLSUMSCORE,
+                        PositionStructureConstant.NCKD_BEGINDATE,
+                        PositionStructureConstant.NCKD_JOBLEVELHR,
+                        PositionStructureConstant.NCKD_PERSON)
+                .addGroup(new String[]{FormConstant.HRPI_PERPROTITLE}, FormConstant.PROLEVEL_KEY)
+                .addGroup(new String[]{FormConstant.HRPI_PEROCPQUAL}, FormConstant.QUALIFICATION_KEY)
+                .addGroup(new String[]{FormConstant.HRPI_PEREDUEXP}, FormConstant.EDUCATION_KEY)
+                .add(FormConstant.COMPANY_KEY)
+                .addIdNumberName(FormConstant.COMPANY_KEY)
+                .add(FormConstant.ADMINORG)
+                .addIdNumberName(FormConstant.POSITION_KEY)
+                .addGroup(new String[]{FormConstant.HBPM_POSITIONHR}, PositionStructureConstant.NCKD_JOBSEQ)
+                .addGroup(new String[]{FormConstant.HBPM_POSITIONHR, PositionStructureConstant.NCKD_JOBSEQ}, FormConstant.NUMBER_KEY);
+    }
+
+    /**
+     * 构建基础查询条件
+     */
+    private QFilter buildBaseQueryFilter() {
+        return new QFilter(String.join(".", PositionStructureConstant.PERSONPOSFILE_ENTITYID,
+                PositionStructureConstant.NCKD_ISCURRENTNEWEST), QCP.equals, EnableEnum.YES.getCode())
+                .and(new QFilter(String.join(".", PositionStructureConstant.PERSONPOSFILE_ENTITYID,
+                        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(FormConstant.IS_PRIMARY, 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));
+    }
+
+    /**
+     * 处理快速过滤条件
+     */
+    private void processFastFilter(ReportQueryParam reportQueryParam, QFilter qFilter) {
+        FastFilter fastFilter = reportQueryParam.getFilter().getFastFilter();
+        if (fastFilter != null) {
+            List<Map<String, List<Object>>> fastFilterList = fastFilter.getFastFilter();
+            for (Map<String, List<Object>> stringListMap : fastFilterList) {
+                //nckd_empnumfastfilter转为employee.empnumber,nckd_namefastfilter转换为employee.name,转换后添加到fields中
+                List<Object> fieldList = stringListMap.get("FieldName");
+                List<Object> valueList = stringListMap.get("Value");
+                String[] fields = new String[fieldList.size()];
+                for (int i = 0; i < fieldList.size(); i++) {
+                    fields[i] = fieldList.get(i).toString()
+                            .replace("nckd_empnumfastfilter", "employee.empnumber")
+                            .replace("nckd_namefastfilter", "employee.name");
+                }
+                //valueList转到values
+                String[] values = new String[valueList.size()];
+                for (int i = 0; i < valueList.size(); i++) {
+                    values[i] = valueList.get(i).toString();
+                }
+                qFilter.and(QFilter.ftlike(values, fields));
+            }
+        }
+    }
+
+    /**
+     * 处理过滤条件
+     */
+    private void processFilter(ReportQueryParam reportQueryParam, QFilter qFilter) {
+        List<QFilter> qFilters = reportQueryParam.getFilter().getQFilters();
+        for (QFilter filter : qFilters) {
+            //由于页面命名限制不允许配置对应数据库字段(例如:position.id的格式),这里只能手动转换为数据库实际的字段
+            String property = filter.getProperty();
+            if ("nckd_positionfilter.id".equalsIgnoreCase(property)){
+                qFilter.and(new QFilter(String.join(".", FormConstant.POSITION_KEY, FormConstant.ID_KEY),filter.getCP(),filter.getValue()));
+            }else if("nckd_orgfilter.id".equalsIgnoreCase(property)){
+                qFilter.and(new QFilter(String.join(".", FormConstant.COMPANY_KEY, FormConstant.ID_KEY),filter.getCP(),filter.getValue()));
+            }else if("nckd_joblevelhrffilter.id".equalsIgnoreCase(property)){
+                qFilter.and(new QFilter(String.join(".", PositionStructureConstant.PERSONPOSFILE_ENTITYID, PositionStructureConstant.NCKD_JOBLEVELHR,FormConstant.ID_KEY),filter.getCP(),filter.getValue()));
+            }else if("nckd_begindatefilter".equalsIgnoreCase(property)){
+                qFilter.and(new QFilter(String.join(".", PositionStructureConstant.PERSONPOSFILE_ENTITYID, PositionStructureConstant.NCKD_BEGINDATE),filter.getCP(),filter.getValue()));
+            }
+        }
+    }
 
-        //TODO 【待修改】-快速筛选不生效
-        DataSet dataSet = event.getDataSet();
-        // 对标准报表结果进行扩展:新增过滤条件
+    /**
+     * 添加扩展过滤条件
+     */
+    private DataSet addExtendedFilters(DataSet dataSet) {
         StringJoiner filters = new StringJoiner(" and ");
-        //主组织分配
-        filters.add("hrpi_assignment.isprimary = true");
         //职位档案不为空
-        filters.add("nckd_personposfile.nckd_person is not null");
+        filters.add(String.join(".", PositionStructureConstant.PERSONPOSFILE_ENTITYID, PositionStructureConstant.NCKD_PERSON) + " is not null");
+
         StringJoiner joinOr = new StringJoiner(" or ");
         //职称等级变化
-        joinOr.add("nckd_personposfile.nckd_protitlelevel <> hrpi_perprotitle.prolevel");
-        //技能等级不变化
-        joinOr.add("nckd_personposfile.nckd_ocpquallevel <> hrpi_perocpqual.qualification");
+        joinOr.add("(" + String.join(".", FormConstant.HBPM_POSITIONHR, PositionStructureConstant.NCKD_JOBSEQ, FormConstant.NUMBER_KEY) + " <> '03' and " +
+                String.join(".", PositionStructureConstant.PERSONPOSFILE_ENTITYID, PositionStructureConstant.NCKD_PROTITLELEVEL) + " <> " +
+                String.join(".", FormConstant.HRPI_PERPROTITLE, FormConstant.PROLEVEL_KEY) + ")");
+        //技能等级变化
+        joinOr.add("(" + String.join(".", FormConstant.HBPM_POSITIONHR, PositionStructureConstant.NCKD_JOBSEQ, FormConstant.NUMBER_KEY) + " = '03' and " +
+                String.join(".", PositionStructureConstant.PERSONPOSFILE_ENTITYID, PositionStructureConstant.NCKD_OCPQUALLEVEL) + " <> " +
+                String.join(".", FormConstant.HRPI_PEROCPQUAL, FormConstant.QUALIFICATION_KEY) + ")");
         //学历变化
-        joinOr.add("nckd_personposfile.nckd_diploma <> hrpi_pereduexp.education");
+        joinOr.add(String.join(".", PositionStructureConstant.PERSONPOSFILE_ENTITYID, PositionStructureConstant.NCKD_DIPLOMA) + " <> " +
+                String.join(".", FormConstant.HRPI_PEREDUEXP, FormConstant.EDUCATION_KEY));
         //公司变化
-        joinOr.add("nckd_personposfile.useorg <> hrpi_empposorgrel.company");
+        joinOr.add(String.join(".", PositionStructureConstant.PERSONPOSFILE_ENTITYID, FormConstant.USEORG_KEY) + " <> " + FormConstant.COMPANY_KEY);
+        //部门变化
+        joinOr.add(String.join(".", PositionStructureConstant.PERSONPOSFILE_ENTITYID, FormConstant.ORG_KEY) + " <> " + FormConstant.ADMINORG);
         //序列变化
-        // filters.add("nckd_personposfile.nckd_diploma <> hrpi_pereduexp.education");
-        filters.add("("+joinOr.toString()+")");
-        DataSet filter = dataSet.filter(filters.toString());
-        event.setDataSet(filter);
+        joinOr.add(String.join(".", PositionStructureConstant.PERSONPOSFILE_ENTITYID, PositionStructureConstant.NCKD_JOBSEQHR) + " <> " +
+                String.join(".", FormConstant.HBPM_POSITIONHR, PositionStructureConstant.NCKD_JOBSEQ));
+
+        filters.add("(" + joinOr.toString() + ")");
+        return dataSet.filter(filters.toString());
+    }
+
+    /**
+     * 添加调整类型字段
+     */
+    private DataSet addAdjustTypeFields(DataSet dataSet) {
+        DataSet newDataSet = dataSet.addField("CASE WHEN " +
+                String.join(".", PositionStructureConstant.PERSONPOSFILE_ENTITYID, PositionStructureConstant.NCKD_DIPLOMA) +
+                " <> " +
+                String.join(".", FormConstant.HRPI_PEREDUEXP, FormConstant.EDUCATION_KEY) +
+                " THEN '学历变化' END", "a");
+
+        newDataSet = newDataSet.addField("CASE WHEN (" +
+                String.join(".", FormConstant.HBPM_POSITIONHR, PositionStructureConstant.NCKD_JOBSEQ, FormConstant.NUMBER_KEY) +
+                " <> '03' and " +
+                String.join(".", PositionStructureConstant.PERSONPOSFILE_ENTITYID, PositionStructureConstant.NCKD_PROTITLELEVEL) +
+                " <> " +
+                String.join(".", FormConstant.HRPI_PERPROTITLE, FormConstant.PROLEVEL_KEY) +
+                ") THEN '职称变化' END", "b");
+
+        newDataSet = newDataSet.addField("CASE WHEN (" +
+                String.join(".", FormConstant.HBPM_POSITIONHR, PositionStructureConstant.NCKD_JOBSEQ, FormConstant.NUMBER_KEY) +
+                " = '03' and " +
+                String.join(".", PositionStructureConstant.PERSONPOSFILE_ENTITYID, PositionStructureConstant.NCKD_OCPQUALLEVEL) +
+                " <> " +
+                String.join(".", FormConstant.HRPI_PEROCPQUAL, FormConstant.QUALIFICATION_KEY) +
+                ") THEN '技能变化' END", "c");
+
+        newDataSet = newDataSet.addField("CASE WHEN " +
+                String.join(".", PositionStructureConstant.PERSONPOSFILE_ENTITYID, FormConstant.USEORG_KEY) +
+                " <> " + FormConstant.COMPANY_KEY +
+                " THEN '跨单位变动' END", "d");
+
+        newDataSet = newDataSet.addField("CASE WHEN " +
+                String.join(".", PositionStructureConstant.PERSONPOSFILE_ENTITYID, FormConstant.ORG_KEY) +
+                " <> " + FormConstant.ADMINORG +
+                " THEN '单位内变动' END", "e");
+
+        newDataSet = newDataSet.addField("CASE WHEN " +
+                String.join(".", PositionStructureConstant.PERSONPOSFILE_ENTITYID, PositionStructureConstant.NCKD_JOBSEQHR) +
+                " <> " +
+                String.join(".", FormConstant.HBPM_POSITIONHR, PositionStructureConstant.NCKD_JOBSEQ) +
+                " THEN '跨序列变动' END", "f");
+
+        // 合并调整类型字符串
+        newDataSet = newDataSet.addField(buildAdjustTypeConcatExpression(), "nckd_adjusttypestr");
+        return newDataSet;
     }
+
+    /**
+     * 构建调整类型连接表达式
+     */
+    private String buildAdjustTypeConcatExpression() {
+        return "CONCAT(" +
+                "CASE WHEN a IS NOT NULL AND a != '' THEN CONCAT(a,',') ELSE '' END," +
+                "CASE WHEN b IS NOT NULL AND b != '' THEN CONCAT(b,',') ELSE '' END," +
+                "CASE WHEN c IS NOT NULL AND c != '' THEN CONCAT(c,',') ELSE '' END," +
+                "CASE WHEN d IS NOT NULL AND d != '' THEN CONCAT(d,',') ELSE '' END," +
+                "CASE WHEN e IS NOT NULL AND e != '' THEN CONCAT(e,',') ELSE '' END," +
+                "CASE WHEN f IS NOT NULL AND f != '' THEN CONCAT(f,',') ELSE '' END" +
+                ")";
+    }
+
+    /**
+     * 处理报表过滤条件
+     */
+    private String processOtherFilter(ReportQueryParam reportQueryParam) {
+        StringJoiner joinAnd = new StringJoiner(" and ");
+        joinAnd.add("1=1");
+
+        List<FilterItemInfo> filterItems = reportQueryParam.getFilter().getFilterItems();
+        for (FilterItemInfo filterItem : filterItems) {
+            String propName = filterItem.getPropName();
+            if ("nckd_adjusttypefilter".equalsIgnoreCase(propName)) {
+                processAdjustTypeFilter(filterItem, joinAnd);
+            }
+        }
+
+        return joinAnd.toString();
+    }
+
+    /**
+     * 处理调整类型过滤条件
+     */
+    private void processAdjustTypeFilter(FilterItemInfo filterItem, StringJoiner joinAnd) {
+        if (filterItem.getValue() instanceof List) {
+            List<String> values = ConvertUtil.toList(filterItem.getValue());
+            StringJoiner or = new StringJoiner(" or ");
+            for (String value : values) {
+                or.add("nckd_adjusttypestr like '%" + value + "%'");
+            }
+            joinAnd.add("(" + or.toString() + ")");
+        } else {
+            joinAnd.add("nckd_adjusttypestr like '%" + filterItem.getValue() + "%'");
+        }
+    }
+
 }

+ 5 - 0
code/opmc/nckd-jxccl-opmc/src/main/java/nckd/jxccl/opmc/pm/plugin/form/PerfManagerWizardFormPlugin.java

@@ -94,6 +94,11 @@ public class PerfManagerWizardFormPlugin extends AbstractFormPlugin implements T
                 break;
             case BTN_PREV_KEY:
                 this.previous();
+            default:
+                // 处理未预期的按钮点击事件
+                this.getView().showTipNotification(ResManager.loadKDString(
+                        "不支持的操作", "WizardPlugin_unsupported_action", "bos-designer-plugin", new Object[0]));
+                break;
         }
 
     }

+ 2 - 1
config.gradle

@@ -17,7 +17,7 @@ if(cosmic_libs_path == null){
 if(cosmic_home == null){
 	//println "NO 'cosmic_home' property was setted in gradle.properties. "
 	//println "Try to find the 'COSMIC_HOME' property in System environment."
-	cosmic_home = System.getenv('COSMIC_HOME')
+	//cosmic_home = System.getenv('COSMIC_HOME')
 }
 
 if(cosmic_home != null){
@@ -38,6 +38,7 @@ ext	{
 		bos : "${cosmic_libs_path}/bos",
 		biz : "${cosmic_libs_path}/biz",
 		cus : "${cosmic_libs_path}/cus",
+        zip : "${cosmic_libs_path}/zip",
 		outputdir : "${cosmic_libs_path}/outputdir",
 		lib : rootDir.path + "/code/lib"
 	]

+ 2 - 1
gradle.properties

@@ -18,7 +18,8 @@ systemProp.developer_flag=nckd
 #--This is the project flag
 systemProp.project_flag=jxccl
 #--This is the project dir
-systemProp.project_dir=E:/2.work/xuding/jxccl
+systemProp.project_dir=/var/jenkins_home/
+systemProp.cosmic_libs_path=/var/jenkins_home/lib
 #--This is the dir for cosmic project libs and static resouces
 systemProp.cosmic_home=E:/cosmic/jtlocal
 #--Performance configuration for gradle build

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff