Преглед изворни кода

Merge branch 'refs/heads/feat-opmc-pm_1.0'

wyc пре 8 часа
родитељ
комит
dff19ed7f1
19 измењених фајлова са 1675 додато и 191 уклоњено
  1. 21 11
      code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/constant/FormConstant.java
  2. 26 0
      code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/utils/QueryFieldBuilder.java
  3. 3 0
      code/base/nckd-jxccl-base-helper/src/main/java/nckd/jxccl/base/entity/helper/EntityHelper.java
  4. 109 0
      code/base/nckd-jxccl-base-helper/src/main/java/nckd/jxccl/base/hrpi/helper/EmpPosOrgRelHelper.java
  5. 11 11
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/common/ContributionConstant.java
  6. 19 19
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/common/PerfRankMgmtConstant.java
  7. 61 62
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/common/PositionStructureConstant.java
  8. 6 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/contribution/ContribLillListPlugin.java
  9. 7 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/performance/PerfRankMgmtSaveOpPlugin.java
  10. 35 8
      code/opmc/nckd-jxccl-opmc/src/main/java/nckd/jxccl/opmc/pm/common/PerfManagerFormConstant.java
  11. 170 0
      code/opmc/nckd-jxccl-opmc/src/main/java/nckd/jxccl/opmc/pm/helper/PerfManagerHelper.java
  12. 0 59
      code/opmc/nckd-jxccl-opmc/src/main/java/nckd/jxccl/opmc/pm/plugin/form/PerfManagerListPlugin.java
  13. 151 0
      code/opmc/nckd-jxccl-opmc/src/main/java/nckd/jxccl/opmc/pm/plugin/form/cycle/BatchEvalCycleFormPlugin.java
  14. 26 21
      code/opmc/nckd-jxccl-opmc/src/main/java/nckd/jxccl/opmc/pm/plugin/form/cycle/PerfManagerFormPlugin.java
  15. 103 0
      code/opmc/nckd-jxccl-opmc/src/main/java/nckd/jxccl/opmc/pm/plugin/form/cycle/PerfManagerListPlugin.java
  16. 0 0
      code/opmc/nckd-jxccl-opmc/src/main/java/nckd/jxccl/opmc/pm/plugin/operate/.gitkeep
  17. 448 0
      code/opmc/nckd-jxccl-opmc/src/main/java/nckd/jxccl/opmc/pm/plugin/operate/cycle/CycleGenerateOpPlugin.java
  18. 73 0
      code/opmc/nckd-jxccl-opmc/src/main/java/nckd/jxccl/opmc/pm/plugin/operate/cycle/PerfManagerDeleteOpPlugin.java
  19. 406 0
      code/opmc/nckd-jxccl-opmc/src/main/java/nckd/jxccl/opmc/pm/plugin/operate/cycle/PerfManagerSaveOpPlugin.java

+ 21 - 11
code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/constant/FormConstant.java

@@ -76,6 +76,8 @@ public class FormConstant {
     public static final String NEWENTRY_OP = "newentry";
     /** 删除分录操作*/
     public static final String DELETEENTRY_OP = "deleteentry";
+    /** 确认操作*/
+    public static final String CONFIRM_OP = "confirm";
 
 
     //====================================== 标品页面控件 ======================================
@@ -96,7 +98,7 @@ public class FormConstant {
     /** 左树右表-是否包含子部门*/
     public static final String CHKINCLUDECHILD = "chkincludechild";
     /** 单据体 */
-    public static final String NCKD_ENTRYENTITY = "NCKD_ENTRYENTITY";
+    public static final String NCKD_ENTRYENTITY = "nckd_entryentity";
 
     //====================================== 通用字段 ======================================
     /** BOID标识 */
@@ -116,15 +118,15 @@ public class FormConstant {
     /** 创建人 */
     public static final String CREATOR_KEY = "CREATOR";
     /** 创建时间 */
-    public static final String CREATE_TIME_KEY = "CREATETIME";
+    public static final String CREATE_TIME_KEY = "createtime";
     /** 修改人 */
-    public static final String MODIFIER_KEY = "MODIFIER";
+    public static final String MODIFIER_KEY = "modifier";
     /** 修改时间 */
-    public static final String MODIFY_TIME_KEY = "MODIFYTIME";
+    public static final String MODIFY_TIME_KEY = "modifytime";
     /** 审核人 */
-    public static final String AUDITOR_KEY = "AUDITOR";
+    public static final String AUDITOR_KEY = "auditor";
     /** 审核日期 */
-    public static final String AUDIT_DATE_KEY = "AUDITDATE";
+    public static final String AUDIT_DATE_KEY = "auditdate";
     /** 数据状态 */
     public static final String STATUS = "status";
     /** 使用状态 */
@@ -147,6 +149,8 @@ public class FormConstant {
     public static final String SRCCREATEORG_KEY = "srccreateorg";
     /** 所属单位*/
     public static final String USEORG_KEY = "useorg";
+    /** 控制策略*/
+    public static final String CTRLSTRATEGY_KEY = "ctrlstrategy";
     /** 分录行号 */
     public static final String SEQ_KEY = "SEQ";
     /** 排序 */
@@ -184,9 +188,9 @@ public class FormConstant {
     /** 结束时间 */
     public static final String ENDDATE = "enddate";
     /** 本次加入集团日期*/
-    public static final String JOINCOMDATE_KEY = "JOINCOMDATE";
+    public static final String JOINCOMDATE_KEY = "joincomdate";
     /** 首次加入集团日期*/
-    public static final String FIRSTJOINCOMDATE_KEY = "FIRSTJOINCOMDATE";
+    public static final String FIRSTJOINCOMDATE_KEY = "firstjoincomdate";
     /** 毕业时间 */
     public static final String GRADUTIONDATE = "gradutiondate";
     /** 最高学历 */
@@ -219,11 +223,11 @@ public class FormConstant {
     /** 职位序列*/
     public static final String NCKD_JOBSEQ = "nckd_jobseq";
     /** 职层/职级-积分*/
-    public static final String NCKD_SCORE = "NCKD_SCORE";
+    public static final String NCKD_SCORE = "nckd_score";
     /** 职层-系数*/
-    public static final String NCKD_COEFFICIENT = "NCKD_COEFFICIENT";
+    public static final String NCKD_COEFFICIENT = "nckd_coefficient";
     /** 职层-资格级别*/
-    public static final String NCKD_JOBLEVELNUMBER = "NCKD_JOBLEVELNUMBER";
+    public static final String NCKD_JOBLEVELNUMBER = "nckd_joblevelnumber";
     /** 备注*/
     public static final String NCKD_REMARK = "nckd_remark";
     /** 组织*/
@@ -236,6 +240,10 @@ public class FormConstant {
     public static final String IS_TRIAL = "istrial";
     /** 任职状态*/
     public static final String POS_STATUS = "posstatus";
+    /** 变动操作*/
+    public static final String CHGACTION = "chgaction";
+    /** 任职类型*/
+    public static final String POSTYPE = "postype";
     /** 任职状态分类*/
     public static final String POST_STATE_CLS = "poststatecls";
     /** 党政职务变更履历*/
@@ -250,6 +258,8 @@ public class FormConstant {
     public static final String NCKD_ENDDATE = "nckd_enddate";
     /** 描述*/
     public static final String DESCRIPTION_KEY = "description";
+    /** 是否当前最新记录 */
+    public static final String NCKD_ISCURRENTNEWEST = "nckd_iscurrentnewest";
     /** 部门*/
     public static final String NCKD_DEP = "nckd_dep";
 

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

@@ -244,6 +244,18 @@ public final class QueryFieldBuilder {
     public String buildSelect() {
         return selectJoiner.toString();
     }
+
+    /**
+     * 构建 SELECT 字段字符串,并转为小写
+     * 使用方式:
+     * @return String 字段字符串
+     * @author
+     * @date: 2025/09/27
+     */
+    public String buildSelectLowerCase() {
+        return buildSelect().toLowerCase();
+    }
+
     /**
      * 构建并返回 SELECT 字段数组
      * 使用方式:
@@ -317,6 +329,20 @@ public final class QueryFieldBuilder {
         return orderJoiner.toString();
     }
 
+    /**
+     * 构建小写的 ORDER BY 字段字符串
+     * 使用方式:
+     * builder.buildOrderLowerCase();
+     * 输出结果:
+     * "nckd_evaluationrule.id asc,useorg_key.id,nckd_sumscore desc"
+     * @return String 小写的排序字段字符串
+     * @author
+     * @date: 2025/09/27
+     */
+    public String buildOrderLowerCase() {
+        return orderJoiner.toString().toLowerCase();
+    }
+
     public String[] buildOrderArray() {
         return orderJoiner.toString().split(",");
     }

+ 3 - 0
code/base/nckd-jxccl-base-helper/src/main/java/nckd/jxccl/base/entity/helper/EntityHelper.java

@@ -21,6 +21,9 @@ import java.util.Date;
  */
 public class EntityHelper {
 
+    public static DynamicObject newEntity(String entityName){
+        return BusinessDataServiceHelper.newDynamicObject(entityName);
+    }
     /**
      * 创建一个状态为"可用"的基础资料对象
      * @param entityName 实体标识

+ 109 - 0
code/base/nckd-jxccl-base-helper/src/main/java/nckd/jxccl/base/hrpi/helper/EmpPosOrgRelHelper.java

@@ -0,0 +1,109 @@
+package nckd.jxccl.base.hrpi.helper;
+
+import com.google.common.collect.Lists;
+import kd.bos.common.enums.EnableEnum;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.sdk.plugin.Plugin;
+import nckd.jxccl.base.common.constant.FormConstant;
+import nckd.jxccl.base.common.utils.QueryFieldBuilder;
+
+import java.util.Collection;
+
+/**
+ * 任职经历Helper类
+ * 提供员工任职经历相关信息的查询功能
+ *
+ * @author W.Y.C
+ * @date 2025/11/4 15:33
+ * @version 1.0
+ */
+public class EmpPosOrgRelHelper implements Plugin {
+
+    /**
+     * 根据员工ID查询最新的任职经历信息
+     *
+     * @param employeeId 员工ID
+     * @return 员工的任职经历信息,如果不存在则返回null
+     * @author W.Y.C
+     * @date: 2025/11/04 16:02
+     */
+    public static DynamicObject queryEmpPosOrgRelByEmployees(Long employeeId) {
+        DynamicObject[] dynamicObjects = queryEmpPosOrgRelByEmployees(Lists.newArrayList(employeeId), null);
+        return dynamicObjects.length > 0 ? dynamicObjects[0] : null;
+    }
+
+    /**
+     * 根据多个员工ID查询最新的任职经历信息
+     *
+     * @param employeeIds 员工ID集合
+     * @return 员工的任职经历信息数组
+     * @author W.Y.C
+     * @date: 2025/11/04 16:02
+     */
+    public static DynamicObject[] queryEmpPosOrgRelByEmployees(Collection<Long> employeeIds) {
+        return queryEmpPosOrgRelByEmployees(employeeIds, null);
+    }
+
+    /**
+     * 获取员工最新有效任职经历
+     * 查询条件包括:
+     * 1. 员工ID在指定范围内
+     * 2. 是最新记录(IS_SEQLATESTRECORD=YES)
+     * 3. 未删除(IS_DELETED=NO)
+     * 4. 是主任职(IS_PRIMARY=YES)
+     *
+     * @param employeeIds 员工ID集合
+     * @param otherFilter 其他查询条件(可选)
+     * @return 员工的任职经历信息数组
+     * @author W.Y.C
+     * @date: 2025/11/04 16:02
+     */
+    public static DynamicObject[] queryEmpPosOrgRelByEmployees(Collection<Long> employeeIds, QFilter otherFilter) {
+        // 构建基础查询条件
+        QFilter queryFilter = new QFilter(FormConstant.EMPLOYEE_KEY, QCP.in, employeeIds)
+                .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());
+
+        // 添加额外查询条件(如果有)
+        if (otherFilter != null) {
+            queryFilter.and(otherFilter);
+        }
+
+        // 构建查询字段和排序规则
+        String selectProperties = selectProperties().buildSelect();
+        String order = selectProperties().buildOrder();
+
+        // 执行查询并返回结果
+        return BusinessDataServiceHelper.load(FormConstant.HRPI_EMPPOSORGREL, selectProperties, new QFilter[]{queryFilter}, order);
+    }
+
+    /**
+     * 构建查询字段和排序规则
+     * 包含员工基本信息、岗位类型、组织架构等相关字段
+     *
+     * @return QueryFieldBuilder 查询字段构建器
+     * @author W.Y.C
+     * @date: 2025/11/04 16:02
+     */
+    public static QueryFieldBuilder selectProperties() {
+        return QueryFieldBuilder.create()
+                .add(FormConstant.ID_KEY)
+                .add(FormConstant.IS_PRIMARY)
+                .addIdNumberName(FormConstant.EMPLOYEE_KEY)
+                .addIdNumberName(FormConstant.CHGACTION)
+                .addIdNumberName(FormConstant.POSTYPE)
+                .addIdNumberName(FormConstant.POS_STATUS)
+                // 所属公司
+                .addIdNumberName(FormConstant.COMPANY_KEY)
+                // 行政组织(部门)
+                .addIdNumberName(FormConstant.ADMINORG)
+                // 岗位
+                .addIdNumberName(FormConstant.POSITION_KEY)
+                .orderDesc(FormConstant.STARTDATE)
+                .orderDesc(FormConstant.ENDDATE);
+    }
+}

+ 11 - 11
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/common/ContributionConstant.java

@@ -20,25 +20,25 @@ public class ContributionConstant extends FormConstant {
     /** 年度贡献积分单据-实体标识 */
     public static final String CONTRIBBILL_ENTITYID = "nckd_contribbill";
     /** 单据体 */
-    public static final String NCKD_CONTRIBBILLENTRY = "NCKD_CONTRIBBILLENTRY";
+    public static final String NCKD_CONTRIBBILLENTRY = "nckd_contribbillentry";
     /** 原始分数 */
-    public static final String NCKD_ORISCORE = "NCKD_ORISCORE";
+    public static final String NCKD_ORISCORE = "nckd_oriscore";
     /** 录入积分 */
-    public static final String NCKD_SCORE = "NCKD_SCORE";
+    public static final String NCKD_SCORE = "nckd_score";
     /** 二级单位 */
-    public static final String NCKD_ADMINORG = "NCKD_ADMINORG";
+    public static final String NCKD_ADMINORG = "nckd_adminorg";
     /** 姓名 */
-    public static final String NCKD_PERSON = "NCKD_PERSON";
+    public static final String NCKD_PERSON = "nckd_person";
     /** 参与人数 */
-    public static final String NCKD_PARTICIPANTS = "NCKD_PARTICIPANTS";
+    public static final String NCKD_PARTICIPANTS = "nckd_participants";
     /** 积分所属年度 */
-    public static final String NCKD_YEAR = "NCKD_YEAR";
+    public static final String NCKD_YEAR = "nckd_year";
     /** 积分项目 */
-    public static final String NCKD_SCOREITEM = "NCKD_SCOREITEM";
+    public static final String NCKD_SCOREITEM = "nckd_scoreitem";
     /** 积分具体项目 */
-    public static final String NCKD_SCOREITEMSUB = "NCKD_SCOREITEMSUB";
+    public static final String NCKD_SCOREITEMSUB = "nckd_scoreitemsub";
     /** 积分项目名次 */
-    public static final String NCKD_SCOREITEMRANK = "NCKD_SCOREITEMRANK";
+    public static final String NCKD_SCOREITEMRANK = "nckd_scoreitemrank";
 
 
 
@@ -46,6 +46,6 @@ public class ContributionConstant extends FormConstant {
     /** 积分项目分数配置-实体标识 */
     public static final String SCOREITEMCONF_ENTITYID = "nckd_scoreitemconf";
     /** 最高分数 */
-    public static final String NCKD_MAXSCORE = "NCKD_MAXSCORE";
+    public static final String NCKD_MAXSCORE = "nckd_maxscore";
     /*-------------------------------------- 积分项目分数配置 begin --------------------------------------*/
 }

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

@@ -15,33 +15,33 @@ public class PerfRankMgmtConstant extends FormConstant {
     /** 单据体 */
     public static final String NCKD_PERFRANKMGMTENTRY = "nckd_perfrankmgmtentry";
     /** 员工 */
-    public static final String NCKD_PERSON = "NCKD_PERSON";
+    public static final String NCKD_PERSON = "nckd_person";
     /** 排名 */
-    public static final String NCKD_TOPRANK = "NCKD_TOPRANK";
+    public static final String NCKD_TOPRANK = "nckd_toprank";
     /** 是否享受职位津贴 */
-    public static final String NCKD_POSTALLOWANCE = "NCKD_POSTALLOWANCE";
+    public static final String NCKD_POSTALLOWANCE = "nckd_postallowance";
     /** 绩效排名R */
-    public static final String NCKD_ALLOWANCERANK = "NCKD_ALLOWANCERANK";
+    public static final String NCKD_ALLOWANCERANK = "nckd_allowancerank";
     /** 考核结果 */
-    public static final String NCKD_APPRAISALRESULT = "NCKD_APPRAISALRESULT";
+    public static final String NCKD_APPRAISALRESULT = "nckd_appraisalresult";
     /** 是否参与排名 */
-    public static final String NCKD_ISRANKING = "NCKD_ISRANKING";
+    public static final String NCKD_ISRANKING = "nckd_isranking";
     /** 不参与排名原因 */
-    public static final String NCKD_EXCLUDERANKREASON = "NCKD_EXCLUDERANKREASON";
+    public static final String NCKD_EXCLUDERANKREASON = "nckd_excluderankreason";
     /** 排名来源(0:系统生成,1:手动添加) */
-    public static final String NCKD_RANKSOURCE = "NCKD_RANKSOURCE";
+    public static final String NCKD_RANKSOURCE = "nckd_ranksource";
     /** 年度 */
-    public static final String NCKD_THEYEAR = "NCKD_THEYEAR";
+    public static final String NCKD_THEYEAR = "nckd_theyear";
     /** 参加排名总人数 */
-    public static final String NCKD_TOPRANKS = "NCKD_TOPRANKS";
+    public static final String NCKD_TOPRANKS = "nckd_topranks";
     /** 参加R排名人数 */
-    public static final String NCKD_ALLOWANCERANKS = "NCKD_ALLOWANCERANKS";
+    public static final String NCKD_ALLOWANCERANKS = "nckd_allowanceranks";
     /** 不合格人数 */
-    public static final String NCKD_FAILS = "NCKD_FAILS";
+    public static final String NCKD_FAILS = "nckd_fails";
     /** 基本合格人数 */
-    public static final String NCKD_BASICS = "NCKD_BASICS";
+    public static final String NCKD_BASICS = "nckd_basics";
     /** 优秀人数 */
-    public static final String NCKD_EXCELLENTS = "NCKD_EXCELLENTS";
+    public static final String NCKD_EXCELLENTS = "nckd_excellents";
     /** 行政组织 */
     public static final String NCKD_ADMINORG = "nckd_adminorg";
     /** 获取排名名单-按钮 */
@@ -63,19 +63,19 @@ public class PerfRankMgmtConstant extends FormConstant {
     /** 年度绩效排名解锁单据-实体标识 */
     public static final String PERFRANKUNLOCK_ENTITYID = "nckd_perfrankunlock";
     public static final String NCKD_PERFRANKUNLOCKENTRY = "nckd_perfrankunlockentry";
-    public static final String NCKD_PERFRANKMGMT = "NCKD_PERFRANKMGMT";
+    public static final String NCKD_PERFRANKMGMT = "nckd_perfrankmgmt";
     /*-------------------------------------- 年度绩效排名解锁单据 begin --------------------------------------*/
 
     /*-------------------------------------- 升保降级条件配置 begin --------------------------------------*/
     /** 升保降级条件配置-实体标识 */
     public static final String LEVELCONDITIONCON_ENTITYID = "nckd_levelconditionconf";
     /** 最小职级 */
-    public static final String NCKD_MINJOBLEVEL = "NCKD_MINJOBLEVEL";
+    public static final String NCKD_MINJOBLEVEL = "nckd_minjoblevel";
     /** 最大职级 */
-    public static final String NCKD_MAXJOBLEVEL = "NCKD_MAXJOBLEVEL";
+    public static final String NCKD_MAXJOBLEVEL = "nckd_maxjoblevel";
     /** 升级百分比阈值 */
-    public static final String NCKD_UPGRADETHRESHOLD = "NCKD_UPGRADETHRESHOLD";
+    public static final String NCKD_UPGRADETHRESHOLD = "nckd_upgradethreshold";
     /** 降级百分比阈值 */
-    public static final String NCKD_DOWNGRADETHRESHOLD = "NCKD_DOWNGRADETHRESHOLD";
+    public static final String NCKD_DOWNGRADETHRESHOLD = "nckd_downgradethreshold";
     /*-------------------------------------- 升保降级条件配置 begin --------------------------------------*/
 }

+ 61 - 62
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/common/PositionStructureConstant.java

@@ -13,107 +13,106 @@ public class PositionStructureConstant extends FormConstant {
     /** 员工职位档案-实体标识 */
     public static final String PERSONPOSFILE_ENTITYID = "nckd_personposfile";
     /** 开始日期 */
-    public static final String NCKD_BEGINDATE = "NCKD_BEGINDATE";
+    public static final String NCKD_BEGINDATE = "nckd_begindate";
     /** 所属序列 */
-    public static final String NCKD_JOBSEQHR = "NCKD_JOBSEQHR";
+    public static final String NCKD_JOBSEQHR = "nckd_jobseqhr";
     /** 职级 */
-    public static final String NCKD_JOBLEVELHR = "NCKD_JOBLEVELHR";
+    public static final String NCKD_JOBLEVELHR = "nckd_joblevelhr";
     /** 是否初次定级 */
-    public static final String NCKD_FIRSTRANK = "NCKD_FIRSTRANK";
+    public static final String NCKD_FIRSTRANK = "nckd_firstrank";
     /** 学历 */
-    public static final String NCKD_DIPLOMA = "NCKD_DIPLOMA";
+    public static final String NCKD_DIPLOMA = "nckd_diploma";
     /** 岗位 */
-    public static final String NCKD_POSITIONHR = "NCKD_POSITIONHR";
+    public static final String NCKD_POSITIONHR = "nckd_positionhr";
     /** 职称名称 */
-    public static final String NCKD_RANKNAME = "NCKD_RANKNAME";
+    public static final String NCKD_RANKNAME = "nckd_rankname";
     /** 职称级别 */
-    public static final String NCKD_PROTITLELEVEL = "NCKD_PROTITLELEVEL";
+    public static final String NCKD_PROTITLELEVEL = "nckd_protitlelevel";
     /** 技能名称 */
-    public static final String NCKD_JOBSTATUSNAME = "NCKD_JOBSTATUSNAME";
+    public static final String NCKD_JOBSTATUSNAME = "nckd_jobstatusname";
     /** 技能等级 */
-    public static final String NCKD_OCPQUALLEVEL = "NCKD_OCPQUALLEVEL";
+    public static final String NCKD_OCPQUALLEVEL = "nckd_ocpquallevel";
     /** 上年度绩效考核结果 */
-    public static final String NCKD_APPRAISALRESULT = "NCKD_APPRAISALRESULT";
+    public static final String NCKD_APPRAISALRESULT = "nckd_appraisalresult";
     /** 上年度员工职位档案 */
-    public static final String NCKD_LASTPERSONPOSFILE = "NCKD_LASTPERSONPOSFILE";
+    public static final String NCKD_LASTPERSONPOSFILE = "nckd_lastpersonposfile";
     /** 聘任状态 */
-    public static final String NCKD_EMPLOYMENTSTATUS = "NCKD_EMPLOYMENTSTATUS";
+    public static final String NCKD_EMPLOYMENTSTATUS = "nckd_employmentstatus";
     /** 类型状态(新入职人员初定1;在职人员初定2;年度调整3;职位调动4;管理序列的聘任5;高级职称的聘任6) */
-    public static final String NCKD_TYPESTATE = "NCKD_TYPESTATE";
+    public static final String NCKD_TYPESTATE = "nckd_typestate";
     /** 连续聘任年限 */
-    public static final String NCKD_EMPLOYMENTYEARS = "NCKD_EMPLOYMENTYEARS";
+    public static final String NCKD_EMPLOYMENTYEARS = "nckd_employmentyears";
     /** 上年度绩效考核分数 */
-    public static final String NCKD_RESULTSCORE = "NCKD_RESULTSCORE";
+    public static final String NCKD_RESULTSCORE = "nckd_resultscore";
     /** 优秀生分 */
-    public static final String NCKD_ORGINSSCORE = "NCKD_ORGINSSCORE";
+    public static final String NCKD_ORGINSSCORE = "nckd_orginsscore";
     /** 职称分 */
-    public static final String NCKD_RANKSCORE = "NCKD_RANKSCORE";
+    public static final String NCKD_RANKSCORE = "nckd_rankscore";
     /** 技能分 */
-    public static final String NCKD_JOBSTATUSSCORE = "NCKD_JOBSTATUSSCORE";
+    public static final String NCKD_JOBSTATUSSCORE = "nckd_jobstatusscore";
     /** 学历分 */
-    public static final String NCKD_DIPLOMASCORE = "NCKD_DIPLOMASCORE";
+    public static final String NCKD_DIPLOMASCORE = "nckd_diplomascore";
     /** 是否失效 */
-    public static final String NCKD_DISABLE = "NCKD_DISABLE";
+    public static final String NCKD_DISABLE = "nckd_disable";
     /** 上年度贡献综合评价分 */
-    public static final String NCKD_LYRCONTRIBSCORE = "NCKD_LYRCONTRIBSCORE";
+    public static final String NCKD_LYRCONTRIBSCORE = "nckd_lyrcontribscore";
     /** 任命结束日期 */
-    public static final String NCKD_ENDDATE = "NCKD_ENDDATE";
+    public static final String NCKD_ENDDATE = "nckd_enddate";
     /** 任命状态 */
-    public static final String NCKD_APPOINTSTATUS = "NCKD_APPOINTSTATUS";
+    public static final String NCKD_APPOINTSTATUS = "nckd_appointstatus";
     /** 年度 */
-    public static final String NCKD_EXECUTEYEAR = "NCKD_EXECUTEYEAR";
+    public static final String NCKD_EXECUTEYEAR = "nckd_executeyear";
     /** 调整类别(升级2;保级1;降级0;首次聘任3;序列变化4;聘任下调5;总分不足6;无聘任7;无考核结果8) */
-    public static final String NCKD_ADJUSTTYPE = "NCKD_ADJUSTTYPE";
+    public static final String NCKD_ADJUSTTYPE = "nckd_adjusttype";
     /** 年度调整状态 */
-    public static final String NCKD_ADJUSSTATUS = "NCKD_ADJUSSTATUS";
+    public static final String NCKD_ADJUSSTATUS = "nckd_adjusstatus";
     /** 上年度绩效排名R百分比 */
-    public static final String NCKD_ALLOWANCERANKPCT = "NCKD_ALLOWANCERANKPCT";
+    public static final String NCKD_ALLOWANCERANKPCT = "nckd_allowancerankpct";
     /** 年度科研与创新分 */
-    public static final String NCKD_YEARSCORESUMA = "NCKD_YEARSCORESUMA";
+    public static final String NCKD_YEARSCORESUMA = "nckd_yearscoresuma";
     /** 年度专利申报分 */
-    public static final String NCKD_YEARSCORESUMB = "NCKD_YEARSCORESUMB";
+    public static final String NCKD_YEARSCORESUMB = "nckd_yearscoresumb";
     /** 年度论文发表分 */
-    public static final String NCKD_YEARSCORESUMC = "NCKD_YEARSCORESUMC";
+    public static final String NCKD_YEARSCORESUMC = "nckd_yearscoresumc";
     /** 年度技能竞赛分 */
-    public static final String NCKD_YEARSCORESUMD = "NCKD_YEARSCORESUMD";
+    public static final String NCKD_YEARSCORESUMD = "nckd_yearscoresumd";
     /** 年度培训教材分 */
-    public static final String NCKD_YEARSCORESUME = "NCKD_YEARSCORESUME";
+    public static final String NCKD_YEARSCORESUME = "nckd_yearscoresume";
     /** 年度技术标准分 */
-    public static final String NCKD_YEARSCORESUMF = "NCKD_YEARSCORESUMF";
+    public static final String NCKD_YEARSCORESUMF = "nckd_yearscoresumf";
     /** 年度管理规范分 */
-    public static final String NCKD_YEARSCORESUMG = "NCKD_YEARSCORESUMG";
+    public static final String NCKD_YEARSCORESUMG = "nckd_yearscoresumg";
     /** 年度师带徒分 */
-    public static final String NCKD_YEARSCORESUMH = "NCKD_YEARSCORESUMH";
+    public static final String NCKD_YEARSCORESUMH = "nckd_yearscoresumh";
     /** 年度培训授课分 */
-    public static final String NCKD_YEARSCORESUMI = "NCKD_YEARSCORESUMI";
+    public static final String NCKD_YEARSCORESUMI = "nckd_yearscoresumi";
     /** 贡献单据分数之和 */
-    public static final String NCKD_ALLYEARSCORESUM = "NCKD_ALLYEARSCORESUM";
+    public static final String NCKD_ALLYEARSCORESUM = "nckd_allyearscoresum";
     /** 累计总积分 */
-    public static final String NCKD_ALLSUMSCORE = "NCKD_ALLSUMSCORE";
+    public static final String NCKD_ALLSUMSCORE = "nckd_allsumscore";
     /** 累计的积分池 */
-    public static final String NCKD_SUMSCORE = "NCKD_SUMSCORE";
+    public static final String NCKD_SUMSCORE = "nckd_sumscore";
     /** 年度新增的贡献积分 */
-    public static final String NCKD_ADDYCONTRIBSCORE = "NCKD_ADDYCONTRIBSCORE";
+    public static final String NCKD_ADDYCONTRIBSCORE = "nckd_addycontribscore";
     /** 升降级数 */
-    public static final String NCKD_ADJUSTINT = "NCKD_ADJUSTINT";
+    public static final String NCKD_ADJUSTINT = "nckd_adjustint";
     /** 上年度绩效排名R名次 */
-    public static final String NCKD_ALLOWANCERANK = "NCKD_ALLOWANCERANK";
+    public static final String NCKD_ALLOWANCERANK = "nckd_allowancerank";
     /** 上年度排名名次 */
-    public static final String NCKD_TOPRANK = "NCKD_TOPRANK";
+    public static final String NCKD_TOPRANK = "nckd_toprank";
     /** 上年度排名百分比 */
-    public static final String NCKD_TOPRANKPERCENT = "NCKD_TOPRANKPERCENT";
+    public static final String NCKD_TOPRANKPERCENT = "nckd_toprankpercent";
     /** 备注 */
-    public static final String KEY_NCKD_CAUSEREMARK = "NCKD_CAUSEREMARK";
+    public static final String KEY_NCKD_CAUSEREMARK = "nckd_causeremark";
     /** 学历分计算过程 */
-    public static final String NCKD_WHYDIPLOMASCORE = "NCKD_WHYDIPLOMASCORE";
+    public static final String NCKD_WHYDIPLOMASCORE = "nckd_whydiplomascore";
     /** R排名名次/R排名总人数 */
-    public static final String NCKD_ALLOWANCERANKMARK = "NCKD_ALLOWANCERANKMARK";
+    public static final String NCKD_ALLOWANCERANKMARK = "nckd_allowancerankmark";
     /** R排名百分比 */
-    public static final String NCKD_ALLOWANCERANKSEL = "NCKD_ALLOWANCERANKSEL";
-    /** 是否当前最新记录 */
-    public static final String NCKD_ISCURRENTNEWEST = "NCKD_ISCURRENTNEWEST";
+    public static final String NCKD_ALLOWANCERANKSEL = "nckd_allowanceranksel";
+
     /** 锁定状态 */
-    public static final String NCKD_LOCKSTATUS = "NCKD_LOCKSTATUS";
+    public static final String NCKD_LOCKSTATUS = "nckd_lockstatus";
     /** 锁定状态 */
     public static final String KEY_NCKD_LOCKUSER = "nckd_lockuser";
     /** 锁定人 */
@@ -129,11 +128,11 @@ public class PositionStructureConstant extends FormConstant {
     /** 职位及积分初定-实体标识 */
     public static final String SCOREINITIAL_ENTITYID = "nckd_scoreinitial";
     /** 页签控件 */
-    public static final String NCKD_TABAP = "NCKD_TABAP";
+    public static final String NCKD_TABAP = "nckd_tabap";
     /** 页签控件-已定级人员 */
-    public static final String NCKD_GRADEDPERSON = "NCKD_GRADEDPERSON";
+    public static final String NCKD_GRADEDPERSON = "nckd_gradedperson";
     /** 页签控件-未定级人员 */
-    public static final String NCKD_UNGRADEDPERSON = "NCKD_UNGRADEDPERSON";
+    public static final String NCKD_UNGRADEDPERSON = "nckd_ungradedperson";
     /*-------------------------------------- 职位及积分初定 end --------------------------------------*/
 
 
@@ -160,9 +159,9 @@ public class PositionStructureConstant extends FormConstant {
     /** 职位序列(根据岗位需要自动带出) */
     public static final String NCKD_JOBSEQ = "nckd_jobseq";
     /** 优秀生 */
-    public static final String NCKD_EXCELLENT = "NCKD_EXCELLENT";
+    public static final String NCKD_EXCELLENT = "nckd_excellent";
     /** 任职经历 */
-    public static final String NCKD_EMPPOSORGREL = "NCKD_EMPPOSORGREL";
+    public static final String NCKD_EMPPOSORGREL = "nckd_empposorgrel";
     /*-------------------------------------- 在职人员初定(弹窗) end --------------------------------------*/
 
 
@@ -189,11 +188,11 @@ public class PositionStructureConstant extends FormConstant {
     /** 确认定级 */
     public static final String OP_CONFIRMADJUST = "confirmadjust";
     /** R排名名次/R排名总人数 */
-    public static final String NCKD_ALLOWANCERANKPERCENT = "NCKD_ALLOWANCERANKPERCENT";
+    public static final String NCKD_ALLOWANCERANKPERCENT = "nckd_allowancerankpercent";
     /** 职称等级 */
-    public static final String NCKD_PERPROTITLENAME = "NCKD_PERPROTITLENAME";
+    public static final String NCKD_PERPROTITLENAME = "nckd_perprotitlename";
     /** 技能等级 */
-    public static final String NCKD_QUALEVELNAME = "NCKD_QUALEVELNAME";
+    public static final String NCKD_QUALEVELNAME = "nckd_qualevelname";
 
     /*-------------------------------------- 新建动态调整(弹窗) end --------------------------------------*/
 
@@ -242,9 +241,9 @@ public class PositionStructureConstant extends FormConstant {
     /** 年度调整解锁单据-实体标识 */
     public static final String POSFILEUNLOCK_ENTITYID = "nckd_posfileunlock";
     /** 单据体 */
-    public static final String NCKD_POSFILEUNLOCKENTRY = "NCKD_POSFILEUNLOCKENTRY";
+    public static final String NCKD_POSFILEUNLOCKENTRY = "nckd_posfileunlockentry";
     /** 职位档案 */
-    public static final String NCKD_PERSONPOSFILE = "NCKD_PERSONPOSFILE";
+    public static final String NCKD_PERSONPOSFILE = "nckd_personposfile";
     /** 解锁原因 */
     public static final String NCKD_REASON = "nckd_reason";
 

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

@@ -1,5 +1,6 @@
 package nckd.jxccl.hr.psms.plugin.form.contribution;
 
+import kd.bos.form.events.ClosedCallBackEvent;
 import kd.bos.form.events.SetFilterEvent;
 import kd.bos.list.IListView;
 import kd.bos.list.plugin.AbstractListPlugin;
@@ -29,4 +30,9 @@ public class ContribLillListPlugin extends AbstractListPlugin implements Plugin
             e.addCustomQFilter(new QFilter(String.join(".",ContributionConstant.NCKD_SCOREITEM, FormConstant.NUMBER_KEY), QCP.equals, scoreItem.getCode()));
         }
     }
+
+    @Override
+    public void closedCallBack(ClosedCallBackEvent closedCallBackEvent) {
+        super.closedCallBack(closedCallBackEvent);
+    }
 }

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

@@ -2,6 +2,7 @@ package nckd.jxccl.hr.psms.plugin.operate.performance;
 
 import kd.bos.entity.plugin.AbstractOperationServicePlugIn;
 import kd.bos.entity.plugin.AddValidatorsEventArgs;
+import kd.bos.entity.plugin.args.EndOperationTransactionArgs;
 import kd.sdk.plugin.Plugin;
 import nckd.jxccl.hr.psms.plugin.operate.performance.validate.PerfRankMgmtSaveValidate;
 
@@ -18,4 +19,10 @@ public class PerfRankMgmtSaveOpPlugin extends AbstractOperationServicePlugIn imp
         e.addValidator(new PerfRankMgmtSaveValidate());
     }
 
+
+    @Override
+    public void endOperationTransaction(EndOperationTransactionArgs e) {
+        //TODO 【待修改】-职位体系-全排名同步到考核周期
+        super.endOperationTransaction(e);
+    }
 }

+ 35 - 8
code/opmc/nckd-jxccl-opmc/src/main/java/nckd/jxccl/opmc/pm/common/PerfManagerFormConstant.java

@@ -11,18 +11,45 @@ import nckd.jxccl.base.common.constant.FormConstant;
 public class PerfManagerFormConstant extends FormConstant {
 
     /** 人员考评实体名称 */
-    public static final String ENTITY_NAME_KEY = "nckd_perfmanager";
+    public static final String PERFMANAGER_ENTITYID = "nckd_perfmanager";
+    /** 周期开始年份 */
+    public static final String NCKD_BEGINYEAR = "nckd_beginyear";
+    /** 实际周期结束年份 */
+    public static final String NCKD_ACTENDYEAR = "nckd_actendyear";
     /** 人员考评向导页面标识*/
     public static final String PERF_MANAGER_WIZARD_KEY = "nckd_perfmanagerwizard";
-    /** 周期开始年份 */
-    public static final String BEGIN_YEAR_KEY = "nckd_beginyear";
+    /** 第一年考核结果 */
+    public static final String NCKD_FIRSTYEARRESULT = "nckd_firstyearresult";
+    /** 第二年考核结果 */
+    public static final String NCKD_SECONDYEARRESULT = "nckd_secondyearresult";
+    /** 第三年考核结果 */
+    public static final String NCKD_THIRDYEARRESULT = "nckd_thirdyearresult";
+    /** 调薪说明 */
+    public static final String NCKD_WAGEEXPLAIN = "nckd_wageexplain";
     /** 周期结束年份 */
-    public static final String END_YEAR_KEY = "nckd_endyear";
-    /** 分录-考核年份 */
-    public static final String APPRAISAL_YEAR_KEY = "nckd_appraisalyear";
+    public static final String NCKD_ENDYEAR = "nckd_endyear";
+    /** 周期状态 */
+    public static final String NCKD_THESTATUS = "nckd_thestatus";
+    /** 锁定人 */
+    public static final String NCKD_LOCKUSER = "nckd_lockuser";
+    /** 周期结束原因 */
+    public static final String NCKD_WHYEND = "nckd_whyend";
+    /** 周期结束原因 */
+    public static final String NCKD_LASTPERFMANAGER = "nckd_lastperfmanager";
     /**生成调薪按钮*/
-    public static final String GENERATE_KEY = "nckd_generate";
+    public static final String NCKD_GENERATE = "nckd_generate";
 
     /** 人员考评管理分录实体名称 */
-    public static final String PERFMANAGER_ENTRY_ENTITYID = "nckd_perfmanagerentry";
+    public static final String NCKD_PERFMANAGERENTRY = "nckd_perfmanagerentry";
+    public static final String PERFMANAGERENTRY = "perfmanagerentry";
+    /** 分录-考核年份 */
+    public static final String NCKD_APPRAISALYEAR = "nckd_appraisalyear";
+    /** 分录-考评结果 */
+    public static final String NCKD_APPRAISALRESULT = "nckd_appraisalresult";
+    /** 分录-是否来源全排名 */
+    public static final String NCKD_ISALLRANKSOURCE = "nckd_isallranksource";
+
+
+    /** 批量新增考核周期(弹窗)-实体标识 */
+    public static final String BATCHEVALCYCLE_ENTITYID = "nckd_batchevalcycle";
 }

+ 170 - 0
code/opmc/nckd-jxccl-opmc/src/main/java/nckd/jxccl/opmc/pm/helper/PerfManagerHelper.java

@@ -0,0 +1,170 @@
+package nckd.jxccl.opmc.pm.helper;
+
+import kd.bos.common.enums.EnableEnum;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.servicehelper.operation.SaveServiceHelper;
+import nckd.jxccl.base.common.constant.FormConstant;
+import nckd.jxccl.base.common.utils.QueryFieldBuilder;
+import nckd.jxccl.opmc.pm.common.PerfManagerFormConstant;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+* 考核周期帮助类
+* @author W.Y.C
+* @date 2025/11/4 17:06
+* @version 1.0
+*/
+public class PerfManagerHelper {
+
+    /**
+     * 根据人员获取当前最新的考核周期
+     * @param personIds 人员ID
+     * @return: kd.bos.dataentity.entity.DynamicObject[]
+     * @author W.Y.C
+     * @date: 2025/11/04 17:31
+     */
+    public static DynamicObject[] getNewestPerfManagerByPerson(Collection<Long> personIds) {
+        return getNewestPerfManagerByPerson(personIds,null);
+    }
+
+
+    /**
+     * 根据人员获取当前最新的考核周期(“是否当前最新记录 = true” 并且  “周期状态 = 1”)
+     * @param personIds 人员ID
+     * @param otherFilter 其他过滤条件
+     * @return: kd.bos.dataentity.entity.DynamicObject[]
+     * @author W.Y.C
+     * @date: 2025/11/04 17:30
+     */
+    public static DynamicObject[] getNewestPerfManagerByPerson(Collection<Long> personIds,QFilter otherFilter) {
+        QueryFieldBuilder queryFieldBuilder = QueryFieldBuilder.create()
+                .add(FormConstant.ID_KEY)
+                .addIdNumberName(FormConstant.NCKD_PERSON)
+                .addIdNumberName(PerfManagerFormConstant.NCKD_FIRSTYEARRESULT)
+                .addIdNumberName(PerfManagerFormConstant.NCKD_SECONDYEARRESULT)
+                .addIdNumberName(PerfManagerFormConstant.NCKD_THIRDYEARRESULT)
+                .add(PerfManagerFormConstant.NCKD_BEGINYEAR)
+                .add(PerfManagerFormConstant.NCKD_ENDYEAR)
+                .add(PerfManagerFormConstant.NCKD_ACTENDYEAR)
+                .add(PerfManagerFormConstant.NCKD_WHYEND)
+                .add(PerfManagerFormConstant.NCKD_THESTATUS)
+                .add(PerfManagerFormConstant.NCKD_ISCURRENTNEWEST)
+                .addIdNumberName(new String[]{PerfManagerFormConstant.NCKD_PERFMANAGERENTRY,PerfManagerFormConstant.NCKD_APPRAISALRESULT})
+                .addIdNumberName(new String[]{PerfManagerFormConstant.NCKD_PERFMANAGERENTRY,PerfManagerFormConstant.NCKD_APPRAISALYEAR});
+
+        QFilter filter = new QFilter(PerfManagerFormConstant.NCKD_ISCURRENTNEWEST, QCP.equals, EnableEnum.YES.getCode())
+                .and(new QFilter(PerfManagerFormConstant.NCKD_THESTATUS, QCP.equals, EnableEnum.YES.getCode()))
+                .and(new QFilter(PerfManagerFormConstant.NCKD_PERSON, QCP.in, personIds));
+        if(otherFilter != null) {
+            filter.and(otherFilter);
+        }
+
+        return BusinessDataServiceHelper.load(PerfManagerFormConstant.PERFMANAGER_ENTITYID, queryFieldBuilder.buildSelect(), new QFilter[]{filter});
+    }
+
+    /**
+     * 根据人员获取当前最新的考核周期(根据“周期开始时间”)
+     * @param personIds 人员ID
+     * @param otherFilter 其他过滤条件
+     * @return: kd.bos.dataentity.entity.DynamicObject[]
+     * @author W.Y.C
+     * @date: 2025/11/04 17:30
+     */
+    public static List<DynamicObject> getNewestPerfManagerByPersonAndBeginYear(Collection<Long> personIds,QFilter otherFilter) {
+        QueryFieldBuilder queryFieldBuilder = QueryFieldBuilder.create()
+                .addIdNumberName()
+                .addIdNumberName(FormConstant.NCKD_PERSON)
+                .addIdNumberName(PerfManagerFormConstant.NCKD_FIRSTYEARRESULT)
+                .addIdNumberName(PerfManagerFormConstant.NCKD_SECONDYEARRESULT)
+                .addIdNumberName(PerfManagerFormConstant.NCKD_THIRDYEARRESULT)
+                .add(PerfManagerFormConstant.NCKD_BEGINYEAR)
+                .add(PerfManagerFormConstant.NCKD_ENDYEAR)
+                .add(PerfManagerFormConstant.NCKD_ACTENDYEAR)
+                .add(PerfManagerFormConstant.NCKD_WHYEND)
+                .add(PerfManagerFormConstant.NCKD_THESTATUS)
+                .add(PerfManagerFormConstant.NCKD_ISCURRENTNEWEST)
+                .add(PerfManagerFormConstant.CREATE_TIME_KEY)
+                .addIdNumberName(new String[]{PerfManagerFormConstant.NCKD_PERFMANAGERENTRY,PerfManagerFormConstant.NCKD_APPRAISALRESULT})
+                .addIdNumberName(new String[]{PerfManagerFormConstant.NCKD_PERFMANAGERENTRY,PerfManagerFormConstant.NCKD_APPRAISALYEAR})
+                .orderDesc(PerfManagerFormConstant.NCKD_BEGINYEAR);
+
+        QFilter filter = new QFilter(PerfManagerFormConstant.NCKD_PERSON, QCP.in, personIds);
+        if(otherFilter != null) {
+            filter.and(otherFilter);
+        }
+        DynamicObject[] load = BusinessDataServiceHelper.load(PerfManagerFormConstant.PERFMANAGER_ENTITYID, queryFieldBuilder.buildSelect(), new QFilter[]{filter}, queryFieldBuilder.buildOrder());
+
+        //只取每个人员最新记录(先按NCKD_BEGINYEAR,再按CREATE_TIME_KEY)
+        Map<Long, DynamicObject> latestRecordMap = new HashMap<>();
+        for (DynamicObject dynamicObject : load) {
+            DynamicObject person = dynamicObject.getDynamicObject(FormConstant.NCKD_PERSON);
+            Date beginDate = dynamicObject.getDate(PerfManagerFormConstant.NCKD_BEGINYEAR);
+            Date createTime = dynamicObject.getDate(PerfManagerFormConstant.CREATE_TIME_KEY);
+            long personId = person.getLong(FormConstant.ID_KEY);
+            // 如果map中没有该personId的记录,或者当前记录的beginDate更晚,
+            // 或者beginDate相同但createTime更晚,则更新记录
+            if (!latestRecordMap.containsKey(personId)) {
+                latestRecordMap.put(personId, dynamicObject);
+            } else {
+                Date existingBeginDate = latestRecordMap.get(personId).getDate(PerfManagerFormConstant.NCKD_BEGINYEAR);
+                if (beginDate.after(existingBeginDate)) {
+                    latestRecordMap.put(personId, dynamicObject);
+                } else if (beginDate.equals(existingBeginDate)) {
+                    Date existingCreateTime = latestRecordMap.get(personId).getDate(PerfManagerFormConstant.CREATE_TIME_KEY);
+                    if (createTime != null && (existingCreateTime == null || createTime.after(existingCreateTime))) {
+                        latestRecordMap.put(personId, dynamicObject);
+                    }
+                }
+            }
+        }
+        return new ArrayList<>(latestRecordMap.values());
+    }
+
+
+    /**
+     * 根据人员将所有IsCurrentNewest更新为非最新的
+     * @param personId 人员ID(可传多个)
+     * @return: void
+     * @author W.Y.C
+     * @date: 2025/09/28 21:18
+     */
+    public static void markAsNotCurrentNewest(Long... personId) {
+        QFilter filter = new QFilter(String.join(".", FormConstant.NCKD_PERSON, FormConstant.ID_KEY), QCP.in, personId);
+        QueryFieldBuilder queryField = QueryFieldBuilder.create()
+                .add(FormConstant.ID_KEY)
+                .add(FormConstant.NCKD_ISCURRENTNEWEST);
+        DynamicObject[] personPosFileColl = BusinessDataServiceHelper.load(PerfManagerFormConstant.PERFMANAGER_ENTITYID,queryField.buildSelect(), new QFilter[]{filter});
+        for (DynamicObject personPosFile : personPosFileColl) {
+            personPosFile.set(FormConstant.NCKD_ISCURRENTNEWEST, Boolean.FALSE);
+        }
+        SaveServiceHelper.update(personPosFileColl);
+    }
+
+    /**
+     * 将人员最新考核周期标记为最新的
+     * @param personIds 人员ID(可传多个)
+     * @param otherFilter 其他条件
+     * @return: void
+     * @author W.Y.C
+     * @date: 2025/09/28 21:18
+     */
+    public static void markAsCurrentNewest(Collection personIds,QFilter otherFilter) {
+        List<DynamicObject> perfManagerColl = getNewestPerfManagerByPersonAndBeginYear(personIds,otherFilter);
+        DynamicObject[] updatePerfManagerColl = new DynamicObject[perfManagerColl.size()];
+        for (int i = 0; i < perfManagerColl.size(); i++) {
+            DynamicObject dynamicObject = perfManagerColl.get(i);
+            dynamicObject.set(FormConstant.NCKD_ISCURRENTNEWEST, Boolean.TRUE);
+            updatePerfManagerColl[i] = dynamicObject;
+        }
+        SaveServiceHelper.update(updatePerfManagerColl);
+    }
+}

+ 0 - 59
code/opmc/nckd-jxccl-opmc/src/main/java/nckd/jxccl/opmc/pm/plugin/form/PerfManagerListPlugin.java

@@ -1,59 +0,0 @@
-package nckd.jxccl.opmc.pm.plugin.form;
-
-import kd.bos.entity.datamodel.ListSelectedRow;
-import kd.bos.form.IFormView;
-import kd.bos.list.events.ListRowClickEvent;
-import kd.bos.list.plugin.AbstractListPlugin;
-import kd.bos.mvc.form.FormView;
-import kd.sdk.plugin.Plugin;
-import nckd.jxccl.base.common.constant.FormConstant;
-import nckd.jxccl.opmc.pm.common.PerfManagerFormConstant;
-import org.apache.commons.lang3.StringUtils;
-
-import java.util.ArrayList;
-import java.util.EventObject;
-import java.util.List;
-import java.util.StringJoiner;
-
-/**
-* 人员考评管理列表插件
-* @author W.Y.C
-* @date 2025/6/9 18:43
-* @version 1.0
-*/
-public class PerfManagerListPlugin extends AbstractListPlugin implements Plugin {
-
-
-
-    @Override
-    public void afterBindData(EventObject e) {
-        super.afterBindData(e);
-        this.getView().setVisible(false, FormConstant.SEARCH_DATE_KEY);
-        IFormView parentView = this.getView().getParentView();
-        if(parentView != null &&  ((FormView) parentView).getFormId().equals(PerfManagerFormConstant.PERF_MANAGER_WIZARD_KEY)) {
-            //是否向导方式进入
-            this.getPageCache().put("isWizard","1");
-            //隐藏退出按钮/生成调薪按钮
-            this.getView().setVisible(Boolean.FALSE,PerfManagerFormConstant.CLOSE_OP);
-            this.getView().setVisible(Boolean.FALSE,PerfManagerFormConstant.GENERATE_KEY);
-        }
-    }
-
-
-    @Override
-    public void listRowClick(ListRowClickEvent evt) {
-        IFormView parentView = this.getView().getParentView();
-        if(StringUtils.isNotBlank(this.getPageCache().get("isWizard"))){
-            //缓存选中的id(用于人员考评向导)
-            List<Long> rowKeys = new ArrayList<>(evt.getListSelectedRowCollection().size());
-            StringJoiner rowJoin = new StringJoiner(",");
-            for (ListSelectedRow listSelectedRow : evt.getListSelectedRowCollection()) {
-                Object primaryKeyValue = listSelectedRow.getPrimaryKeyValue();
-                if (primaryKeyValue != null) {
-                    rowJoin.add(primaryKeyValue.toString());
-                }
-            }
-            this.getView().getParentView().getPageCache().put("rowKeys", rowJoin.toString());
-        }
-    }
-}

+ 151 - 0
code/opmc/nckd-jxccl-opmc/src/main/java/nckd/jxccl/opmc/pm/plugin/form/cycle/BatchEvalCycleFormPlugin.java

@@ -0,0 +1,151 @@
+package nckd.jxccl.opmc.pm.plugin.form.cycle;
+
+import kd.bos.bd.pojo.CtrlStrategyEnum;
+import kd.bos.dataentity.OperateOption;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.entity.operate.OperateOptionConst;
+import kd.bos.entity.operate.result.IOperateInfo;
+import kd.bos.entity.operate.result.OperationResult;
+import kd.bos.form.ConfirmCallBackListener;
+import kd.bos.form.ConfirmTypes;
+import kd.bos.form.MessageBoxOptions;
+import kd.bos.form.MessageBoxResult;
+import kd.bos.form.control.Control;
+import kd.bos.form.events.MessageBoxClosedEvent;
+import kd.bos.form.plugin.AbstractFormPlugin;
+import kd.bos.org.utils.DynamicObjectUtils;
+import kd.bos.servicehelper.operation.OperationServiceHelper;
+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.StrFormatter;
+import nckd.jxccl.base.entity.helper.EntityHelper;
+import nckd.jxccl.opmc.pm.common.PerfManagerFormConstant;
+import nckd.jxccl.opmc.pm.helper.PerfManagerHelper;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.EventObject;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringJoiner;
+import java.util.stream.Collectors;
+
+/**
+* 批量新增考核周期插件
+* 实体标识:nckd_batchevalcycle
+* @author W.Y.C
+* @date 2025/11/5 11:22
+* @version 1.0
+*/
+public class BatchEvalCycleFormPlugin extends AbstractFormPlugin implements Plugin {
+
+    @Override
+    public void registerListener(EventObject e) {
+        this.addClickListeners(FormConstant.BTN_OK_OP);
+    }
+
+
+    @Override
+    public void click(EventObject evt) {
+
+        super.click(evt);
+        Control source = (Control)evt.getSource();
+        if(FormConstant.BTN_OK_OP.equalsIgnoreCase(source.getKey())){
+            DynamicObjectCollection perfManagerList = this.getModel().getEntryEntity(FormConstant.NCKD_ENTRYENTITY);
+            Set<Long> personIds = new HashSet<>(perfManagerList.size());
+            //转换为考核周期实体
+            for (DynamicObject dynamicObject : perfManagerList) {
+                DynamicObject person = dynamicObject.getDynamicObject(FormConstant.NCKD_PERSON);
+                personIds.add(person.getLong(FormConstant.ID_KEY));
+            }
+
+            //最新上一考核周期
+            DynamicObject[] newestPerfManagerByPerson = PerfManagerHelper.getNewestPerfManagerByPerson(personIds);
+            Map<Long, DynamicObject> lastPerfManagerMap; lastPerfManagerMap = Arrays.stream(newestPerfManagerByPerson)
+                    .collect(Collectors.toMap(
+                            perfManager -> perfManager.getDynamicObject(FormConstant.NCKD_PERSON).getLong(FormConstant.ID_KEY),
+                            perfManager -> perfManager,
+                            (existing, replacement) -> existing
+                    ));
+            StringJoiner confirMmsg = new StringJoiner(",");
+            for (DynamicObject lastPerfManager : lastPerfManagerMap.values()) {
+                String personName = lastPerfManager.getDynamicObject(FormConstant.NCKD_PERSON).getString(FormConstant.NAME_KEY);
+                LocalDateTime beginYear = DateUtil.toLocalDateTime(lastPerfManager.getDate(PerfManagerFormConstant.NCKD_BEGINYEAR));
+                LocalDateTime endYear = DateUtil.toLocalDateTime(lastPerfManager.getDate(PerfManagerFormConstant.NCKD_ENDYEAR));
+                confirMmsg.add(StrFormatter.format("{}(周期:{}~{})", personName, beginYear.getYear(), endYear.getYear()));
+            }
+            if(confirMmsg.length() > 0){
+                // 创建回调监听器,指定回调ID和处理此回调的插件(this)
+                ConfirmCallBackListener confirmCallBacks = new ConfirmCallBackListener(PerfManagerFormConstant.BATCHEVALCYCLE_ENTITYID, this);
+                // 弹出确认框
+                this.getView().showConfirm(StrFormatter.format("检测到本次新增的人员中存在【未结束】的考核周期,新周期新增成功后会自动结束上一周期,请确认是否继续。{}", confirMmsg),
+                        MessageBoxOptions.OKCancel,
+                        ConfirmTypes.Default,
+                        confirmCallBacks);
+            }else{
+                executeOperateSave();
+            }
+
+        }
+
+    }
+
+    @Override
+    public void confirmCallBack(MessageBoxClosedEvent messageBoxClosedEvent) {
+        // 判断回调ID和用户点击的按钮
+        if (PerfManagerFormConstant.BATCHEVALCYCLE_ENTITYID.equals(messageBoxClosedEvent.getCallBackId())
+                && messageBoxClosedEvent.getResult() == MessageBoxResult.Yes) {
+
+            executeOperateSave();
+        }
+    }
+
+    private void executeOperateSave() {
+        DynamicObject createorg = ConvertUtil.toDynamicObjectOrNull(this.getModel().getValue(FormConstant.CREATEORG_KEY));
+        DynamicObject useorg = ConvertUtil.toDynamicObjectOrNull(this.getModel().getValue(FormConstant.USEORG_KEY));
+        DynamicObject org = ConvertUtil.toDynamicObjectOrNull(this.getModel().getValue(FormConstant.ORG_KEY));
+
+        DynamicObjectCollection perfManagerList = this.getModel().getEntryEntity(FormConstant.NCKD_ENTRYENTITY);
+        Set<Long> personIds = new HashSet<>(perfManagerList.size());
+        List<DynamicObject> savePerfManager = new ArrayList<>();
+        //转换为考核周期实体
+        for (DynamicObject dynamicObject : perfManagerList) {
+            DynamicObject person = dynamicObject.getDynamicObject(FormConstant.NCKD_PERSON);
+            String personName = person.getString(FormConstant.NAME_KEY);
+            LocalDateTime beginYear = DateUtil.toLocalDateTime(dynamicObject.getDate(PerfManagerFormConstant.NCKD_BEGINYEAR));
+            LocalDateTime endYear = DateUtil.toLocalDateTime(dynamicObject.getDate(PerfManagerFormConstant.NCKD_ENDYEAR));
+            DynamicObject newPerfManager = EntityHelper.newAvailableBasicEntity(PerfManagerFormConstant.PERFMANAGER_ENTITYID);
+            DynamicObjectUtils.copy(dynamicObject, newPerfManager);
+            newPerfManager.set(PerfManagerFormConstant.CREATEORG_KEY, createorg);
+            newPerfManager.set(PerfManagerFormConstant.USEORG_KEY, useorg);
+            newPerfManager.set(PerfManagerFormConstant.ORG_KEY, org);
+            newPerfManager.set(PerfManagerFormConstant.CTRLSTRATEGY_KEY, CtrlStrategyEnum.GLOBAL_SHARE.getCtrlStrategy());
+            newPerfManager.set(FormConstant.NAME_KEY, StrFormatter.format("【{}】{}~{}的考核周期",personName,beginYear.getYear(),endYear.getYear()));
+            savePerfManager.add(newPerfManager);
+            personIds.add(person.getLong(FormConstant.ID_KEY));
+        }
+
+        OperateOption option = OperateOption.create();
+        option.setVariableValue(OperateOptionConst.IGNOREINTERACTION, Boolean.TRUE+"");
+        OperationResult operationResult =  OperationServiceHelper.executeOperate(FormConstant.SAVE_OP, PerfManagerFormConstant.PERFMANAGER_ENTITYID, savePerfManager.toArray(new DynamicObject[0]), option);
+
+        if (!operationResult.isSuccess()) {
+            StringJoiner errorMsg = new StringJoiner(StrFormatter.LINE_SEPARATOR);
+            for (IOperateInfo error : operationResult.getAllErrorOrValidateInfo()) {
+                errorMsg.add(error.getMessage());
+            }
+            throw new ValidationException(errorMsg.toString());
+        }else{
+            this.getView().getParentView().showSuccessNotification("保存成功");
+            this.getView().getParentView().invokeOperation(FormConstant.REFRESH_OP);
+            this.getView().close();
+        }
+    }
+}

+ 26 - 21
code/opmc/nckd-jxccl-opmc/src/main/java/nckd/jxccl/opmc/pm/plugin/form/PerfManagerBillPlugin.java → code/opmc/nckd-jxccl-opmc/src/main/java/nckd/jxccl/opmc/pm/plugin/form/cycle/PerfManagerFormPlugin.java

@@ -1,14 +1,16 @@
-package nckd.jxccl.opmc.pm.plugin.form;
+package nckd.jxccl.opmc.pm.plugin.form.cycle;
 
 import kd.bos.bill.AbstractBillPlugIn;
 import kd.bos.dataentity.entity.DynamicObject;
 import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.entity.constant.StatusEnum;
 import kd.bos.entity.datamodel.events.PropertyChangedArgs;
 import kd.bos.form.control.EntryGrid;
 import kd.bos.form.events.BeforeDoOperationEventArgs;
 import kd.bos.form.field.DateEdit;
 import kd.bos.form.field.FieldEdit;
 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;
@@ -31,50 +33,53 @@ import java.util.Set;
 import java.util.stream.Collectors;
 
 /**
-* 人员考评管理单据插件
+* 考核周期表单插件
+* 实体标识:nckd_perfmanager
 * @author W.Y.C
-* @date 2025/6/9 9:59
+* @date 2025/11/4 12:30
 * @version 1.0
 */
-public class PerfManagerBillPlugin extends AbstractBillPlugIn implements Plugin {
+public class PerfManagerFormPlugin extends AbstractFormPlugin implements Plugin {
 
-    private final static Log logger = LogFactory.getLog(PerfManagerBillPlugin.class);
+    private final static Log logger = LogFactory.getLog(PerfManagerFormPlugin.class);
 
 
-    public PerfManagerBillPlugin() {
+    public PerfManagerFormPlugin() {
         super();
     }
 
     @Override
     public void afterBindData(EventObject e) {
         setEntryYear();
+        // this.getModel().setValue(FormConstant.STATUS, StatusEnum.A.toString());
     }
 
+
     @Override
     public void propertyChanged(PropertyChangedArgs e) {
         super.propertyChanged(e);
         String fieldKey = e.getProperty().getName();
-        if(PerfManagerFormConstant.END_YEAR_KEY.equals(fieldKey)){
+        if(PerfManagerFormConstant.NCKD_ENDYEAR.equalsIgnoreCase(fieldKey)){
             setEntryYear();
         }
     }
 
 
 
-    @Override
+    /*@Override
     public void beforeDoOperation(BeforeDoOperationEventArgs e) {
         String operateKey = ((FormOperate) e.getSource()).getOperateKey();
         DynamicObject dataEntity = this.getModel().getDataEntity();
-        DynamicObjectCollection entrys = dataEntity.getDynamicObjectCollection(PerfManagerFormConstant.PERFMANAGER_ENTRY_ENTITYID);
-        List<Date> dateList = entrys.stream().map(entry -> entry.getDate(PerfManagerFormConstant.APPRAISAL_YEAR_KEY)).collect(Collectors.toList());
+        DynamicObjectCollection entrys = dataEntity.getDynamicObjectCollection(PerfManagerFormConstant.NCKD_PERFMANAGERENTRY);
+        List<Date> dateList = entrys.stream().map(entry -> entry.getDate(PerfManagerFormConstant.NCKD_APPRAISALYEAR)).collect(Collectors.toList());
         if(FormConstant.SAVE_OP.equals(operateKey)){
             if(CollectionUtils.isNotEmpty(dateList) && !dateList.stream().allMatch(Objects::isNull)) {
-                Date beginYear = dataEntity.getDate(PerfManagerFormConstant.BEGIN_YEAR_KEY);
-                Date endYear = dataEntity.getDate(PerfManagerFormConstant.END_YEAR_KEY);
+                Date beginYear = dataEntity.getDate(PerfManagerFormConstant.NCKD_BEGINYEAR);
+                Date endYear = dataEntity.getDate(PerfManagerFormConstant.NCKD_ENDYEAR);
                 if(beginYear != null && endYear != null){
                     //校验考核年份是否在周期内
                     boolean hasOutOfRangeEntry = entrys != null ? entrys.stream()
-                            .map(entry -> entry.getDate(PerfManagerFormConstant.APPRAISAL_YEAR_KEY))
+                            .map(entry -> entry.getDate(PerfManagerFormConstant.NCKD_APPRAISALYEAR))
                             .filter(Objects::nonNull)
                             .anyMatch(date -> !DateUtil.isInRange(DateUtil.toLocalDateTime(date), DateUtil.toLocalDateTime(beginYear), DateUtil.toLocalDateTime(endYear))) : false;
                     if(hasOutOfRangeEntry){
@@ -99,13 +104,13 @@ public class PerfManagerBillPlugin extends AbstractBillPlugIn implements Plugin
                     QFilter filter = new QFilter(PerfManagerFormConstant.ID_KEY, QCP.not_equals, id);
                     finalFilter.and(filter);
                 }
-                QFilter filter = new QFilter(PerfManagerFormConstant.PERFMANAGER_ENTRY_ENTITYID + "." + PerfManagerFormConstant.APPRAISAL_YEAR_KEY, QCP.in, dateList);
+                QFilter filter = new QFilter(PerfManagerFormConstant.NCKD_PERFMANAGERENTRY + "." + PerfManagerFormConstant.NCKD_APPRAISALYEAR, QCP.in, dateList);
                 finalFilter.and(filter);
-                DynamicObjectCollection query = QueryServiceHelper.query(PerfManagerFormConstant.ENTITY_NAME_KEY, PerfManagerFormConstant.PERFMANAGER_ENTRY_ENTITYID + "." + PerfManagerFormConstant.APPRAISAL_YEAR_KEY, new QFilter[]{finalFilter}, null);
+                DynamicObjectCollection query = QueryServiceHelper.query(PerfManagerFormConstant.PERFMANAGER_ENTITYID, PerfManagerFormConstant.NCKD_PERFMANAGERENTRY + "." + PerfManagerFormConstant.NCKD_APPRAISALYEAR, new QFilter[]{finalFilter}, null);
                 if(!query.isEmpty()){
                     List<String> yearList = query.stream()
                             .map(dynamicObject -> {
-                                Date date = dynamicObject.getDate(PerfManagerFormConstant.PERFMANAGER_ENTRY_ENTITYID + "." + PerfManagerFormConstant.APPRAISAL_YEAR_KEY);
+                                Date date = dynamicObject.getDate(PerfManagerFormConstant.NCKD_PERFMANAGERENTRY + "." + PerfManagerFormConstant.NCKD_APPRAISALYEAR);
                                 return DateUtil.format(date, "yyyy");
                             })
                             .collect(Collectors.toList());
@@ -116,7 +121,7 @@ public class PerfManagerBillPlugin extends AbstractBillPlugIn implements Plugin
             }
         }
 
-    }
+    }*/
 
     /**
      * 设置分录考核年份只能在周期开始和结束范围内
@@ -125,14 +130,14 @@ public class PerfManagerBillPlugin extends AbstractBillPlugIn implements Plugin
      * @version 1.0
      */
     private void setEntryYear() {
-        EntryGrid entryGrid = this.getControl(PerfManagerFormConstant.PERFMANAGER_ENTRY_ENTITYID);
+        EntryGrid entryGrid = this.getControl(PerfManagerFormConstant.NCKD_PERFMANAGERENTRY);
         List<FieldEdit> fieldEdits = entryGrid.getFieldEdits();
         for (FieldEdit fieldEdit : fieldEdits) {
             //选择周期结束年份后设置分录考核年份只能在周期开始和结束范围内
-            if(PerfManagerFormConstant.APPRAISAL_YEAR_KEY.equalsIgnoreCase(fieldEdit.getProperty().getName())) {
+            if(PerfManagerFormConstant.NCKD_APPRAISALYEAR.equalsIgnoreCase(fieldEdit.getProperty().getName())) {
                 DateEdit appraisalYearEdit = (DateEdit) fieldEdit;
-                Date beginYear = ConvertUtil.toDate(this.getModel().getValue(PerfManagerFormConstant.BEGIN_YEAR_KEY));
-                Date endYear = ConvertUtil.toDate(this.getModel().getValue(PerfManagerFormConstant.END_YEAR_KEY));
+                Date beginYear = ConvertUtil.toDate(this.getModel().getValue(PerfManagerFormConstant.NCKD_BEGINYEAR));
+                Date endYear = ConvertUtil.toDate(this.getModel().getValue(PerfManagerFormConstant.NCKD_ENDYEAR));
                 appraisalYearEdit.setMinDate(beginYear);
                 appraisalYearEdit.setMaxDate(endYear);
             }

+ 103 - 0
code/opmc/nckd-jxccl-opmc/src/main/java/nckd/jxccl/opmc/pm/plugin/form/cycle/PerfManagerListPlugin.java

@@ -0,0 +1,103 @@
+package nckd.jxccl.opmc.pm.plugin.form.cycle;
+
+import kd.bos.algo.Algo;
+import kd.bos.algo.AlgoContext;
+import kd.bos.algo.DataSet;
+import kd.bos.algo.Row;
+import kd.bos.common.enums.EnableEnum;
+import kd.bos.consts.PermItemConst;
+import kd.bos.context.RequestContext;
+import kd.bos.dataentity.OperateOption;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.entity.EntityMetadataCache;
+import kd.bos.entity.QueryEntityType;
+import kd.bos.entity.operate.OperateOptionConst;
+import kd.bos.entity.operate.result.IOperateInfo;
+import kd.bos.entity.operate.result.OperationResult;
+import kd.bos.ext.hr.service.query.QueryEntityHelper;
+import kd.bos.form.CloseCallBack;
+import kd.bos.form.FormShowParameter;
+import kd.bos.form.MessageBoxOptions;
+import kd.bos.form.ShowType;
+import kd.bos.form.control.events.ItemClickEvent;
+import kd.bos.form.events.BeforeDoOperationEventArgs;
+import kd.bos.form.events.ClosedCallBackEvent;
+import kd.bos.form.operate.FormOperate;
+import kd.bos.list.plugin.AbstractListPlugin;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.operation.OperationServiceHelper;
+import kd.hr.hbp.common.model.AuthorizedOrgResultWithSub;
+import kd.hr.hbp.common.model.OrgSubInfo;
+import kd.sdk.hr.hbp.business.helper.permission.HRPermissionServiceHelper;
+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.exception.ValidationException;
+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.entity.helper.EntityHelper;
+import nckd.jxccl.opmc.pm.common.PerfManagerFormConstant;
+import nckd.jxccl.opmc.pm.plugin.operate.cycle.PerfManagerSaveOpPlugin;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.StringJoiner;
+import java.util.stream.Collectors;
+
+/**
+ * 考核周期列表插件
+ * 实体标识:nckd_perfmanager
+ * @author W.Y.C
+ * @date 2025/11/4 13:07
+ * @version 1.0
+ */
+public class PerfManagerListPlugin extends AbstractListPlugin implements Plugin {
+
+    @Override
+    public void itemClick(ItemClickEvent evt) {
+        String itemKey = evt.getItemKey();
+        if ("nckd_new".equals(itemKey)) {
+            //弹出【批量】在职人员初定窗口
+            FormShowParameter showParameter = new FormShowParameter();
+            showParameter.setFormId(PerfManagerFormConstant.BATCHEVALCYCLE_ENTITYID);
+            showParameter.getOpenStyle().setShowType(ShowType.Modal);
+            showParameter.setCaption("新增考核周期");
+            showParameter.setSendToClient(true);
+            showParameter.setCloseCallBack(new CloseCallBack(this, PerfManagerFormConstant.BATCHEVALCYCLE_ENTITYID));
+            this.getView().showForm(showParameter);
+        }
+    }
+
+    @Override
+    public void beforeDoOperation(BeforeDoOperationEventArgs args) {
+        String operateKey = ((FormOperate) args.getSource()).getOperateKey();
+        if (operateKey.equals("list_cyclegenerate")) {
+            OperateOption option = OperateOption.create();
+            option.setVariableValue(OperateOptionConst.IGNOREINTERACTION, Boolean.TRUE+"");
+            DynamicObject perfManager = EntityHelper.newEntity(PerfManagerFormConstant.PERFMANAGER_ENTITYID);
+            OperationResult operationResult =  OperationServiceHelper.executeOperate("cyclegenerate", PerfManagerFormConstant.PERFMANAGER_ENTITYID, new DynamicObject[]{perfManager}, option);
+            if (!operationResult.isSuccess()) {
+                int messageType = operationResult.getMessageType();
+                if (messageType == 1) {
+                    this.getView().showTipNotification(operationResult.getMessage());
+                } else {
+                    String message = operationResult.getMessage();
+                    throw new ValidationException(message);
+                }
+
+            }else{
+                //刷新列表
+                this.getView().invokeOperation(FormConstant.REFRESH_OP);
+                this.getView().showConfirm("提示",operationResult.getMessage(), MessageBoxOptions.OK,null,null,null,null);
+            }
+        }
+    }
+}

+ 0 - 0
code/opmc/nckd-jxccl-opmc/src/main/java/nckd/jxccl/opmc/pm/plugin/operate/.gitkeep


+ 448 - 0
code/opmc/nckd-jxccl-opmc/src/main/java/nckd/jxccl/opmc/pm/plugin/operate/cycle/CycleGenerateOpPlugin.java

@@ -0,0 +1,448 @@
+package nckd.jxccl.opmc.pm.plugin.operate.cycle;
+
+import com.google.common.collect.Maps;
+import kd.bos.algo.Algo;
+import kd.bos.algo.AlgoContext;
+import kd.bos.algo.DataSet;
+import kd.bos.algo.Row;
+import kd.bos.bd.pojo.CtrlStrategyEnum;
+import kd.bos.common.enums.EnableEnum;
+import kd.bos.consts.PermItemConst;
+import kd.bos.context.RequestContext;
+import kd.bos.dataentity.OperateOption;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.entity.EntityMetadataCache;
+import kd.bos.entity.QueryEntityType;
+import kd.bos.entity.operate.OperateOptionConst;
+import kd.bos.entity.operate.result.IOperateInfo;
+import kd.bos.entity.operate.result.OperationResult;
+import kd.bos.entity.plugin.AbstractOperationServicePlugIn;
+import kd.bos.entity.plugin.args.BeginOperationTransactionArgs;
+import kd.bos.entity.plugin.args.RollbackOperationArgs;
+import kd.bos.ext.hr.service.query.QueryEntityHelper;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.servicehelper.operation.OperationServiceHelper;
+import kd.bos.servicehelper.operation.SaveServiceHelper;
+import kd.hr.hbp.common.model.AuthorizedOrgResultWithSub;
+import kd.hr.hbp.common.model.OrgSubInfo;
+import kd.sdk.hr.hbp.business.helper.permission.HRPermissionServiceHelper;
+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.exception.ValidationException;
+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.entity.helper.EntityHelper;
+import nckd.jxccl.base.orm.helper.QFilterCommonHelper;
+import nckd.jxccl.opmc.pm.common.PerfManagerFormConstant;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.StringJoiner;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+
+/**
+* 周期生成
+* 实体标识:nckd_cyclegenerate
+* @author W.Y.C
+* @date 2025/11/5 17:10
+* @version 1.0
+*/
+public class CycleGenerateOpPlugin extends AbstractOperationServicePlugIn implements Plugin {
+
+    @Override
+    public void beginOperationTransaction(BeginOperationTransactionArgs e) {
+        //获取待生成新周期人员
+        Map<Long, PerfManagerSaveOpPlugin.PersonPerfInfo> pendingCyclePersonnelMap = getPendingCyclePersonnel();
+        List<Long> perfManagerId = pendingCyclePersonnelMap.values().stream()
+                .map(PerfManagerSaveOpPlugin.PersonPerfInfo::getId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+        //先结束当前周期
+        QueryFieldBuilder queryFieldBuilder = QueryFieldBuilder.create()
+                .add(FormConstant.ID_KEY)
+                .add(PerfManagerFormConstant.NCKD_WHYEND)
+                .add(PerfManagerFormConstant.NCKD_WAGEEXPLAIN)
+                .add(PerfManagerFormConstant.NCKD_THESTATUS)
+                .add(PerfManagerFormConstant.NCKD_ACTENDYEAR)
+                .addIdNumberName(FormConstant.NCKD_PERSON);
+
+        QFilter filter = QFilterCommonHelper.getIdInFilter(perfManagerId);
+        DynamicObject[] dbPerfManagerArray = BusinessDataServiceHelper.load(PerfManagerFormConstant.PERFMANAGER_ENTITYID, queryFieldBuilder.buildSelect(), new QFilter[]{filter});
+        for (DynamicObject dbPerfManager : dbPerfManagerArray) {
+            long personId = dbPerfManager.getDynamicObject(FormConstant.NCKD_PERSON).getLong(FormConstant.ID_KEY);
+            PerfManagerSaveOpPlugin.PersonPerfInfo personPerfInfo = pendingCyclePersonnelMap.get(personId);
+            //结束原因
+            dbPerfManager.set(PerfManagerFormConstant.NCKD_WHYEND, personPerfInfo.getWhyEnd());
+            dbPerfManager.set(PerfManagerFormConstant.NCKD_THESTATUS, "3");
+            //实际结束时间(如果endYear大于当前时间(年)则使用endYear否则使用当前时间(年))
+            dbPerfManager.set(PerfManagerFormConstant.NCKD_ACTENDYEAR,DateUtil.toDate(personPerfInfo.getActEndYear()));
+        }
+        SaveServiceHelper.update(dbPerfManagerArray);
+
+
+        //调用save-op
+        List<DynamicObject> savePerfManager = new ArrayList<>();
+        for (PerfManagerSaveOpPlugin.PersonPerfInfo value : pendingCyclePersonnelMap.values()) {
+            DynamicObject newPerfManager = EntityHelper.newAvailableBasicEntity(PerfManagerFormConstant.PERFMANAGER_ENTITYID);
+            String personName = value.getPerson().getString(FormConstant.NAME_KEY);
+            newPerfManager.set(FormConstant.NCKD_PERSON, value.getPerson());
+            newPerfManager.set(PerfManagerFormConstant.NCKD_BEGINYEAR, DateUtil.toDate(value.getBeginYear()));
+            newPerfManager.set(PerfManagerFormConstant.NCKD_ENDYEAR, DateUtil.toDate(value.getEndYear()));
+            newPerfManager.set(PerfManagerFormConstant.DESCRIPTION_KEY, value.getDescription());
+            newPerfManager.set(PerfManagerFormConstant.CTRLSTRATEGY_KEY, CtrlStrategyEnum.GLOBAL_SHARE.getCtrlStrategy());
+            newPerfManager.set(FormConstant.NAME_KEY, StrFormatter.format("【{}】{}~{}的考核周期",personName,value.getBeginYear().getYear(),value.getEndYear().getYear()));
+            savePerfManager.add(newPerfManager);
+        }
+        if(savePerfManager.isEmpty()){
+            this.operationResult.setShowMessage(true);
+            this.operationResult.setSuccess(false);
+            this.operationResult.setMessage("生成周期完成,本次没有需要生成周期的人员");
+            this.operationResult.setMessageType(1);
+        }else {
+            OperateOption option = OperateOption.create();
+            option.setVariableValue(OperateOptionConst.IGNOREINTERACTION, Boolean.TRUE + "");
+            OperationResult operationResult = OperationServiceHelper.executeOperate(FormConstant.SAVE_OP, PerfManagerFormConstant.PERFMANAGER_ENTITYID, savePerfManager.toArray(new DynamicObject[0]), option);
+
+            if (!operationResult.isSuccess()) {
+                StringJoiner errorMsg = new StringJoiner(StrFormatter.LINE_SEPARATOR);
+                for (IOperateInfo error : operationResult.getAllErrorOrValidateInfo()) {
+                    errorMsg.add(error.getMessage());
+                }
+                throw new ValidationException(errorMsg.toString());
+            }else{
+                //周期生成成功,以下是本次周期生成的人员:
+                this.operationResult.setSuccess(true);
+                StringJoiner msg = new StringJoiner(StrFormatter.LINE_SEPARATOR);
+                msg.add(StrFormatter.format("周期生成成功,本次生成人数【{}】人,以下是本次周期生成的人员:",savePerfManager.size()));
+                AtomicInteger index = new AtomicInteger(1);
+                for (PerfManagerSaveOpPlugin.PersonPerfInfo value : pendingCyclePersonnelMap.values()) {
+                    String personName = value.getPerson().getString(FormConstant.NAME_KEY);
+                    String description = value.getDescription();
+                    msg.add(StrFormatter.format("{}、【{}】{}",index.getAndIncrement(),personName,value.getWhyEnd()));
+                }
+                this.operationResult.setMessage(msg.toString());
+            }
+        }
+
+    }
+    /**
+     * 获取待生成新周期人员
+     * 入职次年生成:新员工入职后的次年,系统自动生成一个三年考评周期。
+     * 上年度绩效结果不合格或基本合格:如果员工上年度绩效结果为"基本合格"或"不合格",系统会重算考核周期,生成新的三年考评周期。
+     * 三年考评周期结束:当一个三年考评周期结束后,次年会自动生成新的三年考评周期。
+     *
+     * @return: java.util.Map<java.lang.Long, nckd.jxccl.opmc.pm.plugin.operate.cycle.PerfManagerSaveOpPlugin.PersonPerfInfo>
+     * @author W.Y.C
+     * @date: 2025/11/06 14:19
+     */
+    public static Map<Long, PerfManagerSaveOpPlugin.PersonPerfInfo> getPendingCyclePersonnel() {
+        LocalDateTime now = DateUtil.beginOfYear(DateUtil.now());
+        LocalDateTime lastDate = now;
+        // LocalDateTime lastDate = DateUtil.minusYears(now, 1);
+        LocalDateTime lastBegin = DateUtil.beginOfYear(lastDate);
+        LocalDateTime lastEnd = DateUtil.endOfYear(lastDate);
+
+        Long currUserId = RequestContext.get().getCurrUserId();
+
+        // 在职并去年入职的人员
+        QFilter newHireFilter = buildNewHireFilter(lastBegin, lastEnd);
+
+        // 只处理权限范围内的人员
+        AuthorizedOrgResultWithSub userAdminOrgWithSub = HRPermissionServiceHelper.getUserAdminOrgsWithSub(
+                currUserId, "nckd_pm", PerfManagerFormConstant.PERFMANAGER_ENTITYID,
+                PermItemConst.ITEM_VIEW, "hrpi_empposorgrel.adminorg", new HashMap<>());
+
+        if (!userAdminOrgWithSub.isHasAllOrgPerm()) {
+            List<Long> orgIds = extractOrgIds(userAdminOrgWithSub.getHasPermOrgsWithSub());
+            newHireFilter.and(String.join(".", FormConstant.HRPI_EMPPOSORGREL, FormConstant.ADMINORG), QCP.in, orgIds);
+        }
+
+        Map<Long, PerfManagerSaveOpPlugin.PersonPerfInfo> personPerfInfoMap = new HashMap<>();
+        DynamicObjectCollection newHirePersonList = queryNewHirePersons(newHireFilter);
+
+        // 构建考核周期查询条件
+        QFilter perfManagerFilter = buildPerfManagerFilter(userAdminOrgWithSub);
+        QueryFieldBuilder perfManagerQueryFieldBuilder = buildPerfManagerQueryFieldBuilder();
+        QueryEntityType perfManagerQueryEntityType = (QueryEntityType) EntityMetadataCache.getDataEntityTypeNoCache("nckd_perfmanager_query");
+
+        Set<Long> personIds = new HashSet<>();
+        try (AlgoContext context = Algo.newContext()) {
+            // 查询权限范围内的考核周期
+            DataSet perfManagerQueryDataSet = QueryEntityHelper.getInstance().getQueryDataSet(
+                    perfManagerQueryEntityType, perfManagerQueryFieldBuilder.buildSelectLowerCase(),
+                    new QFilter[]{perfManagerFilter}, perfManagerQueryFieldBuilder.buildOrder());
+
+            // 收集所有人员ID
+            collectPersonIds(personIds, perfManagerQueryDataSet.copy());
+
+            // 1、去年入职人员生成周期
+            generateCycleForNewHires(personPerfInfoMap, newHirePersonList, personIds, now);
+
+            // 2、上年度绩效结果不合格或基本合格的人员,并且当前周期是最新的
+            handleUnqualifiedLastYearResults(personPerfInfoMap, perfManagerQueryDataSet.copy(),
+                    perfManagerQueryFieldBuilder, lastBegin, now);
+
+            // 3、上年度无绩效结果(周期开始及结束年份含上年)
+            handleMissingLastYearResults(personPerfInfoMap, perfManagerQueryDataSet.copy(),
+                    perfManagerQueryFieldBuilder, lastBegin, now);
+
+            // 4、三年考评周期结束(周期结束年份为去年,不管有没有结果)
+            handleEndedThreeYearCycles(personPerfInfoMap, perfManagerQueryDataSet.copy(),
+                    perfManagerQueryFieldBuilder, lastBegin, now);
+        }
+        return personPerfInfoMap;
+    }
+
+    /**
+     * 构建新入职员工查询条件
+     */
+    private static QFilter buildNewHireFilter(LocalDateTime lastBegin, LocalDateTime lastEnd) {
+        return new QFilter(String.join(".", FormConstant.HRPI_EMPENTREL, FormConstant.ENTRYDATE), QCP.large_equals, lastBegin)
+                .and(String.join(".", FormConstant.HRPI_EMPENTREL, FormConstant.ENTRYDATE), QCP.less_equals, lastEnd)
+                .and(String.join(".", FormConstant.HRPI_EMPENTREL, FormConstant.LABOR_REL_STATUS, FormConstant.IS_HIRED),
+                        QCP.equals, EnableEnum.YES.getCode());
+    }
+
+    /**
+     * 提取组织ID列表
+     */
+    private static List<Long> extractOrgIds(List<OrgSubInfo> orgSubInfos) {
+        return orgSubInfos.stream()
+                .map(OrgSubInfo::getOrgId)
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * 查询新入职员工信息
+     */
+    private static DynamicObjectCollection queryNewHirePersons(QFilter filter) {
+        QueryFieldBuilder queryFieldBuilder = QueryFieldBuilder.create()
+                .addIdNumberName(FormConstant.HRPI_EMPLOYEE);
+        QueryEntityType queryEntityType = (QueryEntityType) EntityMetadataCache.getDataEntityType(SystemQueryConstant.HSPM_ASSIGNMENTQUERY);
+        return QueryEntityHelper.getInstance().getQueryDyoColl(queryEntityType, queryFieldBuilder.buildSelect(), new QFilter[]{filter}, null);
+    }
+
+    /**
+     * 构建考核周期查询条件
+     */
+    private static QFilter buildPerfManagerFilter(AuthorizedOrgResultWithSub userAdminOrgWithSub) {
+        QFilter perfManagerFilter = QFilter.of("1=1");
+        if (!userAdminOrgWithSub.isHasAllOrgPerm()) {
+            List<Long> orgIds = extractOrgIds(userAdminOrgWithSub.getHasPermOrgsWithSub());
+            perfManagerFilter.and(String.join(".", FormConstant.HRPI_EMPPOSORGREL, FormConstant.ADMINORG), QCP.in, orgIds);
+        }
+        return perfManagerFilter;
+    }
+
+    /**
+     * 构建考核周期查询字段
+     */
+    private static QueryFieldBuilder buildPerfManagerQueryFieldBuilder() {
+        return QueryFieldBuilder.create()
+                .add(FormConstant.ID_KEY)
+                .add(PerfManagerFormConstant.NCKD_ISCURRENTNEWEST)
+                .add(PerfManagerFormConstant.NCKD_BEGINYEAR)
+                .add(PerfManagerFormConstant.NCKD_ENDYEAR)
+                .addIdNumberName(FormConstant.NCKD_PERSON)
+                .addGroup(new String[]{PerfManagerFormConstant.PERFMANAGERENTRY}, FormConstant.ID_KEY, PerfManagerFormConstant.NCKD_APPRAISALYEAR)
+                .addIdNumberName(PerfManagerFormConstant.PERFMANAGERENTRY, PerfManagerFormConstant.NCKD_APPRAISALRESULT);
+    }
+
+    /**
+     * 收集所有人员ID
+     */
+    private static void collectPersonIds(Set<Long> personIds, DataSet dataSet) {
+        while (dataSet.hasNext()) {
+            Row next = dataSet.next();
+            Long personId = next.getLong(String.join(".", FormConstant.NCKD_PERSON, FormConstant.ID_KEY));
+            personIds.add(personId);
+        }
+    }
+
+    /**
+     * 为新入职员工生成考核周期
+     */
+    private static void generateCycleForNewHires(Map<Long, PerfManagerSaveOpPlugin.PersonPerfInfo> personPerfInfoMap,
+                                                 DynamicObjectCollection newHirePersonList,
+                                                 Set<Long> personIds,
+                                                 LocalDateTime now) {
+        for (DynamicObject person : newHirePersonList) {
+            long personId = person.getLong(String.join(".", FormConstant.HRPI_EMPLOYEE, FormConstant.ID_KEY));
+            // 判断是否已有周期,如果有则不生成
+            if (!personIds.contains(personId)) {
+                String personName = person.getString(String.join(".", FormConstant.HRPI_EMPLOYEE, FormConstant.NAME_KEY));
+                DynamicObject cyclePerson = EntityHelper.newEntity(FormConstant.HRPI_EMPLOYEE);
+                cyclePerson.set(FormConstant.ID_KEY, personId);
+                cyclePerson.set(FormConstant.NAME_KEY, personName);
+                PerfManagerSaveOpPlugin.PersonPerfInfo personPerfInfo = new PerfManagerSaveOpPlugin.PersonPerfInfo(
+                        cyclePerson, DateUtil.toDate(now), DateUtil.toDate(DateUtil.addYears(now, 2)), "周期生成(新入职)");
+                personPerfInfo.setWhyEnd("新入职员工");
+                personPerfInfoMap.put(personId, personPerfInfo);
+            }
+        }
+    }
+
+    /**
+     * 处理上年度绩效不合格或基本合格的人员
+     */
+    private static void handleUnqualifiedLastYearResults(Map<Long, PerfManagerSaveOpPlugin.PersonPerfInfo> personPerfInfoMap,
+                                                         DataSet perfManagerQueryDataSet,
+                                                         QueryFieldBuilder perfManagerQueryFieldBuilder,
+                                                         LocalDateTime lastBegin,
+                                                         LocalDateTime now) {
+        DataSet lastYearFailFilter = perfManagerQueryDataSet.copy().filter(
+                        "perfmanagerentry.nckd_appraisalresult.number in ('" + AppraisalResultEnum.UN_QUALIFIED.getCode() + "','" +
+                                AppraisalResultEnum.BASICALLY_QUALIFIED.getCode() + "') " +
+                                "and YEAR(perfmanagerentry.nckd_appraisalyear) = " + lastBegin.getYear() +
+                                " and nckd_iscurrentnewest = true")
+                .select(perfManagerQueryFieldBuilder.buildSelect());
+
+        while (lastYearFailFilter.hasNext()) {
+            Row next = lastYearFailFilter.next();
+            Long id = next.getLong(FormConstant.ID_KEY);
+            Long personId = next.getLong(String.join(".", FormConstant.NCKD_PERSON, FormConstant.ID_KEY));
+            String personName = next.getString(String.join(".", FormConstant.NCKD_PERSON, FormConstant.NAME_KEY));
+            String appraisalResult = next.getString(String.join(".", PerfManagerFormConstant.PERFMANAGERENTRY,
+                    PerfManagerFormConstant.NCKD_APPRAISALRESULT, FormConstant.NUMBER_KEY));
+            Date endYear = next.getDate(PerfManagerFormConstant.NCKD_ENDYEAR);
+
+            DynamicObject person = EntityHelper.newEntity(FormConstant.HRPI_EMPLOYEE);
+            person.set(FormConstant.ID_KEY, personId);
+            person.set(FormConstant.NAME_KEY, personName);
+
+            PerfManagerSaveOpPlugin.PersonPerfInfo personPerfInfo = new PerfManagerSaveOpPlugin.PersonPerfInfo(
+                    person, DateUtil.toDate(now), DateUtil.toDate(DateUtil.addYears(now, 2)), "周期重生成");
+            personPerfInfo.setId(id);
+
+            LocalDateTime endYearLocalDateTime = DateUtil.toLocalDateTime(endYear);
+            LocalDateTime actualEndYear = endYearLocalDateTime.getYear() > now.getYear() ? now : endYearLocalDateTime;
+            personPerfInfo.setActEndYear(actualEndYear);
+
+            if (AppraisalResultEnum.UN_QUALIFIED.getCode().equalsIgnoreCase(appraisalResult)) {
+                personPerfInfo.setWhyEnd("上年度绩效【不合格】");
+            } else {
+                personPerfInfo.setWhyEnd("上年度绩效【基本合格】");
+            }
+
+            personPerfInfoMap.put(personId, personPerfInfo);
+        }
+    }
+
+    /**
+     * 处理上年度无绩效结果的人员
+     */
+    private static void handleMissingLastYearResults(Map<Long, PerfManagerSaveOpPlugin.PersonPerfInfo> personPerfInfoMap,
+                                                     DataSet perfManagerQueryDataSet,
+                                                     QueryFieldBuilder perfManagerQueryFieldBuilder,
+                                                     LocalDateTime lastBegin,
+                                                     LocalDateTime now) {
+        DataSet lastYearNoPerfFilter = perfManagerQueryDataSet.copy().filter(
+                        "YEAR(NCKD_BEGINYEAR) <= " + lastBegin.getYear() +
+                                " AND YEAR(NCKD_ENDYEAR) >= " + lastBegin.getYear() +
+                                " and nckd_iscurrentnewest = true ")
+                .select(perfManagerQueryFieldBuilder.buildSelect());
+
+        // 临时存储人员对应的考核结果。key:personId,Value:考核结果编码
+        Map<Long, String> personIdAndAppraisalYearMap = new HashMap<>();
+        Map<Long, String> personIdAndPersonNameMap = new HashMap<>();
+        Map<Long, Long> personIdAndIdMap = new HashMap<>();
+        Map<Long, Date> personIdAndEndDateMap = new HashMap<>();
+
+        while (lastYearNoPerfFilter.hasNext()) {
+            Row next = lastYearNoPerfFilter.next();
+            Long id = next.getLong(FormConstant.ID_KEY);
+            Long personId = next.getLong(String.join(".", FormConstant.NCKD_PERSON, FormConstant.ID_KEY));
+            String personName = next.getString(String.join(".", FormConstant.NCKD_PERSON, FormConstant.NAME_KEY));
+            String appraisalResultNumber = next.getString(String.join(".", PerfManagerFormConstant.PERFMANAGERENTRY,
+                    PerfManagerFormConstant.NCKD_APPRAISALRESULT, FormConstant.NUMBER_KEY));
+            Date appraisalYear = next.getDate(String.join(".", PerfManagerFormConstant.PERFMANAGERENTRY,
+                    PerfManagerFormConstant.NCKD_APPRAISALYEAR));
+            Date endYear = next.getDate(PerfManagerFormConstant.NCKD_ENDYEAR);
+
+            if (appraisalYear == null || lastBegin.getYear() == DateUtil.getYear(appraisalYear)) {
+                // 没有绩效记录或等于上年度则记录到临时变量
+                personIdAndAppraisalYearMap.put(personId, appraisalResultNumber);
+                personIdAndPersonNameMap.put(personId, personName);
+                personIdAndIdMap.put(personId, id);
+                personIdAndEndDateMap.put(personId, endYear);
+            }
+        }
+
+        // 找出上年度绩效记录为空或等于"无"的人员
+        List<Long> filteredPersonIds = personIdAndAppraisalYearMap.entrySet().stream()
+                .filter(entry -> AppraisalResultEnum.NONE.getCode().equals(entry.getValue()) || entry.getValue() == null)
+                .map(Map.Entry::getKey)
+                .collect(Collectors.toList());
+
+        for (Long filteredPersonId : filteredPersonIds) {
+            String personName = personIdAndPersonNameMap.get(filteredPersonId);
+            DynamicObject person = EntityHelper.newEntity(FormConstant.HRPI_EMPLOYEE);
+            person.set(FormConstant.ID_KEY, filteredPersonId);
+            person.set(FormConstant.NAME_KEY, personName);
+
+            PerfManagerSaveOpPlugin.PersonPerfInfo personPerfInfo = new PerfManagerSaveOpPlugin.PersonPerfInfo(
+                    person, DateUtil.toDate(now), DateUtil.toDate(DateUtil.addYears(now, 2)), "周期重生成");
+            personPerfInfo.setId(personIdAndIdMap.get(filteredPersonId));
+            personPerfInfo.setWhyEnd("上年度无绩效结果");
+
+            LocalDateTime endYearLocalDateTime = DateUtil.toLocalDateTime(personIdAndEndDateMap.get(filteredPersonId));
+            LocalDateTime actualEndYear = endYearLocalDateTime.getYear() > now.getYear() ? now : endYearLocalDateTime;
+            personPerfInfo.setActEndYear(actualEndYear);
+
+            personPerfInfoMap.put(filteredPersonId, personPerfInfo);
+        }
+    }
+
+    /**
+     * 处理三年考评周期结束的人员
+     */
+    private static void handleEndedThreeYearCycles(Map<Long, PerfManagerSaveOpPlugin.PersonPerfInfo> personPerfInfoMap,
+                                                   DataSet perfManagerQueryDataSet,
+                                                   QueryFieldBuilder perfManagerQueryFieldBuilder,
+                                                   LocalDateTime lastBegin,
+                                                   LocalDateTime now) {
+        DataSet yearEvalEndFilter = perfManagerQueryDataSet.copy().filter(
+                        "YEAR(nckd_endyear) = " + lastBegin.getYear() + " and nckd_iscurrentnewest = true ")
+                .select(perfManagerQueryFieldBuilder.buildSelect());
+
+        while (yearEvalEndFilter.hasNext()) {
+            Row next = yearEvalEndFilter.next();
+            Long id = next.getLong(FormConstant.ID_KEY);
+            Long personId = next.getLong(String.join(".", FormConstant.NCKD_PERSON, FormConstant.ID_KEY));
+            String personName = next.getString(String.join(".", FormConstant.NCKD_PERSON, FormConstant.NAME_KEY));
+            Date endYear = next.getDate(PerfManagerFormConstant.NCKD_ENDYEAR);
+
+            if (personPerfInfoMap.get(personId) == null) {
+                DynamicObject person = EntityHelper.newEntity(FormConstant.HRPI_EMPLOYEE);
+                person.set(FormConstant.ID_KEY, personId);
+                person.set(FormConstant.NAME_KEY, personName);
+
+                PerfManagerSaveOpPlugin.PersonPerfInfo personPerfInfo = new PerfManagerSaveOpPlugin.PersonPerfInfo(
+                        person, DateUtil.toDate(now), DateUtil.toDate(DateUtil.addYears(now, 2)), "周期重生成");
+                personPerfInfo.setId(id);
+                personPerfInfo.setWhyEnd("三年考评周期结束(周期生成)");
+
+                LocalDateTime endYearLocalDateTime = DateUtil.toLocalDateTime(endYear);
+                LocalDateTime actualEndYear = endYearLocalDateTime.getYear() > now.getYear() ? now : endYearLocalDateTime;
+                personPerfInfo.setActEndYear(actualEndYear);
+
+                personPerfInfoMap.put(personId, personPerfInfo);
+            }
+        }
+    }
+
+}

+ 73 - 0
code/opmc/nckd-jxccl-opmc/src/main/java/nckd/jxccl/opmc/pm/plugin/operate/cycle/PerfManagerDeleteOpPlugin.java

@@ -0,0 +1,73 @@
+package nckd.jxccl.opmc.pm.plugin.operate.cycle;
+
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.entity.ExtendedDataEntity;
+import kd.bos.entity.plugin.AbstractOperationServicePlugIn;
+import kd.bos.entity.plugin.AddValidatorsEventArgs;
+import kd.bos.entity.plugin.PreparePropertysEventArgs;
+import kd.bos.entity.plugin.args.BeginOperationTransactionArgs;
+import kd.bos.entity.validate.AbstractValidator;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.sdk.plugin.Plugin;
+import nckd.jxccl.base.common.constant.FormConstant;
+import nckd.jxccl.base.common.utils.StrFormatter;
+import nckd.jxccl.opmc.pm.common.PerfManagerFormConstant;
+import nckd.jxccl.opmc.pm.helper.PerfManagerHelper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+* 考核周期删除OP
+* 实体标识:nckd_perfmanager
+* @author W.Y.C
+* @date 2025/11/5 15:46
+* @version 1.0
+*/
+public class PerfManagerDeleteOpPlugin extends AbstractOperationServicePlugIn implements Plugin {
+
+    @Override
+    public void onPreparePropertys(PreparePropertysEventArgs e) {
+        e.getFieldKeys().add(FormConstant.NCKD_PERSON);
+        e.getFieldKeys().add(PerfManagerFormConstant.NCKD_THESTATUS);
+        e.getFieldKeys().add(PerfManagerFormConstant.NCKD_ISCURRENTNEWEST);
+    }
+
+    @Override
+    public void onAddValidators(AddValidatorsEventArgs e) {
+        e.addValidator(new AbstractValidator() {
+            @Override
+            public void validate() {
+                for (ExtendedDataEntity rowDataEntity : getDataEntities()) {
+                    DynamicObject data = rowDataEntity.getDataEntity();
+                    boolean isCurrentNewest = data.getBoolean(PerfManagerFormConstant.NCKD_ISCURRENTNEWEST);
+                    if(!isCurrentNewest){
+                        addMessage(rowDataEntity,"不能删除历史周期,只能删除最新考核周期");
+                    }
+                    String theStatus = data.getString(PerfManagerFormConstant.NCKD_THESTATUS);
+                    if(!"1".equalsIgnoreCase(theStatus)){
+                        addMessage(rowDataEntity, StrFormatter.format("考核周期状态【{}】,不能删除","2".equals(theStatus) ? "已锁定":"已结束"));
+                    }
+                }
+            }
+        });
+    }
+
+    @Override
+    public void beginOperationTransaction(BeginOperationTransactionArgs e) {
+
+        List<Long> personIds = new ArrayList<>();
+        List<Long> ids = new ArrayList<>();
+        for (DynamicObject data : e.getDataEntities()) {
+            DynamicObject person = data.getDynamicObject(FormConstant.NCKD_PERSON);
+            long personId = person.getLong(FormConstant.ID_KEY);
+            personIds.add(personId);
+            ids.add(data.getLong(FormConstant.ID_KEY));
+        }
+        //将人员所有周期标记为非最新
+        PerfManagerHelper.markAsNotCurrentNewest(personIds.toArray(new Long[0]));
+        //将当前人员最新周期(周期开始时间最近的一条)标记为最新的
+        PerfManagerHelper.markAsCurrentNewest(personIds,new QFilter(FormConstant.ID_KEY, QCP.not_in,ids));
+    }
+}

+ 406 - 0
code/opmc/nckd-jxccl-opmc/src/main/java/nckd/jxccl/opmc/pm/plugin/operate/cycle/PerfManagerSaveOpPlugin.java

@@ -0,0 +1,406 @@
+package nckd.jxccl.opmc.pm.plugin.operate.cycle;
+
+import kd.bos.common.enums.EnableEnum;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.entity.ExtendedDataEntity;
+import kd.bos.entity.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.plugin.args.BeginOperationTransactionArgs;
+import kd.bos.entity.plugin.args.EndOperationTransactionArgs;
+import kd.bos.entity.validate.AbstractValidator;
+import kd.bos.entity.validate.ErrorLevel;
+import kd.bos.form.MessageBoxResult;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.QueryServiceHelper;
+import kd.bos.servicehelper.operation.SaveServiceHelper;
+import kd.sdk.plugin.Plugin;
+import nckd.jxccl.base.common.constant.FormConstant;
+import nckd.jxccl.base.common.utils.DateUtil;
+import nckd.jxccl.base.common.utils.QueryFieldBuilder;
+import nckd.jxccl.base.common.utils.StrFormatter;
+import nckd.jxccl.opmc.pm.common.PerfManagerFormConstant;
+import nckd.jxccl.opmc.pm.helper.PerfManagerHelper;
+import org.apache.commons.lang3.StringUtils;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.StringJoiner;
+import java.util.stream.Collectors;
+
+/**
+ * 考核周期保存OP
+ * 实体标识:nckd_perfmanager
+ * @author W.Y.C
+ * @date 2025/11/4 12:29
+ * @version 1.0
+ */
+public class PerfManagerSaveOpPlugin extends AbstractOperationServicePlugIn implements Plugin {
+
+    private List<PersonPerfInfo> personPerfInfos = new ArrayList<>();
+
+    private Map<Long, DynamicObject> lastPerfManagerMap;
+
+    private  List<Long> personIds;
+    private  List<Long> ids = new ArrayList<>();
+
+    private final static String INTERACTION_SPONORE = PerfManagerSaveOpPlugin.class.getName();
+
+
+    @Override
+    public void onAddValidators(AddValidatorsEventArgs e) {
+        e.addValidator(new AbstractValidator() {
+            @Override
+            public void validate() {
+                //第一个循环先获取表单数据,这里需要兼容单人和批量的数据包
+                for (ExtendedDataEntity rowDataEntity : getDataEntities()) {
+                    DynamicObject data = rowDataEntity.getDataEntity();
+                    int dataEntityIndex = rowDataEntity.getDataEntityIndex();
+                    long id = data.getLong(FormConstant.ID_KEY);
+
+                    DynamicObject person = data.getDynamicObject(FormConstant.NCKD_PERSON);
+                    Date beginYear = data.getDate(PerfManagerFormConstant.NCKD_BEGINYEAR);
+                    Date endYear = data.getDate(PerfManagerFormConstant.NCKD_ENDYEAR);
+                    String description = data.getString(FormConstant.DESCRIPTION_KEY);
+                    DynamicObjectCollection perfManagerEntry = data.getDynamicObjectCollection(PerfManagerFormConstant.NCKD_PERFMANAGERENTRY);
+                    PersonPerfInfo personPerfInfo = new PersonPerfInfo(person, beginYear, endYear, description, dataEntityIndex, perfManagerEntry);
+                    if(id > 0){
+                        personPerfInfo.setId(id);
+                        ids.add(id);
+                    }
+                    personPerfInfos.add(personPerfInfo);
+                }
+
+                QueryFieldBuilder queryFieldBuilder = QueryFieldBuilder.create()
+                        .addIdNumberName(FormConstant.NCKD_PERSON)
+                        .add(PerfManagerFormConstant.NCKD_BEGINYEAR)
+                        .add(PerfManagerFormConstant.NCKD_ENDYEAR);
+                personIds = personPerfInfos.stream()
+                        .map(personPerfInfo -> personPerfInfo.getPerson().getLong(FormConstant.ID_KEY))
+                        .collect(Collectors.toList());
+                QFilter filter = new QFilter(FormConstant.NCKD_PERSON, QCP.in, personIds)
+                        .and(PerfManagerFormConstant.NCKD_THESTATUS,QCP.equals,"1");
+                if(!ids.isEmpty()){
+                    filter = filter.and(new QFilter(FormConstant.ID_KEY, QCP.not_in, ids));
+                }
+                DynamicObjectCollection query = QueryServiceHelper.query(PerfManagerFormConstant.PERFMANAGER_ENTITYID, queryFieldBuilder.buildSelect(), new QFilter[]{filter});
+
+                //开始校验 - 优化后的代码
+                Map<Long, List<DynamicObject>> groupedQueryResults = query.stream()
+                        .collect(Collectors.groupingBy(
+                                dynamicObject -> dynamicObject.getLong(String.join(".", FormConstant.NCKD_PERSON, FormConstant.ID_KEY))
+                        ));
+
+                for (PersonPerfInfo personPerfInfo : personPerfInfos) {
+
+                    long personId = personPerfInfo.getPerson().getLong(FormConstant.ID_KEY);
+                    LocalDateTime beginYear = personPerfInfo.getBeginYear();
+                    LocalDateTime endYear = personPerfInfo.getEndYear();
+
+                    // 判断beginYear和endYear是不是间隔3年,例如:开始2025年,结束必须为2027年
+                    if (beginYear != null && endYear != null) {
+                        int beginYearValue = beginYear.getYear();
+                        int endYearValue = endYear.getYear();
+                        if (endYearValue - beginYearValue != 2) {
+                            addFatalErrorMessage(getDataEntities()[personPerfInfo.getDataEntityIndex()],
+                                    StrFormatter.format("周期开始年份【{}】与结束年份【{}】必须间隔3年,请检查!",
+                                            beginYearValue, endYearValue));
+                        }
+                    }
+
+
+                    //校验是否存在相同周期开始年的记录 begin
+                    List<DynamicObject> personRecords = groupedQueryResults.getOrDefault(personId, Collections.emptyList());
+                    for (DynamicObject dynamicObject : personRecords) {
+                        LocalDateTime dbBeginYear = DateUtil.toLocalDateTime(dynamicObject.getDate(PerfManagerFormConstant.NCKD_BEGINYEAR));
+                        //判断beginYear和dbBeginYear的年份是否相同
+                        if (isSameYear(beginYear, dbBeginYear)) {
+                            addFatalErrorMessage(getDataEntities()[personPerfInfo.getDataEntityIndex()],
+                                    StrFormatter.format("人员【{}】已经存在周期开始年份【{}】的周期,无需进行创建。",
+                                            personPerfInfo.getPerson().getString(FormConstant.NAME_KEY),
+                                            beginYear.getYear()));
+                        }
+                    }
+                    //校验是否存在相同周期开始年的记录 end
+                    DynamicObjectCollection entrys = personPerfInfo.getEntrys();
+                    if(entrys != null) {
+                        List<Date> dateList = entrys.stream().map(entry -> entry.getDate(PerfManagerFormConstant.NCKD_APPRAISALYEAR)).collect(Collectors.toList());
+                        if (beginYear != null && endYear != null) {
+                            // 校验考核年份是否在周期内
+                            List<String> outOfRangeYears = entrys.stream()
+                                    .map(entry -> entry.getDate(PerfManagerFormConstant.NCKD_APPRAISALYEAR))
+                                    .filter(Objects::nonNull)
+                                    .filter(date -> !DateUtil.isInRange(DateUtil.toLocalDateTime(date), beginYear, endYear))
+                                    .map(date -> String.valueOf(DateUtil.getYear(DateUtil.toLocalDateTime(date))))
+                                    .distinct()
+                                    .collect(Collectors.toList());
+
+                            if (!outOfRangeYears.isEmpty()) {
+                                String outOfRangeYearsStr = String.join(",", outOfRangeYears);
+                                addFatalErrorMessage(getDataEntities()[personPerfInfo.getDataEntityIndex()],
+                                        StrFormatter.format("考评年份【{}】不在周期范围内,请检查!", outOfRangeYearsStr));
+                            }
+                        }
+                        // 校验考核年份是否重复
+                        List<String> duplicateYears = getDuplicateYears(dateList);
+                        if (!duplicateYears.isEmpty()) {
+                            String duplicateYearsStr = String.join(",", duplicateYears);
+                            addFatalErrorMessage(getDataEntities()[personPerfInfo.getDataEntityIndex()],
+                                    StrFormatter.format("考评结果存在重复的考核年份【{}】,请检查!", duplicateYearsStr));
+                        }
+                    }
+
+                }
+            }
+
+            private boolean isSameYear(LocalDateTime date1, LocalDateTime date2) {
+                return date1 != null && date2 != null && date1.getYear() == date2.getYear();
+            }
+        });
+    }
+
+    @Override
+    public void beforeExecuteOperationTransaction(BeforeOperationArgs e) {
+        //最新上一考核周期
+        QFilter filter = null;
+        if(!ids.isEmpty()) {
+            filter = new QFilter(FormConstant.ID_KEY, QCP.not_in, ids);
+        }
+        List<DynamicObject> newestPerfManagerByPerson = PerfManagerHelper.getNewestPerfManagerByPersonAndBeginYear(personIds,filter);
+        lastPerfManagerMap = newestPerfManagerByPerson.stream()
+                .collect(Collectors.toMap(
+                        perfManager -> perfManager.getDynamicObject(FormConstant.NCKD_PERSON).getLong(FormConstant.ID_KEY),
+                        perfManager -> perfManager,
+                        (existing, replacement) -> existing
+                ));
+
+        String ignoreInteraction = this.getOption().getVariableValue(OperateOptionConst.IGNOREINTERACTION, "");
+        if(!"true".equalsIgnoreCase(ignoreInteraction)){
+            personIds = personPerfInfos.stream()
+                    .map(personPerfInfo -> personPerfInfo.getPerson().getLong(FormConstant.ID_KEY))
+                    .collect(Collectors.toList());
+
+            StringJoiner confirMmsg = new StringJoiner(",");
+            for (DynamicObject lastPerfManager : lastPerfManagerMap.values()) {
+                String personName = lastPerfManager.getDynamicObject(FormConstant.NCKD_PERSON).getString(FormConstant.NAME_KEY);
+                LocalDateTime beginYear = DateUtil.toLocalDateTime(lastPerfManager.getDate(PerfManagerFormConstant.NCKD_BEGINYEAR));
+                LocalDateTime endYear = DateUtil.toLocalDateTime(lastPerfManager.getDate(PerfManagerFormConstant.NCKD_ENDYEAR));
+                confirMmsg.add(StrFormatter.format("{}(周期:{}~{})", personName, beginYear.getYear(), endYear.getYear()));
+            }
+            if(!lastPerfManagerMap.isEmpty()){
+                e.cancel = !this.showInteractionMessage(confirMmsg.toString());
+            }
+        }
+
+    }
+    @Override
+    public void beginOperationTransaction(BeginOperationTransactionArgs e) {
+        //事务开始之后将其他考核周期设置为非最新
+        PerfManagerHelper.markAsNotCurrentNewest(personIds.toArray(new Long[0]));
+
+        for (DynamicObject dataEntity : e.getDataEntities()) {
+            DynamicObject person = dataEntity.getDynamicObject(FormConstant.NCKD_PERSON);
+            LocalDateTime beginYear = DateUtil.toLocalDateTime(dataEntity.getDate(PerfManagerFormConstant.NCKD_BEGINYEAR));
+            LocalDateTime endYear = DateUtil.toLocalDateTime(dataEntity.getDate(PerfManagerFormConstant.NCKD_ENDYEAR));
+            long personId = person.getLong(FormConstant.ID_KEY);
+            String personName = person.getString(FormConstant.NAME_KEY);
+            dataEntity.set(PerfManagerFormConstant.NCKD_THESTATUS,EnableEnum.YES.getCode());
+            // dataEntity.set(FormConstant.NCKD_ISCURRENTNEWEST, EnableEnum.YES.getCode());
+            String name = dataEntity.getString(FormConstant.NAME_KEY);
+            if(StringUtils.isBlank(name)){
+                dataEntity.set(FormConstant.NAME_KEY, StrFormatter.format("【{}】{}~{}的考核周期",personName,beginYear.getYear(),endYear.getYear()));
+            }
+            //上一周期标记为“已结束”并设置“实际结束时间” begin
+            DynamicObject lastPerfManager = lastPerfManagerMap.get(personId);
+            if(lastPerfManager != null && "1".equalsIgnoreCase(lastPerfManager.getString(PerfManagerFormConstant.NCKD_THESTATUS))) {
+                lastPerfManager.set(PerfManagerFormConstant.NCKD_THESTATUS, "3");
+                LocalDateTime localDateTime = DateUtil.minusYears(beginYear, 1);
+                lastPerfManager.set(PerfManagerFormConstant.NCKD_ACTENDYEAR, DateUtil.toDate(localDateTime));
+                dataEntity.set(PerfManagerFormConstant.NCKD_LASTPERFMANAGER, lastPerfManagerMap.get(personId));
+            }
+            //上一周期标记为“已结束”并设置“实际结束时间” end
+        }
+
+
+        DynamicObject[] lastPerfManagerArray = lastPerfManagerMap.values().toArray(new DynamicObject[0]);
+        SaveServiceHelper.update(lastPerfManagerArray);
+    }
+
+    @Override
+    public void endOperationTransaction(EndOperationTransactionArgs e) {
+        PerfManagerHelper.markAsCurrentNewest(personIds,null);
+    }
+
+    private boolean showInteractionMessage(String confirMmsg) {
+        //交互式操作提示
+        // 检查是否为回调:避免死循环
+        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("检测到本次新增的人员中存在【未结束】的考核周期,新周期新增成功后会自动结束上一周期。{}", confirMmsg));
+        errorInfo.setLevel(ErrorLevel.Warning);
+
+        Map<String, String> custInfos = new HashMap<>();
+        custInfos.put("source", "business validation");
+        custInfos.put("validator", "SampleValidator");
+        errorInfo.setErrorCustInfos(custInfos);
+
+        interactionContext.addOperateInfo(errorInfo);
+        throw new KDInteractionException(INTERACTION_SPONORE, interactionContext);
+    }
+
+    /**
+     * 获取日期列表中的重复年份
+     *
+     * @param dateList 日期列表
+     * @return 重复年份的字符串列表
+     */
+    public static List<String> getDuplicateYears(List<Date> dateList) {
+        if (dateList == null || dateList.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        Map<Integer, Long> yearCountMap = dateList.stream()
+                .filter(Objects::nonNull)
+                .map(date -> DateUtil.getYear(DateUtil.toLocalDateTime(date)))
+                .collect(Collectors.groupingBy(year -> year, Collectors.counting()));
+
+        return yearCountMap.entrySet().stream()
+                .filter(entry -> entry.getValue() > 1)
+                .map(entry -> String.valueOf(entry.getKey()))
+                .collect(Collectors.toList());
+    }
+
+    public static class PersonPerfInfo {
+        private  Long id;
+        private final DynamicObject person;
+        private final LocalDateTime beginYear;
+        private final LocalDateTime endYear;
+        private final String description;
+        private int dataEntityIndex;
+        private DynamicObjectCollection entrys;
+        private DynamicObject useorg;
+        private DynamicObject org;
+        private DynamicObject createorg;
+        private String whyEnd;
+        private LocalDateTime actEndYear;
+
+        public PersonPerfInfo(DynamicObject person, Date beginYear, Date endYear, String description) {
+            this.person = person;
+            this.beginYear = DateUtil.toLocalDateTime(beginYear);
+            this.endYear = DateUtil.toLocalDateTime(endYear);
+            this.description = description;
+        }
+
+        public PersonPerfInfo(DynamicObject person, Date beginYear, Date endYear, String description, int dataEntityIndex) {
+            this.person = person;
+            this.beginYear = DateUtil.toLocalDateTime(beginYear);
+            this.endYear = DateUtil.toLocalDateTime(endYear);
+            this.description = description;
+            this.dataEntityIndex = dataEntityIndex;
+        }
+
+        public PersonPerfInfo(DynamicObject person, Date beginYear, Date endYear, String description, int dataEntityIndex, DynamicObjectCollection entrys) {
+            this.person = person;
+            this.beginYear = DateUtil.toLocalDateTime(beginYear);
+            this.endYear = DateUtil.toLocalDateTime(endYear);
+            this.description = description;
+            this.dataEntityIndex = dataEntityIndex;
+            this.entrys = entrys;
+        }
+
+        public LocalDateTime getBeginYear() {
+            return beginYear;
+        }
+
+        public int getDataEntityIndex() {
+            return dataEntityIndex;
+        }
+
+        public String getDescription() {
+            return description;
+        }
+
+        public LocalDateTime getEndYear() {
+            return endYear;
+        }
+
+        public DynamicObject getPerson() {
+            return person;
+        }
+
+        public DynamicObjectCollection getEntrys() {
+            return entrys;
+        }
+
+        public DynamicObject getCreateorg() {
+            return createorg;
+        }
+
+        public void setCreateorg(DynamicObject createorg) {
+            this.createorg = createorg;
+        }
+
+        public DynamicObject getOrg() {
+            return org;
+        }
+
+        public void setOrg(DynamicObject org) {
+            this.org = org;
+        }
+
+        public DynamicObject getUseorg() {
+            return useorg;
+        }
+
+        public void setUseorg(DynamicObject useorg) {
+            this.useorg = useorg;
+        }
+
+        public Long getId() {
+            return id;
+        }
+        public void setId(Long id) {
+            this.id = id;
+        }
+
+        public String getWhyEnd() {
+            return whyEnd;
+        }
+
+        public void setWhyEnd(String whyEnd) {
+            this.whyEnd = whyEnd;
+        }
+
+        public LocalDateTime getActEndYear() {
+            return actEndYear;
+        }
+
+        public void setActEndYear(LocalDateTime actEndYear) {
+            this.actEndYear = actEndYear;
+        }
+    }
+}