|
|
@@ -0,0 +1,322 @@
|
|
|
+package nckd.jxccl.opmc.pm.plugin.form.print;
|
|
|
+
|
|
|
+import kd.bos.algo.Algo;
|
|
|
+import kd.bos.algo.AlgoContext;
|
|
|
+import kd.bos.algo.DataSet;
|
|
|
+import kd.bos.algo.GroupbyDataSet;
|
|
|
+import kd.bos.algo.Row;
|
|
|
+import kd.bos.common.enums.EnableEnum;
|
|
|
+import kd.bos.dataentity.entity.DynamicObject;
|
|
|
+import kd.bos.dataentity.metadata.dynamicobject.DynamicObjectType;
|
|
|
+import kd.bos.entity.plugin.args.CustomPrintDataEntitiesArgs;
|
|
|
+import kd.bos.orm.query.QCP;
|
|
|
+import kd.bos.orm.query.QFilter;
|
|
|
+import kd.bos.print.core.data.DataRowSet;
|
|
|
+import kd.bos.print.core.data.datasource.CustomDataSource;
|
|
|
+import kd.bos.print.core.data.field.CollectionField;
|
|
|
+import kd.bos.print.core.data.field.Field;
|
|
|
+import kd.bos.print.core.data.field.IntegerField;
|
|
|
+import kd.bos.print.core.plugin.AbstractPrintPlugin;
|
|
|
+import kd.bos.print.core.plugin.event.AfterLoadDataEvent;
|
|
|
+import kd.bos.print.core.plugin.event.CustomDataLoadEvent;
|
|
|
+import kd.bos.servicehelper.QueryServiceHelper;
|
|
|
+import kd.hr.hbp.common.cache.HRAppCache;
|
|
|
+import kd.sdk.plugin.Plugin;
|
|
|
+import nckd.jxccl.base.common.algo.GroupMaxStrFunction;
|
|
|
+import nckd.jxccl.base.common.constant.FormConstant;
|
|
|
+import nckd.jxccl.base.common.utils.ConvertUtil;
|
|
|
+import nckd.jxccl.base.common.utils.DateUtil;
|
|
|
+import nckd.jxccl.base.common.utils.QueryFieldBuilder;
|
|
|
+
|
|
|
+import java.time.LocalDate;
|
|
|
+import java.time.LocalDateTime;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.Collection;
|
|
|
+import java.util.Date;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+/**
|
|
|
+* 绩效结果打印
|
|
|
+* @author W.Y.C
|
|
|
+* @date 2026/1/6 15:15
|
|
|
+* @version 1.0
|
|
|
+*/
|
|
|
+public class EmployeePrintPlugin extends AbstractPrintPlugin implements Plugin {
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void loadCustomData(CustomDataLoadEvent evt) {
|
|
|
+ Date printYear = HRAppCache.get("nckd_pm").get("printYear", Date.class);
|
|
|
+
|
|
|
+ // 1. 获取数据源标识
|
|
|
+ CustomDataSource dataSource = evt.getDataSource();
|
|
|
+ if (!"custom".equals(dataSource.getDsName())) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 获取或创建数据结果集
|
|
|
+ List<DataRowSet> customRows = evt.getCustomDataRows();
|
|
|
+
|
|
|
+ // 3. 组装数据(例如:从数据库查询、计算衍生字段等)
|
|
|
+
|
|
|
+ DataRowSet rowSet = new DataRowSet();
|
|
|
+ rowSet.put("year", new IntegerField(getYearIndexFromPrintYear(DateUtil.getYear(printYear))));
|
|
|
+ int printYearValue = DateUtil.getYear(printYear);
|
|
|
+ rowSet.put("yearIndex", new IntegerField(getYearIndexFromPrintYear(printYearValue)));
|
|
|
+
|
|
|
+ // 4. 将数据添加到结果集
|
|
|
+ customRows.add(rowSet);
|
|
|
+ evt.setDataSource(dataSource);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据年份获取对应的序号,近5年(以当前年份为基准)的序号分别为5/4/3/2/1
|
|
|
+ * 例如:当前年是2026,printYear 为2025,则序号为5(上年)
|
|
|
+ * @param year 年份
|
|
|
+ * @return 序号,最近的年份序号为5,依次递减
|
|
|
+ */
|
|
|
+ private int getYearIndexFromPrintYear(int year) {
|
|
|
+ int currentYear = LocalDate.now().getYear();
|
|
|
+
|
|
|
+ // 计算最近5年的范围,即从当前年份往前推5年到前1年(不包含当前年)
|
|
|
+ // 例如:当前年是2026年,最近5年是2021-2025,即 [2021, 2022, 2023, 2024, 2025]
|
|
|
+ int startYear = currentYear - 5; // 最早的年份
|
|
|
+ int endYear = currentYear - 1; // 最近的年份
|
|
|
+
|
|
|
+ // 检查年份是否在最近5年范围内
|
|
|
+ if (year >= startYear && year <= endYear) {
|
|
|
+ // 计算序号:最近的年份(endYear)序号为5,前一年为4,以此类推
|
|
|
+ // 序号 = (year - startYear) + 1
|
|
|
+ // 例如:当前年是2026年,startYear=2021,endYear=2025
|
|
|
+ // 2025 (最近): 2025 - 2021 + 1 = 5 ✓
|
|
|
+ // 2024: 2024 - 2021 + 1 = 4 ✓
|
|
|
+ // 2023: 2023 - 2021 + 1 = 3 ✓
|
|
|
+ // 2022: 2022 - 2021 + 1 = 2 ✓
|
|
|
+ // 2021 (最远): 2021 - 2021 + 1 = 1 ✓
|
|
|
+ return year - startYear + 1;
|
|
|
+ } else {
|
|
|
+ // 如果不在最近5年内,返回0表示超出范围
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void afterLoadData(AfterLoadDataEvent evt) {
|
|
|
+ String dsName = evt.getDataSource().getDsName();
|
|
|
+ if(!dsName.equalsIgnoreCase("custom")) {
|
|
|
+ Date printYear = HRAppCache.get("nckd_pm").get("printYear", Date.class);
|
|
|
+ Map<Integer, Long> selectPrintPersonIdMap = HRAppCache.get("nckd_pm").get("employeePrintIdMap", Map.class);
|
|
|
+ List<String> seqs = ConvertUtil.toList(selectPrintPersonIdMap.keySet(), ArrayList::new);
|
|
|
+ LocalDate now = LocalDate.now();
|
|
|
+ // 获取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));
|
|
|
+
|
|
|
+ Map<String, String> positionMap = new HashMap<>();
|
|
|
+ Map<String, Integer> perfRankMgmtMap = new HashMap<>();
|
|
|
+ try (AlgoContext context = Algo.newContext()) {
|
|
|
+ Collection<Long> personIds = selectPrintPersonIdMap.values();
|
|
|
+ DataSet positionDateSet = getPositions(fiveYearsAgo, currentYear, personIds);
|
|
|
+ if (positionDateSet != null) {
|
|
|
+ while (positionDateSet.hasNext()) {
|
|
|
+ Row next = positionDateSet.next();
|
|
|
+ Long personId = next.getLong(String.join(".", FormConstant.EMPLOYEE_KEY, FormConstant.ID_KEY));
|
|
|
+ int tempIndex = 1;
|
|
|
+ for (int year = fiveYearsAgo; year <= currentYear; year++) {
|
|
|
+ String positionName = next.getString("position_" + tempIndex);
|
|
|
+ positionMap.put(personId + "nckd_position_" + tempIndex, positionName);
|
|
|
+ tempIndex++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ DataSet perfRankMgmtDataSet = getPerfRankMgmt(personIds, fiveYearsAgo, currentYear);
|
|
|
+ if (perfRankMgmtDataSet != null) {
|
|
|
+ while (perfRankMgmtDataSet.hasNext()) {
|
|
|
+ Row next = perfRankMgmtDataSet.next();
|
|
|
+ Long personId = next.getLong(String.join(".", FormConstant.NCKD_PERSON, FormConstant.ID_KEY));
|
|
|
+ int tempIndex = 1;
|
|
|
+ for (int year = fiveYearsAgo; year <= currentYear; year++) {
|
|
|
+ Integer topran = next.getInteger("perfrank_topran_" + tempIndex);
|
|
|
+ Integer topranks = next.getInteger("perfrank_topranks_" + tempIndex);
|
|
|
+ perfRankMgmtMap.put(personId + "nckd_perfrank_topran_" + tempIndex, topran);
|
|
|
+ perfRankMgmtMap.put(personId + "nckd_perfrank_topranks_" + tempIndex, topranks);
|
|
|
+ tempIndex++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // 获取当前打印的数据集并准备过滤后的集合
|
|
|
+ // 2. 获取当前打印的数据集
|
|
|
+ List<DataRowSet> dataRowSets = evt.getDataRowSets();
|
|
|
+ List<DataRowSet> filteredDataRowSets = new ArrayList<>();
|
|
|
+ for (DataRowSet dataRowSet : dataRowSets) {
|
|
|
+ if (dataRowSet.containerKey("reportEntry")) {
|
|
|
+ CollectionField entryField = (CollectionField) dataRowSet.getField("reportEntry");
|
|
|
+ List<DataRowSet> entryRows = entryField.getValue();
|
|
|
+ List<DataRowSet> filteredEntryRows = new ArrayList<>();
|
|
|
+ for (DataRowSet entryRow : entryRows) {
|
|
|
+ String seq = ConvertUtil.toStr(entryRow.getField(FormConstant.SEQ_KEY).getValue());
|
|
|
+ Long personId = selectPrintPersonIdMap.get(seq + "");
|
|
|
+ // 判断该分录行是否在用户勾选的列表中
|
|
|
+ if (seqs.contains(seq)) {
|
|
|
+ filteredEntryRows.add(entryRow); // 保留被勾选的行
|
|
|
+ }
|
|
|
+ for (String fieldKey : entryRow.getFieldKeys()) {
|
|
|
+ if (fieldKey.startsWith("nckd_position_")) {
|
|
|
+ String positionName = positionMap.get(personId + fieldKey);
|
|
|
+ Field field = entryRow.getField(fieldKey);
|
|
|
+ field.setValue(positionName != null ? positionName : "");
|
|
|
+ entryRow.put(fieldKey, field);
|
|
|
+ }
|
|
|
+ if (fieldKey.startsWith("nckd_perfrank_topran_")) {
|
|
|
+ Integer perfRankMgmt = perfRankMgmtMap.get(personId + fieldKey);
|
|
|
+ Field field = entryRow.getField(fieldKey);
|
|
|
+ field.setValue(perfRankMgmt != null ? perfRankMgmt : 0);
|
|
|
+ entryRow.put(fieldKey, field);
|
|
|
+ }
|
|
|
+ if (fieldKey.startsWith("nckd_perfrank_topranks_")) {
|
|
|
+ Integer perfRankMgmt = perfRankMgmtMap.get(personId + fieldKey);
|
|
|
+ Field field = entryRow.getField(fieldKey);
|
|
|
+ field.setValue(perfRankMgmt != null ? perfRankMgmt : 0);
|
|
|
+ entryRow.put(fieldKey, field);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 用过滤后的分录数据替换原数据
|
|
|
+ if (!filteredEntryRows.isEmpty()) {
|
|
|
+ DataRowSet newDataRowSet = dataRowSet.deepCopy(); // 深拷贝,避免影响原数据
|
|
|
+ newDataRowSet.put("reportEntry", new CollectionField(filteredEntryRows));
|
|
|
+ filteredDataRowSets.add(newDataRowSet);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ // 将过滤后的数据集设置回事件,实现只打印勾选行
|
|
|
+ evt.setDataRowSets(filteredDataRowSets);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ private static DataSet getPerfRankMgmt(Collection<Long> personIds, int beginYear, int endYear) {
|
|
|
+ // 年度绩效排名管理
|
|
|
+ QueryFieldBuilder perfRankMgmtFieldBuilder = QueryFieldBuilder.create()
|
|
|
+ .addIdNumberName(FormConstant.NCKD_PERSON)
|
|
|
+ .add("nckd_toprank")
|
|
|
+ .addGroup(new String[]{"nckd_perfrankmgmt"},"nckd_topranks","nckd_theyear");
|
|
|
+ QFilter perfRankMgmtFilter = new QFilter("nckd_isranking", "=", EnableEnum.YES.getCode())
|
|
|
+ .and(FormConstant.NCKD_PERSON, QCP.in, personIds)
|
|
|
+ .and("nckd_perfrankmgmt.nckd_theyear",QCP.large_equals, beginYear)
|
|
|
+ .and("nckd_perfrankmgmt.nckd_theyear",QCP.less_equals, endYear);
|
|
|
+ DataSet perfRankMgmtDataSet = QueryServiceHelper.queryDataSet("PrintPerfDetailReportListDataPlugin_perfRank", "nckd_perfrankmgmtentry", perfRankMgmtFieldBuilder.buildSelect(), new QFilter[]{perfRankMgmtFilter}, perfRankMgmtFieldBuilder.buildOrder(), 10000);
|
|
|
+ int tempIndex = 1;
|
|
|
+ for (int year = beginYear; year <= endYear; year++) {
|
|
|
+ //排名
|
|
|
+ String toprankField = "CASE WHEN nckd_perfrankmgmt.nckd_theyear = "+year+" THEN nckd_toprank ELSE null END";
|
|
|
+ perfRankMgmtDataSet = perfRankMgmtDataSet.addField(toprankField, "perfrankmgmt_topran_temp_" + tempIndex);
|
|
|
+ //总人数
|
|
|
+ String topranksField = "CASE WHEN nckd_perfrankmgmt.nckd_theyear = "+year+" THEN nckd_perfrankmgmt.nckd_topranks ELSE null END";
|
|
|
+ perfRankMgmtDataSet = perfRankMgmtDataSet.addField(topranksField, "perfrankmgmt_topranks_temp_" + tempIndex);
|
|
|
+ tempIndex++;
|
|
|
+ }
|
|
|
+ GroupbyDataSet groupbyDataSet = perfRankMgmtDataSet.groupBy(new String[]{String.join(".", FormConstant.NCKD_PERSON, FormConstant.ID_KEY)});
|
|
|
+ tempIndex = 1;
|
|
|
+ for (int year = beginYear; year <= endYear; year++) {
|
|
|
+ groupbyDataSet.max("perfrankmgmt_topran_temp_"+tempIndex, "perfrank_topran_"+tempIndex);
|
|
|
+ groupbyDataSet.max("perfrankmgmt_topranks_temp_"+tempIndex, "perfrank_topranks_"+tempIndex);
|
|
|
+ tempIndex++;
|
|
|
+ }
|
|
|
+ return groupbyDataSet.finish();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取近N年的岗位信息
|
|
|
+ * 用于查询每个员工在指定年份范围内每年年底(12月31日)所在的有效岗位
|
|
|
+ * 当一个员工在同一年有多个有效岗位时,选择结束日期最晚的那个岗位
|
|
|
+ *
|
|
|
+ * @param beginYear 起始年份(包含)
|
|
|
+ * @param endYear 结束年份(包含)
|
|
|
+ * @return 包含每个员工每年岗位信息的数据集
|
|
|
+ */
|
|
|
+ private DataSet getPositions(int beginYear,int endYear,Collection<Long> personIds){
|
|
|
+ // 查询全职任职关系,获取员工、岗位、任职起止时间等信息
|
|
|
+ QueryFieldBuilder queryFieldBuilder = QueryFieldBuilder.create()
|
|
|
+ .add(FormConstant.STARTDATE) // 任职开始日期
|
|
|
+ .add(FormConstant.ENDDATE) // 任职结束日期
|
|
|
+ .addIdNumberName(FormConstant.POSITION_KEY) // 岗位信息(ID、编码、名称)
|
|
|
+ .addIdNumberNameWithExtras(new String[]{FormConstant.EMPLOYEE_KEY},FormConstant.EMP_NUMBER_KEY) // 员工信息(ID、编码、姓名)+ 工号
|
|
|
+ .orderDesc(FormConstant.EMPLOYEE_KEY,FormConstant.STARTDATE); // 按员工ID和开始日期降序排列
|
|
|
+ // 筛选条件:只查询全职任职(IS_PRIMARY = YES)
|
|
|
+ QFilter qFilter = new QFilter(FormConstant.IS_PRIMARY,QCP.equals, EnableEnum.YES.getCode());
|
|
|
+ if(!personIds.isEmpty()){
|
|
|
+ qFilter = new QFilter(FormConstant.EMPLOYEE_KEY,QCP.in,personIds.toArray());
|
|
|
+ }
|
|
|
+ DataSet dataSet = QueryServiceHelper.queryDataSet("PrintPerfDetailReportListDataPlugin_EMPREL", FormConstant.HRPI_EMPPOSORGREL, queryFieldBuilder.buildSelect(), new QFilter[]{qFilter}, queryFieldBuilder.buildOrder(), 1000000);
|
|
|
+
|
|
|
+ int tempIndex = 1;
|
|
|
+ // 遍历指定年份范围,为每一年生成对应的岗位查询字段
|
|
|
+ for (int year = beginYear; year <= endYear; year++) {
|
|
|
+ // 获取每年12-31 23:59:59作为目标日期,用于判断该员工当年年底的岗位状态
|
|
|
+ Date targetDate = DateUtil.toDate(LocalDateTime.of(year, 12, 31, 23, 59, 59));
|
|
|
+ String targetDateStr = DateUtil.format(targetDate, DateUtil.NORM_DATE_PATTERN); // 使用日期格式而非日期时间格式
|
|
|
+
|
|
|
+ // 创建复合字段:如果在目标日期员工处于有效任职状态,则生成"结束日期|岗位名称"格式的字符串
|
|
|
+ // 这样使用GroupMaxStrFunction时,会根据结束日期选择最晚的那个任职记录
|
|
|
+ //为什么用“00000000000000|”,不用null。因为当GroupMaxStrFunction进行字符串比较时,有效的日期值(如'20231231235959|经济技术部部长')会比'00000000000000|'大,从而被正确选择
|
|
|
+ String combinedField = "CASE WHEN " + FormConstant.STARTDATE + " <= to_date('" + targetDateStr + "','yyyy-MM-dd') " +
|
|
|
+ "AND " + FormConstant.ENDDATE + " >= to_date('" + targetDateStr + "','yyyy-MM-dd') " +
|
|
|
+ "THEN CONCAT(TO_CHAR(" + FormConstant.ENDDATE + ", 'YYYYMMDDHHmmss'), '|', " +
|
|
|
+ String.join(".", FormConstant.POSITION_KEY, FormConstant.NAME_KEY) + ") ELSE '00000000000000|' END";
|
|
|
+
|
|
|
+ // 添加岗位复合字段(包含结束日期和岗位名称)
|
|
|
+ dataSet = dataSet.addField(combinedField, "position_temp_" + tempIndex);
|
|
|
+
|
|
|
+ // 同时创建仅包含日期部分的字段,用于后续解析时去除日期部分,只保留岗位名称
|
|
|
+ String dateField = "CASE WHEN " + FormConstant.STARTDATE + " <= to_date('" + targetDateStr + "','yyyy-MM-dd') " +
|
|
|
+ "AND " + FormConstant.ENDDATE + " >= to_date('" + targetDateStr + "','yyyy-MM-dd') " +
|
|
|
+ "THEN CONCAT(TO_CHAR(" + FormConstant.ENDDATE + ", 'YYYYMMDDHHmmss'),'|') ELSE '00000000000000|' END";
|
|
|
+ dataSet = dataSet.addField(dateField, "position_date_" + tempIndex);
|
|
|
+
|
|
|
+// dataSet = dataSet.addField("'"+year+"'", "position_year_" + tempIndex);
|
|
|
+ tempIndex++;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 行转列处理:按员工ID分组,使用GroupMaxStrFunction获取每个员工每年结束日期最晚的任职记录
|
|
|
+ tempIndex = 1;
|
|
|
+ GroupMaxStrFunction groupMaxStr = new GroupMaxStrFunction();
|
|
|
+ // 按员工ID进行分组聚合
|
|
|
+ GroupbyDataSet groupbyDataSet = dataSet.groupBy(new String[]{String.join(".", FormConstant.EMPLOYEE_KEY, FormConstant.ID_KEY)});
|
|
|
+ // 对每一年的数据进行聚合,获取结束日期最晚的任职记录
|
|
|
+ for (int year = beginYear; year <= endYear; year++) {
|
|
|
+ // 获取岗位复合字段的最大值(即结束日期最晚的记录)
|
|
|
+ groupbyDataSet.agg(new GroupMaxStrFunction(),"position_temp_"+tempIndex, "position_max_"+tempIndex);
|
|
|
+ // 同时获取日期字段的最大值,用于后续解析
|
|
|
+ groupbyDataSet.agg(groupMaxStr,"position_date_"+tempIndex, "position_date_max_"+tempIndex);
|
|
|
+ tempIndex++;
|
|
|
+ }
|
|
|
+ DataSet result = groupbyDataSet.finish();
|
|
|
+
|
|
|
+ // 解析复合字段,提取真正的岗位名称
|
|
|
+ // 通过去除日期部分(YYYYMMDDHHmmss|)来获取原始的岗位名称
|
|
|
+ tempIndex = 1;
|
|
|
+ for (int year = beginYear; year <= endYear; year++) {
|
|
|
+ final int currentYearIndex = year - beginYear + 1;
|
|
|
+ result = result.addField(
|
|
|
+ // 如果岗位字段有效(不为默认值'00000000000000|'),则去除日期部分,只保留岗位名称
|
|
|
+ "CASE WHEN position_max_" + tempIndex + " IS NOT NULL AND position_max_" + tempIndex + " != '00000000000000|' THEN " +
|
|
|
+ "REPLACE(position_max_" + tempIndex + ",position_date_max_"+tempIndex+",'') " + // 去除日期部分,得到岗位名称
|
|
|
+ "ELSE null END",
|
|
|
+ "position_" + tempIndex // 最终的岗位名称字段
|
|
|
+ );
|
|
|
+ tempIndex++;
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+}
|