3 커밋 2a53a76c43 ... 335c95509c

작성자 SHA1 메시지 날짜
  wyc 335c95509c Merge branch 'refs/heads/feat-hr-psms_1.0' 1 주 전
  wyc d08be3ddd0 feat(hr): 新增年度贡献积分计算功能 1 주 전
  wyc 1b6c55e7e3 feat(hr): 新增年度绩效排名管理功能及相关枚举常量 1 주 전
36개의 변경된 파일2209개의 추가작업 그리고 107개의 파일을 삭제
  1. 29 1
      code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/constant/FormConstant.java
  2. 13 0
      code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/constant/SystemQueryConstant.java
  3. 63 0
      code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/enums/psms/AwardLevelEnum.java
  4. 71 0
      code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/enums/psms/ScoreItemEnum.java
  5. 17 0
      code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/utils/ConvertUtil.java
  6. 3 3
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/business/AnnualAdjustmentService.java
  7. 6 6
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/business/JobLevelCalculatorService.java
  8. 51 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/common/ContributionEvaluationConstant.java
  9. 17 2
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/common/PerfRankMgmtConstant.java
  10. 5 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/common/PositionStructureConstant.java
  11. 397 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/helper/ContributionHelper.java
  12. 59 1
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/helper/PositionFileHelper.java
  13. 5 5
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/adjust/NewDynamicAdjustmentDiaLogFormPlugin.java
  14. 6 4
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/annualadjust/UnAnnualAdjustListPlugin.java
  15. 178 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/contribution/ContribBillFormPlugin.java
  16. 69 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/contribution/ScoreItemConfFormPlugin.java
  17. 0 1
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/initial/NewHireInitialBatchFormPlugin.java
  18. 2 6
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/initial/NewHireInitialFormPlugin.java
  19. 2 2
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/initial/ServingInitialFormPlugin.java
  20. 7 4
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/initial/UngradedPersonQueryListPlugin.java
  21. 411 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/performance/PerfrankMgmtFormPlugin.java
  22. 6 6
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/adjust/NewDynamicAdjustmentOperationPlugIn.java
  23. 6 6
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/annualadjust/AnnualAdjustmentOperationPlugin.java
  24. 3 3
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/annualadjust/AnnualEffectiveOpPlugin.java
  25. 32 45
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/annualadjust/AnnualLockOrUnLockedOpPlugin.java
  26. 3 3
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/annualadjust/AnnualSetinActiveOpPlugin.java
  27. 5 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/annualadjust/PosFileUnlockBillOpPlugin.java
  28. 134 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/contribution/ContribBillOpPlugin.java
  29. 3 3
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/file/PersonPosFileDeleteOpPlugin.java
  30. 2 2
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/initial/BaseInitialOperationPlugIn.java
  31. 2 2
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/initial/NewHireInitialOperationPlugIn.java
  32. 2 2
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/initial/ServingInitialOperationPlugIn.java
  33. 21 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/performance/PerfRankMgmtSaveOpPlugin.java
  34. 538 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/performance/validate/PerfRankMgmtSaveValidate.java
  35. 39 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/performance/validate/RankRatioConfValidate.java
  36. 2 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/report/adjust/UnAdjustedReportReportListDataPlugin.java

+ 29 - 1
code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/constant/FormConstant.java

@@ -43,6 +43,8 @@ public class FormConstant {
     public static final String HRPI_PERSERLEN = "hrpi_perserlen";
     /** 雇佣信息-实体标识*/
     public static final String HRPI_EMPENTREL = "hrpi_empentrel";
+    /** 员工-实体标识*/
+    public static final String HRPI_EMPLOYEE = "hrpi_employee";
 
 
     //====================================== 标品op ======================================
@@ -70,6 +72,10 @@ public class FormConstant {
     public static final String OP_BAR_AUDIT = "bar_audit";
     /** 反审核操作标识*/
     public static final String OP_BAR_UNAUDIT = "bar_unaudit";
+    /** 新增分录操作*/
+    public static final String NEWENTRY_OP = "newentry";
+    /** 删除分录操作*/
+    public static final String DELETEENTRY_OP = "deleteentry";
 
 
     //====================================== 标品页面控件 ======================================
@@ -215,6 +221,28 @@ public class FormConstant {
     /** 职层-资格级别*/
     public static final String NCKD_JOBLEVELNUMBER = "NCKD_JOBLEVELNUMBER";
     /** 备注*/
-    public static final String NCKD_REMARK = "NCKD_REMARK";
+    public static final String NCKD_REMARK = "nckd_remark";
+    /** 组织*/
+    public static final String NCKD_ADMINORG = "nckd_adminorg";
+    /** 用工关系状态*/
+    public static final String LABOR_REL_STATUS = "laborrelstatus";
+    /** 是否在职*/
+    public static final String IS_HIRED = "ishired";
+    /** 是否试用*/
+    public static final String IS_TRIAL = "istrial";
+    /** 任职状态*/
+    public static final String POS_STATUS = "posstatus";
+    /** 任职状态分类*/
+    public static final String POST_STATE_CLS = "poststatecls";
+    /** 党政职务变更履历*/
+    public static final String NCKD_HRPI_PARTYPOSH = "nckd_hrpi_partyposh";
+    /** 职位级别*/
+    public static final String NCKD_POSGRADE = "nckd_posgrade";
+    /** 排序号*/
+    public static final String NCKD_SORTNUM = "nckd_sortnum";
+    /** 开始日期*/
+    public static final String NCKD_STARTDATE = "nckd_startdate";
+    /** 结束日期*/
+    public static final String NCKD_ENDDATE = "nckd_enddate";
 
 }

+ 13 - 0
code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/constant/SystemQueryConstant.java

@@ -0,0 +1,13 @@
+package nckd.jxccl.base.common.constant;
+
+/**
+ * 系统内置常量
+ * @author W.Y.C
+ * @date 2025/10/20 16:05
+ * @version 1.0
+ */
+public class SystemQueryConstant {
+
+    /**人员列表*/
+    public static final String HSPM_ASSIGNMENTQUERY = "hspm_assignmentquery";
+}

+ 63 - 0
code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/enums/psms/AwardLevelEnum.java

@@ -0,0 +1,63 @@
+package nckd.jxccl.base.common.enums.psms;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+* 奖项级别
+* @author W.Y.C
+* @date 2025/10/22 20:35
+* @version 1.0
+*/
+public enum AwardLevelEnum {
+
+    /**国家级*/
+    NATIONAL_LEVEL("1", "国家级"),
+    /**省部级*/
+    PROVINCIAL_LEVEL("2", "省部级"),
+    /**公司级*/
+    COMPANY_LEVEL("3", "公司级"),
+    /**矿级*/
+    MINE_LEVEL("4", "厂矿级"),
+    /**车间级*/
+    WORKSHOP_LEVEL("5", "车间级");
+
+
+    private static final Map<String, AwardLevelEnum> CODE_MAP = new HashMap<>();
+    static {
+        for (AwardLevelEnum value : AwardLevelEnum.values()) {
+            CODE_MAP.put(value.code, value);
+        }
+    }
+
+    private final String code;
+    private final String name;
+
+    AwardLevelEnum(String code, String name) {
+        this.code = code;
+        this.name = name;
+    }
+
+    /**
+     * 根据编码获取枚举
+     * @param code
+     * @return: AwardLevelEnum
+     * @author W.Y.C
+     * @date: 2025/09/12 14:33
+     */
+    public static AwardLevelEnum getByCode(String code) {
+        if (code == null) {
+            return null;
+        }
+        return CODE_MAP.get(code);
+    }
+
+
+    public String getCode() {
+        return code;
+    }
+
+    public String getName() {
+        return name;
+    }
+}

+ 71 - 0
code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/enums/psms/ScoreItemEnum.java

@@ -0,0 +1,71 @@
+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 ScoreItemEnum {
+
+    /**科研与创新积分*/
+    RESEARCH_SCORE("1", "科研与创新积分"),
+    /**专利申报积分*/
+    PATENT_SCORE("2", "专利申报积分"),
+    /**论文发表积分*/
+    PAPER_SCORE("3", "论文发表积分"),
+    /**技能竞赛积分*/
+    SKILL_CONTEST_SCORE("4", "技能竞赛积分"),
+    /**培训教材积分*/
+    TRAINING_MATERIAL_SCORE("5", "培训教材积分"),
+    /**技术标准积分*/
+    TECH_STANDARD_SCORE("6", "技术标准积分"),
+    /**管理规范积分*/
+    MGMT_SPEC_SCORE("7", "管理规范积分"),
+    /**培训授课积分*/
+    TRAINING_SCORE("8", "培训授课积分"),
+    /**师带徒积分*/
+    MENTOR_SCORE("9", "师带徒积分");
+
+    private static final Map<String, ScoreItemEnum> CODE_MAP = new HashMap<>();
+
+    static {
+        for (ScoreItemEnum value : ScoreItemEnum.values()) {
+            CODE_MAP.put(value.code, value);
+        }
+    }
+
+    private final String code;
+    private final String name;
+
+    ScoreItemEnum(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 ScoreItemEnum getByCode(String code) {
+        if (code == null) {
+            return null;
+        }
+        return CODE_MAP.get(code);
+    }
+
+
+    public String getCode() {
+        return code;
+    }
+
+    public String getName() {
+        return name;
+    }
+}

+ 17 - 0
code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/utils/ConvertUtil.java

@@ -693,6 +693,23 @@ public class ConvertUtil {
         return defaultValue;
     }
 
+    /**
+     * 将对象转换为DynamicObject类型,为空时返回null
+     * @param value 要转换的对象
+     * @return 转换后的DynamicObject值,输入为null时返回null
+     * @author W.Y.C
+     * @date: 2025/08/04 12:05
+     */
+    public static DynamicObject toDynamicObjectOrNull(Object value) {
+        if (value == null) {
+            return null;
+        }
+        if (value instanceof DynamicObject) {
+            return (DynamicObject) value;
+        }
+        return null;
+    }
+
     /**
      * 将对象转换为DynamicObject类型
      * @param value 要转换的对象

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

@@ -18,7 +18,7 @@ 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 nckd.jxccl.hr.psms.helper.PositionFileHelper;
 import org.apache.commons.lang3.StringUtils;
 
 import java.math.BigDecimal;
@@ -247,7 +247,7 @@ public class AnnualAdjustmentService {
         data.setRankingResultInfo(rankingInfo);
 
         //4.查询上一条有效年度调整记录(对应SHR:PersonpositionfileUtils:946~997行)
-        DynamicObject[] personPosFileByPersonAndState = PositionStructureHelper.getPersonPosFileByPersonAndState(ac.personId,
+        DynamicObject[] personPosFileByPersonAndState = PositionFileHelper.getPersonPosFileByPersonAndState(ac.personId,
                 new String[]{"3","4"},
                 null,null,
                 QueryFieldBuilder.create()
@@ -393,7 +393,7 @@ public class AnnualAdjustmentService {
      */
     private static void evaluateFirstAndAppraisalUsage(AdjustmentContext ac) {
 
-        DynamicObject nowYearPersonPosFile = PositionStructureHelper.getLatsPersonPosFileByPerson(ac.personId, new QFilter(PositionStructureConstant.NCKD_EXECUTEYEAR, QCP.equals, ac.nowYear));
+        DynamicObject nowYearPersonPosFile = PositionFileHelper.getLatsPersonPosFileByPerson(ac.personId, new QFilter(PositionStructureConstant.NCKD_EXECUTEYEAR, QCP.equals, ac.nowYear));
 
         boolean isyearfirstdo = false;
         if (nowYearPersonPosFile != null) {

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

@@ -27,7 +27,7 @@ import nckd.jxccl.base.pm.helper.PerformanceManagerHelper;
 import nckd.jxccl.hr.psms.common.PerfRankMgmtConstant;
 import nckd.jxccl.hr.psms.common.PositionStructureConstant;
 import nckd.jxccl.hr.psms.common.bo.PositionAppointmentBO;
-import nckd.jxccl.hr.psms.helper.PositionStructureHelper;
+import nckd.jxccl.hr.psms.helper.PositionFileHelper;
 import org.apache.commons.lang3.StringUtils;
 
 import java.math.BigDecimal;
@@ -91,7 +91,7 @@ public class JobLevelCalculatorService {
 
         // 1. 获取最近的职位档案(必须存在,否则抛错)
         //对应SHR:personpositionfileInfo
-        DynamicObject currentPersonPosFile = PositionStructureHelper.getLatsPersonPosFileByPerson(personId);
+        DynamicObject currentPersonPosFile = PositionFileHelper.getLatsPersonPosFileByPerson(personId);
         if(currentPersonPosFile == null){
             throw new ValidationException(StrFormatter.format("当前无法为【{}】进行调整,因为他/她尚未建立职位档案。请前往“职位及积分初定” -> 进行初定!", person.getString(FormConstant.NAME_KEY)));
         }
@@ -251,7 +251,7 @@ public class JobLevelCalculatorService {
         String lastYearAppraisalResultNumber = lastYearAppraisalResult != null ? lastYearAppraisalResult.getString(FormConstant.NUMBER_KEY) : null;
         //当前年度职位档案;
         // 对应SHR:nowyearpersonpositionfileCollection
-        DynamicObject currentYearPersonPosFileByPerson = PositionStructureHelper.getLatsPersonPosFileByPerson(personId,new QFilter(PositionStructureConstant.NCKD_EXECUTEYEAR,QCP.equals,year));
+        DynamicObject currentYearPersonPosFileByPerson = PositionFileHelper.getLatsPersonPosFileByPerson(personId,new QFilter(PositionStructureConstant.NCKD_EXECUTEYEAR,QCP.equals,year));
         //对应SHR:newpersonfirstrank
         //对应SHR:878行
         boolean newPersonFirstRank = isNewPersonFirstRank(date, personId);
@@ -561,7 +561,7 @@ public class JobLevelCalculatorService {
      * @date: 2025/09/20 19:04
      */
     private static boolean isNewPersonFirstRank(Date beginYear, long personId) {
-        DynamicObject firstRankDynamicObject = PositionStructureHelper.getFirstRank(personId);
+        DynamicObject firstRankDynamicObject = PositionFileHelper.getFirstRank(personId);
         if(firstRankDynamicObject == null){
             throw new ValidationException(StrFormatter.format("未获取到人员【{}】首次初定档案", personId));
         }
@@ -879,13 +879,13 @@ public class JobLevelCalculatorService {
         LocalDateTime lastYearDateTime = DateUtil.minusYears(DateUtil.toLocalDateTime(date), 1);
         //取上年度绩效排名
         QFilter groupFilter = new QFilter(PerfRankMgmtConstant.NCKD_THEYEAR,QCP.equals,lastYearDateTime.getYear());
-        groupFilter.and(PerfRankMgmtConstant.BILL_STATUS_KEY,QCP.equals, StatusEnum.C.toString());
+        groupFilter.and(QFilterCommonHelper.getStatusFilter());
         groupFilter.and(String.join(".",PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY,PerfRankMgmtConstant.NCKD_PERSON),QCP.equals, personId);
         groupFilter.and(String.join(".",PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY,PerfRankMgmtConstant.NCKD_ISRANKING),QCP.equals,EnableEnum.YES.getCode());
         //获取该员工上年度所在的排名分组
         DynamicObject[] groupArray = BusinessDataServiceHelper.load(PerfRankMgmtConstant.NCKD_PERFRANKMGMT_ENTITYID, FormConstant.ID_KEY+","+PerfRankMgmtConstant.NCKD_GROUPNAME,
                 new QFilter[]{groupFilter},
-                FormConstant.CREATE_TIME_KEY + " desc," + FormConstant.AUDIT_DATE_KEY + " desc," + FormConstant.MODIFY_TIME_KEY + " desc");
+                FormConstant.CREATE_TIME_KEY + " desc," + FormConstant.MODIFY_TIME_KEY + " desc");
         //避免一个员工存在多个排名分组中,只取最新排名分组
         Long groupId = null;
         String groupName = null;

+ 51 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/common/ContributionEvaluationConstant.java

@@ -0,0 +1,51 @@
+package nckd.jxccl.hr.psms.common;
+
+import nckd.jxccl.base.common.constant.FormConstant;
+
+/**
+ * 动态表单插件
+ */
+/**
+* 年度贡献综合评价-常量类
+* @author W.Y.C
+* @date 2025/10/22 21:53
+* @version 1.0
+*/
+public class ContributionEvaluationConstant extends FormConstant {
+
+
+
+
+
+    /** 年度贡献积分单据-实体标识 */
+    public static final String CONTRIBBILL_ENTITYID = "nckd_contribbill";
+    /** 单据体 */
+    public static final String NCKD_CONTRIBBILLENTRY = "NCKD_CONTRIBBILLENTRY";
+    /** 原始分数 */
+    public static final String NCKD_ORISCORE = "NCKD_ORISCORE";
+    /** 录入积分 */
+    public static final String NCKD_SCORE = "NCKD_SCORE";
+    /** 二级单位 */
+    public static final String NCKD_ADMINORG = "NCKD_ADMINORG";
+    /** 姓名 */
+    public static final String NCKD_PERSON = "NCKD_PERSON";
+    /** 参与人数 */
+    public static final String NCKD_PARTICIPANTS = "NCKD_PARTICIPANTS";
+    /** 积分所属年度 */
+    public static final String NCKD_YEAR = "NCKD_YEAR";
+    /** 积分项目 */
+    public static final String NCKD_SCOREITEM = "NCKD_SCOREITEM";
+    /** 积分具体项目 */
+    public static final String NCKD_SCOREITEMSUB = "NCKD_SCOREITEMSUB";
+    /** 积分项目名次 */
+    public static final String NCKD_SCOREITEMRANK = "NCKD_SCOREITEMRANK";
+
+
+
+    /*-------------------------------------- 积分项目分数配置 begin --------------------------------------*/
+    /** 积分项目分数配置-实体标识 */
+    public static final String SCOREITEMCONF_ENTITYID = "nckd_scoreitemconf";
+    /** 最高分数 */
+    public static final String NCKD_MAXSCORE = "NCKD_MAXSCORE";
+    /*-------------------------------------- 积分项目分数配置 begin --------------------------------------*/
+}

+ 17 - 2
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/common/PerfRankMgmtConstant.java

@@ -13,7 +13,7 @@ public class PerfRankMgmtConstant extends FormConstant {
     /** 年度绩效排名管理-实体标识 */
     public static final String NCKD_PERFRANKMGMT_ENTITYID = "nckd_perfrankmgmt";
     /** 单据体 */
-    public static final String NCKD_PERFRANKMGMTENTRY = "NCKD_PERFRANKMGMTENTRY";
+    public static final String NCKD_PERFRANKMGMTENTRY = "nckd_perfrankmgmtentry";
     /** 员工 */
     public static final String NCKD_PERSON = "NCKD_PERSON";
     /** 排名 */
@@ -28,7 +28,7 @@ public class PerfRankMgmtConstant extends FormConstant {
     public static final String NCKD_ISRANKING = "NCKD_ISRANKING";
     /** 不参与排名原因 */
     public static final String NCKD_EXCLUDERANKREASON = "NCKD_EXCLUDERANKREASON";
-    /** 排名来源 */
+    /** 排名来源(0:系统生成,1:手动添加) */
     public static final String NCKD_RANKSOURCE = "NCKD_RANKSOURCE";
     /** 组名 */
     public static final String NCKD_GROUPNAME = "NCKD_GROUPNAME";
@@ -44,4 +44,19 @@ public class PerfRankMgmtConstant extends FormConstant {
     public static final String NCKD_BASICS = "NCKD_BASICS";
     /** 优秀人数 */
     public static final String NCKD_EXCELLENTS = "NCKD_EXCELLENTS";
+    /** 行政组织 */
+    public static final String NCKD_ADMINORG = "nckd_adminorg";
+    /** 获取排名名单-按钮 */
+    public static final String NCKD_GETRANKLIST = "nckd_getranklist";
+    /** 获取排名名单-OP */
+    public static final String GETRANKLIST_OP = "getranklist";
+
+
+
+    /*-------------------------------------- 排名考核结果比例配置 begin --------------------------------------*/
+    /** 新建动态调整(弹窗)-实体标识 */
+    public static final String RANKRATIOCONF_ENTITYID = "nckd_rankratioconf";
+    /** 比例 */
+    public static final String NCKD_RATIO = "nckd_ratio";
+    /*-------------------------------------- 排名考核结果比例配置 end --------------------------------------*/
 }

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

@@ -250,6 +250,11 @@ public class PositionStructureConstant extends FormConstant {
 
     /*-------------------------------------- 年度调整解锁单据 end --------------------------------------*/
 
+    /*-------------------------------------- 配置有职位津贴的管理人员 begin --------------------------------------*/
+    /** 配置有职位津贴的管理人员-实体标识 */
+    public static final String MANAGERALLOWANCE_ENTITYID = "nckd_managerallowance";
+    /*-------------------------------------- 配置有职位津贴的管理人员 end --------------------------------------*/
+
 
     /**职位序列对应职级查询*/
     public static final String JOBSEQTOJOBLEVEL_QUERY = "jobseqtojoblevelquery";

+ 397 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/helper/ContributionHelper.java

@@ -0,0 +1,397 @@
+package nckd.jxccl.hr.psms.helper;
+
+import kd.bos.algo.Algo;
+import kd.bos.algo.AlgoContext;
+import kd.bos.algo.DataSet;
+import kd.bos.algo.Row;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+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.DateUtil;
+import nckd.jxccl.base.common.utils.QueryFieldBuilder;
+import nckd.jxccl.base.orm.helper.QFilterCommonHelper;
+import nckd.jxccl.hr.psms.common.ContributionEvaluationConstant;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * 年度贡献积分辅助类
+ * @author W.Y.C
+ * @date 2025/10/26 13:34
+ * @version 1.0
+ */
+public class ContributionHelper {
+
+    /**
+     * 根据人员获取积分项目有效分数
+     * 依据人员某年度已获得的积分(含在途单据)+积分配置的最高分计算出可获得的分数
+     *
+     * @param scoreItemValidScoreList 待计算列表
+     * @param year 年份
+     * @param excludeBillId 排除的单据ID(计算有效分数不含当前单据),可为空
+     * @return: ScoreItemValidScore
+     * @author W.Y.C
+     * @date: 2025/10/26 13:38
+     */
+    public static List<ScoreItemValidScore> getScoreItemValidScore(List<ScoreItemValidScore> scoreItemValidScoreList, Date year, Long excludeBillId) {
+        if (scoreItemValidScoreList == null || scoreItemValidScoreList.isEmpty()) {
+            return scoreItemValidScoreList;
+        }
+
+        //获取积分项目分数配置
+        Map<Long, BigDecimal> scoreConfMap = getScoreConf();
+        //统计人员各项目已累计积分
+        Long[] personIdArray = scoreItemValidScoreList.stream()
+                .map(ScoreItemValidScore::getPersonId)
+                .filter(Objects::nonNull)
+                .distinct()
+                .toArray(Long[]::new);
+
+        List<ScoreItemValidScore> scoreItemAccumulateScoreList = getScoreItemAccumulateScore(personIdArray, year, excludeBillId);
+
+        //按人员分组已累计积分数据,提高查找效率
+        Map<Long, List<ScoreItemValidScore>> personAccumulateScoreMap = scoreItemAccumulateScoreList.stream()
+                .collect(Collectors.groupingBy(ScoreItemValidScore::getPersonId));
+
+        for (ScoreItemValidScore scoreItemValidScore : scoreItemValidScoreList) {
+            if (scoreItemValidScore == null) {
+                continue;
+            }
+
+            Long personId = scoreItemValidScore.getPersonId();
+            Long scoreItemId = scoreItemValidScore.getScoreItemId();
+            Long scoreItemSubId = scoreItemValidScore.getScoreItemSubId();
+
+            //获取该人员的累计积分数据
+            List<ScoreItemValidScore> personAccumulateScores = personAccumulateScoreMap.getOrDefault(personId, new ArrayList<>());
+
+            //计算积分项目和具体项目累计分数
+            BigDecimal scoreItemSumScore = BigDecimal.ZERO;
+            BigDecimal scoreItemSubSumScore = BigDecimal.ZERO;
+
+            for (ScoreItemValidScore accumulateScore : personAccumulateScores) {
+                Long tempScoreItemId = accumulateScore.getScoreItemId();
+                Long tempScoreItemSubId = accumulateScore.getScoreItemSubId();
+
+                if (Objects.equals(tempScoreItemId, scoreItemId)) {
+                    scoreItemSumScore = scoreItemSumScore.add(accumulateScore.getSumScore());
+                    if (Objects.equals(tempScoreItemSubId, scoreItemSubId)) {
+                        scoreItemSubSumScore = accumulateScore.getSumScore();
+                    }
+                }
+            }
+
+            //根据配置决定使用哪种累计分数
+            BigDecimal sumScore = determineSumScore(scoreConfMap, scoreItemId, scoreItemSumScore, scoreItemSubSumScore);
+
+            BigDecimal validScore = calculateAvailableScore(scoreConfMap, scoreItemId, scoreItemSubId, sumScore);
+            scoreItemValidScore.setValidScore(validScore);
+            scoreItemValidScore.setSumScore(sumScore);
+        }
+        return scoreItemValidScoreList;
+    }
+
+    /**
+     * 根据人员、年度、积分项目、具体积分项目获取有效分数
+     * 依据人员某年度已获得的积分(含在途单据)+积分配置的最高分计算出可获得的分数
+     *
+     * @param scoreItemValidScore 待计算积分信息
+     * @param year                年份
+     * @param excludeBillId       排除的单据ID(计算有效分数不含当前单据),可为空
+     * @author W.Y.C
+     * @date: 2025/10/26 13:38
+     */
+    public static void getScoreItemValidScore(ScoreItemValidScore scoreItemValidScore, Date year, Long excludeBillId) {
+        if (scoreItemValidScore == null) {
+            return;
+        }
+
+        //复用批量处理方法
+        List<ScoreItemValidScore> inputList = new ArrayList<>();
+        inputList.add(scoreItemValidScore);
+        List<ScoreItemValidScore> result = getScoreItemValidScore(inputList, year, excludeBillId);
+    }
+
+    /**
+     * 根据人员和年度获取积分项目已累计分数
+     * @param personIds 人员
+     * @param year 年度
+     * @param excludeBillId 排除的单据ID(计算有效分数不含当前单据),可为空
+     * @return: List<ScoreItemValidScore> 积分项目已累计分数
+     * @author W.Y.C
+     * @date: 2025/10/26 16:20
+     */
+    public static List<ScoreItemValidScore> getScoreItemAccumulateScore(Long[] personIds, Date year, Long excludeBillId) {
+        if (personIds == null || personIds.length == 0) {
+            return new ArrayList<>();
+        }
+
+        LocalDateTime yearStarDateTime = DateUtil.beginOfYear(DateUtil.toLocalDateTime(year));
+        QueryFieldBuilder scoreItemConfFieldBuilder = QueryFieldBuilder.create()
+                .add(ContributionEvaluationConstant.NCKD_YEAR)
+                .addIdNumberName(ContributionEvaluationConstant.NCKD_SCOREITEM)
+                .addIdNumberName(ContributionEvaluationConstant.NCKD_SCOREITEMSUB)
+                .addGroup(new String[]{FormConstant.NCKD_ENTRYENTITY,ContributionEvaluationConstant.NCKD_PERSON},FormConstant.ID_KEY)
+                .addGroup(new String[]{FormConstant.NCKD_ENTRYENTITY}, ContributionEvaluationConstant.NCKD_SCORE,ContributionEvaluationConstant.NCKD_ORISCORE);
+
+        //查询非"审批不通过"、"已废弃"年度贡献积分
+        QFilter filter = new QFilter(FormConstant.BILL_STATUS_KEY, QCP.not_in, new String[]{"E", "F"})
+                .and(ContributionEvaluationConstant.NCKD_YEAR,QCP.large_equals,DateUtil.beginOfDay(yearStarDateTime))
+                .and(ContributionEvaluationConstant.NCKD_YEAR,QCP.less_equals, DateUtil.endOfDay(yearStarDateTime))
+                .and(String.join( ".",FormConstant.NCKD_ENTRYENTITY,ContributionEvaluationConstant.NCKD_PERSON),QCP.in,personIds);
+
+        if(excludeBillId != null && excludeBillId > 0){
+            filter.and(FormConstant.ID_KEY,QCP.not_equals,excludeBillId);
+        }
+
+        List<ScoreItemValidScore> scoreItemValidScoreList = new ArrayList<>();
+        try(AlgoContext context = Algo.newContext()) {
+            DataSet dateSet = QueryServiceHelper.queryDataSet(ContributionHelper.class.getName(),
+                    ContributionEvaluationConstant.CONTRIBBILL_ENTITYID,
+                    scoreItemConfFieldBuilder.buildSelect(),
+                    new QFilter[]{filter}, null, 5000);
+
+            DataSet sumDateSet = dateSet.copy()
+                    // 按人员、积分项目、积分项目子项进行分组
+                    .groupBy(new String[]{String.join(".", ContributionEvaluationConstant.NCKD_ENTRYENTITY, ContributionEvaluationConstant.NCKD_PERSON, FormConstant.ID_KEY),
+                            String.join(".", ContributionEvaluationConstant.NCKD_SCOREITEM, FormConstant.ID_KEY),
+                            String.join(".", ContributionEvaluationConstant.NCKD_SCOREITEMSUB, FormConstant.ID_KEY)})
+                    .sum(String.join(".", ContributionEvaluationConstant.NCKD_ENTRYENTITY, FormConstant.NCKD_SCORE),"sumScore").finish();
+
+            while (sumDateSet.hasNext()) {
+                Row row = sumDateSet.next();
+                Long personId = row.getLong(String.join(".", ContributionEvaluationConstant.NCKD_ENTRYENTITY, ContributionEvaluationConstant.NCKD_PERSON, FormConstant.ID_KEY));
+                Long scoreItemId = row.getLong(String.join(".", ContributionEvaluationConstant.NCKD_SCOREITEM, FormConstant.ID_KEY));
+                Long scoreItemSubId = row.getLong(String.join(".", ContributionEvaluationConstant.NCKD_SCOREITEMSUB, FormConstant.ID_KEY));
+                //已获得的累计积分
+                BigDecimal sumScore = row.getBigDecimal("sumScore");
+                ScoreItemValidScore scoreItemValidScore = new ScoreItemValidScore(personId, scoreItemId, scoreItemSubId, sumScore);
+                scoreItemValidScoreList.add(scoreItemValidScore);
+            }
+        }
+        return scoreItemValidScoreList;
+    }
+
+    /**
+     * 计算可获得的有效分数
+     * 基于累计总分和限制分数,计算还可以获得多少分数(不超过限制)
+     *
+     * @param scoreConfMap 分数配置映射表,key为配置ID,value为限分数值
+     * @param scoreItemId 积分项目ID
+     * @param scoreItemSubId 具体积分项目ID
+     * @param accumulatedScore 累计总分
+     * @return 可获得的有效分数
+     *         如果没有任何限制返回null
+     *         如果已超过限制返回BigDecimal.ZERO
+     *         否则返回剩余可加分数
+     * @author W.Y.C
+     * @date: 2025/10/26 16:53
+     */
+    private static BigDecimal calculateAvailableScore(Map<Long, BigDecimal> scoreConfMap, Long scoreItemId, Long scoreItemSubId, BigDecimal accumulatedScore) {
+        // 积分项目限分 - 如果没有找到则不限分
+        BigDecimal scoreItemMaxScore = scoreConfMap.get(scoreItemId);
+
+        // 具体积分项目限分 - 如果没有找到则不限分
+        BigDecimal scoreItemSubMaxScore = null;
+        if (scoreItemSubId != null && scoreItemSubId > 0) {
+            scoreItemSubMaxScore = scoreConfMap.get(scoreItemSubId);
+        }
+
+        // 处理累计分数的空值
+        BigDecimal actualAccumulatedScore = accumulatedScore == null ? BigDecimal.ZERO : accumulatedScore;
+
+        // 计算实际的最大限制
+        BigDecimal actualMaxScore = null;
+
+        // 检查具体积分项目限分是否存在且大于0
+        if (scoreItemSubMaxScore != null && scoreItemSubMaxScore.compareTo(BigDecimal.ZERO) > 0) {
+            actualMaxScore = scoreItemSubMaxScore;
+        }
+
+        // 检查积分项目限分是否存在且大于0
+        if (scoreItemMaxScore != null && scoreItemMaxScore.compareTo(BigDecimal.ZERO) > 0) {
+            if (actualMaxScore == null) {
+                actualMaxScore = scoreItemMaxScore;
+            } else {
+                // 双重限制,取较小值
+                actualMaxScore = actualMaxScore.min(scoreItemMaxScore);
+            }
+        }
+
+        // 如果没有限制,返回null
+        if (actualMaxScore == null) {
+            return null;
+        }
+
+        // 计算可获得的有效分数
+        BigDecimal availableScore;
+        if (actualAccumulatedScore.compareTo(actualMaxScore) >= 0) {
+            // 已经超过限制,不能再获得分数
+            availableScore = BigDecimal.ZERO;
+        } else {
+            // 计算剩余可加分数
+            availableScore = actualMaxScore.subtract(actualAccumulatedScore);
+        }
+
+        return availableScore;
+    }
+
+    /**
+     * 决定使用哪种累计分数
+     * @param scoreConfMap 分数配置映射
+     * @param scoreItemId 积分项目ID
+     * @param scoreItemSumScore 积分项目累计分数
+     * @param scoreItemSubSumScore 积分项目具体项目累计分数
+     * @return 应该使用的累计分数
+     */
+    private static BigDecimal determineSumScore(Map<Long, BigDecimal> scoreConfMap, Long scoreItemId,
+                                                BigDecimal scoreItemSumScore, BigDecimal scoreItemSubSumScore) {
+        BigDecimal scoreItemMaxScore = scoreConfMap.get(scoreItemId);
+        if (scoreItemMaxScore != null) {
+            //如果设置积分项目大类分数限制,则取大类的累计积分
+            return scoreItemSumScore;
+        } else {
+            //如果没有设置积分项目大类分数限制,则取小类的累计积分
+            return scoreItemSubSumScore;
+        }
+    }
+
+    /**
+     * 获取积分配置
+     * @return: java.util.Map<java.lang.Long, java.math.BigDecimal>;key为积分项目子项ID(项目子项为空时key为项目大项),value为积分项目对应的最大分数
+     * @author W.Y.C
+     * @date: 2025/10/26 13:38
+     */
+    public static Map<Long, BigDecimal> getScoreConf() {
+        //查询积分项目分数配置
+        QueryFieldBuilder scoreItemConfFieldBuilder = QueryFieldBuilder.create()
+                .addIdNumberName(FormConstant.NCKD_ENTRYENTITY, ContributionEvaluationConstant.NCKD_SCOREITEM)
+                .addIdNumberName(FormConstant.NCKD_ENTRYENTITY, ContributionEvaluationConstant.NCKD_SCOREITEMSUB)
+                .addGroup(new String[]{FormConstant.NCKD_ENTRYENTITY}, ContributionEvaluationConstant.NCKD_MAXSCORE);
+
+        DynamicObjectCollection scoreItemConfQuery = QueryServiceHelper.query(ContributionEvaluationConstant.SCOREITEMCONF_ENTITYID,
+                scoreItemConfFieldBuilder.buildSelect(),
+                new QFilter[]{QFilterCommonHelper.getEnableFilter()});
+
+        //scoreMap:key为积分项目子项ID(项目子项为空时key为项目大项),value为积分项目对应的最大分数
+        Map<Long, BigDecimal> scoreMap = scoreItemConfQuery.stream().collect(Collectors.toMap(
+                item -> {
+                    long scoreItemSubId = item.getLong(String.join(".", FormConstant.NCKD_ENTRYENTITY, ContributionEvaluationConstant.NCKD_SCOREITEMSUB, FormConstant.ID_KEY));
+                    if (scoreItemSubId <= 0) {
+                        return item.getLong(String.join(".", FormConstant.NCKD_ENTRYENTITY, ContributionEvaluationConstant.NCKD_SCOREITEM, FormConstant.ID_KEY));
+                    } else {
+                        return scoreItemSubId;
+                    }
+                },
+                item -> item.getBigDecimal(String.join(".", FormConstant.NCKD_ENTRYENTITY, ContributionEvaluationConstant.NCKD_MAXSCORE)),
+                //处理重复key的情况,取最大值
+                (existing, replacement) -> existing.max(replacement)
+        ));
+        return scoreMap;
+    }
+
+    public static class ScoreItemValidScore{
+        private Long personId;
+        private Long scoreItemId;
+        private Long scoreItemSubId;
+        /**本次积分*/
+        private BigDecimal currentScore;
+        private BigDecimal sumScore;
+        /** 可获得的有效分数(如果没有任何限制返回null,如果已超过限制返回0,否则返回剩余可加分数) */
+        private BigDecimal validScore;
+        //没有业务含义,只用来记录分录索引号;
+        private int rowIndex;
+
+        public ScoreItemValidScore(Long personId, Long scoreItemId, Long scoreItemSubId) {
+            this.personId = personId;
+            this.scoreItemId = scoreItemId;
+            this.scoreItemSubId = scoreItemSubId;
+        }
+
+        public ScoreItemValidScore(Long personId, Long scoreItemId, Long scoreItemSubId, BigDecimal sumScore) {
+            this.personId = personId;
+            this.scoreItemId = scoreItemId;
+            this.scoreItemSubId = scoreItemSubId;
+            this.sumScore = sumScore;
+        }
+
+        public ScoreItemValidScore(Long personId, Long scoreItemId, Long scoreItemSubId, BigDecimal sumScore, BigDecimal validScore) {
+            this.personId = personId;
+            this.scoreItemId = scoreItemId;
+            this.scoreItemSubId = scoreItemSubId;
+            this.sumScore = sumScore;
+            this.validScore = validScore;
+        }
+
+        public Long getPersonId() {
+            return personId;
+        }
+
+        public void setPersonId(Long personId) {
+            this.personId = personId;
+        }
+
+        public Long getScoreItemId() {
+            return scoreItemId;
+        }
+
+        public void setScoreItemId(Long scoreItemId) {
+            this.scoreItemId = scoreItemId;
+        }
+
+        public Long getScoreItemSubId() {
+            return scoreItemSubId;
+        }
+
+        public void setScoreItemSubId(Long scoreItemSubId) {
+            this.scoreItemSubId = scoreItemSubId;
+        }
+
+        public BigDecimal getSumScore() {
+            return sumScore;
+        }
+
+        public void setSumScore(BigDecimal sumScore) {
+            this.sumScore = sumScore;
+        }
+
+        public BigDecimal getValidScore() {
+            //为空时说明不限制分数
+            BigDecimal trulyValidScore = this.currentScore;
+            if(this.validScore != null){
+                //如果可使用分数超过录入分数则使用录入分数(oriScore),否则使用可使用分数
+                trulyValidScore = this.validScore.min(this.currentScore);
+            }
+            return trulyValidScore;
+        }
+
+        public void setValidScore(BigDecimal validScore) {
+            this.validScore = validScore;
+        }
+
+        public BigDecimal getCurrentScore() {
+            return currentScore;
+        }
+
+        public void setCurrentScore(BigDecimal currentScore) {
+            this.currentScore = currentScore;
+        }
+
+        public int getRowIndex() {
+            return rowIndex;
+        }
+
+        public void setRowIndex(int rowIndex) {
+            this.rowIndex = rowIndex;
+        }
+    }
+}

+ 59 - 1
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/helper/PositionStructureHelper.java → code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/helper/PositionFileHelper.java

@@ -26,7 +26,7 @@ import java.util.Map;
  * @version 1.0
  * @date 2025/9/13 14:07
  */
-public class PositionStructureHelper {
+public class PositionFileHelper {
 
 
     /**
@@ -43,6 +43,56 @@ public class PositionStructureHelper {
         return QueryServiceHelper.exists(PositionStructureConstant.PERSONPOSFILE_ENTITYID, new QFilter[]{filter});
     }
 
+    /**
+     * 获取最新人员档案
+     * @param personId 人员ID
+     * @return: kd.bos.dataentity.entity.DynamicObject[]
+     * @author W.Y.C
+     * @date: 2025/10/20 21:56
+     */
+    public static DynamicObject[] getNewestPersonPosFileByPerson(Long... personId) {
+        QFilter filer = new QFilter(String.join(".", FormConstant.NCKD_PERSON, FormConstant.ID_KEY), QCP.in, personId)
+                .and(PositionStructureConstant.NCKD_DISABLE, QCP.equals, EnableEnum.NO.getCode())
+                .and(PositionStructureConstant.NCKD_ISCURRENTNEWEST, QCP.equals, EnableEnum.YES.getCode());
+        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)
+                .addIdNumberName(PositionStructureConstant.NCKD_PERSON)
+                .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);
+        DynamicObject[] load = BusinessDataServiceHelper.load(
+                PositionStructureConstant.PERSONPOSFILE_ENTITYID,
+                queryFieldBuilder.buildSelect(),
+                new QFilter[]{filer},
+                queryFieldBuilder.buildOrder()
+        );
+        return load;
+    }
+
     /**
      * 根据人员和状态过滤职位档案
      * @param personId 人员ID
@@ -291,11 +341,15 @@ public class PositionStructureHelper {
                 .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)
@@ -305,6 +359,10 @@ public class PositionStructureHelper {
                         new String[]{PositionStructureConstant.NCKD_JOBLEVELHR},
                         FormConstant.JOBLEVELSEQ
                 )
+                .addIdNumberNameWithExtras(
+                        new String[]{PositionStructureConstant.NCKD_DIPLOMA},
+                        FormConstant.NCKD_SCORE
+                )
                 .addIdNumberName(PositionStructureConstant.NCKD_JOBSEQHR)
                 .orderDesc(PositionStructureConstant.NCKD_BEGINDATE);
 

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

@@ -17,7 +17,7 @@ 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 nckd.jxccl.hr.psms.helper.PositionFileHelper;
 
 import java.time.LocalDateTime;
 import java.util.Date;
@@ -69,14 +69,14 @@ public class NewDynamicAdjustmentDiaLogFormPlugin extends AbstractFormPlugin {
                     Date adjustDate = ConvertUtil.toDate(this.getModel().getValue(PositionStructureConstant.NCKD_ADJUSTDATE));
                     DynamicObject person = ConvertUtil.toDynamicObject(this.getModel().getValue(PositionStructureConstant.NCKD_PERSON));
                     if(adjustDate != null && person.getDataEntityType() != null){
-                        DynamicObject latsPersonPosFileByPerson = PositionStructureHelper.getLatsPersonPosFileByPerson(person.getLong(FormConstant.ID_KEY));
+                        DynamicObject latsPersonPosFileByPerson = PositionFileHelper.getLatsPersonPosFileByPerson(person.getLong(FormConstant.ID_KEY));
                         if(latsPersonPosFileByPerson == null){
                             this.getView().showErrorNotification(StrFormatter.format("当前无法为【{}】进行动态调整,因为他/她尚未建立职位档案。请前往“职位及积分初定” -> 进行初定!", person.getString(FormConstant.NAME_KEY)));
                             clearFormInfo();
                         }else{
                             try{
                                 LocalDateTime endDay = DateUtil.endOfDay(DateUtil.toLocalDateTime(adjustDate));
-                                PositionAppointmentBO positionAppointment = PositionStructureHelper.positionAppointmentQuery(person.getLong(FormConstant.ID_KEY), DateUtil.toDate(endDay));
+                                PositionAppointmentBO positionAppointment = PositionFileHelper.positionAppointmentQuery(person.getLong(FormConstant.ID_KEY), DateUtil.toDate(endDay));
                                 JobLevelCalculatorService.JobLevelResult jobLevelResult = getJobLevel(person, adjustDate,positionAppointment);
 
                                 updateFormInfo(person, adjustDate, latsPersonPosFileByPerson,jobLevelResult,positionAppointment);
@@ -100,7 +100,7 @@ public class NewDynamicAdjustmentDiaLogFormPlugin extends AbstractFormPlugin {
         String personName = person.getString(FormConstant.NAME_KEY);
         // 1、获取员工职位档案信息
         // 对应SHR:personpositionfileInfo & appraisalCollection
-        DynamicObject currentPersonPosFile = PositionStructureHelper.getLatsPersonPosFileByPerson(personId);
+        DynamicObject currentPersonPosFile = PositionFileHelper.getLatsPersonPosFileByPerson(personId);
         if (currentPersonPosFile == null || currentPersonPosFile.getDataEntityType() == null) {
             throw new ValidationException(StrFormatter.format("当前无法为【{}】进行调整,因为他/她尚未建立职位档案。请前往“职位及积分初定” -> 进行初定!", personName));
         }
@@ -147,7 +147,7 @@ public class NewDynamicAdjustmentDiaLogFormPlugin extends AbstractFormPlugin {
         // 8、检查指定年份是否存在未生效的年度调整记录
         int currentYear = DateUtil.getYear(new Date());
         int year = DateUtil.getYear(date);
-        DynamicObject[] personPosFileByPersonAndState = PositionStructureHelper.getPersonPosFileByPersonAndState(personId,
+        DynamicObject[] personPosFileByPersonAndState = PositionFileHelper.getPersonPosFileByPersonAndState(personId,
                 new String[]{"3"},
                 null,
                 new QFilter(PositionStructureConstant.NCKD_ADJUSTTYPE, QCP.not_in, new String[]{"1", "2"}).and(new QFilter(PositionStructureConstant.NCKD_EXECUTEYEAR, QCP.in, new Integer[]{currentYear, year})));

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

@@ -62,10 +62,12 @@ public class UnAnnualAdjustListPlugin extends AbstractListPlugin implements Plug
 
         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(FormConstant.IS_SEQLATESTRECORD, QCP.equals, EnableEnum.YES.getCode())
+                .and(FormConstant.IS_DELETED, QCP.equals, EnableEnum.NO.getCode())
+                .and(String.join(".", FormConstant.ASSIGNMENT, FormConstant.IS_PRIMARY), QCP.equals, EnableEnum.YES.getCode())
+                .and(String.join(".", FormConstant.ASSIGNMENT, FormConstant.IS_DELETED), QCP.equals, EnableEnum.NO.getCode())
+                .and(String.join(".", PositionStructureConstant.PERSONPOSFILE_ENTITYID, PositionStructureConstant.NCKD_DISABLE), QCP.equals, EnableEnum.NO.getCode())
+                .and(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()))
                 //初定时间不等于当年

+ 178 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/contribution/ContribBillFormPlugin.java

@@ -0,0 +1,178 @@
+package nckd.jxccl.hr.psms.plugin.form.contribution;
+
+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.field.BasedataEdit;
+import kd.bos.form.field.events.BeforeF7SelectEvent;
+import kd.bos.form.field.events.BeforeF7SelectListener;
+import kd.bos.form.plugin.AbstractFormPlugin;
+import kd.bos.list.ListShowParameter;
+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.ContributionEvaluationConstant;
+import nckd.jxccl.hr.psms.helper.ContributionHelper;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.EventObject;
+import java.util.List;
+import java.util.Objects;
+
+/**
+* 年度贡献积分单据-表单插件
+* 实体标识:nckd_contribbill
+* @author W.Y.C
+* @date 2025/10/25 22:57
+* @version 1.0
+*/
+public class ContribBillFormPlugin extends AbstractFormPlugin implements Plugin, BeforeF7SelectListener {
+
+    @Override
+    public void registerListener(EventObject e) {
+        super.registerListener(e);
+        // 积分具体项目字段添加值变化监听
+        BasedataEdit scoreItemSubField = this.getView().getControl(ContributionEvaluationConstant.NCKD_SCOREITEMSUB);
+        if (scoreItemSubField != null) {
+            scoreItemSubField.addBeforeF7SelectListener(this);
+        }
+
+        BasedataEdit scoreItemRankField = this.getView().getControl(ContributionEvaluationConstant.NCKD_SCOREITEMRANK);
+        if (scoreItemRankField != null) {
+            scoreItemRankField.addBeforeF7SelectListener(this);
+        }
+    }
+
+    @Override
+    public void beforeF7Select(BeforeF7SelectEvent beforeF7SelectEvent) {
+        String fieldKey = beforeF7SelectEvent.getProperty().getName();
+        if (ContributionEvaluationConstant.NCKD_SCOREITEMSUB.equalsIgnoreCase(fieldKey) || ContributionEvaluationConstant.NCKD_SCOREITEMRANK.equalsIgnoreCase(fieldKey)) {
+            DynamicObject scoreItem = ConvertUtil.toDynamicObjectOrNull(this.getModel().getValue(ContributionEvaluationConstant.NCKD_SCOREITEM));
+            if(scoreItem == null){
+                this.getView().showTipNotification("请先选择“积分项目”");
+                beforeF7SelectEvent.setCancel( true);
+                return;
+            }
+            // 构建过滤条件:假设B字段的F7列表关联实体中有一个字段与A字段值关联(例如"relatedField")
+            QFilter filter = new QFilter(ContributionEvaluationConstant.NCKD_SCOREITEM, QCP.equals, scoreItem.getLong(FormConstant.ID_KEY));
+            // 获取F7列表的显示参数并设置过滤条件
+            ListShowParameter showParameter = (ListShowParameter) beforeF7SelectEvent.getFormShowParameter();
+            showParameter.getListFilterParameter().setFilter(filter);
+        }
+    }
+
+    @Override
+    public void propertyChanged(PropertyChangedArgs e) {
+        String fieldKey = e.getProperty().getName();
+        ChangeData[] changeSet = e.getChangeSet();
+        int rowIndex = changeSet[0].getRowIndex();
+        Object oldValue = changeSet[0].getOldValue();
+        Object newValue = changeSet[0].getNewValue();
+
+        //年度、积分项目、积分子项目、人员、原始积分发生变更将触发"录入积分"重算
+        if(isScoreCalculationTriggerField(fieldKey) && !Objects.equals(oldValue, newValue)) {
+            if(rowIndex > -1) {
+                //分录内容变更
+                handleEntryRowChange(rowIndex);
+            } else {
+                //表头字段变更
+                handleHeadFieldChange();
+            }
+        }
+    }
+
+    /**
+     * 判断是否为触发积分计算的字段
+     * @param fieldKey 变更字段
+     * @return: boolean
+     * @author W.Y.C
+     * @date: 2025/10/26 19:46
+     */
+    private boolean isScoreCalculationTriggerField(String fieldKey) {
+        return ContributionEvaluationConstant.NCKD_YEAR.equalsIgnoreCase(fieldKey)
+                || ContributionEvaluationConstant.NCKD_PERSON.equalsIgnoreCase(fieldKey)
+                || ContributionEvaluationConstant.NCKD_SCOREITEMSUB.equalsIgnoreCase(fieldKey)
+                || ContributionEvaluationConstant.NCKD_SCOREITEM.equalsIgnoreCase(fieldKey)
+                || ContributionEvaluationConstant.NCKD_ORISCORE.equalsIgnoreCase(fieldKey);
+    }
+
+    /**
+     * 处理分录行变更
+     * 分录变更则针对当前分录计算积分
+     * @param rowIndex 行索引
+     * @return: void
+     * @author W.Y.C
+     * @date: 2025/10/26 19:47
+     */
+    private void handleEntryRowChange(int rowIndex) {
+        Long id = ConvertUtil.toLong(this.getModel().getValue(FormConstant.ID_KEY));
+        Date date = ConvertUtil.toDate(this.getModel().getValue(ContributionEvaluationConstant.NCKD_YEAR));
+        DynamicObject scoreItem = ConvertUtil.toDynamicObjectOrNull(this.getModel().getValue(ContributionEvaluationConstant.NCKD_SCOREITEM));
+        DynamicObject scoreItemSub = ConvertUtil.toDynamicObjectOrNull(this.getModel().getValue(ContributionEvaluationConstant.NCKD_SCOREITEMSUB));
+        DynamicObject person = ConvertUtil.toDynamicObjectOrNull(this.getModel().getValue(ContributionEvaluationConstant.NCKD_PERSON, rowIndex));
+        BigDecimal oriScore = ConvertUtil.toBigDecimal(this.getModel().getValue(ContributionEvaluationConstant.NCKD_ORISCORE, rowIndex));
+
+        if (date != null && scoreItem != null && scoreItemSub != null && person != null
+                && oriScore != null && oriScore.compareTo(BigDecimal.ZERO) > 0) {
+            //计算本次可用积分
+            ContributionHelper.ScoreItemValidScore scoreItemValidScore = new ContributionHelper.ScoreItemValidScore(
+                    person.getLong(FormConstant.ID_KEY),
+                    scoreItem.getLong(FormConstant.ID_KEY),
+                    scoreItemSub.getLong(FormConstant.ID_KEY));
+            scoreItemValidScore.setCurrentScore(oriScore);
+            ContributionHelper.getScoreItemValidScore(scoreItemValidScore, date, id);
+            this.getModel().setValue(ContributionEvaluationConstant.NCKD_SCORE, scoreItemValidScore.getValidScore(), rowIndex);
+            this.getView().updateView(ContributionEvaluationConstant.NCKD_SCORE, rowIndex);
+        }
+    }
+
+    /**
+     * 处理表头字段变更
+     * 表头变更则重新计算所有分录(已维护人员和原积分的数据)的积分
+     * @param
+     * @return: void
+     * @author W.Y.C
+     * @date: 2025/10/26 19:47
+     */
+    private void handleHeadFieldChange() {
+        Long id = ConvertUtil.toLong(this.getModel().getValue(FormConstant.ID_KEY));
+        Date date = ConvertUtil.toDate(this.getModel().getValue(ContributionEvaluationConstant.NCKD_YEAR));
+        DynamicObject scoreItem = ConvertUtil.toDynamicObjectOrNull(this.getModel().getValue(ContributionEvaluationConstant.NCKD_SCOREITEM));
+        DynamicObject scoreItemSub = ConvertUtil.toDynamicObjectOrNull(this.getModel().getValue(ContributionEvaluationConstant.NCKD_SCOREITEMSUB));
+
+        if (date != null && scoreItem != null && scoreItemSub != null) {
+            //重置分录所有分录的"录用积分"字段
+            DynamicObjectCollection entryEntity = this.getModel().getEntryEntity(FormConstant.NCKD_ENTRYENTITY);
+            List<ContributionHelper.ScoreItemValidScore> scoreItemValidScoreList = new ArrayList<>(entryEntity.size());
+
+            for (int i = 0; i < entryEntity.size(); i++) {
+                DynamicObject person = ConvertUtil.toDynamicObjectOrNull(this.getModel().getValue(ContributionEvaluationConstant.NCKD_PERSON, i));
+                BigDecimal oriScore = ConvertUtil.toBigDecimal(this.getModel().getValue(ContributionEvaluationConstant.NCKD_ORISCORE, i));
+
+                if(person != null && oriScore != null && oriScore.compareTo(BigDecimal.ZERO) > 0){
+                    ContributionHelper.ScoreItemValidScore scoreItemValidScore = new ContributionHelper.ScoreItemValidScore(
+                            person.getLong(FormConstant.ID_KEY),
+                            scoreItem.getLong(FormConstant.ID_KEY),
+                            scoreItemSub.getLong(FormConstant.ID_KEY));
+                    scoreItemValidScore.setCurrentScore(oriScore);
+                    scoreItemValidScore.setRowIndex(i);
+                    scoreItemValidScoreList.add(scoreItemValidScore);
+                }
+            }
+
+            if (!scoreItemValidScoreList.isEmpty()) {
+                ContributionHelper.getScoreItemValidScore(scoreItemValidScoreList, date, id);
+                for (ContributionHelper.ScoreItemValidScore scoreItemValidScore : scoreItemValidScoreList) {
+                    this.getModel().setValue(ContributionEvaluationConstant.NCKD_SCORE, scoreItemValidScore.getValidScore(), scoreItemValidScore.getRowIndex());
+                }
+                this.getView().updateView(FormConstant.NCKD_ENTRYENTITY);
+            }
+        }
+    }
+
+}

+ 69 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/contribution/ScoreItemConfFormPlugin.java

@@ -0,0 +1,69 @@
+package nckd.jxccl.hr.psms.plugin.form.contribution;
+
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.entity.datamodel.events.ChangeData;
+import kd.bos.entity.datamodel.events.PropertyChangedArgs;
+import kd.bos.form.control.EntryGrid;
+import kd.bos.form.control.events.BaseDataColumnDependFieldSetEvent;
+import kd.bos.form.field.BasedataEdit;
+import kd.bos.form.field.events.BeforeF7SelectEvent;
+import kd.bos.form.field.events.BeforeF7SelectListener;
+import kd.bos.form.plugin.AbstractFormPlugin;
+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.ContributionEvaluationConstant;
+import nckd.jxccl.hr.psms.helper.ContributionHelper;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.EventObject;
+import java.util.Objects;
+
+/**
+* 积分项目分数配置插件
+* 实体标识:nckd_scoreitemconf
+* @author W.Y.C
+* @date 2025/10/25 22:13
+* @version 1.0
+*/
+public class ScoreItemConfFormPlugin extends AbstractFormPlugin implements Plugin, BeforeF7SelectListener {
+
+    @Override
+    public void registerListener(EventObject e) {
+        super.registerListener(e);
+
+        // 为分录中的A字段添加值变化监听
+        BasedataEdit bFieldEdit = this.getView().getControl(ContributionEvaluationConstant.NCKD_SCOREITEMSUB);
+        if (bFieldEdit != null) {
+            bFieldEdit.addBeforeF7SelectListener(this);
+        }
+    }
+
+
+    @Override
+    public void beforeF7Select(BeforeF7SelectEvent e) {
+        String fieldKey = e.getProperty().getName();
+
+        //基础资料联动
+        // 仅处理B字段的F7事件
+        if (ContributionEvaluationConstant.NCKD_SCOREITEMSUB.equalsIgnoreCase(fieldKey)) {
+            // 获取当前分录行的行号
+            int currentRow = e.getRow();
+
+            if (currentRow >= 0) {
+                // 获取当前行A字段的值
+                DynamicObject fieldAValue = ConvertUtil.toDynamicObjectOrNull(this.getModel().getValue(ContributionEvaluationConstant.NCKD_SCOREITEM, currentRow));
+                if(fieldAValue == null){
+                    this.getView().showTipNotification("请先选择“积分项目”");
+                    e.setCancel( true);
+                    return;
+                }
+                QFilter filter = new QFilter(ContributionEvaluationConstant.NCKD_SCOREITEM, QCP.equals, fieldAValue.getLong(FormConstant.ID_KEY));
+                e.addCustomQFilter(filter);
+            }
+        }
+    }
+
+}

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

@@ -10,7 +10,6 @@ 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;

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

@@ -1,9 +1,7 @@
 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;
@@ -15,14 +13,12 @@ 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 nckd.jxccl.hr.psms.helper.PositionFileHelper;
 
 import java.time.LocalDateTime;
 import java.util.Date;
@@ -87,7 +83,7 @@ public class NewHireInitialFormPlugin extends AbstractFormPlugin implements Plug
         if(date == null){
             date = new Date();
         }
-        PositionAppointmentBO positionAppointMen = PositionStructureHelper.positionAppointmentQuery(personId, date);
+        PositionAppointmentBO positionAppointMen = PositionFileHelper.positionAppointmentQuery(personId, date);
         if(positionAppointMen != null) {
             //学历
             DynamicObject perEduExp = positionAppointMen.getPerEduExp();

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

@@ -12,7 +12,7 @@ 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 nckd.jxccl.hr.psms.helper.PositionFileHelper;
 
 import java.util.Date;
 import java.util.EventObject;
@@ -74,7 +74,7 @@ public class ServingInitialFormPlugin extends AbstractFormPlugin implements Plug
         if (date == null) {
             date = new Date();
         }
-        PositionAppointmentBO positionAppointMen = PositionStructureHelper.positionAppointmentQuery(personId, date);
+        PositionAppointmentBO positionAppointMen = PositionFileHelper.positionAppointmentQuery(personId, date);
         if (positionAppointMen != null) {
             // 学历
             DynamicObject perEduExp = positionAppointMen.getPerEduExp();

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

@@ -41,10 +41,13 @@ public class UngradedPersonQueryListPlugin extends AbstractListPlugin implements
         //只查询没有初定的人员
         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()));
+                .and(String.join(".",FormConstant.ASSIGNMENT_ENTITYID, FormConstant.IS_PRIMARY), QCP.equals,EnableEnum.YES.getCode())
+                .and(FormConstant.IS_SEQLATESTRECORD, QCP.equals,EnableEnum.YES.getCode())
+                .and(FormConstant.IS_DELETED, QCP.equals,EnableEnum.NO.getCode())
+                .and(FormConstant.IS_PRIMARY, QCP.equals,EnableEnum.YES.getCode())
+
+                //只查询在职人员
+                .and(String.join(".",FormConstant.HRPI_EMPENTREL, FormConstant.LABOR_REL_STATUS,FormConstant.IS_HIRED), QCP.equals,EnableEnum.YES.getCode());
         setFilterEvent.addCustomQFilter(filter);
     }
 

+ 411 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/performance/PerfrankMgmtFormPlugin.java

@@ -0,0 +1,411 @@
+package nckd.jxccl.hr.psms.plugin.form.performance;
+
+import kd.bos.bill.IBillView;
+import kd.bos.common.enums.EnableEnum;
+import kd.bos.dataentity.OperateOption;
+import kd.bos.dataentity.RefObject;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.dataentity.metadata.dynamicobject.DynamicObjectType;
+import kd.bos.entity.EntityMetadataCache;
+import kd.bos.entity.QueryEntityType;
+import kd.bos.entity.datamodel.IDataModel;
+import kd.bos.entity.datamodel.RowDataEntity;
+import kd.bos.entity.datamodel.events.AfterAddRowEventArgs;
+import kd.bos.entity.datamodel.events.PropertyChangedArgs;
+import kd.bos.ext.hr.service.query.QueryEntityHelper;
+import kd.bos.form.ConfirmCallBackListener;
+import kd.bos.form.ConfirmTypes;
+import kd.bos.form.IClientViewProxy;
+import kd.bos.form.MessageBoxOptions;
+import kd.bos.form.MessageBoxResult;
+import kd.bos.form.events.AfterDoOperationEventArgs;
+import kd.bos.form.events.BeforeDoOperationEventArgs;
+import kd.bos.form.events.MessageBoxClosedEvent;
+import kd.bos.form.operate.FormOperate;
+import kd.bos.form.plugin.AbstractFormPlugin;
+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.QueryServiceHelper;
+import kd.sdk.plugin.Plugin;
+import nckd.jxccl.base.common.constant.FormConstant;
+import nckd.jxccl.base.common.constant.SystemQueryConstant;
+import nckd.jxccl.base.common.enums.AppraisalResultEnum;
+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.PerfRankMgmtConstant;
+import nckd.jxccl.hr.psms.common.PositionStructureConstant;
+import nckd.jxccl.hr.psms.helper.PositionFileHelper;
+
+import java.time.temporal.ChronoUnit;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.EventObject;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+
+/**
+* 年度绩效排名管理表单
+* @author W.Y.C
+* @date 2025/10/20 15:11
+* @version 1.0
+*/
+public class PerfrankMgmtFormPlugin extends AbstractFormPlugin implements Plugin {
+
+    private static final Log logger = LogFactory.getLog(PerfrankMgmtFormPlugin.class);
+
+    private final static String CALLBACK_ID = "interrupt_confirm";
+
+    @Override
+    public void afterBindData(EventObject e) {
+        sortEntry();
+    }
+
+    @Override
+    public void afterAddRow(AfterAddRowEventArgs e) {
+        try {
+            // 获取新增行信息并设置焦点
+            for (RowDataEntity rowDataEntity : e.getRowDataEntities()) {
+                int rowIndex = rowDataEntity.getRowIndex();
+                //获取分录焦点
+                IClientViewProxy viewProxy = this.getView().getService(IClientViewProxy.class);
+                viewProxy.focusCell(PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY, rowIndex, FormConstant.NCKD_PERSON);
+            }
+            this.getView().updateView(PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY);
+        } catch (Exception ex) {
+            logger.error("设置新增行焦点失败", ex);
+        }
+    }
+
+
+    @Override
+    public void propertyChanged(PropertyChangedArgs e) {
+        String name = e.getProperty().getName();
+        if(Stream.of(PerfRankMgmtConstant.NCKD_ISRANKING, PerfRankMgmtConstant.NCKD_APPRAISALRESULT,
+                        PerfRankMgmtConstant.NCKD_ALLOWANCERANK, PerfRankMgmtConstant.NCKD_POSTALLOWANCE,
+                        PerfRankMgmtConstant.NCKD_TOPRANK)
+                .anyMatch(prop -> prop.equalsIgnoreCase(name))){
+            calcRankCount();
+        }
+
+    }
+
+
+    @Override
+    public void beforeDoOperation(BeforeDoOperationEventArgs args) {
+        FormOperate operate = (FormOperate) args.getSource();
+        String itemKey = operate.getOperateKey();
+        if(PerfRankMgmtConstant.GETRANKLIST_OP.equalsIgnoreCase(itemKey)) {
+            RefObject<String> afterConfirm = new RefObject<>();
+            if (!operate.getOption().tryGetVariableValue("afterConfirm", afterConfirm)) {
+                //判断是否已获取过名单,如获取过再获取提示用户数据会丢失
+                DynamicObjectCollection entry = this.getModel().getEntryEntity(PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY);
+                boolean isGetRank = false;
+                if (!entry.isEmpty()) {
+                    for (DynamicObject dynamicObject : entry) {
+                        String rankSource = dynamicObject.getString(PerfRankMgmtConstant.NCKD_RANKSOURCE);
+                        if (EnableEnum.NO.getCode().equals(rankSource)) {
+                            isGetRank = true;
+                            break;
+                        }
+                    }
+                }
+                if (isGetRank) {
+                    // 取消当前操作,等待用户确认
+                    args.setCancel(true);
+                    //询问对话
+                    this.getView().showConfirm(
+                            "已获取过排名名单了,重新获取将会覆盖(系统生成部分)现有人员名单是否继续操作?",
+                            MessageBoxOptions.OKCancel,
+                            ConfirmTypes.Default,
+                            new ConfirmCallBackListener(CALLBACK_ID, this)
+                    );
+                }
+            }
+        }else if(Arrays.asList(FormConstant.SAVE_OP, FormConstant.SUBMIT_OP).contains(itemKey)){
+            //保存或提交先计算一遍
+            // calcRankCount();
+        }
+    }
+
+    @Override
+    public void confirmCallBack(MessageBoxClosedEvent evt) {
+        super.confirmCallBack(evt);
+        if (CALLBACK_ID.equals(evt.getCallBackId())) {
+            if (evt.getResult() == MessageBoxResult.Yes) {
+                // 用户选择“是”,继续执行业务逻辑
+                OperateOption option = OperateOption.create();
+                option.setVariableValue("afterConfirm", "true");
+                this.getView().invokeOperation(PerfRankMgmtConstant.GETRANKLIST_OP,option);
+            } else {
+                // 用户选择“否”,中断操作
+                this.getView().showTipNotification("操作已取消");
+            }
+        }
+    }
+
+    @Override
+    public void afterDoOperation(AfterDoOperationEventArgs afterDoOperationEventArgs) {
+        String itemKey = afterDoOperationEventArgs.getOperateKey();
+        if(PerfRankMgmtConstant.GETRANKLIST_OP.equalsIgnoreCase(itemKey)){
+            IBillView billView = (IBillView)this.getView();
+            boolean isFormDb = billView.getModel().getDataEntity().getDataEntityState().getFromDatabase();
+            if (!isFormDb) {
+                billView.showTipNotification("请保存后再获取排名名单");
+            }else{
+                IDataModel model = this.getModel();
+                DynamicObject adminOrg = ConvertUtil.toDynamicObject(model.getValue(FormConstant.NCKD_ADMINORG));
+                String structLongNumber = adminOrg.getString(FormConstant.STRUCTLONGNUMBER);
+                //查询所选组织下的人员
+                QFilter qFilter = new QFilter(String.join(".", FormConstant.HRPI_EMPPOSORGREL, FormConstant.ADMINORG, FormConstant.STRUCTLONGNUMBER), QCP.like, structLongNumber + "%");
+                //在职人员
+                qFilter.and(String.join(".", FormConstant.HRPI_EMPENTREL, FormConstant.LABOR_REL_STATUS,FormConstant.IS_HIRED), QCP.equals, EnableEnum.YES.getCode());
+                QueryFieldBuilder queryFieldBuilder = QueryFieldBuilder.create()
+                        //用工关系状态.编码(1005_S	在职-试用中、1010_S	在职、1020_S	离职、1030_S	已退休),istrial:是否试用,ishired:是否在职
+                        .addGroup(new String[]{FormConstant.HRPI_EMPENTREL, FormConstant.LABOR_REL_STATUS}, FormConstant.NUMBER_KEY, FormConstant.NAME_KEY, FormConstant.IS_TRIAL, FormConstant.IS_HIRED)
+                        //任职状态分类(1010_S:在岗,1030_S:不在岗)
+                        .addGroup(new String[]{FormConstant.HRPI_EMPPOSORGREL, FormConstant.POS_STATUS, FormConstant.POST_STATE_CLS}, FormConstant.NUMBER_KEY)
+                        //入职日期
+                        .addGroup(new String[]{FormConstant.HRPI_EMPENTREL}, FormConstant.ENTRYDATE)
+                        //职务级别
+                        .addGroup(new String[]{FormConstant.NCKD_HRPI_PARTYPOSH, FormConstant.NCKD_POSGRADE}, FormConstant.NAME_KEY, FormConstant.NUMBER_KEY, FormConstant.NCKD_SORTNUM)
+                        .addGroup(new String[]{FormConstant.EMPLOYEE_KEY}, FormConstant.ID_KEY, FormConstant.NAME_KEY, FormConstant.EMP_NUMBER_KEY);
+
+                // -------------------------------- 1、查询组织下的在职人员 --------------------------------
+                QueryEntityType queryEntityType = (QueryEntityType) EntityMetadataCache.getDataEntityType(SystemQueryConstant.HSPM_ASSIGNMENTQUERY);
+                DynamicObjectCollection personList = QueryEntityHelper.getInstance().getQueryDyoColl(queryEntityType, queryFieldBuilder.buildSelect(), new QFilter[]{qFilter},queryFieldBuilder.buildOrder());
+                //添加分录
+                DynamicObjectCollection entryEntityCols = this.getModel().getDataEntity(true).getDynamicObjectCollection(PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY);
+                //根据条件删除分录
+
+                // -------------------------------- 2、删除系统生成部分人员 --------------------------------
+                //删除系统生成部分人员
+                entryEntityCols.removeIf(obj -> EnableEnum.NO.getCode().equals(obj.getString(PerfRankMgmtConstant.NCKD_RANKSOURCE)));
+
+                List<Long> personIds = personList.stream()
+                        .map(person -> person.getLong(String.join(".", FormConstant.EMPLOYEE_KEY, FormConstant.ID_KEY)))
+                        .collect(Collectors.toList());
+                if(!personIds.isEmpty()) {
+
+                    // -------------------------------- 3、获取人员对应的职级 --------------------------------
+                    //获取职位积分档案,用于判断是否有职级;职级大于0默认勾选“职位津贴”
+                    DynamicObject[] newestPersonPosFileByPerson = PositionFileHelper.getNewestPersonPosFileByPerson(personIds.toArray(new Long[0]));
+                    Map<Long, Long> personJobLevelMap = Arrays.stream(newestPersonPosFileByPerson)
+                            .collect(Collectors.toMap(
+                                    obj -> obj.getLong(String.join(".", FormConstant.NCKD_PERSON, FormConstant.ID_KEY)),
+                                    obj -> obj.getLong(String.join(".", PositionStructureConstant.NCKD_JOBLEVELHR, FormConstant.JOBLEVELSEQ))
+                            ));
+
+                    // -------------------------------- 4、获取管理人员职位津贴配置 --------------------------------
+                    //获取《管理人员职位津贴》,存在则默认勾选“职位津贴”
+                    //查询在当前时间范围内的数据。NCKD_STARTDATE,NCKD_ENDDATE
+                    Date now = new Date();
+                        QFilter managerAllowanceFilter = new QFilter(FormConstant.NCKD_STARTDATE, QCP.less_equals, now)
+                            .and(FormConstant.NCKD_ENDDATE, QCP.large_equals,now)
+                            .and(FormConstant.NCKD_PERSON, QCP.in, personIds);
+                    DynamicObjectCollection managerAllowancQuery = QueryServiceHelper.query(PositionStructureConstant.MANAGERALLOWANCE_ENTITYID, String.join(".", FormConstant.NCKD_PERSON, FormConstant.ID_KEY), new QFilter[]{managerAllowanceFilter});
+                    Map<Long, Boolean> managerAllowanceMap = managerAllowancQuery.stream()
+                            .collect(Collectors.toMap(
+                                    obj -> obj.getLong(String.join(".", FormConstant.NCKD_PERSON, FormConstant.ID_KEY)),
+                                    obj -> Boolean.TRUE
+                            ));
+
+                    // -------------------------------- 4、加载数据到分录 --------------------------------
+                    for (DynamicObject person : personList) {
+                        DynamicObject entryCol = entryEntityCols.addNew();
+                        long personId = person.getLong(String.join(".", FormConstant.EMPLOYEE_KEY, FormConstant.ID_KEY));
+                        String personName = person.getString(String.join(".", FormConstant.EMPLOYEE_KEY, FormConstant.NAME_KEY));
+                        String personNumber = person.getString(String.join(".", FormConstant.EMPLOYEE_KEY, FormConstant.EMP_NUMBER_KEY));
+
+                        DynamicObjectType type = EntityMetadataCache.getDataEntityType(FormConstant.HRPI_EMPLOYEE);
+                        DynamicObject personObj = (DynamicObject)type.createInstance();
+                        personObj.set(FormConstant.ID_KEY, personId);
+                        personObj.set(FormConstant.NAME_KEY, personName);
+                        personObj.set(FormConstant.EMP_NUMBER_KEY, personNumber);
+                        entryCol.set(FormConstant.NCKD_PERSON, personObj);
+                        //是否试用
+                        boolean isTrial = person.getBoolean(String.join(".", FormConstant.HRPI_EMPENTREL, FormConstant.LABOR_REL_STATUS, FormConstant.IS_TRIAL));
+                        //入职日期
+                        Date entryDate = person.getDate(String.join(".", FormConstant.HRPI_EMPENTREL, FormConstant.ENTRYDATE));
+                        //职务级别编码
+                        Integer posGradeSort = null;
+                        if (person.containsProperty(FormConstant.NCKD_HRPI_PARTYPOSH)
+                                && person.containsProperty(String.join(".", FormConstant.NCKD_HRPI_PARTYPOSH, FormConstant.NCKD_POSGRADE))
+                                && person.containsProperty(String.join(".", FormConstant.NCKD_HRPI_PARTYPOSH, FormConstant.NCKD_POSGRADE, FormConstant.NCKD_SORTNUM))) {
+                            posGradeSort = person.getInt(String.join(".", FormConstant.NCKD_HRPI_PARTYPOSH, FormConstant.NCKD_POSGRADE, FormConstant.NCKD_SORTNUM));
+                        }
+                        StringBuilder reason = new StringBuilder();
+                        boolean participateRank = isParticipateRank(isTrial, entryDate, posGradeSort, reason);
+                        entryCol.set(PerfRankMgmtConstant.NCKD_ISRANKING, participateRank);
+                        entryCol.set(PerfRankMgmtConstant.NCKD_EXCLUDERANKREASON, reason.toString());
+                        entryCol.set(PerfRankMgmtConstant.NCKD_RANKSOURCE, EnableEnum.NO.getCode());
+                        //判断是否有职位津贴
+                        boolean hasAllowance = personJobLevelMap.containsKey(personId) && personJobLevelMap.get(personId) > 0 || managerAllowanceMap.containsKey(personId);
+                        entryCol.set(PerfRankMgmtConstant.NCKD_POSTALLOWANCE, hasAllowance);
+
+                    }
+                    this.getView().showSuccessNotification("名单获取完成");
+                }else{
+                    this.getView().showTipNotification("未获取到人员");
+                }
+            }
+        }
+        if(Arrays.asList(FormConstant.DELETEENTRY_OP, PerfRankMgmtConstant.GETRANKLIST_OP).contains(itemKey)){
+            sortEntry();
+            calcRankCount();
+        }
+    }
+
+
+
+    /**
+     * 判断是否参与排名
+     * @param isTrial 是否试用
+     * @param entryDate 入职日期
+     * @param posGradeSort 职务级别排序
+     * @return: boolean
+     * @author W.Y.C
+     * @date: 2025/10/20 20:17
+     */
+    private boolean isParticipateRank(boolean isTrial,Date entryDate,Integer posGradeSort,StringBuilder reason) {
+        //获取入职天数
+        long between = DateUtil.between(DateUtil.toLocalDateTime(entryDate), DateUtil.now(), ChronoUnit.DAYS);
+        if(between < 183){
+            //本所属组织未待满183天
+            reason.append("本所属组织未待满183天");
+            return false;
+        }
+        if(isTrial){
+            reason.append("试用");
+            return false;
+        }
+        if(posGradeSort != null && posGradeSort >= 115){
+            reason.append("副科级及以上不参与");
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 分录排序
+     * @param
+     * @return: void
+     * @author W.Y.C
+     * @date: 2025/10/21 16:00
+     */
+    private void sortEntry(){
+        DynamicObjectCollection entryEntityCols = this.getModel().getDataEntity(true).getDynamicObjectCollection(PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY);
+        int allowanceRank = entryEntityCols.get(0).getInt(PerfRankMgmtConstant.NCKD_ALLOWANCERANK);
+        entryEntityCols.sort((o1, o2) -> {
+            // 先按 NCKD_TOPRANK 排序,空值或 0 视为最大值(排在最后)
+            Integer topRank1 = o1.getInt(PerfRankMgmtConstant.NCKD_TOPRANK);
+            Integer topRank2 = o2.getInt(PerfRankMgmtConstant.NCKD_TOPRANK);
+            if (topRank1 == 0) {
+                topRank1 = Integer.MAX_VALUE;
+            }
+            if (topRank2 == 0) {
+                topRank2 = Integer.MAX_VALUE;
+            }
+
+            int topRankCompare = topRank1.compareTo(topRank2);
+            if (topRankCompare != 0) {
+                return topRankCompare;
+            }
+
+            // 再按 NCKD_ALLOWANCERANK 排序,空值或 0 视为最大值(排在最后)
+            Integer allowanceRank1 = o1.getInt(PerfRankMgmtConstant.NCKD_ALLOWANCERANK);
+            Integer allowanceRank2 = o2.getInt(PerfRankMgmtConstant.NCKD_ALLOWANCERANK);
+            if (allowanceRank1 == 0) {
+                allowanceRank1 = Integer.MAX_VALUE;
+            }
+            if (allowanceRank2 == 0) {
+                allowanceRank2 = Integer.MAX_VALUE;
+            }
+
+            int allowanceRankCompare = allowanceRank1.compareTo(allowanceRank2);
+            if (allowanceRankCompare != 0) {
+                return allowanceRankCompare;
+            }
+
+            // 最后按 NCKD_ISRANKING 排序,true 在前,false 在后
+            Boolean isRanking1 = o1.getBoolean(PerfRankMgmtConstant.NCKD_ISRANKING);
+            Boolean isRanking2 = o2.getBoolean(PerfRankMgmtConstant.NCKD_ISRANKING);
+            return Boolean.compare(isRanking2, isRanking1);
+        });
+        this.getModel().updateEntryCache(entryEntityCols);
+        this.getView().updateView(PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY);
+    }
+
+    /**
+     * 计算排名数
+     * @return: void
+     * @author W.Y.C
+     * @date: 2025/10/21 16:30
+     */
+    private void calcRankCount(){
+        DynamicObjectCollection entryEntity = this.getModel().getEntryEntity(PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY);
+
+        int totalRankCount = 0;
+        int allowanceRankCount = 0;
+        int failCount = 0;
+        int basicCount = 0;
+        int excellentCount = 0;
+
+        for (DynamicObject entry : entryEntity) {
+            DynamicObject person = entry.getDynamicObject(FormConstant.NCKD_PERSON);
+            if(person != null) {
+                //是否排名
+                boolean isRanking = entry.getBoolean(PerfRankMgmtConstant.NCKD_ISRANKING);
+                //是否R排名
+                boolean postAllowance = entry.getBoolean(PerfRankMgmtConstant.NCKD_POSTALLOWANCE);
+                //考核结果
+                DynamicObject appraisalResult = entry.getDynamicObject(PerfRankMgmtConstant.NCKD_APPRAISALRESULT);
+
+                if (isRanking) {
+                    totalRankCount++;  // 统计参加排名人数
+
+                    if (postAllowance) {
+                        allowanceRankCount++;  // 统计参加R排名人数
+                    }
+                }
+
+                if (appraisalResult != null) {
+                    String appraisalResultNumber = appraisalResult.getString(FormConstant.NUMBER_KEY);
+                    AppraisalResultEnum appraisalResultEnum = AppraisalResultEnum.getByCode(appraisalResultNumber);
+
+                    if (appraisalResultEnum != null) {
+                        switch (appraisalResultEnum) {
+                            case EXCELLENT:
+                                excellentCount++;
+                                break;
+                            case BASICALLY_QUALIFIED:
+                                basicCount++;
+                                break;
+                            case UN_QUALIFIED:
+                                failCount++;
+                                break;
+                            default:
+                                break;
+                        }
+                    }
+                }
+            }
+        }
+        //参加排名总人数
+        this.getModel().setValue(PerfRankMgmtConstant.NCKD_TOPRANKS, totalRankCount);
+        //参加R排名人数
+        this.getModel().setValue(PerfRankMgmtConstant.NCKD_ALLOWANCERANKS, allowanceRankCount);
+        //不合格人数
+        this.getModel().setValue(PerfRankMgmtConstant.NCKD_FAILS, failCount);
+        //基本合格人数
+        this.getModel().setValue(PerfRankMgmtConstant.NCKD_BASICS, basicCount);
+        //优秀人数
+        this.getModel().setValue(PerfRankMgmtConstant.NCKD_EXCELLENTS, excellentCount);
+    }
+}

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

@@ -25,7 +25,7 @@ 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.helper.PositionFileHelper;
 
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
@@ -108,12 +108,12 @@ public class NewDynamicAdjustmentOperationPlugIn extends AbstractOperationServic
                 if (adjustDate == null) {
                     addFatalErrorMessage(rowDataEntity,StrFormatter.format("人员【{}】调整时间不能为空",person.getString(FormConstant.NAME_KEY)));
                 }
-                DynamicObject latsPersonPosFileByPerson = PositionStructureHelper.getLatsPersonPosFileByPerson(person.getLong(FormConstant.ID_KEY));
+                DynamicObject latsPersonPosFileByPerson = PositionFileHelper.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"},
+                DynamicObject[] personPosFileByAdjust = PositionFileHelper.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){
@@ -169,8 +169,8 @@ public class NewDynamicAdjustmentOperationPlugIn extends AbstractOperationServic
         }
 
         SaveServiceHelper.save(newPersonPosFiles.toArray(new DynamicObject[0]));
-        PositionStructureHelper.markAsNotCurrentNewest(personIds.toArray(new Long[0]));
-        PositionStructureHelper.markAsCurrentNewest(null,personIds.toArray(new Long[0]));
+        PositionFileHelper.markAsNotCurrentNewest(personIds.toArray(new Long[0]));
+        PositionFileHelper.markAsCurrentNewest(null,personIds.toArray(new Long[0]));
     }
 
     private void execute(DynamicObject data, List<Long> personIds, List<DynamicObject> newPersonPosFiles) {
@@ -189,7 +189,7 @@ public class NewDynamicAdjustmentOperationPlugIn extends AbstractOperationServic
         Date adjustDate = data.getDate(PositionStructureConstant.NCKD_ADJUSTDATE);
         LocalDateTime adjustDateEnd = DateUtil.endOfDay(DateUtil.toLocalDateTime(adjustDate));
         //查询员工在调整日期内的最新信息(职位、部门、序列、学历、职称、技能)
-        PositionAppointmentBO positionAppointment = PositionStructureHelper.positionAppointmentQuery(personId, DateUtil.toDate(adjustDateEnd));
+        PositionAppointmentBO positionAppointment = PositionFileHelper.positionAppointmentQuery(personId, DateUtil.toDate(adjustDateEnd));
         if(positionAppointment.getEmpPosOrgRel() == null){
             throw new ValidationException(StrFormatter.format("当前无法为【{}】进行调整,因为根据时间【{}】未获取到人员任职和聘任信息!", personName,DateUtil.format(adjustDateEnd,DateUtil.NORM_DATE_PATTERN)));
         }

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

@@ -21,7 +21,7 @@ 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 nckd.jxccl.hr.psms.helper.PositionFileHelper;
 
 import java.time.LocalDateTime;
 import java.util.ArrayList;
@@ -117,12 +117,12 @@ public class AnnualAdjustmentOperationPlugin extends AbstractOperationServicePlu
                     addFatalErrorMessage(rowDataEntity,StrFormatter.format("人员【{}】缺少【{}】年考核结果", personName,lastYear));
                 }
 
-                PositionAppointmentBO positionAppointment = PositionStructureHelper.positionAppointmentQuery(personId, adjustDate);
+                PositionAppointmentBO positionAppointment = PositionFileHelper.positionAppointmentQuery(personId, adjustDate);
                 if(positionAppointment.getEmpPosOrgRel() == null){
                     addFatalErrorMessage(rowDataEntity,StrFormatter.format("当前无法为【{}】进行调整,因为根据时间【{}】未获取到人员任职和聘任信息!", personName, DateUtil.format(adjustDate,DateUtil.NORM_DATE_PATTERN)));
                 }
 
-                DynamicObject firstRank = PositionStructureHelper.getFirstRank(personId);
+                DynamicObject firstRank = PositionFileHelper.getFirstRank(personId);
                 if(firstRank == null){
                     addFatalErrorMessage(rowDataEntity,StrFormatter.format("当前无法为【{}】进行调整,因为他/她尚未建立职位档案。请前往“职位及积分初定” -> 进行初定!", personName));
                 }else{
@@ -132,7 +132,7 @@ public class AnnualAdjustmentOperationPlugin extends AbstractOperationServicePlu
                 }
                 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));
+                DynamicObject[] personPosFileByYear = PositionFileHelper.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));
                 }
@@ -173,8 +173,8 @@ public class AnnualAdjustmentOperationPlugin extends AbstractOperationServicePlu
         }
 
         SaveServiceHelper.save(newPersonPosFiles.toArray(new DynamicObject[0]));
-        PositionStructureHelper.markAsNotCurrentNewest(personIds.toArray(new Long[0]));
-        PositionStructureHelper.markAsCurrentNewest(null,personIds.toArray(new Long[0]));
+        PositionFileHelper.markAsNotCurrentNewest(personIds.toArray(new Long[0]));
+        PositionFileHelper.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));

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

@@ -20,7 +20,7 @@ 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 nckd.jxccl.hr.psms.helper.PositionFileHelper;
 
 import java.time.LocalDateTime;
 import java.util.ArrayList;
@@ -168,8 +168,8 @@ public class AnnualEffectiveOpPlugin extends AbstractOperationServicePlugIn impl
         }
 
         //将人员所有职位档案标记为非最新
-        PositionStructureHelper.markAsNotCurrentNewest(personIds.toArray(new Long[0]));
+        PositionFileHelper.markAsNotCurrentNewest(personIds.toArray(new Long[0]));
         //将当前人员最新档案(初定 或者 (年度调整 并且 已生效) 或者 (动态调整) 生效时间为最新的那一条)标记为最新的
-        PositionStructureHelper.markAsCurrentNewest(null,personIds.toArray(new Long[0]));
+        PositionFileHelper.markAsCurrentNewest(null,personIds.toArray(new Long[0]));
     }
 }

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

@@ -55,8 +55,8 @@ import java.util.StringJoiner;
 */
 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 final static String INTERACTION_SPONORE = AnnualLockOrUnLockedOpPlugin.class.getName();
+    private final static String INTERACTION_SPONORE1 = AnnualLockOrUnLockedOpPlugin.class.getName() + "#1";
 
     private List<Long> ids;
 
@@ -105,8 +105,9 @@ public class AnnualLockOrUnLockedOpPlugin extends AbstractOperationServicePlugIn
                     } else if(PositionStructureConstant.UNLOCKED_OP.equals(operateKey)) {
                         if(!lockStatus) {
                             message.add(StrFormatter.format("【{}】的年度调整已是“解锁”状态,忽略此条处理。", person.getString("name")));
+                        }else{
+                            ids.add(id);
                         }
-                        ids.add(id);
                     }
                 }
             }
@@ -123,38 +124,27 @@ public class AnnualLockOrUnLockedOpPlugin extends AbstractOperationServicePlugIn
                 .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)) {
+        boolean isProcessFlag = true;
+        if(PositionStructureConstant.ISLOCKED_OP.equals(operateKey)) {
+            for (DynamicObject dynamicObject : load) {
+                long id = dynamicObject.getLong(FormConstant.ID_KEY);
                 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) {
+            SaveServiceHelper.update(load);
+        }else {
             //操作原因
             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());
+            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);
+            billObj.set(PositionStructureConstant.NCKD_REASON, reason);
 
             DynamicObjectCollection entryColl = billObj.getDynamicObjectCollection(PositionStructureConstant.NCKD_POSFILEUNLOCKENTRY);
             DynamicObjectType entryType = entryColl.getDynamicObjectType();
@@ -171,21 +161,19 @@ public class AnnualLockOrUnLockedOpPlugin extends AbstractOperationServicePlugIn
                     new DynamicObject[]{billObj},
                     option
             );
-            if(!result.isSuccess()) {
+            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{
+            } else {
                 if (this.getOperationResult().getCustomData() == null) {
                     this.getOperationResult().setCustomData(new HashMap<>());
                 }
                 this.getOperationResult().getCustomData().put("bill", "true");
             }
-        }else{
-            SaveServiceHelper.update(load);
         }
 
 
@@ -205,27 +193,26 @@ public class AnnualLockOrUnLockedOpPlugin extends AbstractOperationServicePlugIn
                 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();
+            //从流程启动条件判断非集团本部的不需要审批
+            //校验选中的档案是否已经有申请单据
+            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();
             }
         }
+
     }
 
 
@@ -265,7 +252,7 @@ public class AnnualLockOrUnLockedOpPlugin extends AbstractOperationServicePlugIn
         InteractionContext interactionContext = new InteractionContext();
         interactionContext.setSimpleMessage("存在“未生效的”档案");
         OperateErrorInfo errorInfo = new OperateErrorInfo();
-        errorInfo.setMessage("解锁需要经过总部审批,审批通过之后自动解锁;点击“是”系统自动发起解锁审批流程");
+        errorInfo.setMessage("非总部解锁需要经过总部审批,审批通过之后自动解锁;点击“是”系统自动发起解锁审批流程");
         errorInfo.setLevel(ErrorLevel.Warning);
         interactionContext.addOperateInfo(errorInfo);
         throw new KDInteractionException(INTERACTION_SPONORE1, interactionContext);

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

@@ -21,7 +21,7 @@ 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 nckd.jxccl.hr.psms.helper.PositionFileHelper;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -144,8 +144,8 @@ public class AnnualSetinActiveOpPlugin extends AbstractOperationServicePlugIn im
         }
 
         //将人员所有职位档案标记为非最新
-        PositionStructureHelper.markAsNotCurrentNewest(personIds.toArray(new Long[0]));
+        PositionFileHelper.markAsNotCurrentNewest(personIds.toArray(new Long[0]));
         //将当前人员最新档案(初定 或者 (年度调整 并且 已生效) 或者 (动态调整) 生效时间为最新的那一条)标记为最新的
-        PositionStructureHelper.markAsCurrentNewest(null,personIds.toArray(new Long[0]));
+        PositionFileHelper.markAsCurrentNewest(null,personIds.toArray(new Long[0]));
     }
 }

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

@@ -5,6 +5,7 @@ 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.PreparePropertysEventArgs;
 import kd.bos.entity.plugin.args.BeginOperationTransactionArgs;
 import kd.bos.orm.query.QFilter;
 import kd.bos.servicehelper.BusinessDataServiceHelper;
@@ -27,6 +28,10 @@ import java.util.List;
 */
 public class PosFileUnlockBillOpPlugin extends AbstractOperationServicePlugIn implements Plugin {
 
+    @Override
+    public void onPreparePropertys(PreparePropertysEventArgs e) {
+        e.getFieldKeys().add(PositionStructureConstant.NCKD_PERSONPOSFILE);
+    }
 
     @Override
     public void beginOperationTransaction(BeginOperationTransactionArgs e) {

+ 134 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/contribution/ContribBillOpPlugin.java

@@ -0,0 +1,134 @@
+package nckd.jxccl.hr.psms.plugin.operate.contribution;
+
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+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.OperateErrorInfo;
+import kd.bos.entity.plugin.AbstractOperationServicePlugIn;
+import kd.bos.entity.plugin.AddValidatorsEventArgs;
+import kd.bos.entity.plugin.args.BeforeOperationArgs;
+import kd.bos.entity.validate.AbstractValidator;
+import kd.bos.entity.validate.ErrorLevel;
+import kd.bos.form.MessageBoxResult;
+import kd.sdk.plugin.Plugin;
+import nckd.jxccl.base.common.constant.FormConstant;
+import nckd.jxccl.base.common.utils.StrFormatter;
+import nckd.jxccl.hr.psms.common.ContributionEvaluationConstant;
+import nckd.jxccl.hr.psms.helper.ContributionHelper;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * 年度积分贡献单据保存/提交插件
+ * 实体标识:nckd_contribbill
+ * @author W.Y.C
+ * @date 2025/10/26 20:17
+ * @version 1.0
+ */
+public class ContribBillOpPlugin extends AbstractOperationServicePlugIn implements Plugin {
+
+    private final static String INTERACTION_SPONORE = ContribBillOpPlugin.class.getName();
+
+    @Override
+    public void onAddValidators(AddValidatorsEventArgs e) {
+        e.addValidator(new AbstractValidator() {
+
+            @Override
+            public void validate() {
+                //校验参与人数
+                for (ExtendedDataEntity dataEntity : this.getDataEntities()) {
+                    DynamicObject data = dataEntity.getDataEntity();
+                    int participants = data.getInt(ContributionEvaluationConstant.NCKD_PARTICIPANTS);
+                    if (participants < 1) {
+                        this.addMessage(dataEntity, "参与人数不能小于1;");
+                    }else{
+                        DynamicObjectCollection entryEntity = data.getDynamicObjectCollection(FormConstant.NCKD_ENTRYENTITY);
+                        if(entryEntity.isEmpty()){
+                            this.addMessage(dataEntity, "请添加参与人明细;");
+                        }else{
+                            //校验积分是否超出限制
+                            Long id = data.getLong(FormConstant.ID_KEY);
+                            Date date = data.getDate(ContributionEvaluationConstant.NCKD_YEAR);
+                            DynamicObject scoreItem = data.getDynamicObject(ContributionEvaluationConstant.NCKD_SCOREITEM);
+                            DynamicObject scoreItemSub = data.getDynamicObject(ContributionEvaluationConstant.NCKD_SCOREITEMSUB);
+                            List<ContributionHelper.ScoreItemValidScore> scoreItemValidScoreList = new ArrayList<>(entryEntity.size());
+                            for (DynamicObject entry : entryEntity) {
+                                DynamicObject person = entry.getDynamicObject(ContributionEvaluationConstant.NCKD_PERSON);
+                                BigDecimal oriScore = entry.getBigDecimal(ContributionEvaluationConstant.NCKD_ORISCORE);
+                                ContributionHelper.ScoreItemValidScore scoreItemValidScore = new ContributionHelper.ScoreItemValidScore(
+                                        person.getLong(FormConstant.ID_KEY),
+                                        scoreItem.getLong(FormConstant.ID_KEY),
+                                        scoreItemSub.getLong(FormConstant.ID_KEY));
+                                scoreItemValidScore.setCurrentScore(oriScore);
+                                scoreItemValidScoreList.add(scoreItemValidScore);
+                            }
+                            if (!scoreItemValidScoreList.isEmpty()) {
+                                ContributionHelper.getScoreItemValidScore(scoreItemValidScoreList, date, id);
+                                for (DynamicObject entry : entryEntity) {
+                                    DynamicObject person = entry.getDynamicObject(ContributionEvaluationConstant.NCKD_PERSON);
+                                    long personId = person.getLong(FormConstant.ID_KEY);
+                                    BigDecimal oriScore = entry.getBigDecimal(ContributionEvaluationConstant.NCKD_ORISCORE);
+                                    BigDecimal score = entry.getBigDecimal(ContributionEvaluationConstant.NCKD_SCORE);
+                                    for (ContributionHelper.ScoreItemValidScore scoreItemValidScore : scoreItemValidScoreList) {
+                                        if(Objects.equals(scoreItemValidScore.getPersonId(),personId)){
+                                            if(scoreItemValidScore.getValidScore() != null){
+                                                if(score.compareTo(scoreItemValidScore.getValidScore()) > 0){
+                                                    this.addMessage(dataEntity, StrFormatter.format("参与人【{}】的录入积分【{}】超出限制积分【{}】。请重新填写“原始积分”,系统将会自动重算“录入积分”;",
+                                                            person.getString(FormConstant.NAME_KEY),
+                                                            score.setScale(2, RoundingMode.HALF_UP),
+                                                            scoreItemValidScore.getValidScore().setScale(2, RoundingMode.HALF_UP)));
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        });
+    }
+
+    @Override
+    public void beforeExecuteOperationTransaction(BeforeOperationArgs e) {
+        for (DynamicObject data : e.getDataEntities()) {
+            int participants = data.getInt(ContributionEvaluationConstant.NCKD_PARTICIPANTS);
+            DynamicObjectCollection dynamicObjectCollection = data.getDynamicObjectCollection(FormConstant.NCKD_ENTRYENTITY);
+            if(participants != dynamicObjectCollection.size()){
+                e.cancel = !this.showInteractionMessage(participants,dynamicObjectCollection.size());
+            }
+        }
+    }
+
+
+    private boolean showInteractionMessage(int participants,int size) {
+        //交互式操作提示
+        // 检查是否为回调:避免死循环
+        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("参与人数【{}】与参与人明细【{}】不匹配", participants,size));
+        errorInfo.setLevel(ErrorLevel.Warning);
+        interactionContext.addOperateInfo(errorInfo);
+        throw new KDInteractionException(INTERACTION_SPONORE, interactionContext);
+    }
+}

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

@@ -18,7 +18,7 @@ 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 nckd.jxccl.hr.psms.helper.PositionFileHelper;
 import org.apache.commons.lang3.StringUtils;
 
 import java.util.HashMap;
@@ -128,8 +128,8 @@ public class PersonPosFileDeleteOpPlugin extends AbstractOperationServicePlugIn
             personIds[i] = person.getLong(FormConstant.ID_KEY);
         }
         //将人员所有职位档案标记为非最新
-        PositionStructureHelper.markAsNotCurrentNewest(personIds);
+        PositionFileHelper.markAsNotCurrentNewest(personIds);
         //将当前人员最新档案(初定 或者 (年度调整 并且 已生效) 或者 (动态调整) 生效时间为最新的那一条)标记为最新的
-        PositionStructureHelper.markAsCurrentNewest(new QFilter(FormConstant.ID_KEY, QCP.not_in,ids),personIds);
+        PositionFileHelper.markAsCurrentNewest(new QFilter(FormConstant.ID_KEY, QCP.not_in,ids),personIds);
     }
 }

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

@@ -21,7 +21,7 @@ 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 nckd.jxccl.hr.psms.helper.PositionFileHelper;
 import org.jetbrains.annotations.NotNull;
 
 import java.math.BigDecimal;
@@ -143,7 +143,7 @@ public abstract class BaseInitialOperationPlugIn extends AbstractOperationServic
         data.causeRemark = initialData.getString(PositionStructureConstant.KEY_NCKD_CAUSEREMARK);
         data.person = initialData.getDynamicObject(PositionStructureConstant.NCKD_PERSON);
 
-        PositionAppointmentBO positionAppointment = PositionStructureHelper.positionAppointmentQuery(data.person.getLong(FormConstant.ID_KEY), data.beginDate);
+        PositionAppointmentBO positionAppointment = PositionFileHelper.positionAppointmentQuery(data.person.getLong(FormConstant.ID_KEY), data.beginDate);
         // 学历
         DynamicObject perEduExp = positionAppointment.getPerEduExp();
         if (perEduExp != null) {

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

@@ -11,7 +11,7 @@ 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.business.JobLevelCalculatorService;
-import nckd.jxccl.hr.psms.helper.PositionStructureHelper;
+import nckd.jxccl.hr.psms.helper.PositionFileHelper;
 
 import java.math.BigDecimal;
 import java.util.Date;
@@ -67,7 +67,7 @@ public class NewHireInitialOperationPlugIn extends BaseInitialOperationPlugIn {
                             addFatalErrorMessage(rowDataEntity,"请选择人员");
                             return;
                         }
-                        if(PositionStructureHelper.isInitial(person.getLong(FormConstant.ID_KEY))){
+                        if(PositionFileHelper.isInitial(person.getLong(FormConstant.ID_KEY))){
                             addFatalErrorMessage(rowDataEntity,StrFormatter.format("人员【{}】已初定,不能重复初定",person.getString(FormConstant.NAME_KEY)));
                         }
                         if(newHireInitialData.empPosOrgRel == null){

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

@@ -13,7 +13,7 @@ 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.helper.PositionFileHelper;
 
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
@@ -70,7 +70,7 @@ public class ServingInitialOperationPlugIn extends BaseInitialOperationPlugIn {
                             addFatalErrorMessage(rowDataEntity,"请选择人员");
                             return;
                         }
-                        if(PositionStructureHelper.isInitial(person.getLong(FormConstant.ID_KEY))){
+                        if(PositionFileHelper.isInitial(person.getLong(FormConstant.ID_KEY))){
                             addFatalErrorMessage(rowDataEntity,StrFormatter.format("人员【{}】已初定,不能重复初定",person.getString(FormConstant.NAME_KEY)));
                         }
                         if(servingInitialData.empPosOrgRel == null){

+ 21 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/performance/PerfRankMgmtSaveOpPlugin.java

@@ -0,0 +1,21 @@
+package nckd.jxccl.hr.psms.plugin.operate.performance;
+
+import kd.bos.entity.plugin.AbstractOperationServicePlugIn;
+import kd.bos.entity.plugin.AddValidatorsEventArgs;
+import kd.sdk.plugin.Plugin;
+import nckd.jxccl.hr.psms.plugin.operate.performance.validate.PerfRankMgmtSaveValidate;
+
+/**
+* 年度绩效排名管理-保存插件
+* @author W.Y.C
+* @date 2025/10/21 17:05
+* @version 1.0
+*/
+public class PerfRankMgmtSaveOpPlugin extends AbstractOperationServicePlugIn implements Plugin {
+
+    @Override
+    public void onAddValidators(AddValidatorsEventArgs e) {
+        e.addValidator(new PerfRankMgmtSaveValidate());
+    }
+
+}

+ 538 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/performance/validate/PerfRankMgmtSaveValidate.java

@@ -0,0 +1,538 @@
+package nckd.jxccl.hr.psms.plugin.operate.performance.validate;
+
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.entity.ExtendedDataEntity;
+import kd.bos.entity.validate.AbstractValidator;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.QueryServiceHelper;
+import nckd.jxccl.base.common.constant.FormConstant;
+import nckd.jxccl.base.common.enums.AppraisalResultEnum;
+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.PerfRankMgmtConstant;
+import nckd.jxccl.hr.psms.common.PositionStructureConstant;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.ArrayList;
+import java.util.Collections;
+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/21 22:39
+* @version 1.0
+*/
+public class PerfRankMgmtSaveValidate extends AbstractValidator {
+
+    private final Map<String, BigDecimal> appraisalResultRatioMap = new HashMap<>();
+    @Override
+    public void validate() {
+        QueryFieldBuilder queryFieldBuilder = QueryFieldBuilder.create()
+                .addGroup(new String[]{FormConstant.NCKD_ENTRYENTITY}, PerfRankMgmtConstant.NCKD_RATIO)
+                .addGroup(new String[]{FormConstant.NCKD_ENTRYENTITY, PositionStructureConstant.NCKD_APPRAISALRESULT}, FormConstant.NUMBER_KEY);
+        DynamicObjectCollection query = QueryServiceHelper.query(PerfRankMgmtConstant.RANKRATIOCONF_ENTITYID, queryFieldBuilder.buildSelect(), new QFilter[]{QFilterCommonHelper.getEnableFilter()});
+
+        query.forEach(dynamicObject -> {
+            String key = dynamicObject.getString(String.join(".",FormConstant.NCKD_ENTRYENTITY, PositionStructureConstant.NCKD_APPRAISALRESULT, FormConstant.NUMBER_KEY));
+            BigDecimal value = dynamicObject.getBigDecimal(String.join(".",FormConstant.NCKD_ENTRYENTITY, PerfRankMgmtConstant.NCKD_RATIO));
+            appraisalResultRatioMap.put(key, value);
+        });
+        for (ExtendedDataEntity rowDataEntity : getDataEntities()) {
+            if(appraisalResultRatioMap.isEmpty()){
+                this.addMessage(rowDataEntity, "没有配置【排名考核结果比例】");
+                return;
+            }
+            DynamicObject data = rowDataEntity.getDataEntity();
+            // 年份校验
+            if (validateYear(data, rowDataEntity)) {
+                // 获取明细数据并验证是否为空
+                DynamicObjectCollection entries = data.getDynamicObjectCollection(PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY);
+                if (entries.isEmpty()) {
+                    this.addMessage(rowDataEntity, "请添加排名名单");
+                    continue;
+                }
+
+                // 验证明细数据
+                ValidationContext context = validateEntries(entries, rowDataEntity);
+
+                // 验证统计数据一致性
+                validateDataConsistency(data, context, rowDataEntity);
+            }
+        }
+    }
+
+    //校验考核结果人数比例
+    private void validateRatio(ValidationContext context, ExtendedDataEntity rowDataEntity) {
+        // 总人数
+        int totalRankCount = context.totalRankCount;
+        // 优秀人数
+        int excellentCount = context.excellentCount;
+        // 合格人数
+        int qualifiedCount = context.qualifiedCount;
+        // 基本合格人数
+        int basicCount = context.basicCount;
+        // 不合格人数
+        int failCount = context.failCount;
+
+        // 获取配置比例
+        BigDecimal excellentRatio = appraisalResultRatioMap.get(AppraisalResultEnum.EXCELLENT.getCode());
+        BigDecimal qualifiedRatio = appraisalResultRatioMap.get(AppraisalResultEnum.QUALIFIED.getCode());
+        BigDecimal basicallyQualifiedRatio = appraisalResultRatioMap.get(AppraisalResultEnum.BASICALLY_QUALIFIED.getCode());
+        BigDecimal unQualifiedRatio = appraisalResultRatioMap.get(AppraisalResultEnum.UN_QUALIFIED.getCode());
+
+        // 计算各等级的理论人数(四舍五入)
+        int expectedExcellent = calculateExpectedCount(totalRankCount, excellentRatio);
+        int expectedQualified = calculateExpectedCount(totalRankCount, qualifiedRatio);
+        int expectedBasic = calculateExpectedCount(totalRankCount, basicallyQualifiedRatio);
+        int expectedFail = calculateExpectedCount(totalRankCount, unQualifiedRatio);
+
+
+
+        // 校验各等级人数是否在合理范围内(允许±1的差异)
+        validateCountRange(AppraisalResultEnum.EXCELLENT.getName(), excellentCount, expectedExcellent, totalRankCount,rowDataEntity);
+        validateCountRange(AppraisalResultEnum.QUALIFIED.getName(), qualifiedCount, expectedQualified, totalRankCount,rowDataEntity);
+        validateCountRange(AppraisalResultEnum.BASICALLY_QUALIFIED.getName(), basicCount, expectedBasic, totalRankCount,rowDataEntity);
+        validateCountRange(AppraisalResultEnum.UN_QUALIFIED.getName(), failCount, expectedFail, totalRankCount,rowDataEntity);
+
+    }
+
+    /**
+     * 计算理论人数(四舍五入)
+     */
+    private int calculateExpectedCount(int totalCount, BigDecimal ratio) {
+        // 将百分比转换为小数进行计算
+        BigDecimal decimalRatio = ratio.divide(BigDecimal.valueOf(100), 4, RoundingMode.HALF_UP);
+        BigDecimal expected = decimalRatio.multiply(BigDecimal.valueOf(totalCount));
+        return expected.setScale(0, RoundingMode.HALF_UP).intValue();
+    }
+
+    /**
+     * 校验人数范围(允许±1的差异)
+     */
+    private void validateCountRange(String levelName, int actualCount, int expectedCount, int totalCount,ExtendedDataEntity rowDataEntity) {
+        int difference = Math.abs(actualCount - expectedCount);
+        // 允许的差异阈值,可以根据总人数动态调整
+        int allowedDifference = Math.max(1, totalCount / 100); // 至少允许1人差异,或总人数的1%
+
+        if (difference > allowedDifference) {
+
+            this.addMessage(rowDataEntity,
+                    StrFormatter.format("{}人数与配置比例不符,应为{}人,实际为{}人",
+                            levelName, expectedCount,actualCount));
+        }
+    }
+
+
+    /**
+     * 验证年份字段有效性
+     * @param data 数据对象
+     * @param rowDataEntity 行数据实体
+     * @return: void
+     * @author W.Y.C
+     * @date: 2025/10/21 22:40
+     */
+    private boolean validateYear(DynamicObject data, ExtendedDataEntity rowDataEntity) {
+        int theYear = data.getInt(PerfRankMgmtConstant.NCKD_THEYEAR);
+        int currentYear = DateUtil.now().getYear();
+        if (theYear < 1900 || theYear > currentYear) {
+            this.addMessage(rowDataEntity, "年份不合法或超过当前年份");
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 验证明细数据
+     * @param entries 分录
+     * @param rowDataEntity 行数据实体
+     * @return: nckd.jxccl.hr.psms.plugin.operate.performance.validate.PerfRankMgmtSaveValidate.ValidationContext
+     * @author W.Y.C
+     * @date: 2025/10/21 22:40
+     */
+    private ValidationContext validateEntries(DynamicObjectCollection entries, ExtendedDataEntity rowDataEntity) {
+        ValidationContext context = new ValidationContext();
+        Set<Long> personIds = new HashSet<>();
+
+        for (int i = 0; i < entries.size(); i++) {
+            DynamicObject entry = entries.get(i);
+            validateEntry(entry, rowDataEntity, i + 1, personIds, context);
+        }
+
+        return context;
+    }
+
+    /**
+     * 验证单条明细数据
+     * @param entry 分录
+     * @param rowDataEntity 行数据实体
+     * @param rowIndex 行索引
+     * @param personIds 已处理人员ID集合(用于去重检查)
+     *
+     * @param context 验证上下文
+     * @return: void
+     * @author W.Y.C
+     * @date: 2025/10/21 22:41
+     */
+    private void validateEntry(DynamicObject entry, ExtendedDataEntity rowDataEntity,
+                               int rowIndex, Set<Long> personIds, ValidationContext context) {
+        DynamicObject person = entry.getDynamicObject(PerfRankMgmtConstant.NCKD_PERSON);
+        if (person == null) {
+            this.addMessage(rowDataEntity, StrFormatter.format("第{}行,请选择人员\n", rowIndex));
+        } else {
+            // 人员重复校验
+            long personId = person.getLong(FormConstant.ID_KEY);
+            if (personIds.contains(personId)) {
+                this.addMessage(rowDataEntity, StrFormatter.format("第{}行,人员【{}】重复;\n", rowIndex, person.getString(FormConstant.NAME_KEY)));
+            } else {
+                personIds.add(personId);
+            }
+
+            // 统计各类人数
+            boolean isRanking = entry.getBoolean(PerfRankMgmtConstant.NCKD_ISRANKING);
+            boolean postAllowance = entry.getBoolean(PerfRankMgmtConstant.NCKD_POSTALLOWANCE);
+            DynamicObject appraisalResult = entry.getDynamicObject(PerfRankMgmtConstant.NCKD_APPRAISALRESULT);
+
+            if (isRanking) {
+                context.totalRankCount++;
+
+                if (postAllowance) {
+                    context.allowanceRankCount++;
+                }
+            }
+
+            if (appraisalResult != null) {
+                String appraisalResultNumber = appraisalResult.getString(FormConstant.NUMBER_KEY);
+                AppraisalResultEnum appraisalResultEnum = AppraisalResultEnum.getByCode(appraisalResultNumber);
+
+                if (appraisalResultEnum != null) {
+                    switch (appraisalResultEnum) {
+                        case EXCELLENT:
+                            context.excellentCount++;
+                            break;
+                        case BASICALLY_QUALIFIED:
+                            context.basicCount++;
+                            break;
+                        case UN_QUALIFIED:
+                            context.failCount++;
+                            break;
+                        case QUALIFIED:
+                            context.qualifiedCount++;
+                            break;
+                        default:
+                            break;
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * 验证数据一致性
+     * @param data 数据对象
+     * @param context 验证上下文
+     * @param rowDataEntity 行数据实体
+     * @return: void
+     * @author W.Y.C
+     * @date: 2025/10/21 22:41
+     */
+    private void validateDataConsistency(DynamicObject data, ValidationContext context,
+                                         ExtendedDataEntity rowDataEntity) {
+        // 各类人数一致性校验
+        checkCountConsistency(data, context, rowDataEntity);
+
+        // 排名范围校验
+        validateRankRanges(data.getDynamicObjectCollection(PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY),
+                data.getInt(PerfRankMgmtConstant.NCKD_TOPRANKS), rowDataEntity);
+
+        validateRatio(context, rowDataEntity);
+    }
+
+    /**
+     * 检查统计数据一致性
+     * @param data 数据对象
+     * @param context 验证上下文
+     * @param rowDataEntity 行数据实体
+     * @return: void
+     * @author W.Y.C
+     * @date: 2025/10/21 22:42
+     */
+    private void checkCountConsistency(DynamicObject data, ValidationContext context,
+                                       ExtendedDataEntity rowDataEntity) {
+        int topRanks = data.getInt(PerfRankMgmtConstant.NCKD_TOPRANKS);
+        int allowanceRanks = data.getInt(PerfRankMgmtConstant.NCKD_ALLOWANCERANKS);
+        int fails = data.getInt(PerfRankMgmtConstant.NCKD_FAILS);
+        int basics = data.getInt(PerfRankMgmtConstant.NCKD_BASICS);
+        int excellents = data.getInt(PerfRankMgmtConstant.NCKD_EXCELLENTS);
+
+        if (topRanks != context.totalRankCount) {
+            this.addMessage(rowDataEntity, "总排名人数与实际录入数据不一致;\n");
+        }
+        if (allowanceRanks != context.allowanceRankCount) {
+            this.addMessage(rowDataEntity, "R排名人数与实际录入数据不一致;\n");
+        }
+        if (fails != context.failCount) {
+            this.addMessage(rowDataEntity, "不合格人数与实际录入数据不一致;\n");
+        }
+        if (basics != context.basicCount) {
+            this.addMessage(rowDataEntity, "基本人数与实际录入数据不一致;\n");
+        }
+        if (excellents != context.excellentCount) {
+            this.addMessage(rowDataEntity, "优秀人数与实际录入数据不一致;\n");
+        }
+    }
+
+    /**
+     * 验证排名范围和重复情况
+     * @param entries 分录集合
+     * @param totalRankCount 总排名数
+     * @param rowDataEntity 行数据实体
+     * @return: void
+     * @author W.Y.C
+     * @date: 2025/10/21 22:42
+     */
+    private void validateRankRanges(DynamicObjectCollection entries, int totalRankCount, ExtendedDataEntity rowDataEntity) {
+        ValidationContext context = new ValidationContext();
+
+        // 统计每个排名出现的次数
+        for (int i = 0; i < entries.size(); i++) {
+            DynamicObject entry = entries.get(i);
+            DynamicObject person = entry.getDynamicObject(PerfRankMgmtConstant.NCKD_PERSON);
+
+            if (person != null) {
+                String personName = person.getString(FormConstant.NAME_KEY);
+                boolean isRanking = entry.getBoolean(PerfRankMgmtConstant.NCKD_ISRANKING);
+                boolean postAllowance = entry.getBoolean(PerfRankMgmtConstant.NCKD_POSTALLOWANCE);
+
+                if (isRanking) {
+                    validateAndRecordRank(entry, PerfRankMgmtConstant.NCKD_TOPRANK, "参与排名","排名", personName, rowDataEntity, context.rankCountMap);
+
+                    if (postAllowance) {
+                        validateAndRecordRank(entry, PerfRankMgmtConstant.NCKD_ALLOWANCERANK, "享受职位津贴","R排名", personName, rowDataEntity, context.rankRCountMap);
+                    }
+                }
+
+
+            }
+        }
+
+        // 找出重复的排名
+        findDuplicateRanks(context.rankCountMap, context.duplicateRanks);
+        findDuplicateRanks(context.rankRCountMap, context.duplicateRRanks);
+
+        // 检查排名范围和重复率
+       for (int i = 0; i < entries.size(); i++) {
+            DynamicObject entry = entries.get(i);
+            boolean isRanking = entry.getBoolean(PerfRankMgmtConstant.NCKD_ISRANKING);
+            boolean postAllowance = entry.getBoolean(PerfRankMgmtConstant.NCKD_POSTALLOWANCE);
+
+            if (isRanking) {
+                // 检查排名字段是否在有效范围内
+                int topRank = entry.getInt(PerfRankMgmtConstant.NCKD_TOPRANK);
+                if (topRank < 1 || topRank > totalRankCount) {
+                    this.addMessage(rowDataEntity,
+                            StrFormatter.format("第{}行,排名{}超出有效范围[1-{}]\n", i + 1, topRank, totalRankCount));
+                }
+                if (postAllowance) {
+                    // 检查R排名字段是否在有效范围内
+                    int allowanceRank = entry.getInt(PerfRankMgmtConstant.NCKD_ALLOWANCERANK);
+                    if (allowanceRank < 1 || allowanceRank > totalRankCount) {
+                        this.addMessage(rowDataEntity,
+                                StrFormatter.format("第{}行,R排名{}超出有效范围[1-{}]\n", i + 1, allowanceRank, totalRankCount));
+                    }
+                }
+            }
+        }
+
+        // 验证排名重复率
+        validateRankDuplicateRate(context, totalRankCount, rowDataEntity);
+
+        // 特殊排名组合校验
+        validateSpecialRankCombination(context, rowDataEntity);
+    }
+
+    /**
+     * 验证并记录排名值
+     * @param entry 分录对象
+     * @param rankField 排名字段名
+     * @param rankType 排名类型描述
+     * @param personName 人员姓名
+     * @param rowDataEntity 行数据实体
+     * @param rankCountMap 排名计数映射
+     */
+    private void validateAndRecordRank(DynamicObject entry, String rankField,String type, String rankType,
+                                       String personName, ExtendedDataEntity rowDataEntity,
+                                       Map<Integer, Integer> rankCountMap) {
+        int rankValue = entry.getInt(rankField);
+        if (rankValue < 1) {
+            this.addMessage(rowDataEntity, StrFormatter.format("人员【{}】已勾选{},{}数不能小于1或为空",
+                    personName, type, rankType));
+        } else {
+            rankCountMap.put(rankValue, rankCountMap.getOrDefault(rankValue, 0) + 1);
+        }
+    }
+
+    /**
+     * 查找重复的排名
+     * @param rankCountMap 排名计数映射
+     * @param duplicateRanks 重复排名集合
+     */
+    private void findDuplicateRanks(Map<Integer, Integer> rankCountMap, Set<Integer> duplicateRanks) {
+        for (Map.Entry<Integer, Integer> entry : rankCountMap.entrySet()) {
+            if (entry.getValue() > 1) {
+                duplicateRanks.add(entry.getKey());
+            }
+        }
+    }
+
+    /**
+     * 特殊排名组合校验
+     * 当相同排名重复出现时,其连续序列结束位置不能被其他排名占用
+     * @param context 上下文
+     * @param rowDataEntity 行数据实体
+     * @return: void
+     * @author W.Y.C
+     * @date: 2025/10/21 22:43
+     */
+    private void validateSpecialRankCombination(ValidationContext context, ExtendedDataEntity rowDataEntity) {
+        // 校验普通排名的特殊组合
+        List<String> invalidCombinations = new ArrayList<>();
+
+        // 校验普通排名
+        validateSpecialRankCombinationForType(context.rankCountMap, context.duplicateRanks, "排名", invalidCombinations);
+
+        // 校验R排名
+        validateSpecialRankCombinationForType(context.rankRCountMap, context.duplicateRRanks, "R排名", invalidCombinations);
+
+        // 如果有不合规的组合,添加详细的错误信息
+        if (!invalidCombinations.isEmpty()) {
+            StringBuilder errorMsg = new StringBuilder("排名设置不符合规范!\n");
+            errorMsg.append("【校验规则】\n");
+            errorMsg.append("当相同排名重复出现时,其连续序列结束位置不能被其他排名占用。\n");
+            errorMsg.append("【问题详情】\n");
+            for (int i = 0; i < invalidCombinations.size(); i++) {
+                errorMsg.append(StrFormatter.format("{}. {}\n", i + 1, invalidCombinations.get(i)));
+            }
+            errorMsg.append("【示例说明】\n");
+            errorMsg.append("❌ 不合规示例:[1,2,2,2,4] - 排名2重复3次,结束位置应为2+3-1=4,但4已在列表中\n");
+            errorMsg.append("✅ 合规示例:[1,1,1,4] - 排名1重复3次,结束位置应为1+3-1=3,3不在列表中\n");
+            errorMsg.append("【解决建议】\n");
+            errorMsg.append("请调整重复排名的数值或移除冲突的排名,确保连续序列结束位置未被占用;\n\n");
+            this.addMessage(rowDataEntity, errorMsg.toString());
+        }
+    }
+
+    /**
+     * 针对特定类型排名校验特殊组合规则
+     * @param rankCountMap 排名计数映射
+     * @param duplicateRanks 重复排名集合
+     * @param rankType 排名类型(如"排名"、"R排名")
+     * @param invalidCombinations 不合规组合列表
+     */
+    private void validateSpecialRankCombinationForType(Map<Integer, Integer> rankCountMap,
+                                                       Set<Integer> duplicateRanks,
+                                                       String rankType,
+                                                       List<String> invalidCombinations) {
+        // 遍历所有重复的排名
+        for (Integer rank : duplicateRanks) {
+            int count = rankCountMap.get(rank);
+            // 计算连续序列结束值
+            int mark = rank + count - 1;
+
+            // 检查结束值是否在列表中存在
+            if (rankCountMap.containsKey(mark) && mark != rank) {
+                invalidCombinations.add(StrFormatter.format(
+                        "{}{}重复{}次,结束位置{}被占用",
+                        rankType, rank, count, mark));
+            }
+        }
+    }
+
+    /**
+     * 验证排名重复率是否超过阈值
+     * @param context 上下文
+     * @param totalRankCount 总排名数
+     * @param rowDataEntity 行数据实体
+     * @return: void
+     * @author W.Y.C
+     * @date: 2025/10/21 22:44
+     */
+    private void validateRankDuplicateRate(ValidationContext context, int totalRankCount, ExtendedDataEntity rowDataEntity) {
+        // 验证普通排名重复率
+        validateRankDuplicateRateForType(context.rankCountMap, context.duplicateRanks, totalRankCount,
+                "排名", rowDataEntity);
+
+        // 验证R排名重复率
+        validateRankDuplicateRateForType(context.rankRCountMap, context.duplicateRRanks, totalRankCount,
+                "R排名", rowDataEntity);
+    }
+
+    /**
+     * 针对特定类型排名验证重复率
+     * @param rankCountMap 排名计数映射
+     * @param duplicateRanks 重复排名集合
+     * @param totalRankCount 总排名数
+     * @param rankType 排名类型
+     * @param rowDataEntity 行数据实体
+     */
+    private void validateRankDuplicateRateForType(Map<Integer, Integer> rankCountMap,
+                                                  Set<Integer> duplicateRanks,
+                                                  int totalRankCount,
+                                                  String rankType,
+                                                  ExtendedDataEntity rowDataEntity) {
+        if (totalRankCount > 0) {
+            double duplicateRate = (double) duplicateRanks.size() / totalRankCount;
+            if (duplicateRate > 0.3) {
+                // 构建重复排名详情信息
+                StringBuilder detailMessage = new StringBuilder();
+                detailMessage.append(rankType).append("重复率超过30%,当前重复率: ");
+                detailMessage.append(String.format("%.1f", duplicateRate * 100)).append("%,");
+                detailMessage.append("具体重复的排名有: ");
+
+                List<Integer> sortedDuplicateRanks = new ArrayList<>(duplicateRanks);
+                Collections.sort(sortedDuplicateRanks);
+
+                for (int i = 0; i < sortedDuplicateRanks.size(); i++) {
+                    int rank = sortedDuplicateRanks.get(i);
+                    int count = rankCountMap.get(rank);
+                    detailMessage.append(StrFormatter.format("{}【{}】出现{}次;", rankType, rank, count));
+                }
+                detailMessage.append("\n");
+                this.addMessage(rowDataEntity, detailMessage.toString());
+            }
+        }
+    }
+
+    private void validateUserUniqueAcrossUnits() {
+
+    }
+
+    /**
+     * 验证上下文对象,用于存储验证过程中的统计数据
+     * @author W.Y.C
+     * @date 2025/10/21 22:45
+     * @version 1.0
+     */
+    private static class ValidationContext {
+        int totalRankCount = 0;
+        int allowanceRankCount = 0;
+        int failCount = 0;
+        int basicCount = 0;
+        int excellentCount = 0;
+        int qualifiedCount = 0;
+        Set<Integer> rankSet = new HashSet<>();  // 记录已出现的排名
+        Set<Integer> duplicateRanks = new HashSet<>();  // 记录普通排名中重复的排名
+        Set<Integer> duplicateRRanks = new HashSet<>();  // 记录R排名中重复的排名
+        Map<Integer, Integer> rankCountMap = new HashMap<>();  // 记录每个普通排名出现的次数
+        Map<Integer, Integer> rankRCountMap = new HashMap<>();  // 记录每个R排名出现的次数
+    }
+
+}

+ 39 - 0
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/performance/validate/RankRatioConfValidate.java

@@ -0,0 +1,39 @@
+package nckd.jxccl.hr.psms.plugin.operate.performance.validate;
+
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.entity.ExtendedDataEntity;
+import kd.bos.entity.validate.AbstractValidator;
+import kd.bos.form.plugin.AbstractFormPlugin;
+import kd.sdk.plugin.Plugin;
+import nckd.jxccl.base.common.constant.FormConstant;
+import nckd.jxccl.hr.psms.common.PerfRankMgmtConstant;
+
+import java.math.BigDecimal;
+
+/**
+* 排名考核结果比例配置-校验器
+* @author W.Y.C
+* @date 2025/10/22 12:59
+* @version 1.0
+*/
+public class RankRatioConfValidate extends AbstractValidator {
+
+    @Override
+    public void validate() {
+        for (ExtendedDataEntity dataEntity : this.getDataEntities()) {
+            DynamicObject data = dataEntity.getDataEntity();
+            DynamicObjectCollection entrys = data.getDynamicObjectCollection(FormConstant.NCKD_ENTRYENTITY);
+            BigDecimal ratio = BigDecimal.ZERO;
+            for (DynamicObject entry : entrys) {
+                BigDecimal bigDecimal = entry.getBigDecimal(PerfRankMgmtConstant.NCKD_RATIO);
+                ratio = ratio.add(bigDecimal);
+            }
+            //判断是否等于100
+            if (ratio.compareTo(new BigDecimal(100)) != 0) {
+                addMessage(dataEntity, "比例总和必须等于100");
+            }
+
+        }
+    }
+}

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

@@ -99,6 +99,8 @@ public class UnAdjustedReportReportListDataPlugin extends AbstractReportListData
                 .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(FormConstant.IS_SEQLATESTRECORD, QCP.equals, EnableEnum.YES.getCode())
+                .and(FormConstant.IS_DELETED, QCP.equals, EnableEnum.NO.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))