Browse Source

feat(hr): 绩效排名管理功能增强与考核周期同步优化

- 新增步骤常量定义支持向导操作
- 扩展考核周期相关字段与实体配置
- 实现绩效排名表单向导步骤控制逻辑
- 完善人员任职经历数据加载与展示
- 强化保存验证规则及错误提示机制- 支持考核结果自动同步至人员考评周期
-优化批量生成考核周期的查询条件- 提升考核周期管理界面数据显示完整性
-修复多处字段引用与数据迁移兼容性问题
wyc 1 tuần trước cách đây
mục cha
commit
ff2b05561d
14 tập tin đã thay đổi với 558 bổ sung67 xóa
  1. 25 0
      code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/constant/FormConstant.java
  2. 10 0
      code/base/nckd-jxccl-base-common/src/main/java/nckd/jxccl/base/common/utils/DateUtil.java
  3. 21 1
      code/base/nckd-jxccl-base-helper/src/main/java/nckd/jxccl/base/hrpi/helper/EmpPosOrgRelHelper.java
  4. 18 0
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/common/PerfRankMgmtConstant.java
  5. 1 2
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/common/PositionStructureConstant.java
  6. 77 4
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/form/performance/PerfrankMgmtFormPlugin.java
  7. 151 2
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/performance/PerfRankMgmtSaveOpPlugin.java
  8. 120 46
      code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/plugin/operate/performance/validate/PerfRankMgmtSaveValidate.java
  9. 3 2
      code/opmc/nckd-jxccl-opmc/src/main/java/nckd/jxccl/opmc/pm/common/PerfManagerFormConstant.java
  10. 1 0
      code/opmc/nckd-jxccl-opmc/src/main/java/nckd/jxccl/opmc/pm/helper/PerfManagerHelper.java
  11. 1 0
      code/opmc/nckd-jxccl-opmc/src/main/java/nckd/jxccl/opmc/pm/plugin/form/cycle/BatchEvalCycleFormPlugin.java
  12. 27 7
      code/opmc/nckd-jxccl-opmc/src/main/java/nckd/jxccl/opmc/pm/plugin/operate/cycle/CycleGenerateOpPlugin.java
  13. 11 3
      code/opmc/nckd-jxccl-opmc/src/main/java/nckd/jxccl/opmc/pm/plugin/operate/cycle/PerfManagerSaveOpPlugin.java
  14. 92 0
      code/wtc/nckd-jxccl-wtc/src/main/java/nckd/jxccl/wtc/task/SyncPunchCardTask.java

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

@@ -8,6 +8,11 @@ package nckd.jxccl.base.common.constant;
  */
 public class FormConstant {
 
+    /** 数迁参数 */
+    public static final String HR_INVOKER_PARAM_INVOKER = "hrInvokerParam#invoker";
+    public static final String DATA_MIGRATION = "dataMigration";
+
+
     //====================================== 标品实体标识(需要小写) ======================================
     /**学历-实体标识*/
     public static final String HBSS_DIPLOMA = "hbss_diploma";
@@ -99,6 +104,8 @@ public class FormConstant {
     public static final String CHKINCLUDECHILD = "chkincludechild";
     /** 单据体 */
     public static final String NCKD_ENTRYENTITY = "nckd_entryentity";
+    /** 向导控件 */
+    public static final String NCKD_WIZARDAP = "nckd_wizardap";
 
     //====================================== 通用字段 ======================================
     /** BOID标识 */
@@ -262,5 +269,23 @@ public class FormConstant {
     public static final String NCKD_ISCURRENTNEWEST = "nckd_iscurrentnewest";
     /** 部门*/
     public static final String NCKD_DEP = "nckd_dep";
+    /** 任职经历 */
+    public static final String NCKD_EMPPOSORGREL = "nckd_empposorgrel";
+
+    /** 一级组织 */
+    public static final String NCKD_FIRSTORG = "nckd_firstorg";
+    /** 二级组织 */
+    public static final String NCKD_SECONDORG = "nckd_secondorg";
+    /** 三级组织 */
+    public static final String NCKD_THIRDORG = "nckd_thirdorg";
+    /** 四级组织 */
+    public static final String NCKD_FOURTHORG = "nckd_fourthorg";
+    /** 五级组织 */
+    public static final String NCKD_FIFTHORG = "nckd_fifthorg";
+    /** 六级组织 */
+    public static final String NCKD_SIXTHORG = "nckd_sixthorg";
+    /** 工作性质 */
+    public static final String NCKD_WORKNATURE = "nckd_worknature";
+
 
 }

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

@@ -57,6 +57,16 @@ public class DateUtil {
         return Date.from(dateTime.atZone(ZoneId.systemDefault()).toInstant());
     }
 
+    /**
+     * 转换为Date对象
+     *
+     * @param localDate LocalDate
+     * @return 对应的Date对象
+     */
+    public static Date toDate(LocalDate localDate) {
+        return  Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
+    }
+
     /**
      * 转换为LocalDateTime对象
      *

+ 21 - 1
code/base/nckd-jxccl-base-helper/src/main/java/nckd/jxccl/base/hrpi/helper/EmpPosOrgRelHelper.java

@@ -20,8 +20,28 @@ import java.util.Collection;
  * @date 2025/11/4 15:33
  * @version 1.0
  */
-public class EmpPosOrgRelHelper implements Plugin {
+public class EmpPosOrgRelHelper {
 
+    //根据id查询任职经历
+    public static DynamicObject[] queryEmpPosOrgRelById(Collection<Long> ids) {
+        // 构建基础查询条件
+        QFilter queryFilter = new QFilter(FormConstant.ID_KEY, QCP.in, ids);
+
+        // 构建查询字段和排序规则
+        QueryFieldBuilder queryFieldBuilder = selectProperties();
+        queryFieldBuilder.addIdNumberName(FormConstant.POSITION_KEY,FormConstant.NCKD_WORKNATURE);
+        queryFieldBuilder.addIdNumberName(FormConstant.ADMINORG,FormConstant.NCKD_FIRSTORG);
+        queryFieldBuilder.addIdNumberName(FormConstant.ADMINORG,FormConstant.NCKD_SECONDORG);
+        queryFieldBuilder.addIdNumberName(FormConstant.ADMINORG,FormConstant.NCKD_THIRDORG);
+        queryFieldBuilder.addIdNumberName(FormConstant.ADMINORG,FormConstant.NCKD_FOURTHORG);
+        queryFieldBuilder.addIdNumberName(FormConstant.ADMINORG,FormConstant.NCKD_FIFTHORG);
+        queryFieldBuilder.addIdNumberName(FormConstant.ADMINORG,FormConstant.NCKD_SIXTHORG);
+        String selectProperties = queryFieldBuilder.buildSelect();
+        String order = selectProperties().buildOrder();
+
+        // 执行查询并返回结果
+        return BusinessDataServiceHelper.load(FormConstant.HRPI_EMPPOSORGREL, selectProperties, new QFilter[]{queryFilter}, order);
+    }
     /**
      * 根据员工ID查询最新的任职经历信息
      *

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

@@ -48,6 +48,8 @@ public class PerfRankMgmtConstant extends FormConstant {
     public static final String NCKD_GETRANKLIST = "nckd_getranklist";
     /** 获取排名名单-OP */
     public static final String GETRANKLIST_OP = "getranklist";
+    /** 步骤 */
+    public static final String NCKD_STEP = "nckd_step";
 
 
 
@@ -78,4 +80,20 @@ public class PerfRankMgmtConstant extends FormConstant {
     /** 降级百分比阈值 */
     public static final String NCKD_DOWNGRADETHRESHOLD = "nckd_downgradethreshold";
     /*-------------------------------------- 升保降级条件配置 begin --------------------------------------*/
+
+
+    /*-------------------------------------- 考核周期(人员考评) begin --------------------------------------*/
+    /** 周期开始年份 */
+    public static final String NCKD_BEGINYEAR = "nckd_beginyear";
+    /** 周期结束年份 */
+    public static final String NCKD_ENDYEAR = "nckd_endyear";
+    /** 人员考评管理实体名称 */
+    public static final String PERFMANAGER_ENTITYID = "nckd_perfmanager";
+    /** 人员考评管理分录实体名称 */
+    public static final String NCKD_PERFMANAGERENTRY = "nckd_perfmanagerentry";
+    /** 分录-考核年份 */
+    public static final String NCKD_APPRAISALYEAR = "nckd_appraisalyear";
+    /** 分录-是否来源全排名 */
+    public static final String NCKD_ISALLRANKSOURCE = "nckd_isallranksource";
+    /*-------------------------------------- 考核周期(人员考评) end --------------------------------------*/
 }

+ 1 - 2
code/hr/nckd-jxccl-hr/src/main/java/nckd/jxccl/hr/psms/common/PositionStructureConstant.java

@@ -160,8 +160,7 @@ 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_EMPPOSORGREL = "nckd_empposorgrel";
+
     /*-------------------------------------- 在职人员初定(弹窗) end --------------------------------------*/
 
 

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

@@ -21,17 +21,18 @@ import kd.bos.form.ConfirmTypes;
 import kd.bos.form.IClientViewProxy;
 import kd.bos.form.MessageBoxOptions;
 import kd.bos.form.MessageBoxResult;
+import kd.bos.form.container.Wizard;
+import kd.bos.form.control.Steps;
 import kd.bos.form.events.AfterDoOperationEventArgs;
 import kd.bos.form.events.BeforeDoOperationEventArgs;
 import kd.bos.form.events.MessageBoxClosedEvent;
 import kd.bos.form.operate.FormOperate;
+import kd.bos.form.plugin.AbstractFormPlugin;
 import kd.bos.logging.Log;
 import kd.bos.logging.LogFactory;
 import kd.bos.orm.query.QCP;
 import kd.bos.orm.query.QFilter;
-import kd.bos.plugin.sample.dynamicform.pcform.form.template.ConfirmCallBack;
 import kd.bos.servicehelper.QueryServiceHelper;
-import kd.sdk.plugin.Plugin;
 import nckd.jxccl.base.common.constant.FormConstant;
 import nckd.jxccl.base.common.constant.SystemQueryConstant;
 import nckd.jxccl.base.common.enums.AppraisalResultEnum;
@@ -39,9 +40,11 @@ import nckd.jxccl.base.common.enums.psms.JobSeqEnum;
 import nckd.jxccl.base.common.utils.ConvertUtil;
 import nckd.jxccl.base.common.utils.DateUtil;
 import nckd.jxccl.base.common.utils.QueryFieldBuilder;
+import nckd.jxccl.base.hrpi.helper.EmpPosOrgRelHelper;
 import nckd.jxccl.hr.psms.common.PerfRankMgmtConstant;
 import nckd.jxccl.hr.psms.common.PositionStructureConstant;
 import nckd.jxccl.hr.psms.helper.PositionFileHelper;
+import org.apache.commons.lang3.StringUtils;
 
 import java.time.temporal.ChronoUnit;
 import java.util.Arrays;
@@ -62,7 +65,7 @@ import java.util.stream.Stream;
 * @date 2025/10/20 15:11
 * @version 1.0
 */
-public class PerfrankMgmtFormPlugin extends ConfirmCallBack implements Plugin {
+public class PerfrankMgmtFormPlugin extends AbstractFormPlugin {
 
     private static final Log logger = LogFactory.getLog(PerfrankMgmtFormPlugin.class);
 
@@ -73,6 +76,13 @@ public class PerfrankMgmtFormPlugin extends ConfirmCallBack implements Plugin {
         sortEntry();
         // 该单据不需要审批,默认为暂存状态
         this.getModel().setValue(FormConstant.STATUS, StatusEnum.A.toString());
+        String step = ConvertUtil.toStr(this.getModel().getValue(PerfRankMgmtConstant.NCKD_STEP));
+        if("1".equals(step)){
+            setStepStatus(0,Steps.FINISH);
+            setStepStatus(1,Steps.PROCESS);
+
+            importResultStep();
+        }
     }
 
     @Override
@@ -249,6 +259,7 @@ public class PerfrankMgmtFormPlugin extends ConfirmCallBack implements Plugin {
                     //在职人员
                     qFilter.and(String.join(".", FormConstant.HRPI_EMPENTREL, FormConstant.LABOR_REL_STATUS, FormConstant.IS_HIRED), QCP.equals, EnableEnum.YES.getCode());
                     QueryFieldBuilder queryFieldBuilder = QueryFieldBuilder.create()
+                            .addGroup(new String[]{FormConstant.HRPI_EMPPOSORGREL}, FormConstant.ID_KEY)
                             //用工关系状态.编码(1005_S	在职-试用中、1010_S	在职、1020_S	离职、1030_S	已退休),istrial:是否试用,ishired:是否在职
                             .addGroup(new String[]{FormConstant.HRPI_EMPENTREL, FormConstant.LABOR_REL_STATUS}, FormConstant.NUMBER_KEY, FormConstant.NAME_KEY, FormConstant.IS_TRIAL, FormConstant.IS_HIRED)
                             //任职状态分类(1010_S:在岗,1030_S:不在岗)
@@ -279,18 +290,38 @@ public class PerfrankMgmtFormPlugin extends ConfirmCallBack implements Plugin {
                         Map<Long, Boolean> entitledToAllowance = isEntitledToAllowance(personIds, now);
 
                         // -------------------------------- 4、加载数据到分录 --------------------------------
+                        //预加载任职经历
+                        List<Long> ids = personList.stream()
+                                .map(person -> person.getLong(String.join(".", FormConstant.HRPI_EMPPOSORGREL, FormConstant.ID_KEY)))
+                                .collect(Collectors.toList());
+                        DynamicObject[] empPosOrgRelArray = EmpPosOrgRelHelper.queryEmpPosOrgRelById(ids);
+                        Map<Long, DynamicObject> empPosOrgRelMap = Arrays.stream(empPosOrgRelArray)
+                                .filter(Objects::nonNull)
+                                .collect(Collectors.toMap(
+                                        obj -> obj.getLong(FormConstant.ID_KEY),
+                                        obj -> obj,
+                                        (existing, replacement) -> existing
+                                ));
                         for (DynamicObject person : personList) {
                             DynamicObject entryCol = entryEntityCols.addNew();
                             long personId = person.getLong(String.join(".", FormConstant.EMPLOYEE_KEY, FormConstant.ID_KEY));
+                            long empPosOrgRelId = person.getLong(String.join(".", FormConstant.HRPI_EMPPOSORGREL, FormConstant.ID_KEY));
                             String personName = person.getString(String.join(".", FormConstant.EMPLOYEE_KEY, FormConstant.NAME_KEY));
                             String personNumber = person.getString(String.join(".", FormConstant.EMPLOYEE_KEY, FormConstant.EMP_NUMBER_KEY));
 
+                            DynamicObject empPosOrgRel = empPosOrgRelMap.get(empPosOrgRelId);
+                            entryCol.set(FormConstant.NCKD_EMPPOSORGREL, empPosOrgRel);
+
+
                             DynamicObjectType type = EntityMetadataCache.getDataEntityType(FormConstant.HRPI_EMPLOYEE);
                             DynamicObject personObj = (DynamicObject) type.createInstance();
                             personObj.set(FormConstant.ID_KEY, personId);
                             personObj.set(FormConstant.NAME_KEY, personName);
                             personObj.set(FormConstant.EMP_NUMBER_KEY, personNumber);
                             entryCol.set(FormConstant.NCKD_PERSON, personObj);
+
+
+
                             //是否试用
                             boolean isTrial = person.getBoolean(String.join(".", FormConstant.HRPI_EMPENTREL, FormConstant.LABOR_REL_STATUS, FormConstant.IS_TRIAL));
                             //入职日期
@@ -312,13 +343,27 @@ public class PerfrankMgmtFormPlugin extends ConfirmCallBack implements Plugin {
                             entryCol.set(PerfRankMgmtConstant.NCKD_POSTALLOWANCE, hasAllowance);
 
                         }
+                        this.getModel().updateEntryCache(entryEntityCols);
+                        this.getView().updateView(PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY);
                         this.getView().showSuccessNotification("名单获取完成");
                     } else {
                         this.getView().showTipNotification("未获取到人员");
                     }
                 }
+            /* }*/
+        }else if(PerfRankMgmtConstant.SAVE_OP.equalsIgnoreCase(itemKey)){
+            if(afterDoOperationEventArgs.getOperationResult() != null && afterDoOperationEventArgs.getOperationResult().isSuccess()){
+                String wizadap = this.getView().getPageCache().get(FormConstant.NCKD_WIZARDAP);
+                if(StringUtils.isBlank(wizadap) || "0".equals(wizadap)){
+                    setStepStatus(0,Steps.FINISH);
+                    setStepStatus(1,Steps.PROCESS);
+                    //控制显示隐藏
+                    importResultStep();
+                }else {
+                    this.getView().showSuccessNotification("同步考核周期成功。");
+                }
             }
-       /* }*/
+        }
         if(Arrays.asList(FormConstant.DELETEENTRY_OP, PerfRankMgmtConstant.GETRANKLIST_OP).contains(itemKey)){
             sortEntry();
             calcRankCount();
@@ -326,6 +371,16 @@ public class PerfrankMgmtFormPlugin extends ConfirmCallBack implements Plugin {
         // this.getView().setStatus(OperationStatus.EDIT);
     }
 
+    private void importResultStep() {
+        this.getModel().setValue(PerfRankMgmtConstant.NCKD_STEP,1);
+        this.getView().setVisible(false, FormConstant.NUMBER_KEY, FormConstant.NAME_KEY, FormConstant.NCKD_ADMINORG, PerfRankMgmtConstant.NCKD_THEYEAR, PerfRankMgmtConstant.NCKD_GETRANKLIST);
+        this.getView().setVisible(true, PerfRankMgmtConstant.NCKD_TOPRANKS, PerfRankMgmtConstant.NCKD_ALLOWANCERANKS, PerfRankMgmtConstant.NCKD_FAILS, PerfRankMgmtConstant.NCKD_BASICS, PerfRankMgmtConstant.NCKD_EXCELLENTS, "nckd_advconbaritemap6");
+        DynamicObjectCollection entryEntity = this.getModel().getEntryEntity(PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY);
+        for (int i = 0; i < entryEntity.size(); i++) {
+            this.getView().setEnable(Boolean.FALSE, i, FormConstant.NCKD_EMPPOSORGREL);
+        }
+    }
+
     /**
      * 判断人员是否享受职位津贴
      * @param personIds 人员ID列表
@@ -528,4 +583,22 @@ public class PerfrankMgmtFormPlugin extends ConfirmCallBack implements Plugin {
         //优秀人数
         this.getModel().setValue(PerfRankMgmtConstant.NCKD_EXCELLENTS, excellentCount);
     }
+
+
+    /**
+     * 设置步骤状态
+     * @param step 步骤索引(从0开始)
+     * @param stepStatus 步骤状态 {@link Steps}
+     * @return: void
+     * @author W.Y.C
+     * @date: 2025/06/14 20:12
+     */
+    private void setStepStatus(int step,String stepStatus) {
+        Wizard wizard = this.getControl(FormConstant.NCKD_WIZARDAP);
+        Map<String, Object> currentStepMap = new HashMap<>();
+        currentStepMap.put("currentStep", step);
+        currentStepMap.put("currentStatus", stepStatus);
+        wizard.setWizardCurrentStep(currentStepMap);
+        this.getView().getPageCache().put(FormConstant.NCKD_WIZARDAP, step+"");
+    }
 }

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

@@ -1,10 +1,46 @@
 package nckd.jxccl.hr.psms.plugin.operate.performance;
 
+import kd.bos.common.enums.EnableEnum;
+import kd.bos.dataentity.OperateOption;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.entity.ExtendedDataEntity;
+import kd.bos.entity.MainEntityType;
+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.AddValidatorsEventArgs;
+import kd.bos.entity.plugin.PreparePropertysEventArgs;
+import kd.bos.entity.plugin.args.BeginOperationTransactionArgs;
 import kd.bos.entity.plugin.args.EndOperationTransactionArgs;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.servicehelper.MetadataServiceHelper;
+import kd.bos.servicehelper.QueryServiceHelper;
+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;
+import nckd.jxccl.base.common.utils.DateUtil;
+import nckd.jxccl.base.common.utils.QueryFieldBuilder;
+import nckd.jxccl.base.common.utils.StrFormatter;
+import nckd.jxccl.base.orm.helper.QFilterCommonHelper;
+import nckd.jxccl.hr.hstu.common.HonorStudentConstant;
+import nckd.jxccl.hr.psms.common.PerfRankMgmtConstant;
 import nckd.jxccl.hr.psms.plugin.operate.performance.validate.PerfRankMgmtSaveValidate;
+import org.apache.commons.lang3.StringUtils;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.StringJoiner;
+import java.util.stream.Collectors;
 
 /**
 * 年度绩效排名管理-保存插件
@@ -14,15 +50,128 @@ import nckd.jxccl.hr.psms.plugin.operate.performance.validate.PerfRankMgmtSaveVa
 */
 public class PerfRankMgmtSaveOpPlugin extends AbstractOperationServicePlugIn implements Plugin {
 
+    @Override
+    public void onPreparePropertys(PreparePropertysEventArgs e) {
+        e.getFieldKeys().addAll(this.billEntityType.getAllFields().keySet());
+    }
+
     @Override
     public void onAddValidators(AddValidatorsEventArgs e) {
         e.addValidator(new PerfRankMgmtSaveValidate());
     }
 
+    @Override
+    public void beginOperationTransaction(BeginOperationTransactionArgs e) {
+        for (DynamicObject dataEntity : e.getDataEntities()) {
+            DynamicObjectCollection entryList = dataEntity.getDynamicObjectCollection(PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY);
+            for (DynamicObject entry : entryList) {
+                DynamicObject person = entry.getDynamicObject(FormConstant.NCKD_PERSON);
+                if(person == null){
+                    DynamicObject empPosOrgRel = entry.getDynamicObject(FormConstant.NCKD_EMPPOSORGREL);
+                    entry.set(FormConstant.NCKD_PERSON, empPosOrgRel.getDynamicObject(FormConstant.EMPLOYEE_KEY));
+                }
+            }
+
+        }
+
+    }
 
     @Override
     public void endOperationTransaction(EndOperationTransactionArgs e) {
-        //TODO 【待修改】-职位体系-全排名同步到考核周期
-        super.endOperationTransaction(e);
+        //先查询人员对应考核周期
+        for (DynamicObject data : e.getDataEntities()) {
+            int theYear = data.getInt(PerfRankMgmtConstant.NCKD_THEYEAR);
+            int step = data.getInt(PerfRankMgmtConstant.NCKD_STEP);
+            //当步骤为1时同步考核周期结果
+            if(step == 1) {
+                DynamicObjectCollection entryList = data.getDynamicObjectCollection(PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY);
+
+                List<Long> personIds = entryList.stream()
+                        .map(entry -> entry.getDynamicObject(FormConstant.NCKD_PERSON))
+                        .filter(Objects::nonNull)
+                        .map(person -> person.getLong(FormConstant.ID_KEY))
+                        .filter(id -> id > 0)
+                        .collect(Collectors.toList());
+
+                Map<Long, DynamicObject> personAppraisalMap = entryList.stream()
+                        .filter(entry -> Objects.nonNull(entry.getDynamicObject(FormConstant.NCKD_PERSON)))
+                        .filter(entry -> Objects.nonNull(entry.getDynamicObject(PerfRankMgmtConstant.NCKD_APPRAISALRESULT)))
+                        .collect(Collectors.toMap(
+                                entry -> entry.getDynamicObject(FormConstant.NCKD_PERSON).getLong(FormConstant.ID_KEY),
+                                entry -> entry.getDynamicObject(PerfRankMgmtConstant.NCKD_APPRAISALRESULT)
+                        ));
+
+                //获取考核周期
+                List<Long> perfManagerIds = findPerfManager(theYear, personIds);
+
+                QFilter filter = QFilterCommonHelper.getIdInFilter(perfManagerIds);
+                MainEntityType dataEntityType = MetadataServiceHelper.getDataEntityType(PerfRankMgmtConstant.PERFMANAGER_ENTITYID);
+                DynamicObject[] perfManagerArray = BusinessDataServiceHelper.load(perfManagerIds.toArray(new Long[0]), dataEntityType);
+                LocalDate appraisalYearLocalDate = LocalDate.of(theYear, 1, 1);
+                Date appraisalYearDate = DateUtil.toDate(appraisalYearLocalDate);
+
+                for (DynamicObject perfManager : perfManagerArray) {
+                    DynamicObject person = perfManager.getDynamicObject(FormConstant.NCKD_PERSON);
+                    long personId = person.getLong(FormConstant.ID_KEY);
+                    DynamicObjectCollection perfManagerEntryColl = perfManager.getDynamicObjectCollection(PerfRankMgmtConstant.NCKD_PERFMANAGERENTRY);
+                    boolean isExist = false;
+                    for (DynamicObject perfManagerEntry : perfManagerEntryColl) {
+                        LocalDateTime appraisalYear = DateUtil.toLocalDateTime(perfManagerEntry.getDate(PerfRankMgmtConstant.NCKD_APPRAISALYEAR));
+                        int year = appraisalYear.getYear();
+                        if(year == theYear){
+                            perfManagerEntry.set(PerfRankMgmtConstant.NCKD_APPRAISALRESULT, personAppraisalMap.get(personId));
+                            isExist = true;
+                            break;
+                        }
+                    }
+                    if(!isExist){
+                        DynamicObject perfManagerEntry = perfManagerEntryColl.addNew();
+                        perfManagerEntry.set(PerfRankMgmtConstant.NCKD_APPRAISALYEAR,appraisalYearDate);
+                        perfManagerEntry.set(PerfRankMgmtConstant.NCKD_APPRAISALRESULT, personAppraisalMap.get(personId));
+                        perfManagerEntry.set(PerfRankMgmtConstant.NCKD_ISALLRANKSOURCE, EnableEnum.YES.getCode());
+                    }
+                }
+                OperationResult operationResult = SaveServiceHelper.saveOperate(FormConstant.SAVE_OP, PerfRankMgmtConstant.PERFMANAGER_ENTITYID, perfManagerArray, OperateOption.create());
+                if (!operationResult.isSuccess()) {
+                    StringJoiner errorMsgJoiner = new StringJoiner(StrFormatter.LINE_SEPARATOR);
+                    for (IOperateInfo error : operationResult.getAllErrorOrValidateInfo()) {
+                        errorMsgJoiner.add(error.getMessage());
+                    }
+                    String errorMsg = errorMsgJoiner.toString();
+                    if(StringUtils.isBlank(errorMsg)){
+                        errorMsg = operationResult.getMessage();
+                    }
+                    throw new ValidationException("同步考核周期失败,原因:"+errorMsg);
+                }
+
+            }
+        }
+    }
+
+
+
+
+
+    /**
+     * 根据人员和年度查询人员考核周期
+     * @param theYear 年份
+     * @param personIds 人员ID
+     * @return: java.util.List<java.lang.Long>
+     * @author W.Y.C
+     * @date: 2025/11/12 22:10
+     */
+    private List<Long> findPerfManager(int theYear,List<Long> personIds) {
+        //根据年度校验人员只能在同一个组
+        LocalDate startDate = LocalDate.of(theYear, 1, 1);
+        QFilter filter = new QFilter(PerfRankMgmtConstant.NCKD_PERSON, QCP.in, personIds);
+        filter.and(PerfRankMgmtConstant.NCKD_BEGINYEAR,QCP.less_equals,startDate);
+        filter.and(PerfRankMgmtConstant.NCKD_ENDYEAR,QCP.large_equals,startDate);
+        QueryFieldBuilder queryFieldBuilder = QueryFieldBuilder.create()
+                .add(FormConstant.ID_KEY);
+        DynamicObjectCollection query = QueryServiceHelper.query(PerfRankMgmtConstant.PERFMANAGER_ENTITYID, queryFieldBuilder.buildSelect(), new QFilter[]{filter});
+        List<Long> ids = query.stream()
+                .map(obj -> obj.getLong(FormConstant.ID_KEY))
+                .collect(Collectors.toList());
+        return ids;
     }
 }

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

@@ -4,6 +4,7 @@ import kd.bos.dataentity.entity.DynamicObject;
 import kd.bos.dataentity.entity.DynamicObjectCollection;
 import kd.bos.entity.ExtendedDataEntity;
 import kd.bos.entity.validate.AbstractValidator;
+import kd.bos.orm.query.QCP;
 import kd.bos.orm.query.QFilter;
 import kd.bos.servicehelper.QueryServiceHelper;
 import nckd.jxccl.base.common.constant.FormConstant;
@@ -11,12 +12,12 @@ import nckd.jxccl.base.common.enums.AppraisalResultEnum;
 import nckd.jxccl.base.common.utils.DateUtil;
 import nckd.jxccl.base.common.utils.QueryFieldBuilder;
 import nckd.jxccl.base.common.utils.StrFormatter;
-import nckd.jxccl.base.orm.helper.QFilterCommonHelper;
 import nckd.jxccl.hr.psms.common.PerfRankMgmtConstant;
 import nckd.jxccl.hr.psms.common.PositionStructureConstant;
 
 import java.math.BigDecimal;
 import java.math.RoundingMode;
+import java.time.LocalDate;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -24,6 +25,8 @@ 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;
 
 /**
 * 绩效排名管理保存验证插件
@@ -36,39 +39,53 @@ public class PerfRankMgmtSaveValidate extends AbstractValidator {
     private final Map<String, BigDecimal> appraisalResultRatioMap = new HashMap<>();
     @Override
     public void validate() {
-        QueryFieldBuilder queryFieldBuilder = QueryFieldBuilder.create()
-                .addGroup(new String[]{FormConstant.NCKD_ENTRYENTITY}, PerfRankMgmtConstant.NCKD_RATIO)
-                .addGroup(new String[]{FormConstant.NCKD_ENTRYENTITY, PositionStructureConstant.NCKD_APPRAISALRESULT}, FormConstant.NUMBER_KEY);
-        DynamicObjectCollection query = QueryServiceHelper.query(PerfRankMgmtConstant.RANKRATIOCONF_ENTITYID, queryFieldBuilder.buildSelect(), null);
-
-        query.forEach(dynamicObject -> {
-            String key = dynamicObject.getString(String.join(".",FormConstant.NCKD_ENTRYENTITY, PositionStructureConstant.NCKD_APPRAISALRESULT, FormConstant.NUMBER_KEY));
-            BigDecimal value = dynamicObject.getBigDecimal(String.join(".",FormConstant.NCKD_ENTRYENTITY, PerfRankMgmtConstant.NCKD_RATIO));
-            appraisalResultRatioMap.put(key, value);
-        });
-        for (ExtendedDataEntity rowDataEntity : getDataEntities()) {
-            if(appraisalResultRatioMap.isEmpty()){
-                this.addMessage(rowDataEntity, "没有配置【排名考核结果比例】");
-                return;
-            }
-            DynamicObject data = rowDataEntity.getDataEntity();
-            // 年份校验
-            if (validateYear(data, rowDataEntity)) {
-                // 获取明细数据并验证是否为空
-                DynamicObjectCollection entries = data.getDynamicObjectCollection(PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY);
-                if (entries.isEmpty()) {
-                    String operateKey = this.getOperateKey();
-                    if(!operateKey.equals(FormConstant.SAVE_OP)) {
-                        this.addMessage(rowDataEntity, "请添加排名名单");
-                        continue;
-                    }
+        String invoker = (String)this.getOption().getVariables().get(FormConstant.HR_INVOKER_PARAM_INVOKER);
+        boolean dataMigration = FormConstant.DATA_MIGRATION.equalsIgnoreCase(invoker);
+        if(!dataMigration) {
+            QueryFieldBuilder queryFieldBuilder = QueryFieldBuilder.create()
+                    .addGroup(new String[]{FormConstant.NCKD_ENTRYENTITY}, PerfRankMgmtConstant.NCKD_RATIO)
+                    .addGroup(new String[]{FormConstant.NCKD_ENTRYENTITY, PositionStructureConstant.NCKD_APPRAISALRESULT}, FormConstant.NUMBER_KEY);
+            DynamicObjectCollection query = QueryServiceHelper.query(PerfRankMgmtConstant.RANKRATIOCONF_ENTITYID, queryFieldBuilder.buildSelect(), null);
+
+            query.forEach(dynamicObject -> {
+                String key = dynamicObject.getString(String.join(".", FormConstant.NCKD_ENTRYENTITY, PositionStructureConstant.NCKD_APPRAISALRESULT, FormConstant.NUMBER_KEY));
+                BigDecimal value = dynamicObject.getBigDecimal(String.join(".", FormConstant.NCKD_ENTRYENTITY, PerfRankMgmtConstant.NCKD_RATIO));
+                appraisalResultRatioMap.put(key, value);
+            });
+            for (ExtendedDataEntity rowDataEntity : getDataEntities()) {
+                if (appraisalResultRatioMap.isEmpty()) {
+                    this.addFatalErrorMessage(rowDataEntity, "没有配置【排名考核结果比例】");
+                    return;
                 }
+                DynamicObject data = rowDataEntity.getDataEntity();
+                // 年份校验
+                if (validateYear(data, rowDataEntity)) {
+                    // 获取明细数据并验证是否为空
+                    DynamicObjectCollection entries = data.getDynamicObjectCollection(PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY);
+                    if (entries.isEmpty()) {
+                        String operateKey = this.getOperateKey();
+                        // if (!operateKey.equals(FormConstant.SAVE_OP)) {
+                            this.addFatalErrorMessage(rowDataEntity, "请添加排名名单");
+                            // continue;
+                        // }
+                    }
+                    int step = data.getInt(PerfRankMgmtConstant.NCKD_STEP);
 
-                // 验证明细数据
-                ValidationContext context = validateEntries(entries, rowDataEntity);
+                    //当步骤为导入考核结果时需校验明细数据
 
-                // 验证统计数据一致性
-                validateDataConsistency(data, context, rowDataEntity);
+                    // 验证明细数据
+                    ValidationContext context = validateEntries(entries, rowDataEntity);
+
+                    // 校验人员是否在其他分组中排名
+                    validateRepetition(data, rowDataEntity, context);
+
+                    // 校验人员是否有对应考核周期
+                    validatePerfManager(data,entries,rowDataEntity, context);
+                    if(step == 1) {
+                        // 验证统计数据一致性
+                        validateDataConsistency(data, context, rowDataEntity);
+                    }
+                }
             }
         }
     }
@@ -128,7 +145,7 @@ public class PerfRankMgmtSaveValidate extends AbstractValidator {
 
         if (difference > allowedDifference) {
 
-            this.addMessage(rowDataEntity,
+            this.addFatalErrorMessage(rowDataEntity,
                     StrFormatter.format("{}人数与配置比例不符,应为{}人,实际为{}人",
                             levelName, expectedCount,actualCount));
         }
@@ -147,7 +164,7 @@ public class PerfRankMgmtSaveValidate extends AbstractValidator {
         int theYear = data.getInt(PerfRankMgmtConstant.NCKD_THEYEAR);
         int currentYear = DateUtil.now().getYear();
         if (theYear < 1900 || theYear > currentYear) {
-            this.addMessage(rowDataEntity, "年份不合法或超过当前年份");
+            this.addFatalErrorMessage(rowDataEntity, "年份不合法或超过当前年份");
             return false;
         }
         return true;
@@ -169,10 +186,66 @@ public class PerfRankMgmtSaveValidate extends AbstractValidator {
             DynamicObject entry = entries.get(i);
             validateEntry(entry, rowDataEntity, i + 1, personIds, context);
         }
+        context.personIds = personIds;
+
+        return context;
+    }
 
+
+    private void validateRepetition(DynamicObject data, ExtendedDataEntity rowDataEntity, ValidationContext context) {
         //根据年度校验人员只能在同一个组
+        long id = data.getLong(FormConstant.ID_KEY);
+        int theYear = data.getInt(PerfRankMgmtConstant.NCKD_THEYEAR);
+        QFilter filter = new QFilter(String.join(".", PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY, PerfRankMgmtConstant.NCKD_PERSON), QCP.in, context.personIds);
+        if(id > 0){
+            filter.and(FormConstant.ID_KEY, QCP.not_equals, id);
+        }
+        filter.and(PerfRankMgmtConstant.NCKD_THEYEAR,QCP.equals,theYear);
+        QueryFieldBuilder queryFieldBuilder = QueryFieldBuilder.create()
+                .add(FormConstant.NAME_KEY)
+                .add(PerfRankMgmtConstant.NCKD_THEYEAR)
+                .addIdNumberName(PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY, PerfRankMgmtConstant.NCKD_PERSON);
+        DynamicObjectCollection query = QueryServiceHelper.query(PerfRankMgmtConstant.NCKD_PERFRANKMGMT_ENTITYID, queryFieldBuilder.buildSelect(), new QFilter[]{filter});
+        StringJoiner msgJoiner = new StringJoiner(StrFormatter.LINE_SEPARATOR);
+        for (DynamicObject dynamicObject : query) {
+            String name = dynamicObject.getString(FormConstant.NAME_KEY);
+            String personName = dynamicObject.getString(String.join(".", PerfRankMgmtConstant.NCKD_PERFRANKMGMTENTRY, PerfRankMgmtConstant.NCKD_PERSON,FormConstant.NAME_KEY));
+            msgJoiner.add(StrFormatter.format("人员【{}】已在排名单元【{}】中排名;", personName,name));
+        }
+        if(msgJoiner.length() > 0){
+            this.addFatalErrorMessage(rowDataEntity, msgJoiner.toString());
+        }
+    }
 
-        return context;
+
+
+    private void validatePerfManager(DynamicObject data,DynamicObjectCollection entries, ExtendedDataEntity rowDataEntity, ValidationContext context) {
+        //根据年度校验人员只能在同一个组
+        int theYear = data.getInt(PerfRankMgmtConstant.NCKD_THEYEAR);
+        LocalDate startDate = LocalDate.of(theYear, 1, 1);
+        QFilter filter = new QFilter(PerfRankMgmtConstant.NCKD_PERSON, QCP.in, context.personIds);
+        filter.and(PerfRankMgmtConstant.NCKD_BEGINYEAR,QCP.less_equals,startDate);
+        filter.and(PerfRankMgmtConstant.NCKD_ENDYEAR,QCP.large_equals,startDate);
+        QueryFieldBuilder queryFieldBuilder = QueryFieldBuilder.create()
+                .addIdNumberName(PerfRankMgmtConstant.NCKD_PERSON);
+        DynamicObjectCollection query = QueryServiceHelper.query(PerfRankMgmtConstant.PERFMANAGER_ENTITYID, queryFieldBuilder.buildSelect(), new QFilter[]{filter});
+        Map<Long, DynamicObject> personMap = query.stream()
+                .filter(obj -> obj.getLong(String.join(".", PerfRankMgmtConstant.NCKD_PERSON, FormConstant.ID_KEY)) > 0)
+                .collect(Collectors.toMap(
+                        obj -> obj.getLong(String.join(".", PerfRankMgmtConstant.NCKD_PERSON, FormConstant.ID_KEY)),
+                        obj -> obj
+                ));
+        StringJoiner msgJoiner = new StringJoiner(StrFormatter.LINE_SEPARATOR);
+        for (DynamicObject entry : entries) {
+            long person = entry.getDynamicObject(PerfRankMgmtConstant.NCKD_PERSON).getLong(FormConstant.ID_KEY);
+            if (!personMap.containsKey(person)) {
+                String personName = entry.getDynamicObject(PerfRankMgmtConstant.NCKD_PERSON).getString(FormConstant.NAME_KEY);
+                msgJoiner.add(StrFormatter.format("人员【{}】没有【{}】年度的考核周期;", personName,theYear));
+            }
+        }
+        if(msgJoiner.length() > 0){
+            this.addFatalErrorMessage(rowDataEntity, msgJoiner.toString());
+        }
     }
 
     /**
@@ -191,12 +264,12 @@ public class PerfRankMgmtSaveValidate extends AbstractValidator {
                                int rowIndex, Set<Long> personIds, ValidationContext context) {
         DynamicObject person = entry.getDynamicObject(PerfRankMgmtConstant.NCKD_PERSON);
         if (person == null) {
-            this.addMessage(rowDataEntity, StrFormatter.format("第{}行,请选择人员\n", rowIndex));
+            this.addFatalErrorMessage(rowDataEntity, StrFormatter.format("第{}行,请选择人员\n", rowIndex));
         } else {
             // 人员重复校验
             long personId = person.getLong(FormConstant.ID_KEY);
             if (personIds.contains(personId)) {
-                this.addMessage(rowDataEntity, StrFormatter.format("第{}行,人员【{}】重复;\n", rowIndex, person.getString(FormConstant.NAME_KEY)));
+                this.addFatalErrorMessage(rowDataEntity, StrFormatter.format("第{}行,人员【{}】重复;\n", rowIndex, person.getString(FormConstant.NAME_KEY)));
             } else {
                 personIds.add(personId);
             }
@@ -279,19 +352,19 @@ public class PerfRankMgmtSaveValidate extends AbstractValidator {
         int excellents = data.getInt(PerfRankMgmtConstant.NCKD_EXCELLENTS);
 
         if (topRanks != context.totalRankCount) {
-            this.addMessage(rowDataEntity, "总排名人数与实际录入数据不一致;\n");
+            this.addFatalErrorMessage(rowDataEntity, "总排名人数与实际录入数据不一致;\n");
         }
         if (allowanceRanks != context.allowanceRankCount) {
-            this.addMessage(rowDataEntity, "R排名人数与实际录入数据不一致;\n");
+            this.addFatalErrorMessage(rowDataEntity, "R排名人数与实际录入数据不一致;\n");
         }
         if (fails != context.failCount) {
-            this.addMessage(rowDataEntity, "不合格人数与实际录入数据不一致;\n");
+            this.addFatalErrorMessage(rowDataEntity, "不合格人数与实际录入数据不一致;\n");
         }
         if (basics != context.basicCount) {
-            this.addMessage(rowDataEntity, "基本人数与实际录入数据不一致;\n");
+            this.addFatalErrorMessage(rowDataEntity, "基本人数与实际录入数据不一致;\n");
         }
         if (excellents != context.excellentCount) {
-            this.addMessage(rowDataEntity, "优秀人数与实际录入数据不一致;\n");
+            this.addFatalErrorMessage(rowDataEntity, "优秀人数与实际录入数据不一致;\n");
         }
     }
 
@@ -343,14 +416,14 @@ public class PerfRankMgmtSaveValidate extends AbstractValidator {
                 // 检查排名字段是否在有效范围内
                 int topRank = entry.getInt(PerfRankMgmtConstant.NCKD_TOPRANK);
                 if (topRank < 1 || topRank > totalRankCount) {
-                    this.addMessage(rowDataEntity,
+                    this.addFatalErrorMessage(rowDataEntity,
                             StrFormatter.format("第{}行,排名{}超出有效范围[1-{}]\n", i + 1, topRank, totalRankCount));
                 }
                 if (postAllowance) {
                     // 检查R排名字段是否在有效范围内
                     int allowanceRank = entry.getInt(PerfRankMgmtConstant.NCKD_ALLOWANCERANK);
                     if (allowanceRank < 1 || allowanceRank > totalRankCount) {
-                        this.addMessage(rowDataEntity,
+                        this.addFatalErrorMessage(rowDataEntity,
                                 StrFormatter.format("第{}行,R排名{}超出有效范围[1-{}]\n", i + 1, allowanceRank, totalRankCount));
                     }
                 }
@@ -378,7 +451,7 @@ public class PerfRankMgmtSaveValidate extends AbstractValidator {
                                        Map<Integer, Integer> rankCountMap) {
         int rankValue = entry.getInt(rankField);
         if (rankValue < 1) {
-            this.addMessage(rowDataEntity, StrFormatter.format("人员【{}】已勾选{},{}数不能小于1或为空",
+            this.addFatalErrorMessage(rowDataEntity, StrFormatter.format("人员【{}】已勾选{},{}数不能小于1或为空",
                     personName, type, rankType));
         } else {
             rankCountMap.put(rankValue, rankCountMap.getOrDefault(rankValue, 0) + 1);
@@ -431,7 +504,7 @@ public class PerfRankMgmtSaveValidate extends AbstractValidator {
             errorMsg.append("✅ 合规示例:[1,1,1,4] - 排名1重复3次,结束位置应为1+3-1=3,3不在列表中\n");
             errorMsg.append("【解决建议】\n");
             errorMsg.append("请调整重复排名的数值或移除冲突的排名,确保连续序列结束位置未被占用;\n\n");
-            this.addMessage(rowDataEntity, errorMsg.toString());
+            this.addFatalErrorMessage(rowDataEntity, errorMsg.toString());
         }
     }
 
@@ -511,7 +584,7 @@ public class PerfRankMgmtSaveValidate extends AbstractValidator {
                     detailMessage.append(StrFormatter.format("{}【{}】出现{}次;", rankType, rank, count));
                 }
                 detailMessage.append("\n");
-                this.addMessage(rowDataEntity, detailMessage.toString());
+                this.addFatalErrorMessage(rowDataEntity, detailMessage.toString());
             }
         }
     }
@@ -533,6 +606,7 @@ public class PerfRankMgmtSaveValidate extends AbstractValidator {
         int basicCount = 0;
         int excellentCount = 0;
         int qualifiedCount = 0;
+        Set<Long> personIds = new HashSet<>();
         Set<Integer> rankSet = new HashSet<>();  // 记录已出现的排名
         Set<Integer> duplicateRanks = new HashSet<>();  // 记录普通排名中重复的排名
         Set<Integer> duplicateRRanks = new HashSet<>();  // 记录R排名中重复的排名

+ 3 - 2
code/opmc/nckd-jxccl-opmc/src/main/java/nckd/jxccl/opmc/pm/common/PerfManagerFormConstant.java

@@ -14,6 +14,8 @@ public class PerfManagerFormConstant extends FormConstant {
     public static final String PERFMANAGER_ENTITYID = "nckd_perfmanager";
     /** 周期开始年份 */
     public static final String NCKD_BEGINYEAR = "nckd_beginyear";
+    /** 周期结束年份 */
+    public static final String NCKD_ENDYEAR = "nckd_endyear";
     /** 实际周期结束年份 */
     public static final String NCKD_ACTENDYEAR = "nckd_actendyear";
     /** 人员考评向导页面标识*/
@@ -26,8 +28,7 @@ public class PerfManagerFormConstant extends FormConstant {
     public static final String NCKD_THIRDYEARRESULT = "nckd_thirdyearresult";
     /** 调薪说明 */
     public static final String NCKD_WAGEEXPLAIN = "nckd_wageexplain";
-    /** 周期结束年份 */
-    public static final String NCKD_ENDYEAR = "nckd_endyear";
+
     /** 周期状态 */
     public static final String NCKD_THESTATUS = "nckd_thestatus";
     /** 锁定人 */

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

@@ -58,6 +58,7 @@ public class PerfManagerHelper {
     public static DynamicObject[] getNewestPerfManagerByPerson(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)

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

@@ -76,6 +76,7 @@ public class BatchEvalCycleFormPlugin extends AbstractFormPlugin implements Plug
                 Date beginYear = perfManager.getDate(PerfManagerFormConstant.NCKD_BEGINYEAR);
                 Date endYear = perfManager.getDate(PerfManagerFormConstant.NCKD_ENDYEAR);
                 this.getModel().setValue(PerfManagerFormConstant.PERFMANAGER_ENTITYID,perfManager, i);
+                this.getModel().setValue(PerfManagerFormConstant.NCKD_EMPPOSORGREL,perfManager.getDynamicObject(PerfManagerFormConstant.NCKD_EMPPOSORGREL), i);
                 this.getModel().setValue(PerfManagerFormConstant.NCKD_PERSON,perfManager.getDynamicObject(PerfManagerFormConstant.NCKD_PERSON), i);
                 this.getModel().setValue(PerfManagerFormConstant.NCKD_BEGINYEAR,beginYear, i);
                 this.getModel().setValue(PerfManagerFormConstant.NCKD_ENDYEAR,endYear, i);

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

@@ -1,6 +1,5 @@
 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;
@@ -19,7 +18,6 @@ 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;
@@ -99,6 +97,7 @@ public class CycleGenerateOpPlugin extends AbstractOperationServicePlugIn implem
         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_EMPPOSORGREL, value.getEmpPosOrgRel());
             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()));
@@ -231,7 +230,8 @@ public class CycleGenerateOpPlugin extends AbstractOperationServicePlugIn implem
      */
     private static DynamicObjectCollection queryNewHirePersons(QFilter filter) {
         QueryFieldBuilder queryFieldBuilder = QueryFieldBuilder.create()
-                .addIdNumberName(FormConstant.HRPI_EMPLOYEE);
+                .addIdNumberName(FormConstant.HRPI_EMPLOYEE)
+                .addGroup(new String[]{FormConstant.HRPI_EMPPOSORGREL},FormConstant.ID_KEY);
         QueryEntityType queryEntityType = (QueryEntityType) EntityMetadataCache.getDataEntityType(SystemQueryConstant.HSPM_ASSIGNMENTQUERY);
         return QueryEntityHelper.getInstance().getQueryDyoColl(queryEntityType, queryFieldBuilder.buildSelect(), new QFilter[]{filter}, null);
     }
@@ -245,6 +245,7 @@ public class CycleGenerateOpPlugin extends AbstractOperationServicePlugIn implem
             List<Long> orgIds = extractOrgIds(userAdminOrgWithSub.getHasPermOrgsWithSub());
             perfManagerFilter.and(String.join(".", FormConstant.HRPI_EMPPOSORGREL, FormConstant.ADMINORG), QCP.in, orgIds);
         }
+        perfManagerFilter.and(String.join(".", FormConstant.HRPI_EMPPOSORGREL, FormConstant.IS_SEQLATESTRECORD), QCP.equals,EnableEnum.YES.getCode());
         return perfManagerFilter;
     }
 
@@ -258,6 +259,7 @@ public class CycleGenerateOpPlugin extends AbstractOperationServicePlugIn implem
                 .add(PerfManagerFormConstant.NCKD_BEGINYEAR)
                 .add(PerfManagerFormConstant.NCKD_ENDYEAR)
                 .addIdNumberName(FormConstant.NCKD_PERSON)
+                .addGroup(new String[]{FormConstant.HRPI_EMPPOSORGREL},FormConstant.ID_KEY)
                 .addGroup(new String[]{PerfManagerFormConstant.PERFMANAGERENTRY}, FormConstant.ID_KEY, PerfManagerFormConstant.NCKD_APPRAISALYEAR)
                 .addIdNumberName(PerfManagerFormConstant.PERFMANAGERENTRY, PerfManagerFormConstant.NCKD_APPRAISALRESULT);
     }
@@ -282,14 +284,18 @@ public class CycleGenerateOpPlugin extends AbstractOperationServicePlugIn implem
                                                  LocalDateTime now) {
         for (DynamicObject person : newHirePersonList) {
             long personId = person.getLong(String.join(".", FormConstant.HRPI_EMPLOYEE, FormConstant.ID_KEY));
+            long empPosOrgRelId = person.getLong(String.join(".", FormConstant.HRPI_EMPPOSORGREL, FormConstant.ID_KEY));
             // 判断是否已有周期,如果有则不生成
             if (!personIds.contains(personId)) {
+                DynamicObject empPosOrgRel = EntityHelper.newEntity(FormConstant.HRPI_EMPPOSORGREL);
+                empPosOrgRel.set(FormConstant.ID_KEY, empPosOrgRelId);
+
                 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)), "周期生成(新入职)");
+                        cyclePerson,empPosOrgRel, DateUtil.toDate(now), DateUtil.toDate(DateUtil.addYears(now, 2)), "周期生成(新入职)");
                 personPerfInfo.setWhyEnd("新入职员工");
                 personPerfInfoMap.put(personId, personPerfInfo);
             }
@@ -315,17 +321,21 @@ public class CycleGenerateOpPlugin extends AbstractOperationServicePlugIn implem
             Row next = lastYearFailFilter.next();
             Long id = next.getLong(FormConstant.ID_KEY);
             Long personId = next.getLong(String.join(".", FormConstant.NCKD_PERSON, FormConstant.ID_KEY));
+            Long empPosOrgRelId = next.getLong(String.join(".", FormConstant.HRPI_EMPPOSORGREL, 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 empPosOrgRel = EntityHelper.newEntity(FormConstant.HRPI_EMPPOSORGREL);
+            empPosOrgRel.set(FormConstant.ID_KEY, empPosOrgRelId);
+
             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)), "周期生成");
+                    person,empPosOrgRel, DateUtil.toDate(now), DateUtil.toDate(DateUtil.addYears(now, 2)), "周期生成");
             personPerfInfo.setId(id);
 
             LocalDateTime endYearLocalDateTime = DateUtil.toLocalDateTime(endYear);
@@ -361,11 +371,13 @@ public class CycleGenerateOpPlugin extends AbstractOperationServicePlugIn implem
         Map<Long, String> personIdAndPersonNameMap = new HashMap<>();
         Map<Long, Long> personIdAndIdMap = new HashMap<>();
         Map<Long, Date> personIdAndEndDateMap = new HashMap<>();
+        Map<Long, Long> personIdAndEmpPosOrgRelMap = 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));
+            Long empPosOrgRelId = next.getLong(String.join(".", FormConstant.HRPI_EMPPOSORGREL, 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));
@@ -379,6 +391,7 @@ public class CycleGenerateOpPlugin extends AbstractOperationServicePlugIn implem
                 personIdAndPersonNameMap.put(personId, personName);
                 personIdAndIdMap.put(personId, id);
                 personIdAndEndDateMap.put(personId, endYear);
+                personIdAndEmpPosOrgRelMap.put(personId, empPosOrgRelId);
             }
         }
 
@@ -390,12 +403,15 @@ public class CycleGenerateOpPlugin extends AbstractOperationServicePlugIn implem
 
         for (Long filteredPersonId : filteredPersonIds) {
             String personName = personIdAndPersonNameMap.get(filteredPersonId);
+            DynamicObject empPosOrgRel = EntityHelper.newEntity(FormConstant.HRPI_EMPPOSORGREL);
+            empPosOrgRel.set(FormConstant.ID_KEY, personIdAndEmpPosOrgRelMap.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)), "周期生成");
+                    person,empPosOrgRel, DateUtil.toDate(now), DateUtil.toDate(DateUtil.addYears(now, 2)), "周期生成");
             personPerfInfo.setId(personIdAndIdMap.get(filteredPersonId));
             personPerfInfo.setWhyEnd("上年度无绩效结果");
 
@@ -423,16 +439,20 @@ public class CycleGenerateOpPlugin extends AbstractOperationServicePlugIn implem
             Row next = yearEvalEndFilter.next();
             Long id = next.getLong(FormConstant.ID_KEY);
             Long personId = next.getLong(String.join(".", FormConstant.NCKD_PERSON, FormConstant.ID_KEY));
+            Long empPosOrgRelId = next.getLong(String.join(".", FormConstant.HRPI_EMPPOSORGREL, 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 empPosOrgRel = EntityHelper.newEntity(FormConstant.HRPI_EMPPOSORGREL);
+                empPosOrgRel.set(FormConstant.ID_KEY, empPosOrgRelId);
+
                 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)), "周期生成");
+                        person,empPosOrgRel, DateUtil.toDate(now), DateUtil.toDate(DateUtil.addYears(now, 2)), "周期生成");
                 personPerfInfo.setId(id);
                 personPerfInfo.setWhyEnd("三年考评周期结束(周期生成)");
 

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

@@ -371,6 +371,7 @@ public class PerfManagerSaveOpPlugin extends AbstractOperationServicePlugIn impl
     public static class PersonPerfInfo {
         private  Long id;
         private final DynamicObject person;
+        private final DynamicObject empPosOrgRel;
         private final LocalDateTime beginYear;
         private final LocalDateTime endYear;
         private final String description;
@@ -379,23 +380,26 @@ public class PerfManagerSaveOpPlugin extends AbstractOperationServicePlugIn impl
         private String whyEnd;
         private LocalDateTime actEndYear;
 
-        public PersonPerfInfo(DynamicObject person, Date beginYear, Date endYear, String description) {
+        public PersonPerfInfo(DynamicObject person,DynamicObject empPosOrgRel, Date beginYear, Date endYear, String description) {
             this.person = person;
+            this.empPosOrgRel = empPosOrgRel;
             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) {
+        public PersonPerfInfo(DynamicObject person, DynamicObject empPosOrgRel, Date beginYear, Date endYear, String description, int dataEntityIndex) {
             this.person = person;
+            this.empPosOrgRel = empPosOrgRel;
             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) {
+        public PersonPerfInfo(DynamicObject person, DynamicObject empPosOrgRel, Date beginYear, Date endYear, String description, int dataEntityIndex, DynamicObjectCollection entrys) {
             this.person = person;
+            this.empPosOrgRel = empPosOrgRel;
             this.beginYear = DateUtil.toLocalDateTime(beginYear);
             this.endYear = DateUtil.toLocalDateTime(endYear);
             this.description = description;
@@ -423,6 +427,10 @@ public class PerfManagerSaveOpPlugin extends AbstractOperationServicePlugIn impl
             return person;
         }
 
+        public DynamicObject getEmpPosOrgRel() {
+            return empPosOrgRel;
+        }
+
         public DynamicObjectCollection getEntrys() {
             return entrys;
         }

+ 92 - 0
code/wtc/nckd-jxccl-wtc/src/main/java/nckd/jxccl/wtc/task/SyncPunchCardTask.java

@@ -0,0 +1,92 @@
+package nckd.jxccl.wtc.task;
+
+import com.alibaba.fastjson.JSON;
+import com.hikvision.artemis.sdk.ArtemisHttpUtil;
+import com.hikvision.artemis.sdk.config.ArtemisConfig;
+import kd.bos.context.RequestContext;
+import kd.bos.entity.param.CustomParam;
+import kd.bos.exception.KDException;
+import kd.bos.schedule.executor.AbstractTask;
+import kd.bos.servicehelper.parameter.SystemParamServiceHelper;
+import kd.sdk.plugin.Plugin;
+import nckd.jxccl.wtc.utils.SyncPunchCardHelper;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 后台任务插件
+ */
+public class SyncPunchCardTask extends AbstractTask implements Plugin {
+    private static final String ARTEMIS_PATH = "/artemis";
+    static final String getCamsApi = ARTEMIS_PATH + "/api/acs/v2/door/events";
+    static Map<String, String> path = new HashMap<String, String>(2) {
+        {
+            put("https://", getCamsApi);
+        }
+    };
+
+    public void initConfig() {
+        CustomParam customParam = new CustomParam();
+        customParam.getSearchKeySet().add("PUNCH_CARD_HOST");
+        customParam.getSearchKeySet().add("PUNCH_CARD_APP_KEY");
+        customParam.getSearchKeySet().add("PUNCH_CARD_APP_SECRET");
+        Map<String, String> cusTomMap = SystemParamServiceHelper.loadCustomParameterFromCache(customParam);
+        ArtemisConfig.host  = cusTomMap.get("PUNCH_CARD_HOST");
+        ArtemisConfig.appKey = cusTomMap.get("PUNCH_CARD_APP_KEY");
+        ArtemisConfig.appSecret = cusTomMap.get("PUNCH_CARD_APP_SECRET");
+    }
+
+    @Override
+    public void execute(RequestContext requestContext, Map<String, Object> map) throws KDException {
+        try {
+            initConfig();
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+            Calendar date = Calendar.getInstance();
+            System.out.println(sdf.format(date.getTime()));
+            String endTime = sdf.format(date.getTime()) + "T23:59:59+08:00";
+            Integer days = Integer.valueOf(map.get("days").toString());
+            date.set(Calendar.DATE, date.get(Calendar.DATE) - days);
+            System.out.println(sdf.format(date.getTime()));
+            String startTime = sdf.format(date.getTime()) + "T00:00:00+08:00";
+            getAllCards(startTime, endTime);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static void getAllCards(String startTime, String endTime) {
+        JSONObject firstPageJson = getOnePageCard(startTime, endTime, 1);
+        JSONArray firstPageListJson = firstPageJson.getJSONArray("list");
+        // 保存第一页
+        SyncPunchCardHelper.savePunchCardData(firstPageListJson);
+        Integer totalPage = firstPageJson.getInteger("totalPage");
+        if (totalPage <= 1) {
+            return;
+        }
+        // 从第二页开始保存
+        for(int i=2; i<=totalPage; i++) {
+            JSONObject pageJson = getOnePageCard(startTime, endTime, i);
+            JSONArray pageListJson = pageJson.getJSONArray("list");
+            SyncPunchCardHelper.savePunchCardData(pageListJson);
+        }
+    }
+
+    public static JSONObject getOnePageCard(String startTime, String endTime, int pageNo) {
+        JSONObject jsonBody = new JSONObject();
+        jsonBody.put("pageNo", pageNo);
+        jsonBody.put("pageSize", 1000);
+        jsonBody.put("startTime", startTime);
+        jsonBody.put("endTime", endTime);
+        jsonBody.put("userId", "admin");
+        String body = jsonBody.toString();
+        String result = ArtemisHttpUtil.doPostStringArtemis(path, body, null, null, "application/json", null);
+        JSONObject jsonData = JSON.parseObject(result).getJSONObject("data");
+        return jsonData;
+    }
+
+}