Răsfoiți Sursa

feat(hr):计算及完善职级体系逻辑

- QueryFieldBuilder 类型改为 final 并增强功能
- 绩效管理助手添加评分字段支持
- 职位层级计算服务中修正字段引用及逻辑处理
- 新增职位结构常量用于排名和职称技能等级显示
- 重构 JobFamilyInfo 为 JobSeqInfo 更准确表达含义
- 增强 JobScoreInfo 和 RankingResultInfo 结构以支持更多属性- 更新职级调整类型判断逻辑及相关数据结构
-优化学历积分处理方法参数及内部实现
- 改进序列转换及跨单位调动的处理流程
- 调整考核结果使用情况下的职级计算策略
- 查询构建器支持更灵活的字段组合方式
- 修复字符串拼接中的空格缺失问题提升可读性
wyc 1 lună în urmă
părinte
comite
6042f1a59c

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

@@ -197,5 +197,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";
 
 }

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

@@ -19,7 +19,7 @@ public enum JobSeqEnum {
     /** 技能序列 */
     SKILL("03", "技能序列"),
     /** 管理序列 */
-    MANAGE("03", "技能序列");
+    MANAGE("04", "管理序列");
 
     private static final Map<String, JobSeqEnum> CODE_MAP = new HashMap<>();
 

+ 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));
+    }
     /**
      * 比较两个日期时间
      *

+ 21 - 2
code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/utils/QueryFieldBuilder.java

@@ -11,7 +11,7 @@ import java.util.StringJoiner;
 * @date 2025/9/27 16:34
 * @version 1.0
 */
-public class QueryFieldBuilder {
+public final class QueryFieldBuilder {
 
     private final StringJoiner selectJoiner;
     private final StringJoiner orderJoiner;
@@ -101,6 +101,22 @@ public class QueryFieldBuilder {
         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 ----------------
 
     /**
@@ -244,8 +260,11 @@ public class QueryFieldBuilder {
         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;

+ 2 - 1
code/base/nckd-jxccl-base-helper/src/main/java/nckd/jxccl/base/pm/helper/PerformanceManagerHelper.java

@@ -45,7 +45,8 @@ public class PerformanceManagerHelper {
 
         String selectField = String.join(",",
                 String.join(".",PERFMANAGER_ENTRY_ENTITYID, NCKD_APPRAISALYEAR),
-                String.join(".",PERFMANAGER_ENTRY_ENTITYID, NCKD_APPRAISALRESULT)
+                String.join(".",PERFMANAGER_ENTRY_ENTITYID, NCKD_APPRAISALRESULT),
+                String.join(".",PERFMANAGER_ENTRY_ENTITYID, FormConstant.NCKD_SCORE)
         );
         DynamicObject[] load = BusinessDataServiceHelper.load(PERFMANAGER_ENTITYID, selectField, new QFilter[]{filter}, FormConstant.CREATE_TIME_KEY + " desc");
         if(load != null && load.length > 0){

+ 165 - 106
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/business/JobLevelCalculatorService.java

@@ -92,9 +92,9 @@ public class JobLevelCalculatorService {
             throw new ValidationException(StrFormatter.format("当前无法为【{}】进行调整,因为他/她尚未建立职位档案。请前往“职位及积分初定” -> 进行初定!", person.getString(FormConstant.NAME_KEY)));
         }
         //对应SHR:oldHRJobFamilyid
-        long currentJobSeqId = currentPersonPosFile.getLong(String.join(".",PositionStructureConstant.NCKD_JOBLEVELHR,FormConstant.ID_KEY));
+        long currentJobSeqId = currentPersonPosFile.getLong(String.join(".",PositionStructureConstant.NCKD_JOBSEQHR,FormConstant.ID_KEY));
         //oldHRJobFamilynumber
-        String currentJobSeqNumber = currentPersonPosFile.getString(String.join(".",PositionStructureConstant.NCKD_JOBLEVELHR,FormConstant.NUMBER_KEY));
+        String currentJobSeqNumber = currentPersonPosFile.getString(String.join(".",PositionStructureConstant.NCKD_JOBSEQHR,FormConstant.NUMBER_KEY));
         DynamicObject appraisalResult = currentPersonPosFile.getDynamicObject(PositionStructureConstant.NCKD_APPRAISALRESULT);
         //appraisalresultnumber
         String appraisalResultNumber = appraisalResult != null ? appraisalResult.getString(FormConstant.NUMBER_KEY) : null;
@@ -117,7 +117,7 @@ public class JobLevelCalculatorService {
         appraisalResultNumber = validateAppraisalResultConsistency(date, personId, personName, appraisalResultNumber);
 
 
-        //当前总积分(变动前
+        //调动前累计的积分池(生涯积分
         BigDecimal sumScore = currentPersonPosFile.getBigDecimal(PositionStructureConstant.NCKD_SUMSCORE);
 
         //当前职级(变动前)
@@ -127,7 +127,7 @@ public class JobLevelCalculatorService {
             throw new ValidationException(StrFormatter.format("数据异常,人员【{}】最新职位档案【档案ID:{}】没有职级。请联系管理员处理!", personName, currentPersonPosFile.getLong(FormConstant.ID_KEY)));
         }
         //对应SHR:lastjobgradeindex
-        int currentJobLevelIndex = currentJobLevel.getInt(FormConstant.INDEX_KEY);
+        int currentJobLevelIndex = currentJobLevel.getInt(FormConstant.JOBLEVELSEQ);
 
         // 5.是否首次聘任
         //对应SHR:fistPR;922~926行
@@ -141,27 +141,28 @@ public class JobLevelCalculatorService {
         //对应SHR:minuspersonappraisal;930~945行
         int minusPersonAppraisal = calcMinusPersonAppraisal(appraisalResultNumber, firstPR, usedAppraisalResult, jobLevelResult);
 
-        // 8. 处理学历积分
-        //对应SHR:DiplomaScore;947~978行
-        BigDecimal diplomaScore = handleDiplomaScore(currentPersonPosFile, positionAppointment);
-
         // 9.如果是管理序列,则按职能序列进行调整
         //对应SHR:hrjobfamilynumber;989~993
         jobSeq = handleJobSeq(jobSeq);
 
         // 9. 处理序列相关信息
         //对应SHR:995~1013
-        JobFamilyInfo jobFamilyInfo = processJobFamilyInfo(jobSeq, currentPersonPosFile);
+        JobSeqInfo jobSeqInfo = processJobFamilyInfo(jobSeq, currentPersonPosFile);
 
         // 10. 处理职称/技能等级积分
         // 对应SHR:1015~1036
         JobScoreInfo jobScoreInfo = handleJobScores(jobSeq, currentPersonPosFile, positionAppointment);
 
+        // 8. 处理学历积分
+        //对应SHR:DiplomaScore;947~978行
+        BigDecimal diplomaScore = handleDiplomaScore(currentPersonPosFile, positionAppointment,jobScoreInfo);
+
         // 计算总积分;累计积分池的分 + 学历 + (职称分/技能分)
         // 对应SHR:allsumScore;1041行
         BigDecimal allSumScore = sumScore.add(diplomaScore)
                 .add(jobScoreInfo.perProTitleScore)
                 .add(jobScoreInfo.quaLevelScore);
+        jobScoreInfo.allSumScore = allSumScore;
 
         // 11. 获取序列对应的职级
         DynamicObject[] jobLevelByJobSeq = getJobLevelByJobSeq(jobSeq);
@@ -179,24 +180,30 @@ public class JobLevelCalculatorService {
 
         // 12. 根据不同情况计算职级ID
         //对应SHR:JobGradeInfo;1044~1071行
-        DynamicObject jobLevel = calculateJobGradeId(jobSeq,jobScoreInfo,jobFamilyInfo,allSumScore,currentJobLevelIndex,minusPersonAppraisal,firstPR,jobLevelResult);
+        DynamicObject jobLevel = calculateJobGradeId(jobSeq,jobScoreInfo, jobSeqInfo,allSumScore,currentJobLevelIndex,minusPersonAppraisal,firstPR,jobLevelResult);
         //对应SHR:newjobgradeindex
         int jobLevelIndex = jobLevel.getInt(FormConstant.JOBLEVELSEQ);
         // 13. 处理排名相关信息
         //对应SHR:allowancerankpercent、toprankpercent、allowancerank、toprank;1073~1109行
-        RankingResult rankingInfo = getRankingInfo(personId, personName, date);
+        RankingResultInfo rankingInfo = getRankingInfo(personId, personName, date);
 
         // 14. 处理序列转换或跨单位调动的特殊逻辑
         // SHR对应:1110~1136行
-        if (jobFamilyInfo.isSequenceChange) {
+        if (jobSeqInfo.isSequenceChange) {
             System.out.println("序列切换,新职级号不能大于原序列职级序号");
             handleSequenceChange(jobLevelIndex,currentJobLevelIndex,rankingInfo,jobLevelMap,jobLevelResult,jobSeq);
             jobLevel = jobLevelResult.jobLevel;
+            jobLevelResult.jobSeqInfo = jobSeqInfo;
+            jobLevelResult.jobScoreInfo = jobScoreInfo;
+            jobLevelResult.rankingResultInfo = rankingInfo;
             return jobLevelResult;
-        } else if (jobFamilyInfo.isCrossUnitTransfer) {
+        } else if (jobSeqInfo.isCrossUnitTransfer) {
             System.out.println("跨单位调动且未跨序列,平移处理");
             handleCrossUnitTransfer(jobLevelIndex,currentJobLevelIndex,rankingInfo,jobLevelMap,jobLevelResult,jobSeq);
             jobLevel = jobLevelResult.jobLevel;
+            jobLevelResult.jobSeqInfo = jobSeqInfo;
+            jobLevelResult.jobScoreInfo = jobScoreInfo;
+            jobLevelResult.rankingResultInfo = rankingInfo;
             return jobLevelResult;
         }
 
@@ -220,6 +227,9 @@ public class JobLevelCalculatorService {
         jobLevel = determineFinalJobGradeId(jobLevelIndex, jobScoreInfo, jobLevelMap, jobSeq, jobLevelResult, appraisalResultNumber);
         jobLevelResult.jobLevel = jobLevel;
 
+        jobLevelResult.jobSeqInfo = jobSeqInfo;
+        jobLevelResult.jobScoreInfo = jobScoreInfo;
+        jobLevelResult.rankingResultInfo = rankingInfo;
         return jobLevelResult;
     }
 
@@ -304,7 +314,8 @@ public class JobLevelCalculatorService {
      */
     public static DynamicObject handleJobSeq(DynamicObject jobSeq) {
         if(JobSeqEnum.MANAGE.getCode().equalsIgnoreCase(jobSeq.getString(FormConstant.NUMBER_KEY))){
-            QFilter filter = QFilterCommonHelper.getEnableFilter().and(QFilterCommonHelper.getDataStatusFilter()).and(new QFilter(FormConstant.NUMBER_KEY, QCP.equals, JobSeqEnum.FUNCTIONAL.getCode()));
+            QFilter filter = QFilterCommonHelper.getEnableFilter().and(QFilterCommonHelper.getDataStatusFilter()).and(QFilterCommonHelper.getCurrentVersionFilter())
+                    .and(new QFilter(FormConstant.NUMBER_KEY, QCP.equals, JobSeqEnum.FUNCTIONAL.getCode()));
             QueryFieldBuilder queryFieldBuilder = QueryFieldBuilder.create()
                     .addIdNumberName(FormConstant.HBJM_JOBSEQHR);
 
@@ -371,6 +382,7 @@ public class JobLevelCalculatorService {
                 .add(PositionStructureConstant.NCKD_EXECUTEYEAR)
                 .add(PositionStructureConstant.NCKD_FIRSTRANK)
                 .add(PositionStructureConstant.NCKD_ADJUSTTYPE)
+                .add(PositionStructureConstant.NCKD_BEGINDATE)
                 .addIdNumberNameWithExtras(
                         new String[]{PositionStructureConstant.NCKD_JOBLEVELHR},
                         FormConstant.JOBLEVELSEQ
@@ -405,7 +417,7 @@ public class JobLevelCalculatorService {
 
             //条件3:调整年是否有升降级或首聘(adjusttype:0、2),如果调整年有升降级记录(调整类型为0-降级或2-升级),则考核结果被使用。;返回true
             if(dataSet.copy()
-                    .filter(PositionStructureConstant.NCKD_EXECUTEYEAR + "=" + year +"and "+PositionStructureConstant.NCKD_ADJUSTTYPE + "in ('0','2')")
+                    .filter(PositionStructureConstant.NCKD_EXECUTEYEAR + "=" + year +" and "+PositionStructureConstant.NCKD_ADJUSTTYPE + " in ('0','2')")
                     .count(PositionStructureConstant.NCKD_EXECUTEYEAR, false) > 0){
                 return Boolean.TRUE;
             }
@@ -422,7 +434,7 @@ public class JobLevelCalculatorService {
 
             //条件5:调整年是否只有保级/序列变化等记录,如果调整年只有保级、序列变化、聘任下调、总分不足或无聘任的记录,则考核结果未被使用(adjusttype in ('1','4','5','6','7'));返回false
             int count5 = dataSet.copy()
-                    .filter(PositionStructureConstant.NCKD_EXECUTEYEAR + "=" + year +"and "+PositionStructureConstant.NCKD_ADJUSTTYPE + "in ('1','4','5','6','7')")
+                    .filter(PositionStructureConstant.NCKD_EXECUTEYEAR + "=" + year +" and "+PositionStructureConstant.NCKD_ADJUSTTYPE + " in ('1','4','5','6','7')")
                     .count(PositionStructureConstant.NCKD_EXECUTEYEAR, false);
             if(count5 > 0 && count5 == nowyeardo){
                 return Boolean.FALSE;
@@ -430,35 +442,47 @@ public class JobLevelCalculatorService {
 
 
             //条件6:调整年聘任职级是否变化,如果调整年职级有变化(最大职级-最小职级),则考核结果被使用。返回true
-            try(DataSet finish = dataSet.copy()
-                    .select("max(" + String.join(".", PositionStructureConstant.NCKD_JOBLEVELHR, FormConstant.JOBLEVELSEQ) + ") - min(" + String.join(".", PositionStructureConstant.NCKD_JOBLEVELHR, FormConstant.JOBLEVELSEQ) + ") as differ")
+            DataSet maxSeqDs = dataSet.copy()
                     .filter(PositionStructureConstant.NCKD_EXECUTEYEAR + "=" + year)
                     .groupBy(new String[]{PositionStructureConstant.NCKD_EXECUTEYEAR})
-                    .finish()) {
-                if (finish.hasNext()) {
-                    Row row = finish.next();
-                    Integer differ = row.getInteger("differ");
-                    if (differ != 0) {
-                        return Boolean.TRUE;
-                    }
-                }
+                    .max(String.join(".", PositionStructureConstant.NCKD_JOBLEVELHR, FormConstant.JOBLEVELSEQ), "maxSeq").finish();
+            int maxSeq = 0;
+            if(maxSeqDs.hasNext()){
+                Row row = maxSeqDs.next();
+                maxSeq = row.getInteger("maxSeq");
+            }
+            DataSet minSeqDs = dataSet.copy()
+                    .filter(PositionStructureConstant.NCKD_EXECUTEYEAR + "=" + year)
+                    .groupBy(new String[]{PositionStructureConstant.NCKD_EXECUTEYEAR})
+                    .min(String.join(".", PositionStructureConstant.NCKD_JOBLEVELHR, FormConstant.JOBLEVELSEQ), "maxSeq").finish();
+            int minSeq = 0;
+            if(minSeqDs.hasNext()){
+                Row row = minSeqDs.next();
+                minSeq = row.getInteger("maxSeq");
+            }
+            Integer differ = maxSeq - minSeq;
+            if (differ != 0) {
+                return Boolean.TRUE;
             }
 
+
             //条件7:相比往年聘任职级是否有变化,如果调整年相比往年聘任职级有变化,则考核结果被使用。返回true
             //获取当年最大职级
             DataSet currentYear = dataSet.copy()
                     .orderBy(new String[]{PositionStructureConstant.NCKD_EXECUTEYEAR + " desc", PositionStructureConstant.NCKD_BEGINDATE + " desc"})
                     .filter(PositionStructureConstant.NCKD_EXECUTEYEAR + "=" + year)
-                    .groupBy(new String[]{PositionStructureConstant.NCKD_EXECUTEYEAR}).max(String.join(".", PositionStructureConstant.NCKD_JOBLEVELHR, FormConstant.JOBLEVELSEQ), "maxLevel").finish();
+                    .groupBy(new String[]{PositionStructureConstant.NCKD_EXECUTEYEAR})
+                    .max(String.join(".", PositionStructureConstant.NCKD_JOBLEVELHR, FormConstant.JOBLEVELSEQ), "maxLevel").finish();
 
             Integer maxLevel = 0;
             if (currentYear.hasNext()) {
                 Row row = currentYear.next();
                 maxLevel = row.getInteger("maxLevel");
             }
+
             //获取往年职级
             DataSet pastYear = dataSet.copy()
-                    .select(String.join(".", PositionStructureConstant.NCKD_JOBLEVELHR, FormConstant.JOBLEVELSEQ) +"as level")
+                    .select(String.join(".", PositionStructureConstant.NCKD_JOBLEVELHR, FormConstant.JOBLEVELSEQ) +" as level,"+PositionStructureConstant.NCKD_EXECUTEYEAR+","+PositionStructureConstant.NCKD_BEGINDATE)
                     .filter(PositionStructureConstant.NCKD_EXECUTEYEAR + "<" + year)
                     .topBy(1, new String[]{PositionStructureConstant.NCKD_EXECUTEYEAR + " desc", PositionStructureConstant.NCKD_BEGINDATE + " desc"});
             Integer level = 0;
@@ -503,14 +527,14 @@ public class JobLevelCalculatorService {
                 new QFilter[]{filer},
                 queryFieldBuilder.buildOrder()
         );
-        //如果没有符合条件的记录,或者只有一条记录且为初定且考核结果为空或为考核结果为“无”,为首次聘任
+        //历史档案没有聘任过职称或技能(根据技能分与职称分判定),或者只有一条记录且为初定且考核结果为空或为考核结果为“无”,为首次聘任
         if(query.isEmpty()){
             return Boolean.TRUE;
         }else if(query.size() == 1){
             DynamicObject dynamicObject = query.get(0);
             boolean firstRank = dynamicObject.getBoolean(PositionStructureConstant.NCKD_FIRSTRANK);
-            long appraisalResult = dynamicObject.getLong(String.join(PositionStructureConstant.NCKD_APPRAISALRESULT, FormConstant.ID_KEY));
-            String appraisalResultNumber = dynamicObject.getString(String.join(PositionStructureConstant.NCKD_APPRAISALRESULT, FormConstant.NUMBER_KEY));
+            long appraisalResult = dynamicObject.getLong(String.join(".",PositionStructureConstant.NCKD_APPRAISALRESULT, FormConstant.ID_KEY));
+            String appraisalResultNumber = dynamicObject.getString(String.join(".",PositionStructureConstant.NCKD_APPRAISALRESULT, FormConstant.NUMBER_KEY));
             //考核结果为空或为考核结果为“无”
             boolean none = StringUtils.isBlank(appraisalResultNumber)  || AppraisalResultEnum.NONE.getCode().equals(appraisalResultNumber);
             return firstRank && none;
@@ -590,7 +614,7 @@ public class JobLevelCalculatorService {
      * @author W.Y.C
      * @date: 2025/09/20 22:06
      */
-    public static BigDecimal handleDiplomaScore(DynamicObject currentPersonPosFileByPerson, DynamicObject positionAppointment) {
+    public static BigDecimal handleDiplomaScore(DynamicObject currentPersonPosFileByPerson, DynamicObject positionAppointment,JobScoreInfo scoreInfo) {
 
         //获取上一笔职位档案变动记录学历信息
         //对应SHR:lastDiplomaInfo
@@ -601,11 +625,14 @@ public class JobLevelCalculatorService {
         String diplomaName = diploma.getString(FormConstant.NAME_KEY);
         //对应SHR:lastDiplomaInfo.getDescription()
         BigDecimal diplomaScore = diploma.getBigDecimal(FormConstant.NCKD_SCORE);
+        scoreInfo.diplomaId = diplomaId;
+        scoreInfo.diplomaScore = diplomaScore;
         //获取上一笔职位档案变动记录学历积分
         //对应SHR:lastDiplomaScore
         BigDecimal lastDiplomaScore = currentPersonPosFileByPerson.getBigDecimal(PositionStructureConstant.NCKD_DIPLOMASCORE);
 
 
+
         //对应SHR:Diploma
         Long currentDiplomaId = positionAppointment.getLong(String.join(".", FormConstant.HRPI_PEREDUEXP, FormConstant.EDUCATION_KEY, FormConstant.ID_KEY));
         //对应SHR:newDiplomaInfo.getName()
@@ -633,9 +660,9 @@ public class JobLevelCalculatorService {
     /**
      * 处理序列相关信息
      */
-    private static JobFamilyInfo processJobFamilyInfo(DynamicObject newJobSeq,
-                                               DynamicObject currentPersonPosFileByPerson) {
-        JobFamilyInfo jobFamilyInfo = new JobFamilyInfo();
+    private static JobSeqInfo processJobFamilyInfo(DynamicObject newJobSeq,
+                                                   DynamicObject currentPersonPosFileByPerson) {
+        JobSeqInfo jobSeqInfo = new JobSeqInfo();
 
         long newJonSeqId = newJobSeq.getLong(FormConstant.ID_KEY);
         String newJonSeqNumber = newJobSeq.getString(FormConstant.NUMBER_KEY);
@@ -650,13 +677,13 @@ public class JobLevelCalculatorService {
 
         // 判断是否为序列转换
         //对应SHR:isadjusttype_4
-        jobFamilyInfo.isSequenceChange = !currentJonSeqNumber.equals(newJonSeqNumber);
+        jobSeqInfo.isSequenceChange = !currentJonSeqNumber.equals(newJonSeqNumber);
 
         // 判断是否为跨单位调动
-        jobFamilyInfo.isCrossUnitTransfer = false;
+        jobSeqInfo.isCrossUnitTransfer = false;
         // TODO【待修改】 这里需要根据具体业务逻辑判断是否为跨单位调动
 
-        return jobFamilyInfo;
+        return jobSeqInfo;
     }
 
 
@@ -683,6 +710,8 @@ public class JobLevelCalculatorService {
         //对应SHR:selMap.get("zgjbnumber")
         String newPerProTitleNumber = positionAppointment.getString(String.join(".", FormConstant.HRPI_PERPROTITLE, FormConstant.PROLEVEL_KEY, FormConstant.NUMBER_KEY));
         String newPerProTitleName = positionAppointment.getString(String.join(".", FormConstant.HRPI_PERPROTITLE, FormConstant.PROLEVEL_KEY, FormConstant.NAME_KEY));
+        //职称名称
+        String rankName = positionAppointment.getString(String.join(".", FormConstant.HRPI_PERPROTITLE, FormConstant.PROFESSIONAL_KEY, FormConstant.NAME_KEY));
 
         //当前人员最新技能等级
         Long newQuaLevelId = positionAppointment.getLong(String.join(".", FormConstant.HRPI_PEROCPQUAL, FormConstant.QUALEVEL_KEY, FormConstant.ID_KEY));
@@ -691,9 +720,11 @@ public class JobLevelCalculatorService {
         //对应SHR:selMap.get("zyjndjnumber")
         String newQuaLevelNumber = positionAppointment.getString(String.join(".", FormConstant.HRPI_PEROCPQUAL, FormConstant.QUALEVEL_KEY, FormConstant.NUMBER_KEY));
         String newQuaLevelName = positionAppointment.getString(String.join(".", FormConstant.HRPI_PEROCPQUAL, FormConstant.QUALEVEL_KEY, FormConstant.NAME_KEY));
-
+        //技能名称
+        String jobStatusName = positionAppointment.getString(String.join(".", FormConstant.HRPI_PEROCPQUAL, FormConstant.QUALIFICATION_KEY, FormConstant.NAME_KEY));
 
         JobScoreInfo jobScoreInfo = new JobScoreInfo();
+
         jobScoreInfo.isEndGainJobGrade = true;
 
         // 职称等级分
@@ -709,6 +740,7 @@ public class JobLevelCalculatorService {
             jobScoreInfo.perProTitleId = newPerProTitleId;
             jobScoreInfo.perProTitleNumber = StringUtils.isNotBlank(newPerProTitleNumber) ? newPerProTitleNumber : StringUtils.EMPTY;
             jobScoreInfo.perProTitleName = StringUtils.isNotBlank(newPerProTitleName) ? newPerProTitleName : StringUtils.EMPTY;
+            jobScoreInfo.rankName = rankName;
             jobScoreInfo.isEndGainJobGrade = false;
         } else if (JobSeqEnum.SKILL.getCode().equals(newJobSeqNumber)) {
             if (newQuaLevelScore != null && newQuaLevelScore.compareTo(BigDecimal.ZERO) > 0) {
@@ -717,6 +749,7 @@ public class JobLevelCalculatorService {
                 jobScoreInfo.quaLevelId = newQuaLevelId;
                 jobScoreInfo.quaLevelNumber = StringUtils.isNotBlank(newQuaLevelNumber) ? newQuaLevelNumber: "";
                 jobScoreInfo.quaLevelName = StringUtils.isNotBlank(newQuaLevelName) ? newQuaLevelName: "";
+                jobScoreInfo.jobStatusName = jobStatusName;
                 jobScoreInfo.isEndGainJobGrade = false;
             }
         }
@@ -729,8 +762,8 @@ public class JobLevelCalculatorService {
     /**
      * 计算职级ID
      */
-    private static DynamicObject calculateJobGradeId(DynamicObject newJobSeq, JobScoreInfo jobScoreInfo,JobFamilyInfo jobFamilyInfo,
-                                                          BigDecimal allSumScore, int currentJobLevelIndex, int minusPersonAppraisal, boolean fistPR, JobLevelResult jobLevelResult){
+    private static DynamicObject calculateJobGradeId(DynamicObject newJobSeq, JobScoreInfo jobScoreInfo, JobSeqInfo jobSeqInfo,
+                                                     BigDecimal allSumScore, int currentJobLevelIndex, int minusPersonAppraisal, boolean fistPR, JobLevelResult jobLevelResult){
 
         String perProTitleNumber = jobScoreInfo.perProTitleNumber;
         String quaLevelNumber = jobScoreInfo.quaLevelNumber;
@@ -742,12 +775,12 @@ public class JobLevelCalculatorService {
             jobLevel = getJobLevel(newJobSeq, null, null, null, null, Boolean.TRUE,Boolean.FALSE);
             //对应SHR:selMap.put("adjusttype", "7");
             jobLevelResult.adjustType = "7";
-        } else if (jobFamilyInfo.isSequenceChange) {
+        } else if (jobSeqInfo.isSequenceChange) {
             //对应SHR:JobGradeid = personpositionfileUtils.GainJobGrade(ctx, allsumScore, hrjobfamilynumber, zgjbnumber, zyjndjnumber, 0);
             jobLevel = getJobLevel(newJobSeq, allSumScore, perProTitleNumber, quaLevelNumber, 0, Boolean.FALSE,Boolean.FALSE);
             //序列转换
             jobLevelResult.adjustType = "4";
-        } else if (jobFamilyInfo.isCrossUnitTransfer) {
+        } else if (jobSeqInfo.isCrossUnitTransfer) {
             //对应SHR:JobGradeid = personpositionfileUtils.GainJobGrade(ctx, allsumScore, hrjobfamilynumber, zgjbnumber, zyjndjnumber, 0);
             jobLevel = getJobLevel(newJobSeq, allSumScore, perProTitleNumber, quaLevelNumber, 0, Boolean.FALSE,Boolean.FALSE);
             //保级
@@ -779,7 +812,7 @@ public class JobLevelCalculatorService {
      * @author W.Y.C
      * @date: 2025/09/22 14:19
      */
-    public static RankingResult getRankingInfo(Long personId, String personName,Date date) {
+    public static RankingResultInfo getRankingInfo(Long personId, String personName, Date date) {
         LocalDateTime lastYearDateTime = DateUtil.minusYears(DateUtil.toLocalDateTime(date), 1);
         //取上年度绩效排名
         QFilter groupFilter = new QFilter(PerfRankMgmtConstant.NCKD_THEYEAR,QCP.equals,lastYearDateTime.getYear());
@@ -843,7 +876,12 @@ public class JobLevelCalculatorService {
                 }
 
                 //获取R排名
-                DataSet allowanceRankDataSet = dataSet.copy().select(String.join(".", PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY, PerfRankMgmtConstant.NCKD_ALLOWANCERANK))
+                QueryFieldBuilder queryFieldBuilder = QueryFieldBuilder.create()
+                        .addGroup(new String[]{PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY},
+                                PerfRankMgmtConstant.NCKD_ALLOWANCERANK)
+                        .addGroup(new String[]{PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY,PerfRankMgmtConstant.NCKD_PERSON},
+                                FormConstant.ID_KEY);
+                DataSet allowanceRankDataSet = dataSet.copy().select(queryFieldBuilder.buildSelect())
                         .filter(String.join(".", PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY, PerfRankMgmtConstant.NCKD_PERSON, FormConstant.ID_KEY) + " = " + personId);
                 //对应SHR:allowancerank
                 Integer allowanceRank = null;
@@ -863,23 +901,23 @@ public class JobLevelCalculatorService {
                     throw new ValidationException(StrFormatter.format("R排名百分比必须在0-100之间,请检查排名分组配置是否有误。人员【{}】-排名分组【{}】-R排名总人数【{}】-排名【{}】",personName,groupName,countR,allowanceRank));
                 }
 
-                RankingResult rankingResult = new RankingResult();
+                RankingResultInfo rankingResultInfo = new RankingResultInfo();
                 //获取R排名最后名次
                 DataSet maxAllowanceRank = dataSet.copy().filter(String.join(".", PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY, PerfRankMgmtConstant.NCKD_ISRANKING) + " = true and " + String.join(".", PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY, PerfRankMgmtConstant.NCKD_POSTALLOWANCE) + " = true")
                         .groupBy()
                         .max(String.join(".", PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY, PerfRankMgmtConstant.NCKD_ALLOWANCERANK), "maxAllowanceRank").finish();
                 if(maxAllowanceRank.hasNext()){
                     Row row = maxAllowanceRank.next();
-                    rankingResult.maxAllowanceRank = row.getInteger("maxAllowanceRank");
+                    rankingResultInfo.maxAllowanceRank = row.getInteger("maxAllowanceRank");
                 }
-                rankingResult.topRank = topRank;
-                rankingResult.allowanceRank = allowanceRank;
-                rankingResult.count = count > 0 ? count : null;
-                rankingResult.countR = countR > 0 ? countR : null;
-                rankingResult.topRankPercent = topRankPercent;
-                rankingResult.allowanceRankPercent = allowanceRankPercent;
-                rankingResult.groupName = groupName;
-                return rankingResult;
+                rankingResultInfo.topRank = topRank;
+                rankingResultInfo.allowanceRank = allowanceRank;
+                rankingResultInfo.count = count > 0 ? count : null;
+                rankingResultInfo.countR = countR > 0 ? countR : null;
+                rankingResultInfo.topRankPercent = topRankPercent;
+                rankingResultInfo.allowanceRankPercent = allowanceRankPercent;
+                rankingResultInfo.groupName = groupName;
+                return rankingResultInfo;
             }
         }else{
             logger.warn("员工不存在排名分组");
@@ -891,7 +929,7 @@ public class JobLevelCalculatorService {
      * 处理序列转换
      * 对应SHR:PV.getnewjobgradeindexByrank
      */
-    private static void handleSequenceChange(int jobLevelIndex,int currentJobLevelIndex,RankingResult rankingResult,Map<Integer, DynamicObject> jobLevelMap,JobLevelResult jobLevelResult,DynamicObject jobSeq){
+    private static void handleSequenceChange(int jobLevelIndex, int currentJobLevelIndex, RankingResultInfo rankingResultInfo, Map<Integer, DynamicObject> jobLevelMap, JobLevelResult jobLevelResult, DynamicObject jobSeq){
         System.out.println("序列切换,新职级号不能大于原序列职级序号");
         if (jobLevelIndex > currentJobLevelIndex) {
             jobLevelIndex = currentJobLevelIndex;
@@ -899,13 +937,13 @@ public class JobLevelCalculatorService {
         jobLevelResult.jobLevel = jobLevelMap.get(jobLevelIndex);
         // 序列转换
         jobLevelResult.adjustType = "4";
-        getnewjobgradeindexByrank(jobLevelIndex,currentJobLevelIndex,rankingResult,jobSeq);
+        getnewjobgradeindexByrank(jobLevelIndex,currentJobLevelIndex, rankingResultInfo,jobSeq);
     }
 
     /**
      * 处理跨单位调动
      */
-    private static void handleCrossUnitTransfer(int jobLevelIndex,int currentJobLevelIndex,RankingResult rankingResult,Map<Integer, DynamicObject> jobLevelMap,JobLevelResult jobLevelResult,DynamicObject jobSeq){
+    private static void handleCrossUnitTransfer(int jobLevelIndex, int currentJobLevelIndex, RankingResultInfo rankingResultInfo, Map<Integer, DynamicObject> jobLevelMap, JobLevelResult jobLevelResult, DynamicObject jobSeq){
         System.out.println("跨单位调动且未跨序列,平移处理");
         if (jobLevelIndex > currentJobLevelIndex) {
             jobLevelIndex = currentJobLevelIndex;
@@ -913,35 +951,38 @@ public class JobLevelCalculatorService {
         jobLevelResult.jobLevel = jobLevelMap.get(jobLevelIndex);
         // 保级
         jobLevelResult.adjustType = "1";
-        getnewjobgradeindexByrank(jobLevelIndex,currentJobLevelIndex,rankingResult,jobSeq);
+        getnewjobgradeindexByrank(jobLevelIndex,currentJobLevelIndex, rankingResultInfo,jobSeq);
     }
 
     /**
      * 对应SHR:com.kingdee.shr.customer.web.handler.ContributeScore.PersonpositionfilecreateViewListHandler#getnewjobgradeindexByrank
      * @param jobLevelIndex
      * @param currentJobLevelIndex
-     * @param rankingResult
+     * @param rankingResultInfo
      * @param jobSeq
      * @return: int
      * @author W.Y.C
      * @date: 2025/09/22 19:47
      */
-    public static int getnewjobgradeindexByrank(int jobLevelIndex,int currentJobLevelIndex,RankingResult rankingResult,DynamicObject jobSeq){
+    public static int getnewjobgradeindexByrank(int jobLevelIndex, int currentJobLevelIndex, RankingResultInfo rankingResultInfo, DynamicObject jobSeq){
 
         JobSeqEnum jobSeqEnum = JobSeqEnum.getByCode(jobSeq.getString(FormConstant.NUMBER_KEY));
-        if(rankingResult == null || rankingResult.countR == null || rankingResult.allowanceRank == null){
-            if(rankingResult == null){
-                rankingResult = new RankingResult();
+        if(rankingResultInfo == null || rankingResultInfo.countR == null || rankingResultInfo.allowanceRank == null){
+            if(rankingResultInfo == null){
+                rankingResultInfo = new RankingResultInfo();
             }
-            rankingResult.allowanceRankMark = "无";
-            rankingResult.allowanceRankSel = "无";
+            rankingResultInfo.allowanceRankMark = "无";
+            rankingResultInfo.allowanceRankSel = "无";
             return jobLevelIndex;
         }
-        rankingResult.allowanceRankMark = rankingResult.allowanceRank +"/" +rankingResult.countR;
-        Integer allowanceRank = rankingResult.allowanceRank;
-        Integer countR = rankingResult.countR;
-        Integer maxAllowanceRank = rankingResult.maxAllowanceRank;
-        if(rankingResult.countR < 10 && maxAllowanceRank.equals(rankingResult.allowanceRank) && rankingResult.topRankPercent >= 70){
+        rankingResultInfo.allowanceRankMark = rankingResultInfo.allowanceRank +"/" + rankingResultInfo.countR;
+        Integer allowanceRank = rankingResultInfo.allowanceRank;
+        Integer countR = rankingResultInfo.countR;
+        Integer maxAllowanceRank = rankingResultInfo.maxAllowanceRank;
+        if(rankingResultInfo.countR < 10 && maxAllowanceRank.equals(rankingResultInfo.allowanceRank) && rankingResultInfo.topRankPercent >= 70){
+            //当参与R排名的员工人数少于10人时,只对同时满足以下两个条件的员工降级:
+            //1、R排名处于末尾(即排名最后)。
+            //2、全员绩效排名位于后30%。
             jobLevelIndex--;
         }else{
             //技术或职能序列
@@ -1019,22 +1060,22 @@ public class JobLevelCalculatorService {
         }
 
         if (allowanceRank <= selMaxAllowanceRank(60, countR)) {
-            rankingResult.allowanceRankSel = "前60%";
+            rankingResultInfo.allowanceRankSel = "前60%";
         }
         else if (allowanceRank <= selMaxAllowanceRank(70, countR)) {
-            rankingResult.allowanceRankSel = "前60-70%";
+            rankingResultInfo.allowanceRankSel = "前60-70%";
         }
         else if (allowanceRank <= selMaxAllowanceRank(80, countR)) {
-            rankingResult.allowanceRankSel = "前70-80%";
+            rankingResultInfo.allowanceRankSel = "前70-80%";
         }
         else if (allowanceRank <= selMaxAllowanceRank(95, countR)) {
-            rankingResult.allowanceRankSel = "前80-95%";
+            rankingResultInfo.allowanceRankSel = "前80-95%";
         }
         else if (allowanceRank >= selMinAllowanceRank(3, countR)) {
-            rankingResult.allowanceRankSel = "后3%";
+            rankingResultInfo.allowanceRankSel = "后3%";
         }
         else if (allowanceRank >= selMinAllowanceRank(5, countR)) {
-            rankingResult.allowanceRankSel = "后5%";
+            rankingResultInfo.allowanceRankSel = "后5%";
         }
         return jobLevelIndex;
     }
@@ -1091,8 +1132,8 @@ public class JobLevelCalculatorService {
     /**
      * 根据考核结果使用情况处理职级计算
      */
-    private static int handleAppraisalUsage(DynamicObject jobSeq, RankingResult rankingResult, int jobLevelIndex,
-                                     int lastAppointmentJobGradeIndex, int currentJobLevelIndex, int minuspersonappraisal, boolean fistPR, boolean usedAppraisalResult, JobLevelResult jobLevelResult){
+    private static int handleAppraisalUsage(DynamicObject jobSeq, RankingResultInfo rankingResultInfo, int jobLevelIndex,
+                                            int lastAppointmentJobGradeIndex, int currentJobLevelIndex, int minuspersonappraisal, boolean fistPR, boolean usedAppraisalResult, JobLevelResult jobLevelResult){
         JobSeqEnum jobSeqEnum = JobSeqEnum.getByCode(jobSeq.getString(FormConstant.NUMBER_KEY));
         if (!usedAppraisalResult) {
             if (minuspersonappraisal == 0) {
@@ -1100,11 +1141,11 @@ public class JobLevelCalculatorService {
                     // 考核结果为保级 所以相 等
                     jobLevelIndex = lastAppointmentJobGradeIndex;
                 }
-                if (fistPR && ((rankingResult == null || rankingResult.countR == null || rankingResult.countR == 0) || (rankingResult.allowanceRank == null || rankingResult.allowanceRank == 0))) {
+                if (fistPR && ((rankingResultInfo == null || rankingResultInfo.countR == null || rankingResultInfo.countR == 0) || (rankingResultInfo.allowanceRank == null || rankingResultInfo.allowanceRank == 0))) {
                     System.out.println("首次聘任而且无R排名,保持分数算出来的职级");
                 } else {
                     System.out.println("应用R排名结果");
-                    jobLevelIndex = getnewjobgradeindexByrank(jobLevelIndex, currentJobLevelIndex, rankingResult, jobSeq);
+                    jobLevelIndex = getnewjobgradeindexByrank(jobLevelIndex, currentJobLevelIndex, rankingResultInfo, jobSeq);
                 }
                 System.out.println("newjobgradeindex:::" + jobLevelIndex);
                 System.out.println("PR_lastjobgradeindex:::" + lastAppointmentJobGradeIndex);
@@ -1118,10 +1159,10 @@ public class JobLevelCalculatorService {
                     System.out.println("R排名保级");
                 }
             } else {
-                getnewjobgradeindexByrank(jobLevelIndex, currentJobLevelIndex, rankingResult, jobSeq);
+                getnewjobgradeindexByrank(jobLevelIndex, currentJobLevelIndex, rankingResultInfo, jobSeq);
             }
         } else {
-            getnewjobgradeindexByrank(jobLevelIndex, currentJobLevelIndex, rankingResult, jobSeq);
+            getnewjobgradeindexByrank(jobLevelIndex, currentJobLevelIndex, rankingResultInfo, jobSeq);
             // 保级
             jobLevelResult.adjustType = "1";
             System.out.println("保级");
@@ -1580,7 +1621,6 @@ public class JobLevelCalculatorService {
 
         QueryFieldBuilder queryFieldBuilder = QueryFieldBuilder.create()
                 .addIdNumberNameWithExtras(
-                        new String[]{FormConstant.HBJM_JOBLEVELHR},
                         FormConstant.JOBLEVELSEQ,
                         FormConstant.NCKD_COEFFICIENT,
                         FormConstant.NCKD_SCORE
@@ -1661,13 +1701,13 @@ public class JobLevelCalculatorService {
     }
 
     // 辅助类定义
-    public static class JobFamilyInfo {
+    public static class JobSeqInfo {
         /** 是否为序列转换 */
         //对应SHR:isadjusttype_4
-        boolean isSequenceChange;
+        public boolean isSequenceChange;
         /** 是否为跨单位调动*/
         //对应SHR:isadjusttype_1
-        boolean isCrossUnitTransfer;
+        public boolean isCrossUnitTransfer;
     }
 
     public static class JobScoreInfo {
@@ -1675,25 +1715,35 @@ public class JobLevelCalculatorService {
         public BigDecimal perProTitleScore;
         /** 技能等级分 */
         public BigDecimal quaLevelScore;
+        /** 职称级别ID */
+        public Long perProTitleId;
+        /** 技能等级ID */
+        public Long quaLevelId;
         /** 职称级别编码 */
-        Long perProTitleId;
-        /** 技能等级编码 */
-        Long quaLevelId;
-        /** 职称级别编码 */
-        String perProTitleNumber;
+        public String perProTitleNumber;
         /** 技能等级编码 */
-        String quaLevelNumber;
-        /** 职称级名称 */
-        String perProTitleName;
+        public String quaLevelNumber;
+        /** 职称等级名称 */
+        public String perProTitleName;
         /** 技能等级名称 */
-        String quaLevelName;
+        public String quaLevelName;
         /** 是否缺少聘任 */
-        boolean isEndGainJobGrade;
+        public boolean isEndGainJobGrade;
+        /** 职称名称*/
+        public String rankName;
+        /** 技能名称*/
+        public String jobStatusName;
+        /** 学历ID */
+        public Long diplomaId;
+        /** 学历分 */
+        public BigDecimal diplomaScore;
+        /** 计算后的得分 */
+        public BigDecimal allSumScore;
     }
 
-    public static class RankingResult {
+    public static class RankingResultInfo {
         /** 排名 */
-        Integer topRank;
+        public Integer topRank;
         /** R排名 */
         //对应SHR:allowancerank
         public Integer allowanceRank;
@@ -1701,29 +1751,38 @@ public class JobLevelCalculatorService {
         //对应SHR:maxallowancerank
         public Integer maxAllowanceRank;
         /** 全排名百分比 */
-        Double topRankPercent;
+        public Double topRankPercent;
         /** R排名百分比 */
-        Double allowanceRankPercent;
+        public Double allowanceRankPercent;
         /** 全排名人数 */
-        Integer count;
+        public Integer count;
         /** R排名人数 */
         //对应SHR:countallowancerank
         public Integer countR;
         //排名分组名称
         /** R排名人数 */
-        String groupName;
+        public String groupName;
         /** R排名名次/R排名总人数 */
         //对应SHR:allowancerankmark
-        String allowanceRankMark;
+        public String allowanceRankMark;
         /** R排名百分比 */
         //对应SHR:allowanceranksel
-        String allowanceRankSel;
-
+        public String allowanceRankSel;
     }
 
     public static class JobLevelResult {
+        /** 调整类别(升级2;保级1;降级0;首次聘任3;序列变化4;聘任下调5;总分不足6;无聘任7;无考核结果8)*/
         public String adjustType;
         public String adjustMsg;
+        /** 计算后的职级 */
         public DynamicObject jobLevel;
+
+
+        /** 序列信息 */
+        public JobSeqInfo jobSeqInfo;
+        /** 积分信息 */
+        public JobScoreInfo jobScoreInfo;
+        /** 排名信息 */
+        public RankingResultInfo rankingResultInfo;
     }
 }

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

@@ -174,6 +174,13 @@ public class PositionStructureConstant extends FormConstant {
     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 --------------------------------------*/
     /**职位序列对应职级查询*/
     public static final String JOBSEQTOJOBLEVEL_QUERY = "jobseqtojoblevelquery";

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

@@ -7,14 +7,12 @@ 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 java.math.BigDecimal;
 import java.util.Date;
-import java.util.StringJoiner;
 
 /**
  * 职位体系帮助类
@@ -26,8 +24,6 @@ import java.util.StringJoiner;
 public class PositionStructureHelper {
 
 
-
-
     /**
      * 判断是否已经初定
      * @param personId 人员ID
@@ -42,16 +38,213 @@ public class PositionStructureHelper {
         return QueryServiceHelper.exists(PositionStructureConstant.PERSONPOSFILE_ENTITYID, new QFilter[]{filter});
     }
 
+
     /**
      * 根据人员和状态过滤职位档案
      * @param personId 人员ID
      * @param typeState 类型状态(新入职人员初定1;在职人员初定2;年度调整3;职位调动4;管理序列的聘任5;高级职称的聘任6)
      * @param adjustType 调整类别(升级2;保级1;降级0;首次聘任3;序列变化4;聘任下调5;总分不足6;无聘任7;无考核结果8)
-     * @param otherFilter
+     * @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) {
+        return (DynamicObject[]) getPersonPosFile(personId, typeState, adjustType, otherFilter, false, false);
+    }
+
+    /**
+     * 获取该人员最新的职位档案
+     * @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 (DynamicObject) getPersonPosFile(personId, null, null, null, true, false);
+    }
+
+    /**
+     * 获取该人员最新的职位档案
+     * @param personId 人员ID
+     * @param otherFilter 其他自定义条件
+     * @return: kd.bos.dataentity.entity.DynamicObject 树形结构
+     * @author W.Y.C
+     * @date: 2025/09/20 20:09
+     */
+    public static DynamicObject getLatsPersonPosFileByPerson(Long personId, QFilter otherFilter) {
+        return (DynamicObject) getPersonPosFile(personId, null, null, otherFilter, true, false);
+    }
+
+    /**
+     * 获取首次初定的职位档案
+     * @param personId 人员ID
+     * @return: kd.bos.dataentity.entity.DynamicObject
+     * @author W.Y.C
+     * @date: 2025/09/20 18:57
+     */
+    public static DynamicObject getFirstRank(Long personId) {
+        return (DynamicObject) getPersonPosFile(personId, null, null, null, true, true);
+    }
+
+    /**
+     * 统一的职位档案查询方法
+     * @param personId 人员ID
+     * @param typeState 类型状态过滤条件
+     * @param adjustType 调整类别过滤条件
+     * @param otherFilter 其他过滤条件
+     * @param needLatest 是否只需要最新记录
+     * @param isFirstRank 是否查询首次初定记录
+     * @return 职位档案数组或单个职位档案
+     */
+    private static Object getPersonPosFile(Long personId, String[] typeState, String[] adjustType,
+                                           QFilter otherFilter, boolean needLatest, boolean isFirstRank) {
+        QFilter filter = new QFilter(String.join(".", FormConstant.NCKD_PERSON, FormConstant.ID_KEY), QCP.equals, personId)
+                .and(new QFilter(PositionStructureConstant.NCKD_DISABLE, QCP.equals, EnableEnum.NO.getCode()));
+
+        // 根据是否查询首次初定记录添加条件
+        if (isFirstRank) {
+            filter.and(new QFilter(PositionStructureConstant.NCKD_FIRSTRANK, QCP.equals, EnableEnum.YES.getCode()));
+        }
+        // 如果不是查询首次初定,则使用有效档案条件
+        else if (!isFirstRank && typeState == null && adjustType == null) {
+            // 只有状态为:【初定】、【年度调整】和【职位调动】的职位档案才是有效档案;
+            // 条件:(初定 或者 (年度调整 并且 已生效) 或者 职位调动)
+            filter.and(
+                    new QFilter(PositionStructureConstant.NCKD_FIRSTRANK, QCP.equals, EnableEnum.YES.getCode())
+                            .or(
+                                    new QFilter(PositionStructureConstant.NCKD_TYPESTATE, QCP.equals, "3")
+                                            .and(new QFilter(PositionStructureConstant.NCKD_ADJUSSTATUS, QCP.in, new String[]{"1", "2"}))
+                            )
+                            .or(new QFilter(PositionStructureConstant.NCKD_TYPESTATE, QCP.equals, "4"))
+            );
+        }
+
+        // 添加其他过滤条件
+        if (typeState != null) {
+            filter.and(new QFilter(PositionStructureConstant.NCKD_TYPESTATE, QCP.in, typeState));
+        }
+        if (adjustType != null) {
+            filter.and(new QFilter(PositionStructureConstant.NCKD_ADJUSSTATUS, QCP.in, adjustType));
+        }
+        if (otherFilter != null) {
+            filter.and(otherFilter);
+        }
+
+        // 构建查询字段
+        QueryFieldBuilder queryFieldBuilder = buildQueryField(isFirstRank, !isFirstRank);
+
+        // 添加排序
+        queryFieldBuilder.orderDesc(PositionStructureConstant.NCKD_BEGINDATE);
+
+        // 执行查询
+        DynamicObject[] result = BusinessDataServiceHelper.load(
+                PositionStructureConstant.PERSONPOSFILE_ENTITYID,
+                queryFieldBuilder.buildSelect(),
+                new QFilter[]{filter},
+                queryFieldBuilder.buildOrder()
+        );
+
+        // 根据需要返回单个或多个结果
+        if (needLatest || isFirstRank) {
+            return result.length > 0 ? result[0] : null;
+        } else {
+            return result;
+        }
+    }
+
+    /**
+     * 构建查询字段
+     * @param isMinimal 是否为最小字段集(用于首次初定查询)
+     * @param isFull 是否为完整字段集(用于最新记录查询)
+     * @return QueryFieldBuilder
+     */
+    private static QueryFieldBuilder buildQueryField(boolean isMinimal, boolean isFull) {
+        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(PositionStructureConstant.NCKD_DIPLOMASCORE)
+                .add(PositionStructureConstant.NCKD_RANKSCORE)
+                .add(PositionStructureConstant.NCKD_JOBSTATUSSCORE)
+                .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)
+                .addIdNumberName(PositionStructureConstant.NCKD_PROTITLELEVEL)
+                .add(PositionStructureConstant.NCKD_JOBSTATUSNAME)
+                .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 (isMinimal) {
+            // 首次初定查询只需要基础字段
+            return queryFieldBuilder;
+        }
+
+        // 完整字段集
+        queryFieldBuilder
+                // 岗位
+                .addIdNumberName(PositionStructureConstant.NCKD_POSITIONHR)
+                //其他字段
+                .add(PositionStructureConstant.NCKD_RESULTSCORE)
+                .add(PositionStructureConstant.NCKD_ADJUSTTYPE)
+                .add(PositionStructureConstant.NCKD_ADJUSTINT)
+                .add(PositionStructureConstant.NCKD_ALLOWANCERANK)
+                .add(PositionStructureConstant.NCKD_TOPRANK)
+                .add(PositionStructureConstant.NCKD_TOPRANKPERCENT)
+                .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);
+
+        return queryFieldBuilder;
+    }
+
+/*
+
+    */
+/**
+     * 根据人员和状态过滤职位档案
+     * @param personId 人员ID
+     * @param typeState 类型状态(新入职人员初定1;在职人员初定2;年度调整3;职位调动4;管理序列的聘任5;高级职称的聘任6)
+     * @param adjustType 调整类别(升级2;保级1;降级0;首次聘任3;序列变化4;聘任下调5;总分不足6;无聘任7;无考核结果8)
+     * @param otherFilter
+     * @return: kd.bos.dataentity.entity.DynamicObject[]
+     * @author W.Y.C
+     * @date: 2025/09/23 21:08
+     *//*
+
     public static DynamicObject[] getPersonPosFileByPersonAndState(Long personId,String[] typeState,String[] adjustType,QFilter otherFilter) {
         QFilter filer = new QFilter(String.join(".", FormConstant.NCKD_PERSON, FormConstant.ID_KEY), QCP.equals, personId)
                 .and(new QFilter(PositionStructureConstant.NCKD_DISABLE, QCP.equals, EnableEnum.NO.getCode()));
@@ -105,24 +298,28 @@ public class PositionStructureHelper {
 
     }
 
-    /**
+    */
+/**
      * 获取该人员最新的职位档案
      * @param personId 人员ID
      * @return: kd.bos.dataentity.entity.DynamicObject
      * @author W.Y.C
      * @date: 2025/09/20 20:09
-     */
+     *//*
+
     public static DynamicObject getLatsPersonPosFileByPerson(Long personId) {
         return getLatsPersonPosFileByPerson(personId,null);
     }
-    /**
+    */
+/**
      * 获取该人员最新的职位档案
      * @param personId 人员ID
      * @param otherFilter 其他条件
-     * @return: kd.bos.dataentity.entity.DynamicObject
+     * @return: kd.bos.dataentity.entity.DynamicObject 树形结构
      * @author W.Y.C
      * @date: 2025/09/20 20:09
-     */
+     *//*
+
     public static DynamicObject getLatsPersonPosFileByPerson(Long personId,QFilter otherFilter) {
         QFilter filer = new QFilter(String.join(".", FormConstant.NCKD_PERSON, FormConstant.ID_KEY), QCP.equals, personId)
                 // .and(new QFilter(PositionStructureConstant.NCKD_ISCURRENTNEWEST, QCP.equals, EnableEnum.YES.getCode()))
@@ -172,6 +369,41 @@ public class PositionStructureHelper {
                 )
                 // 职位序列
                 .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(
@@ -184,13 +416,15 @@ public class PositionStructureHelper {
 
     }
 
-    /**
+    */
+/**
      * 获取首次初定的职位档案
      * @param personId 人员ID
      * @return: kd.bos.dataentity.entity.DynamicObject
      * @author W.Y.C
      * @date: 2025/09/20 18:57
-     */
+     *//*
+
     public static DynamicObject getFirstRank(Long personId) {
         QFilter filer = new QFilter(String.join(".", FormConstant.NCKD_PERSON, FormConstant.ID_KEY), QCP.equals, personId)
                 .and(new QFilter(PositionStructureConstant.NCKD_DISABLE, QCP.equals, EnableEnum.NO.getCode()))
@@ -225,12 +459,13 @@ public class PositionStructureHelper {
         );
         return load.length > 0 ? load[0] : null;
     }
+*/
 
     /**
      * 根据时间获取员工任职(所属公司、岗位、职位序列)、聘任信息(职称等级、技能等级)和学历
      * @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
      */
@@ -289,6 +524,25 @@ public class PositionStructureHelper {
 
         DynamicObjectCollection query = QueryServiceHelper.query(PositionStructureConstant.POSITIONAPPOINTMENTQUERY, queryFieldBuilder.buildSelect(), new QFilter[]{filer});
         return !query.isEmpty() ? query.get(0) : null;
+    }
 
+    /**
+     * 根据人员将所有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);
     }
 }

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

@@ -40,6 +40,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();
         // 获取列表选择的人员
@@ -52,7 +53,7 @@ public class NewDynamicAdjustmentDiaLogFormPlugin extends AbstractFormPlugin {
     }
 
     @Override
-    public void beforePropertyChanged(PropertyChangedArgs e) {
+    public void propertyChanged(PropertyChangedArgs e) {
         String fieldKey = e.getProperty().getName();
         ChangeData[] changeSet = e.getChangeSet();
 
@@ -70,11 +71,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);
-                            this.getModel().setValue(PositionStructureConstant.NCKD_JOBLEVEL, jobLevel);
+                            try{
+                                LocalDateTime endDay = DateUtil.endOfDay(DateUtil.toLocalDateTime(adjustDate));
+                                DynamicObject 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());
+                            }
                         }
                     }
                 }
@@ -84,7 +93,7 @@ public class NewDynamicAdjustmentDiaLogFormPlugin extends AbstractFormPlugin {
 
 
 
-    public static DynamicObject getJobLevel(DynamicObject person,Date date){
+    public static JobLevelCalculatorService.JobLevelResult getJobLevel(DynamicObject person,Date date,DynamicObject positionAppointment){
         //移植代码:com.kingdee.shr.customer.web.handler.ContributeScore.PersonpositionfileFluctuationListHandler#selaboutJobGradeAction
         long personId = person.getLong(FormConstant.ID_KEY);
         String personName = person.getString(FormConstant.NAME_KEY);
@@ -105,7 +114,7 @@ public class NewDynamicAdjustmentDiaLogFormPlugin extends AbstractFormPlugin {
         LocalDateTime endDay = DateUtil.endOfDay(DateUtil.toLocalDateTime(date));
         // 3、根据时间获取员工信息(任职经历、聘任、学历)
         //对应SHR:resultMap
-        DynamicObject positionAppointment = PositionStructureHelper.positionAppointmentQuery(personId, DateUtil.toDate(endDay));
+        // DynamicObject positionAppointment = PositionStructureHelper.positionAppointmentQuery(personId, DateUtil.toDate(endDay));
         if(positionAppointment == null || positionAppointment.getDataEntityType() == null){
             throw new ValidationException(StrFormatter.format("当前无法为【{}】进行调整,因为根据时间【{}】未获取到人员任职和聘任信息!", personName,DateUtil.format(date,DateUtil.NORM_DATE_PATTERN)));
         }
@@ -158,7 +167,7 @@ public class NewDynamicAdjustmentDiaLogFormPlugin extends AbstractFormPlugin {
         // 10、处理R排名百分比
         // JobLevelCalculatorService.RankingResult rankingInfo = JobLevelCalculatorService.getRankingInfo(personId, personName, date);
 
-        return jobLevelResult.jobLevel;
+        return jobLevelResult;
     }
 
     @Override
@@ -172,4 +181,39 @@ public class NewDynamicAdjustmentDiaLogFormPlugin extends AbstractFormPlugin {
             this.getView().showConfirm("提示","新建调整成功,职位档案已生成。",jobLeveStr, MessageBoxOptions.OK,null,null,null,null);
         }
     }
+
+    private void updateFormInfo(DynamicObject person, Date adjustDate, DynamicObject personPosFile,JobLevelCalculatorService.JobLevelResult jobLevelResult,DynamicObject positionAppointment) {
+        LocalDateTime endDay = DateUtil.endOfDay(DateUtil.toLocalDateTime(adjustDate));
+        this.getModel().setValue(PositionStructureConstant.NCKD_POSITIONHR,
+                positionAppointment.getLong(String.join(".", FormConstant.POSITION_KEY, FormConstant.ID_KEY)));
+        this.getModel().setValue(PositionStructureConstant.NCKD_ALLOWANCERANKMARK,
+                jobLevelResult.rankingResultInfo.allowanceRankMark);
+        this.getModel().setValue(PositionStructureConstant.NCKD_ALLOWANCERANKPERCENT,
+                jobLevelResult.rankingResultInfo.allowanceRankPercent);
+        this.getModel().setValue(PositionStructureConstant.NCKD_RANKNAME,
+                jobLevelResult.jobScoreInfo.rankName);
+        this.getModel().setValue(PositionStructureConstant.NCKD_PERPROTITLENAME,
+                jobLevelResult.jobScoreInfo.perProTitleName);
+        this.getModel().setValue(PositionStructureConstant.NCKD_QUALEVELNAME,
+                jobLevelResult.jobScoreInfo.quaLevelName);
+        this.getModel().setValue(PositionStructureConstant.NCKD_JOBSTATUSNAME,
+                jobLevelResult.jobScoreInfo.jobStatusName);
+        this.getModel().setValue(PositionStructureConstant.PERSONPOSFILE_ENTITYID, personPosFile);
+        this.getModel().setValue(PositionStructureConstant.NCKD_JOBLEVEL, jobLevelResult.jobLevel);
+        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.getView().setEnable(false, FormConstant.BTN_OK_OP);
+    }
 }

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

@@ -1,27 +1,35 @@
 package nckd.jxccl.hr.psms.plugin.operate.adjust;
 
+import kd.bos.common.enums.EnableEnum;
 import kd.bos.dataentity.entity.DynamicObject;
 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.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.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.List;
 import java.util.Map;
+import java.util.Objects;
 
 /**
 * 新建动态调整OP
@@ -58,10 +66,6 @@ public class NewDynamicAdjustmentOperationPlugIn extends AbstractOperationServic
                         addFatalErrorMessage(rowDataEntity,"调整时间不能为空");
                         return;
                     }
-                    if (adjustDate.after(currentDate)) {
-                        addFatalErrorMessage(rowDataEntity,"调整时间不能晚于当前日期,请检查输入的日期是否正确");
-                        return;
-                    }
                     DynamicObject person = data.getDynamicObject(FormConstant.NCKD_PERSON);
                     if(person == null){
                         addFatalErrorMessage(rowDataEntity,"请选择要调整的员工");
@@ -87,7 +91,7 @@ public class NewDynamicAdjustmentOperationPlugIn extends AbstractOperationServic
                     }
 
                     latsPersonPosFileMap.put(person.getLong(FormConstant.ID_KEY),latsPersonPosFileByPerson);
-                    lastYearPerformanceResultMap.put(person.getLong(FormConstant.ID_KEY),latsPersonPosFileByPerson);
+                    lastYearPerformanceResultMap.put(person.getLong(FormConstant.ID_KEY),lastYearPerformanceResult);
 
                 }
             }
@@ -97,9 +101,12 @@ public class NewDynamicAdjustmentOperationPlugIn extends AbstractOperationServic
     @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()) {
             DynamicObject person = data.getDynamicObject(FormConstant.NCKD_PERSON);
             long personId = person.getLong(FormConstant.ID_KEY);
+            String personName = person.getString(FormConstant.NAME_KEY);
             //最近一次档案
             DynamicObject latsPersonPosFile = latsPersonPosFileMap.get(personId);
             //上一次职级
@@ -109,19 +116,172 @@ public class NewDynamicAdjustmentOperationPlugIn extends AbstractOperationServic
             LocalDateTime adjustDateEnd = DateUtil.endOfDay(DateUtil.toLocalDateTime(adjustDate));
             //查询员工在调整日期内的最新信息(职位、部门、序列、学历、职称、技能)
             DynamicObject positionAppointment = PositionStructureHelper.positionAppointmentQuery(personId, DateUtil.toDate(adjustDateEnd));
+            if(positionAppointment == null || positionAppointment.getDataEntityType() == 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);
+            }
 
+            createPersonPosFile(person, positionAppointment, adjustDate, jobLevelResult, latsPersonPosFile, allSumScore, personId, frontendJobLevelId, jobLevelId, remark, personIds, newPersonPosFiles,lastYearPerformanceResultMap);
+        }
 
+        PositionStructureHelper.markAsNotCurrentNewest(personIds.toArray(new Long[0]));
+        SaveServiceHelper.save(newPersonPosFiles.toArray(new DynamicObject[0]));
+    }
+
+    private static void createPersonPosFile(DynamicObject person, DynamicObject 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 newPersonPosFile = BusinessDataServiceHelper.newDynamicObject(
+                PositionStructureConstant.PERSONPOSFILE_ENTITYID);
 
-            //上年度考核结果
-            DynamicObject lastYearPerformanceResult = lastYearPerformanceResultMap.get(personId);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_PERSON, person);
+        DynamicObject company = BusinessDataServiceHelper.newDynamicObject(FormConstant.ADMINORGHR_ENTITYID);
+        company.set(FormConstant.ID_KEY, positionAppointment.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, positionAppointment.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));
+        DynamicObject position = positionAppointment.getDynamicObject(FormConstant.HBPM_POSITIONHR);
+        DynamicObject jobSeq = position.getDynamicObject(FormConstant.NCKD_JOBSEQ);
+        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, 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.topRank);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_ALLOWANCERANK, jobLevelResult.rankingResultInfo.allowanceRank);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_TOPRANKPERCENT, jobLevelResult.rankingResultInfo.topRankPercent);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_ALLOWANCERANKMARK, jobLevelResult.rankingResultInfo.allowanceRankMark);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_ALLOWANCERANKSEL, jobLevelResult.rankingResultInfo.allowanceRankSel);
+        newPersonPosFile.set(PositionStructureConstant.NCKD_ALLOWANCERANKPCT, jobLevelResult.rankingResultInfo.allowanceRankPercent);
+        //上年度考核结果
+        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());
 
+        // 聘任状态
+        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);
+        // 备注
+        String causeRemark = latsPersonPosFile.getString(PositionStructureConstant.KEY_NCKD_CAUSEREMARK);
+        newPersonPosFile.set(PositionStructureConstant.KEY_NCKD_CAUSEREMARK, remark);
 
-        }
+        personIds.add(personId);
+        newPersonPosFiles.add(newPersonPosFile);
     }
 }