ソースを参照

feat(opmc): 实现年度绩效结果明细报表及考核周期优化

- 新增年度绩效结果明细报表取数插件,支持近5年数据查询
- 优化考核周期保存逻辑,增加周期重叠校验功能
- 完善考核周期交互提示,精确显示待结束的历史周期信息
- 改进考核周期更新策略,支持按人员和开始时间精准匹配
- 增加考核周期实际结束时间计算逻辑,确保周期连续性
- 更新绩效管理常量定义,补充年度和实体标识相关字段
-重构批量评估周期表单插件,提升考核周期处理准确性
- 移除冗余的打卡同步任务代码,精简项目结构
wyc 1 週間 前
コミット
e2a693f980

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

@@ -131,7 +131,11 @@ public class PerfRankMgmtSaveOpPlugin extends AbstractOperationServicePlugIn imp
                         perfManagerEntry.set(PerfRankMgmtConstant.NCKD_ISALLRANKSOURCE, EnableEnum.YES.getCode());
                     }
                 }
-                OperationResult operationResult = SaveServiceHelper.saveOperate(FormConstant.SAVE_OP, PerfRankMgmtConstant.PERFMANAGER_ENTITYID, perfManagerArray, OperateOption.create());
+                OperateOption option = OperateOption.create();
+                option.setVariableValue(OperateOptionConst.IGNOREINTERACTION, Boolean.TRUE+"");
+                //告诉OP此次操作类型
+                option.setVariableValue("isUpdate", Boolean.TRUE+"");
+                OperationResult operationResult = SaveServiceHelper.saveOperate(FormConstant.SAVE_OP, PerfRankMgmtConstant.PERFMANAGER_ENTITYID, perfManagerArray, option);
                 if (!operationResult.isSuccess()) {
                     StringJoiner errorMsgJoiner = new StringJoiner(StrFormatter.LINE_SEPARATOR);
                     for (IOperateInfo error : operationResult.getAllErrorOrValidateInfo()) {

+ 5 - 0
code/opmc/nckd-jxccl-opmc/src/main/java/nckd/jxccl/opmc/pm/common/PerfManagerFormConstant.java

@@ -53,4 +53,9 @@ public class PerfManagerFormConstant extends FormConstant {
 
     /** 批量新增考核周期(弹窗)-实体标识 */
     public static final String BATCHEVALCYCLE_ENTITYID = "nckd_batchevalcycle";
+
+    /** 年度 */
+    public static final String NCKD_THEYEAR = "nckd_theyear";
+    /** 年度绩效排名管理-实体标识 */
+    public static final String NCKD_PERFRANKMGMT_ENTITYID = "nckd_perfrankmgmt";
 }

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

@@ -28,6 +28,76 @@ import java.util.Map;
 */
 public class PerfManagerHelper {
 
+
+    /**
+     * 获取当前人员所有考核周期
+     * @param personIds
+     * @param otherFilter
+     * @return: kd.bos.dataentity.entity.DynamicObject[]
+     * @author W.Y.C
+     * @date: 2025/11/13 22:16
+     */
+    public static DynamicObject[] getByPersonId(Collection<Long> personIds,QFilter otherFilter) {
+        QueryFieldBuilder queryFieldBuilder = QueryFieldBuilder.create()
+                .add(FormConstant.ID_KEY)
+                .addIdNumberName(FormConstant.NCKD_EMPPOSORGREL)
+                .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(PerfManagerFormConstant.NCKD_PERFMANAGERENTRY,PerfManagerFormConstant.NCKD_APPRAISALRESULT)
+                .addGroup(new String[]{PerfManagerFormConstant.NCKD_PERFMANAGERENTRY},PerfManagerFormConstant.NCKD_APPRAISALRESULT,PerfManagerFormConstant.NCKD_APPRAISALYEAR,PerfManagerFormConstant.NCKD_ISALLRANKSOURCE)
+                .orderDesc(PerfManagerFormConstant.NCKD_BEGINYEAR);
+
+        QFilter filter = 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 beginYear
+     * @param personIds
+     * @param otherFilter
+     * @return: kd.bos.dataentity.entity.DynamicObject[]
+     * @author W.Y.C
+     * @date: 2025/11/13 22:16
+     */
+    public static DynamicObject[] getBeforeBeginYear(Date beginYear,Collection<Long> personIds,QFilter otherFilter) {
+        QueryFieldBuilder queryFieldBuilder = QueryFieldBuilder.create()
+                .add(FormConstant.ID_KEY)
+                .addIdNumberName(FormConstant.NCKD_EMPPOSORGREL)
+                .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(PerfManagerFormConstant.NCKD_PERFMANAGERENTRY,PerfManagerFormConstant.NCKD_APPRAISALRESULT)
+                .addGroup(new String[]{PerfManagerFormConstant.NCKD_PERFMANAGERENTRY},PerfManagerFormConstant.NCKD_APPRAISALRESULT,PerfManagerFormConstant.NCKD_APPRAISALYEAR,PerfManagerFormConstant.NCKD_ISALLRANKSOURCE);
+
+        QFilter filter = new QFilter(PerfManagerFormConstant.NCKD_BEGINYEAR, QCP.less_equals, beginYear)
+                .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

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

@@ -17,6 +17,7 @@ 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.bos.servicehelper.operation.SaveServiceHelper;
 import kd.sdk.plugin.Plugin;
 import nckd.jxccl.base.common.constant.FormConstant;
 import nckd.jxccl.base.common.exception.ValidationException;
@@ -34,6 +35,7 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.Date;
 import java.util.EventObject;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -127,37 +129,44 @@ public class BatchEvalCycleFormPlugin extends AbstractFormPlugin implements Plug
                 boolean isUpdate = "2".equalsIgnoreCase(isAddOrUpdate);
 
                 DynamicObjectCollection perfManagerList = this.getModel().getEntryEntity(FormConstant.NCKD_ENTRYENTITY);
-                Set<Long> personIds = new HashSet<>(perfManagerList.size());
+                Map<Long, Date> personBeginYearMap = new HashMap<>(perfManagerList.size());
                 //转换为考核周期实体
                 for (DynamicObject dynamicObject : perfManagerList) {
                     DynamicObject person = dynamicObject.getDynamicObject(FormConstant.NCKD_PERSON);
-                    personIds.add(person.getLong(FormConstant.ID_KEY));
+                    Long personId = person.getLong(FormConstant.ID_KEY);
+                    Date beginYear = dynamicObject.getDate(PerfManagerFormConstant.NCKD_BEGINYEAR);
+
+                    if (beginYear != null) {
+                        personBeginYearMap.put(personId, beginYear);
+                    }
                 }
                 if(isUpdate){
                     //修改
                     executeOperateSave();
                 }else{
-                    //新增
-                    //最新上一考核周期
-                    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()));
+                    //获取开始时间之前状态为正常的考核周期
+                    Map<Long, DynamicObject[]> lastPerfManagerMap = new HashMap<>(personBeginYearMap.size());
+                    for (Map.Entry<Long, Date> longDateEntry : personBeginYearMap.entrySet()) {
+
+                        DynamicObject[] beforeBeginYear = PerfManagerHelper.getBeforeBeginYear(longDateEntry.getValue(), Collections.singletonList(longDateEntry.getKey()), null);
+                        lastPerfManagerMap.put(longDateEntry.getKey(), beforeBeginYear);
+                    }
+
+                    StringJoiner confirmMsg = new StringJoiner(",");
+                    for (DynamicObject[] lastPerfManagerArray : lastPerfManagerMap.values()) {
+                        for (DynamicObject lastPerfManager : lastPerfManagerArray) {
+                            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){
+                    if(confirmMsg.length() > 0){
                         // 创建回调监听器,指定回调ID和处理此回调的插件(this)
                         ConfirmCallBackListener confirmCallBacks = new ConfirmCallBackListener(PerfManagerFormConstant.BATCHEVALCYCLE_ENTITYID, this);
                         // 弹出确认框
-                        this.getView().showConfirm(StrFormatter.format("检测到本次新增的人员中存在【未结束】的考核周期,新周期新增成功后会自动结束上一周期,并将上一周期结束时间更新为当前新周期开始年前一年。请确认是否继续。{}", confirMmsg),
+                        this.getView().showConfirm(StrFormatter.format("检测到本次新增的人员中存在【未结束】的考核周期,新周期新增成功后会自动结束上一周期,并将上一周期结束时间更新为当前新周期开始年前一年。请确认是否继续。{}", confirmMsg),
                                 MessageBoxOptions.OKCancel,
                                 ConfirmTypes.Default,
                                 confirmCallBacks);
@@ -297,7 +306,8 @@ public class BatchEvalCycleFormPlugin extends AbstractFormPlugin implements Plug
         option.setVariableValue(OperateOptionConst.IGNOREINTERACTION, Boolean.TRUE+"");
         //告诉OP此次操作类型
         option.setVariableValue("isUpdate", isUpdate+"");
-        OperationResult operationResult =  OperationServiceHelper.executeOperate(FormConstant.SAVE_OP, PerfManagerFormConstant.PERFMANAGER_ENTITYID, savePerfManagerList.toArray(new DynamicObject[0]), option);
+
+        OperationResult operationResult =   SaveServiceHelper.saveOperate(FormConstant.SAVE_OP, PerfManagerFormConstant.PERFMANAGER_ENTITYID, savePerfManagerList.toArray(new DynamicObject[0]), option);
 
         if (!operationResult.isSuccess()) {
             StringJoiner errorMsgJoiner = new StringJoiner(StrFormatter.LINE_SEPARATOR);

+ 148 - 0
code/opmc/nckd-jxccl-opmc/src/main/java/nckd/jxccl/opmc/pm/plugin/form/result/AnnualPerfDetailReportListDataPlugin.java

@@ -0,0 +1,148 @@
+package nckd.jxccl.opmc.pm.plugin.form.result;
+
+import kd.bos.algo.DataSet;
+import kd.bos.dataentity.entity.LocaleString;
+import kd.bos.entity.EntityMetadataCache;
+import kd.bos.entity.QueryEntityType;
+import kd.bos.entity.report.AbstractReportColumn;
+import kd.bos.entity.report.AbstractReportListDataPlugin;
+import kd.bos.entity.report.ReportColumn;
+import kd.bos.entity.report.ReportQueryParam;
+import kd.bos.ext.hr.service.query.QueryEntityHelper;
+import kd.bos.metadata.entity.QueryEntity;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.QueryServiceHelper;
+import kd.sdk.plugin.Plugin;
+import nckd.jxccl.base.common.constant.FormConstant;
+import nckd.jxccl.base.common.constant.SystemQueryConstant;
+import nckd.jxccl.base.common.utils.DateUtil;
+import nckd.jxccl.base.common.utils.QueryFieldBuilder;
+import nckd.jxccl.opmc.pm.common.PerfManagerFormConstant;
+
+import java.time.LocalDate;
+import java.util.Date;
+import java.util.List;
+
+/**
+* 年度绩效结果明细-报表取数插件
+* 实体标识:nckd_annualperfdetail
+* @author W.Y.C
+* @date 2025/11/13 16:09
+* @version 1.0
+*/
+public class AnnualPerfDetailReportListDataPlugin extends AbstractReportListDataPlugin implements Plugin {
+
+    @Override
+    public DataSet query(ReportQueryParam reportQueryParam, Object o) throws Throwable {
+        // 构建查询字段
+        QueryFieldBuilder queryFieldBuilder = buildQueryFieldBuilder();
+
+
+        // 获取5年前的年份
+        int fiveYearsAgo = LocalDate.now().minusYears(5).getYear();
+        Date beginDate = DateUtil.toDate(LocalDate.of(fiveYearsAgo, 1, 1));
+
+        int currentYear = LocalDate.now().getYear() - 1; // 不包含今年
+        Date endDate = DateUtil.toDate(LocalDate.of(currentYear, 1, 1));
+        //查询最近5年的数据
+        QFilter qFilter = new QFilter(String.join(".", PerfManagerFormConstant.NCKD_PERFMANAGERENTRY, PerfManagerFormConstant.NCKD_APPRAISALYEAR), QCP.large_equals, beginDate);
+        qFilter.and(String.join(".", PerfManagerFormConstant.NCKD_PERFMANAGERENTRY, PerfManagerFormConstant.NCKD_APPRAISALYEAR), QCP.less_equals, endDate);
+
+        // 执行基础查询
+        QueryEntityType queryEntityType = (QueryEntityType) EntityMetadataCache.getDataEntityType("annualperfdetailquery");
+        DataSet dataSet = QueryEntityHelper.getInstance().getQueryDataSet(queryEntityType,queryFieldBuilder.buildSelect(), new QFilter[]{qFilter}, null);
+
+        dataSet.print( true);
+
+        //过滤调实际结束实际之外的年份分录
+        DataSet withYearFields = dataSet.filter("year("+String.join(".", PerfManagerFormConstant.NCKD_PERFMANAGERENTRY, PerfManagerFormConstant.NCKD_APPRAISALYEAR) +") " +
+                "<= year(CASE WHEN nckd_actendyear is not null THEN nckd_actendyear ELSE "+PerfManagerFormConstant.NCKD_ENDYEAR+" end)");
+        withYearFields.print(true);
+
+        // 动态添加年份字段;行转列
+        int tempIndex = 1;
+        for (int year = fiveYearsAgo; year <= currentYear; year++) {
+            withYearFields = withYearFields.addField(
+                    "CASE WHEN year(" + String.join(".", PerfManagerFormConstant.NCKD_PERFMANAGERENTRY, PerfManagerFormConstant.NCKD_APPRAISALYEAR) + ") = " + year + " THEN " + String.join(".", PerfManagerFormConstant.NCKD_PERFMANAGERENTRY, PerfManagerFormConstant.NCKD_APPRAISALRESULT)+ " ELSE null END",
+                    "temp_" + tempIndex
+            );
+            tempIndex++;
+        }
+        withYearFields.print(true);
+
+        // 按分组字段聚合
+        DataSet result = withYearFields.groupBy(new String[]{
+                        String.join(".", FormConstant.NCKD_EMPPOSORGREL,FormConstant.EMPLOYEE_KEY, FormConstant.EMP_NUMBER_KEY),
+                        String.join(".", FormConstant.NCKD_EMPPOSORGREL,FormConstant.EMPLOYEE_KEY, FormConstant.NAME_KEY),
+                        String.join(".", FormConstant.HRPI_EMPPOSORGREL, FormConstant.ADMINORG, FormConstant.NCKD_FIRSTORG),
+                        String.join(".", FormConstant.HRPI_EMPPOSORGREL, FormConstant.ADMINORG, FormConstant.NCKD_SECONDORG),
+                        String.join(".", FormConstant.HRPI_EMPPOSORGREL, FormConstant.ADMINORG, FormConstant.NCKD_THIRDORG),
+                        String.join(".", FormConstant.HRPI_EMPPOSORGREL, FormConstant.ADMINORG, FormConstant.NCKD_FOURTHORG),
+                        String.join(".", FormConstant.HRPI_EMPPOSORGREL, FormConstant.ADMINORG, FormConstant.NCKD_FIFTHORG),
+                        String.join(".", FormConstant.HRPI_EMPPOSORGREL, FormConstant.ADMINORG, FormConstant.NCKD_SIXTHORG),
+                        String.join(".", FormConstant.HRPI_EMPPOSORGREL, FormConstant.POS_STATUS),
+                        String.join(".", "last_perfmanager", PerfManagerFormConstant.NCKD_BEGINYEAR),
+                        String.join(".", "last_perfmanager", PerfManagerFormConstant.NCKD_ENDYEAR)
+                })
+                .max("temp_1", "1_result")
+                .max("temp_2", "2_result")
+                .max("temp_3", "3_result")
+                .max("temp_4", "4_result")
+                .max("temp_5", "5_result")
+                .finish();
+        result.print( true);
+        return result;
+    }
+
+    @Override
+    public List<AbstractReportColumn> getColumns(List<AbstractReportColumn> columns) throws Throwable {
+        // 获取5年前的年份
+        int fiveYearsAgo = LocalDate.now().minusYears(5).getYear();
+        int currentYear = LocalDate.now().getYear() - 1; // 不包含今年
+
+        for (AbstractReportColumn column : columns) {
+            if (column instanceof ReportColumn) {
+                ReportColumn rColumn = (ReportColumn) column;
+                String fieldKey = rColumn.getFieldKey();
+                // 检查是否为年份相关的字段
+                if (fieldKey != null && fieldKey.startsWith("nckd_year")) {
+                    try {
+                        // 提取年份索引(从字段名中提取数字)
+                        int yearIndex = Integer.parseInt(fieldKey.replace("nckd_year", ""));
+                        // 设置对应年份的标题
+                        int displayYear = fiveYearsAgo + yearIndex - 1;
+                        rColumn.setCaption(new LocaleString(displayYear + "年"));
+                    } catch (NumberFormatException ignored) {
+                    }
+                }
+            }
+        }
+        return columns;
+    }
+
+    /**
+     * 构建查询字段
+     */
+    private QueryFieldBuilder buildQueryFieldBuilder() {
+        return QueryFieldBuilder.create()
+                .add(PerfManagerFormConstant.NCKD_BEGINYEAR)
+                .add(PerfManagerFormConstant.NCKD_ENDYEAR)
+                .add(PerfManagerFormConstant.NCKD_ACTENDYEAR)
+                .addGroup(new String[]{PerfManagerFormConstant.NCKD_PERFMANAGERENTRY},PerfManagerFormConstant.NCKD_APPRAISALRESULT)
+                .addGroup(new String[]{PerfManagerFormConstant.NCKD_PERFMANAGERENTRY}, PerfManagerFormConstant.NCKD_APPRAISALYEAR)
+                .addGroup(new String[]{FormConstant.NCKD_EMPPOSORGREL,FormConstant.EMPLOYEE_KEY}, FormConstant.EMP_NUMBER_KEY, FormConstant.NAME_KEY)
+                .addGroup(new String[]{FormConstant.HRPI_EMPPOSORGREL},FormConstant.POS_STATUS)
+                .addGroup(new String[]{FormConstant.HRPI_EMPPOSORGREL, FormConstant.ADMINORG},
+                        FormConstant.NCKD_FIRSTORG,
+                        FormConstant.NCKD_SECONDORG,
+                        FormConstant.NCKD_THIRDORG,
+                        FormConstant.NCKD_FOURTHORG,
+                        FormConstant.NCKD_FIFTHORG,
+                        FormConstant.NCKD_SIXTHORG)
+                .addGroup(new String[]{"last_perfmanager"},
+                        PerfManagerFormConstant.NCKD_BEGINYEAR,
+                        PerfManagerFormConstant.NCKD_ENDYEAR);
+
+    }
+}

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

@@ -33,6 +33,7 @@ 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;
@@ -52,9 +53,8 @@ import java.util.stream.Collectors;
 public class PerfManagerSaveOpPlugin extends AbstractOperationServicePlugIn implements Plugin {
 
 
-    private Map<Long, DynamicObject> lastPerfManagerMap = new HashMap<>();
 
-    private  List<Long> personIds = new ArrayList<>();
+    Map<Long, Date> personBeginYearMap = new HashMap<>();
     private  List<Long> ids = new ArrayList<>();
 
     private final static String INTERACTION_SPONORE = PerfManagerSaveOpPlugin.class.getName();
@@ -77,15 +77,19 @@ public class PerfManagerSaveOpPlugin extends AbstractOperationServicePlugIn impl
                     Date endYear = data.getDate(PerfManagerFormConstant.NCKD_ENDYEAR);
                     String description = data.getString(FormConstant.DESCRIPTION_KEY);
                     DynamicObjectCollection perfManagerEntry = data.getDynamicObjectCollection(PerfManagerFormConstant.NCKD_PERFMANAGERENTRY);
-                    personIds.add(person.getLong(FormConstant.ID_KEY));
+                    Long personId = person.getLong(FormConstant.ID_KEY);
+                    if (beginYear != null) {
+                        personBeginYearMap.put(personId, beginYear);
+                    }
                     ids.add(id);
                 }
 
                 QueryFieldBuilder queryFieldBuilder = QueryFieldBuilder.create()
                         .addIdNumberName(FormConstant.NCKD_PERSON)
                         .add(PerfManagerFormConstant.NCKD_BEGINYEAR)
-                        .add(PerfManagerFormConstant.NCKD_ENDYEAR);
-                QFilter filter = new QFilter(FormConstant.NCKD_PERSON, QCP.in, personIds);
+                        .add(PerfManagerFormConstant.NCKD_ENDYEAR)
+                        .add(PerfManagerFormConstant.NCKD_ACTENDYEAR);
+                QFilter filter = new QFilter(FormConstant.NCKD_PERSON, QCP.in, personBeginYearMap.keySet());
                 if(!ids.isEmpty()) {
                     filter = filter.and(new QFilter(FormConstant.ID_KEY, QCP.not_in, ids));
                 }
@@ -120,6 +124,18 @@ public class PerfManagerSaveOpPlugin extends AbstractOperationServicePlugIn impl
                     List<DynamicObject> personRecords = groupedQueryResults.getOrDefault(personId, Collections.emptyList());
                     for (DynamicObject dynamicObject : personRecords) {
                         LocalDateTime dbBeginYear = DateUtil.toLocalDateTime(dynamicObject.getDate(PerfManagerFormConstant.NCKD_BEGINYEAR));
+                        LocalDateTime dbEndYear = DateUtil.toLocalDateTime(dynamicObject.getDate(PerfManagerFormConstant.NCKD_ENDYEAR));
+                        Date actEndYear = dynamicObject.getDate(PerfManagerFormConstant.NCKD_ACTENDYEAR);
+                        LocalDateTime dbActEndYear = actEndYear != null ? DateUtil.toLocalDateTime(actEndYear) : null;
+
+                        // 判断周期是否重叠并获取重叠信息,已结束周期使用实际结束时间
+                        String overlapInfo = getCycleOverlapInfo(beginYear, endYear, null, dbBeginYear, dbEndYear, dbActEndYear);
+                        if (StringUtils.isNotBlank(overlapInfo)) {
+                            addFatalErrorMessage(rowDataEntity,
+                                    StrFormatter.format("人员【{}】的考核周期与已有周期在{}重叠,请检查!",
+                                            personName, overlapInfo));
+                        }
+
                         //判断beginYear和dbBeginYear的年份是否相同
                         if (isSameYear(beginYear, dbBeginYear)) {
                             addFatalErrorMessage(rowDataEntity,
@@ -177,36 +193,71 @@ public class PerfManagerSaveOpPlugin extends AbstractOperationServicePlugIn impl
         });
     }
 
+    /**
+     * 判断两个考核周期是否重叠,并返回重叠描述
+     *
+     * @param beginYear1 第一个周期开始年份
+     * @param endYear1 第一个周期结束年份
+     * @param actEndYear1 第一个周期实际结束年份(如果已结束)
+     * @param beginYear2 第二个周期开始年份
+     * @param endYear2 第二个周期结束年份
+     * @param actEndYear2 第二个周期实际结束年份(如果已结束)
+     * @return 如果周期重叠返回重叠描述,否则返回空字符串
+     */
+    private String getCycleOverlapInfo(LocalDateTime beginYear1, LocalDateTime endYear1, LocalDateTime actEndYear1,
+                                       LocalDateTime beginYear2, LocalDateTime endYear2, LocalDateTime actEndYear2) {
+        if (beginYear1 == null || endYear1 == null || beginYear2 == null || endYear2 == null) {
+            return "";
+        }
+
+        // 如果周期已结束,使用实际结束时间,否则使用计划结束时间
+        LocalDateTime realEndYear1 = actEndYear1 != null ? actEndYear1 : endYear1;
+        LocalDateTime realEndYear2 = actEndYear2 != null ? actEndYear2 : endYear2;
+
+        // 两个区间重叠的条件是:一个区间的开始时间小于等于另一个区间的结束时间,
+        // 且该区间的结束时间大于等于另一个区间的开始时间
+        if (!beginYear1.isAfter(realEndYear2) && !realEndYear1.isBefore(beginYear2)) {
+            // 计算重叠区间
+            LocalDateTime overlapStart = beginYear1.isAfter(beginYear2) ? beginYear1 : beginYear2;
+            LocalDateTime overlapEnd = realEndYear1.isBefore(realEndYear2) ? realEndYear1 : realEndYear2;
+
+            if (overlapStart.equals(overlapEnd)) {
+                return StrFormatter.format("{}年", overlapStart.getYear());
+            } else {
+                return StrFormatter.format("{}年~{}年", overlapStart.getYear(), overlapEnd.getYear());
+            }
+        }
+
+        return "";
+    }
+
     @Override
     public void beforeExecuteOperationTransaction(BeforeOperationArgs e) {
         if(!this.getOperationResult().isSuccess()){
             e.setCancel(true);
             return;
         }
-        //最新上一考核周期
-        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)){
-            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()));
+        String ignoreInteraction = this.getOption().getVariableValue(OperateOptionConst.IGNOREINTERACTION,StringUtils.EMPTY);
+        if(StringUtils.isBlank(ignoreInteraction) || !"true".equalsIgnoreCase(ignoreInteraction)) {
+            StringJoiner confirmMsg = new StringJoiner(",");
+            for (DynamicObject dataEntity : e.getDataEntities()) {
+                long id = dataEntity.getLong(FormConstant.ID_KEY);
+                Date date = dataEntity.getDate(PerfManagerFormConstant.NCKD_BEGINYEAR);
+                long personId = dataEntity.getDynamicObject(FormConstant.NCKD_PERSON).getLong(FormConstant.ID_KEY);
+                QFilter filter = null;
+                if (id > 0) {
+                    filter = new QFilter(FormConstant.ID_KEY, QCP.not_in, id);
+                }
+                DynamicObject[] lastPerfManagerArray = PerfManagerHelper.getBeforeBeginYear(date, Collections.singletonList(personId), filter);
+                for (DynamicObject lastPerfManager : lastPerfManagerArray) {
+                    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());
+            if (confirmMsg.length() > 0) {
+                e.cancel = !this.showInteractionMessage(confirmMsg.toString());
             }
         }
 
@@ -214,9 +265,11 @@ public class PerfManagerSaveOpPlugin extends AbstractOperationServicePlugIn impl
     @Override
         public void beginOperationTransaction(BeginOperationTransactionArgs e) {
         //事务开始之后将其他考核周期设置为非最新
-        PerfManagerHelper.markAsNotCurrentNewest(personIds.toArray(new Long[0]));
+        PerfManagerHelper.markAsNotCurrentNewest(personBeginYearMap.keySet().toArray(new Long[0]));
 
+        List<DynamicObject> updatePerManager = new ArrayList<>();
         for (DynamicObject dataEntity : e.getDataEntities()) {
+            long id = dataEntity.getLong(FormConstant.ID_KEY);
             DynamicObject person = dataEntity.getDynamicObject(FormConstant.NCKD_PERSON);
             person = person == null ? dataEntity.getDynamicObject(FormConstant.NCKD_EMPPOSORGREL).getDynamicObject(FormConstant.EMPLOYEE_KEY) : person;
             dataEntity.set(FormConstant.NCKD_PERSON, person);
@@ -231,16 +284,59 @@ public class PerfManagerSaveOpPlugin extends AbstractOperationServicePlugIn impl
                 dataEntity.set(FormConstant.NAME_KEY, StrFormatter.format("【{}】{}~{}的考核周期",personName,beginYear.getYear(),endYear.getYear()));
             }
             //上一周期标记为“已结束”并设置“实际结束时间” begin
-            DynamicObject lastPerfManager = lastPerfManagerMap.get(personId);
-            if(lastPerfManager != null) {
-                if("1".equalsIgnoreCase(lastPerfManager.getString(PerfManagerFormConstant.NCKD_THESTATUS))) {
+            //找出当前周期前还未结束的考核周期
+            QFilter filter = null;
+            if (id > 0) {
+                filter = new QFilter(FormConstant.ID_KEY, QCP.not_in, id);
+            }
+            DynamicObject[] beforeBeginYear = PerfManagerHelper.getBeforeBeginYear(DateUtil.toDate(beginYear), Collections.singletonList(personId), filter);
+            if(beforeBeginYear != null && beforeBeginYear.length > 0) {
+                DynamicObject[] allPerfManagerArray = PerfManagerHelper.getByPersonId(Collections.singletonList(personId), filter);
+                List<DynamicObject> allPerfManagerList = new ArrayList<>(Arrays.asList(allPerfManagerArray));
+                allPerfManagerList.add(dataEntity);
+                for (DynamicObject lastPerfManager : beforeBeginYear) {
                     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));
+                    //找到当前周期的下一周期
+                    Date currentBeginYear = lastPerfManager.getDate(PerfManagerFormConstant.NCKD_BEGINYEAR);
+                    Date currentEndYear = lastPerfManager.getDate(PerfManagerFormConstant.NCKD_ENDYEAR);
+
+                    DynamicObject nextCycle = findNextCycle(allPerfManagerList, currentBeginYear);
+                    if (nextCycle != null) {
+                        // 获取下一周期的开始年份
+                        Date nextBeginYear = nextCycle.getDate(PerfManagerFormConstant.NCKD_BEGINYEAR);
+                        LocalDateTime nextBeginLocalDateTime = DateUtil.toLocalDateTime(nextBeginYear);
+                        // 计算预期的实际结束年份:下一周期开始时间-1年
+                        LocalDateTime expectedActEndYear = DateUtil.minusYears(nextBeginLocalDateTime, 1);
+                        LocalDateTime currentEndLocalDateTime = DateUtil.toLocalDateTime(currentEndYear);
+                        // 如果预期结束时间大于等于当前周期的结束时间,说明中间有断层
+                        // 则实际结束时间设置为当前周期的结束时间
+                        // 否则设置为预期的实际结束时间(下一周期开始时间-1年)
+                        LocalDateTime actEndYear = expectedActEndYear.isAfter(currentEndLocalDateTime) ?
+                                currentEndLocalDateTime : expectedActEndYear;
+                        lastPerfManager.set(PerfManagerFormConstant.NCKD_ACTENDYEAR, DateUtil.toDate(actEndYear));
+                    } else {
+                        // 如果没有找到下一周期,则实际结束时间设置为当前周期的结束时间
+                        lastPerfManager.set(PerfManagerFormConstant.NCKD_ACTENDYEAR, currentEndYear);
+                    }
+                    lastPerfManager.set(PerfManagerFormConstant.NCKD_WHYEND, StrFormatter.format("新增周期({}~{}),系统自动结束前周期",beginYear.getYear(), endYear.getYear()));
+                    updatePerManager.add(lastPerfManager);
+                }
+                //找到当前周期的上一周期
+                DynamicObject previousCycle = findPreviousCycle(allPerfManagerList, DateUtil.toDate(beginYear));
+                if(previousCycle != null) {
+                    dataEntity.set(PerfManagerFormConstant.NCKD_LASTPERFMANAGER, previousCycle);
                 }
+            }
+            //上一周期标记为“已结束”并设置“实际结束时间” end
+
+            /*DynamicObject lastPerfManager = lastPerfManagerMap.get(personId);
+            if(lastPerfManager != null) {
+                LocalDateTime date = DateUtil.toLocalDateTime(lastPerfManager.getDate(PerfManagerFormConstant.NCKD_BEGINYEAR));
+                if("1".equalsIgnoreCase(lastPerfManager.getString(PerfManagerFormConstant.NCKD_THESTATUS)) && date.getYear() >= beginYear.getYear()) {
 
-                LocalDateTime lastBeginYear = DateUtil.toLocalDateTime(lastPerfManager.getDate(PerfManagerFormConstant.NCKD_BEGINYEAR));
+                }
+
+               LocalDateTime lastBeginYear = DateUtil.toLocalDateTime(lastPerfManager.getDate(PerfManagerFormConstant.NCKD_BEGINYEAR));
                 LocalDateTime lastEndYear = DateUtil.toLocalDateTime(lastPerfManager.getDate(PerfManagerFormConstant.NCKD_ENDYEAR));
                 if(!Objects.equals(lastBeginYear, beginYear) || !Objects.equals(lastEndYear, endYear)) {
                     //当周期范围发生变化,需要保障数据连续性:若新旧考评周期存在重叠年份,且该年份在旧周期中已存在来源于【年度绩效排名】的考核结果,则新周期将自动继承该结果。
@@ -279,9 +375,10 @@ public class PerfManagerSaveOpPlugin extends AbstractOperationServicePlugIn impl
                         }
                     }
                 }
-            }
-            //上一周期标记为“已结束”并设置“实际结束时间” end
+            }*/
 
+
+            //分录考核结果写入到表头 begin
             dataEntity.set(PerfManagerFormConstant.NCKD_FIRSTYEARRESULT, null);
             dataEntity.set(PerfManagerFormConstant.NCKD_SECONDYEARRESULT, null);
             dataEntity.set(PerfManagerFormConstant.NCKD_THIRDYEARRESULT, null);
@@ -311,16 +408,81 @@ public class PerfManagerSaveOpPlugin extends AbstractOperationServicePlugIn impl
                     }
                 }
             }
+            //分录考核结果写入到表头 end
         }
-        if(lastPerfManagerMap != null && !lastPerfManagerMap.values().isEmpty()) {
-            DynamicObject[] lastPerfManagerArray = lastPerfManagerMap.values().toArray(new DynamicObject[0]);
+        if(!updatePerManager.isEmpty()) {
+            DynamicObject[] lastPerfManagerArray = updatePerManager.toArray(new DynamicObject[0]);
             SaveServiceHelper.update(lastPerfManagerArray);
         }
     }
 
+    /**
+     * 查找给定日期之后的第一个考核周期
+     *
+     * @param allPerfManagerList 该人员的所有考核周期列表
+     * @param currentDate 当前周期的开始日期
+     * @return 下一个考核周期,如果不存在则返回null
+     */
+    private DynamicObject findNextCycle(List<DynamicObject> allPerfManagerList, Date currentDate) {
+        if (allPerfManagerList == null || allPerfManagerList.isEmpty() || currentDate == null) {
+            return null;
+        }
+
+        LocalDateTime currentLocalDate = DateUtil.toLocalDateTime(currentDate);
+
+        return allPerfManagerList.stream()
+                .filter(Objects::nonNull)
+                .filter(perfManager -> {
+                    Date beginYear = perfManager.getDate(PerfManagerFormConstant.NCKD_BEGINYEAR);
+                    if (beginYear != null) {
+                        LocalDateTime beginYearLocal = DateUtil.toLocalDateTime(beginYear);
+                        return beginYearLocal.isAfter(currentLocalDate);
+                    }
+                    return false;
+                })
+                .min((perf1, perf2) -> {
+                    LocalDateTime begin1 = DateUtil.toLocalDateTime(perf1.getDate(PerfManagerFormConstant.NCKD_BEGINYEAR));
+                    LocalDateTime begin2 = DateUtil.toLocalDateTime(perf2.getDate(PerfManagerFormConstant.NCKD_BEGINYEAR));
+                    return begin1.compareTo(begin2);
+                })
+                .orElse(null);
+    }
+
+    /**
+     * 查找给定日期之前的最后一个考核周期
+     *
+     * @param allPerfManagerList 该人员的所有考核周期列表
+     * @param currentDate 当前周期的开始日期
+     * @return 上一个考核周期,如果不存在则返回null
+     */
+    private DynamicObject findPreviousCycle(List<DynamicObject> allPerfManagerList, Date currentDate) {
+        if (allPerfManagerList == null || allPerfManagerList.isEmpty() || currentDate == null) {
+            return null;
+        }
+
+        LocalDateTime currentLocalDate = DateUtil.toLocalDateTime(currentDate);
+
+        return allPerfManagerList.stream()
+                .filter(Objects::nonNull)
+                .filter(perfManager -> {
+                    Date beginYear = perfManager.getDate(PerfManagerFormConstant.NCKD_BEGINYEAR);
+                    if (beginYear != null) {
+                        LocalDateTime beginYearLocal = DateUtil.toLocalDateTime(beginYear);
+                        return beginYearLocal.isBefore(currentLocalDate);
+                    }
+                    return false;
+                })
+                .max((perf1, perf2) -> {
+                    LocalDateTime begin1 = DateUtil.toLocalDateTime(perf1.getDate(PerfManagerFormConstant.NCKD_BEGINYEAR));
+                    LocalDateTime begin2 = DateUtil.toLocalDateTime(perf2.getDate(PerfManagerFormConstant.NCKD_BEGINYEAR));
+                    return begin1.compareTo(begin2);
+                })
+                .orElse(null);
+    }
+
     @Override
     public void endOperationTransaction(EndOperationTransactionArgs e) {
-        PerfManagerHelper.markAsCurrentNewest(personIds,null);
+        PerfManagerHelper.markAsCurrentNewest(personBeginYearMap.keySet(),null);
     }
 
     private boolean showInteractionMessage(String confirMmsg) {