Procházet zdrojové kódy

feat(swc): 新增延迟退休日期计算及预警任务插件

新增后台任务插件 DelayedRetirementDateCalcTask,用于根据中国延迟退休政策计算员工的法定退休日期与延迟后退休日期,并在法定退休日期前3个月向HR发出预警信息。该插件会自动更新员工社保档案中的退休日期,并将预警人员信息保存至退休预警单据中,支持按性别、工种等条件计算不同退休年龄及延迟月数,满足业务对退休人员的提前通知需求。
turborao před 1 týdnem
rodič
revize
fd193deeac

+ 502 - 0
code/swc/nckd-jxccl-swc/src/main/java/nckd/jxccl/swc/init/plugin/other/DelayedRetirementDateCalcTask.java

@@ -0,0 +1,502 @@
+package nckd.jxccl.swc.init.plugin.other;
+
+import kd.bos.context.RequestContext;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.exception.KDException;
+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.schedule.executor.AbstractTask;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.servicehelper.QueryServiceHelper;
+import kd.bos.servicehelper.operation.SaveServiceHelper;
+import kd.bos.util.StringUtils;
+import kd.sdk.plugin.Plugin;
+
+
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.Period;
+import java.time.ZoneId;
+import java.time.temporal.ChronoUnit;
+import java.util.*;
+import java.util.stream.Collectors;
+
+
+/**
+ * 后台任务插件
+ * author: turborao
+ * date: 2025/10/20
+ * note: 延迟退休业务:对即将退休的人员,发出预警消息(预警信息要在原法定退休日期前3个月发给HR,
+ * 并且在员工最终确认生效的退休日期提前3个月再次预警)(HR),
+ * 业务人员收到消息,发送业务通知,告诉员工,原法定退休日期、新法定退休日期、
+ * 员工确认一个最终生效的时间(这些仅为业务人员的线下业务,不作为系统流程),
+ * 系统记录日期结果。延迟退休的算法可以根据中国人力资源和社会保障部的规定进行计算
+ */
+public class DelayedRetirementDateCalcTask  extends AbstractTask implements Plugin {
+    private static Log logger = LogFactory.getLog(DelayedRetirementDateCalcTask.class);
+
+    @Override
+    public void execute(RequestContext requestContext, Map<String, Object> map) throws KDException {
+
+        logger.info("延迟退休  开始执行任务");
+        /**
+         * 先计算员工社保档案(hcsi_sinsurfile)单据中延迟退休时间
+         */
+        int count = calcRetirementDate();
+        logger.info("延迟退休计算完成,本次更新数量:{};", count);
+        /**
+         * 然后将前三个月预警(法定退休日期)的退休员工保存到退休人员信息(nckd_retirewarning)单据中
+         */
+        int count1 = saveRetireWarning();
+        logger.info("延迟退休预警信息保存完成,本次更新数量:{};", count1);
+
+        logger.info("延迟退休  任务执行完成");
+    }
+
+    @Override
+    public boolean isSupportReSchedule() {
+        return true;
+    }
+
+    /**
+     * 将前三个月预警(法定退休日期)的退休员工保存到退休人员信息(nckd_retirewarning)单据中
+     * @return
+     * @throws KDException
+     */
+    public int saveRetireWarning() throws KDException {
+        // 假设你有一个 Date 对象
+        Date currentDate = new Date(); // 当前日期
+        // 将 Date 转换为 Instant 并减去8天
+        Instant ninetyDaysAgoInstant = currentDate.toInstant().plus(90, ChronoUnit.DAYS);
+        // 将 Instant 转换回 Date
+        Date ninetyDaysAgoDate = Date.from(ninetyDaysAgoInstant);
+        //取员工档案
+        String billKey = "hcsi_sinsurfile";
+        QFilter filter1 = new QFilter("iscurrentversion", QCP.equals, true); // 启用
+        QFilter filter2 = new QFilter("datastatus", QCP.equals, "1"); // 启用
+        QFilter filter3 = new QFilter("nckd_legaldate", QCP.large_equals, currentDate);
+        QFilter filter4 = new QFilter("nckd_legaldate", QCP.less_equals, ninetyDaysAgoDate);
+
+        String selectFields="id,employee.id,employee.number,employee.name,certificatenumber,nckd_recorddate,nckd_legaldate,nckd_newlegaldate";
+        DynamicObject[] recordDyn = BusinessDataServiceHelper.load(billKey, selectFields, new QFilter[]{filter1.and(filter2).and(filter3).and(filter4)});
+
+        Map<Long, DynamicObject> employeeFileMaps = Arrays.stream(recordDyn).collect(
+                Collectors.toMap(
+                        obj -> obj.getLong("employee.id"),
+                        obj -> obj,
+                        (k1, k2) -> k1
+                )
+        );
+        //将Map转换为List
+        List<Long> employeeIDs = employeeFileMaps.keySet().stream().collect(Collectors.toList());
+
+        ///查询 公司信息
+        QFilter filter7 = new QFilter("iscurrentdata", QCP.equals, true); // 启用
+        //QFilter filter8 = new QFilter("datastatus", QCP.equals, "1"); // 启用
+        QFilter filter9 = new QFilter("employee.id", QCP.in, employeeIDs);
+
+        String selectFields1 = "id,employee.id,company.id";
+        DynamicObjectCollection empposorgreDyns = QueryServiceHelper.query("hrpi_empposorgrel", selectFields1, new QFilter[]{filter7 ,filter9});
+
+        Map<Long, DynamicObject> empposorgreMap = (Map)empposorgreDyns.stream().collect(Collectors.toMap((obj) -> {
+            return obj.getLong("employee.id");
+        }, (obj) -> {
+            return obj;
+        }, (k1, k2) -> {
+            return k1;
+        }));
+
+
+        // 查人员基础信息中的生日和身份证号
+
+        QFilter filter12 = new QFilter("credentialstype.id", QCP.equals, 1010L); // 启用
+        QFilter filter13 = new QFilter("employee.id", QCP.in, employeeIDs);
+        String selectFields2="id,employee.id,number,birthday";
+        DynamicObjectCollection personDyns = QueryServiceHelper.query("hrpi_percre", selectFields2,new QFilter[]{ filter12, filter13});
+
+        Map<Long, DynamicObject> personMap = (Map)personDyns.stream().collect(Collectors.toMap((obj) -> {
+            return obj.getLong("employee.id");
+        }, (obj) -> {
+            return obj;
+        }, (k1, k2) -> {
+            return k1;
+        }));
+
+        // 查人员基础信息中的员工用工关系状态
+        QFilter filter21 = new QFilter("iscurrentdata", QCP.equals, true); // 启用
+        QFilter filter22 = new QFilter("ishired", QCP.equals, "1"); // 是否在职
+        QFilter filter23 = new QFilter("employee.id", QCP.in, employeeIDs);
+
+        String selectFields3="id,employee.id,laborrelstatus.id";
+        DynamicObjectCollection empentrelDyns = QueryServiceHelper.query("hrpi_empentrel", selectFields3,new QFilter[]{filter21, filter22, filter23});
+
+        Map<Long, DynamicObject> empentrelMap = (Map)empentrelDyns.stream().collect(Collectors.toMap((obj) -> {
+            return obj.getLong("employee.id");
+        }, (obj) -> {
+            return obj;
+        }, (k1, k2) -> {
+            return k1;
+        }));
+
+        //查退休人员信息数据
+        QFilter qFilter = new QFilter("nckd_person.id", QCP.in, employeeIDs);
+        List<Object> listIDs = QueryServiceHelper.queryPrimaryKeys("nckd_retirewarning",new QFilter[]{qFilter}, null, Integer.MAX_VALUE);
+        DynamicObject retirewarnDyn =  BusinessDataServiceHelper.newDynamicObject("nckd_retirewarning");
+        DynamicObject[] retirewarnDyns = BusinessDataServiceHelper.load(listIDs.toArray(),retirewarnDyn.getDynamicObjectType());
+
+        Map<Long, DynamicObject> retirewarnMap =
+                Arrays.stream(retirewarnDyns)
+                        .collect(Collectors.toMap(
+                                detail -> detail.getLong("nckd_person.id"),
+                                detail -> detail, // 整个 DynamicObject 作为 value
+                                (existing, replacement) -> existing // 保留前面的值
+                        ));
+
+        //在职员工ID  将Map转换为List
+        List<Long> employeeHiredIDs = empentrelMap.keySet().stream().collect(Collectors.toList());
+
+
+        ArrayList<DynamicObject> retirewarnDynList = new ArrayList<>();
+
+        for (Long employeeID : employeeHiredIDs) {
+            DynamicObject dyn = retirewarnMap.get(employeeID);
+            DynamicObject employeeFileDyn = employeeFileMaps.get(employeeID);
+            if(dyn ==  null){
+                dyn = BusinessDataServiceHelper.newDynamicObject("nckd_retirewarning");
+                dyn.set("billno", employeeFileDyn.getString("employee.number"));
+                //员工
+                dyn.set("nckd_person",employeeID);
+                dyn.set("billstatus", "A");
+            }
+            if(employeeFileDyn != null) {
+                Date startDate = employeeFileDyn.getDate("nckd_recorddate");
+                Date legaldate = employeeFileDyn.getDate("nckd_legaldate");
+                Date newlegaldate = employeeFileDyn.getDate("nckd_newlegaldate");
+                dyn.set("nckd_recorddate", employeeFileDyn.getDate("nckd_recorddate"));
+
+
+                if(legaldate != null) {
+                    dyn.set("nckd_legaldate", legaldate);
+                    String oldYear = caclOldYear(startDate, legaldate);
+                    dyn.set("nckd_legalage", oldYear);
+                }
+                if(newlegaldate != null){
+                    dyn.set("nckd_newlegaldate", newlegaldate);
+                    String newOldYear = caclOldYear(startDate, newlegaldate);
+                    dyn.set("nckd_newlegalage", newOldYear);
+                }
+            }
+            DynamicObject employeeDyn = personMap.get(employeeID);
+            if(employeeDyn != null) {
+                Date birthday =  employeeDyn.getDate("birthday");
+                if(birthday != null) {
+                    dyn.set("nckd_birthday", birthday);
+                }
+                dyn.set("nckd_idcard", employeeDyn.getString("number"));
+            }
+            DynamicObject empposorgreDyn = empposorgreMap.get(employeeID);
+            if(empposorgreDyn != null) {
+                dyn.set("nckd_company",empposorgreDyn.getLong("company.id"));
+            }
+            DynamicObject empentreDyn = empentrelMap.get(employeeID);
+            if(empentreDyn != null) {
+                dyn.set("nckd_laborreltype",empentreDyn.getLong("laborrelstatus.id"));
+            }
+
+            retirewarnDynList.add(dyn);
+
+        }
+
+        int length = 0;
+        if(retirewarnDynList.size() != 0) {
+            //保存
+            Object[] update = SaveServiceHelper.save(retirewarnDynList.toArray(new DynamicObject[0]));
+            length = update.length;
+
+            return length;
+        }
+        return length;
+
+    }
+
+    /**
+     * 计算延迟退休时间
+     * @throws KDException
+     */
+    public int calcRetirementDate() throws KDException {
+        // 假设你有一个 Date 对象
+        Date currentDate = new Date(); // 当前日期
+        // 将 Date 转换为 Instant 并减去8天
+        Instant threeDaysAgoInstant = currentDate.toInstant().minus(8, ChronoUnit.DAYS);
+        // 将 Instant 转换回 Date
+        Date threeDaysAgoDate = Date.from(threeDaysAgoInstant);
+        //取员工档案
+        String billKey = "hcsi_sinsurfile";
+        QFilter filter1 = new QFilter("iscurrentversion", QCP.equals, true); // 启用
+        QFilter filter2 = new QFilter("datastatus", QCP.equals, "1"); // 启用
+        QFilter filter5 = new QFilter("modifytime", QCP.large_equals, threeDaysAgoDate);
+
+        String selectFields="id,employee.id,employee.number,nckd_recorddate,nckd_legaldate,nckd_newlegaldate";
+        DynamicObject[] datas = BusinessDataServiceHelper.load(billKey, selectFields, new QFilter[]{filter1.and(filter2).and(filter5)});
+
+        Map<Long, DynamicObject> employeeFileMaps = Arrays.stream(datas).collect(
+                Collectors.toMap(
+                        obj -> obj.getLong("employee.id"),
+                        obj -> obj,
+                        (k1, k2) -> k1
+                )
+        );
+        //将Map转换为List
+        List<Long> employeeIDs = employeeFileMaps.keySet().stream().collect(Collectors.toList());
+
+        ///查询 干部信息
+        QFilter filter7 = new QFilter("iscurrentdata", QCP.equals, true); // 启用
+        //QFilter filter8 = new QFilter("datastatus", QCP.equals, "1"); // 启用
+        QFilter filter9 = new QFilter("employee.id", QCP.in, employeeIDs);
+
+        String selectFields1 = "id,employee.id,employee.number,position.nckd_jobseq.number";
+        DynamicObjectCollection empposorgreDyns = QueryServiceHelper.query("hrpi_empposorgrel", selectFields1, new QFilter[]{filter7 ,filter9});
+
+        Map<Long, DynamicObject> empposorgreMap = (Map)empposorgreDyns.stream().collect(Collectors.toMap((obj) -> {
+            return obj.getLong("employee.id");
+        }, (obj) -> {
+            return obj;
+        }, (k1, k2) -> {
+            return k1;
+        }));
+
+        QFilter filter11 = new QFilter("iscurrentversion", QCP.equals, true); // 启用
+        QFilter filter12 = new QFilter("datastatus", QCP.equals, "1"); // 启用
+        QFilter filter13 = new QFilter("id", QCP.in, employeeIDs);
+        // 查人员基础信息中的性别
+        String selectFields2="id,number,name,birthday,gender.masterid";
+        DynamicObjectCollection personDyns = QueryServiceHelper.query("hrpi_employee", selectFields2,new QFilter[]{filter11, filter12, filter13});
+
+        Map<Long, DynamicObject> personMap = (Map)personDyns.stream().collect(Collectors.toMap((obj) -> {
+            return obj.getLong("id");
+        }, (obj) -> {
+            return obj;
+        }, (k1, k2) -> {
+            return k1;
+        }));
+
+        ArrayList<DynamicObject> perList = new ArrayList<>();
+        for (DynamicObject data : datas) {
+            int jobType = 2;
+            int gender = 0;
+            Long personId = data.getLong("employee.id");
+            DynamicObject person  = personMap.get(personId);
+            Long genderID = person.getLong("gender.masterid");
+            Date birthday = person.getDate("birthday");
+            if(genderID == 1020L){
+                gender = 2;
+            }else if (genderID == 1010L){
+                gender = 1;
+            }
+            if (gender == 2) {
+                //职位序列 为技能序列(03)的为工人,其他为管理干部
+                DynamicObject empposorgre = empposorgreMap.get(personId);
+                if(empposorgre!= null) {
+                    String jobSeq = empposorgre.getString("position.nckd_jobseq.number");
+                    if (!StringUtils.isEmpty(jobSeq)) {
+                        if (jobSeq.equals("03")) {
+                            jobType = 2;
+                        } else {
+                            jobType = 1;
+                        }
+                    }
+                }
+            }
+            Date personBirthday = data.getDate("nckd_recorddate");
+            if(personBirthday == null){
+                personBirthday = birthday;
+                data.set("nckd_recorddate", birthday);
+            }
+            if(personBirthday != null && gender != 0 ){
+
+                LocalDate originalRetirementDate = retirementCalculator(personBirthday,gender,jobType);
+                Date originalRetirementDate1 = Date.from(originalRetirementDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
+                if(originalRetirementDate1 != null) {
+                    data.set("nckd_legaldate", originalRetirementDate1);
+                }
+
+                LocalDate delayedRetirementDate = delayedRetirementCalculator(personBirthday,gender,jobType);
+                if(delayedRetirementDate != null ) {
+                    Date delayedRetirementDate1 = Date.from(delayedRetirementDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
+                    data.set("nckd_newlegaldate", delayedRetirementDate1);
+                }else{
+                    if(originalRetirementDate1 != null) {
+                        data.set("nckd_newlegaldate", originalRetirementDate1);
+                    }
+                }
+                perList.add(data);
+            }
+
+        }
+
+        int length = 0;
+        if(perList.size() != 0) {
+            //保存
+            Object[] update = SaveServiceHelper.save(perList.toArray(new DynamicObject[0]));
+            length = update.length;
+
+            return length;
+        }
+        return length;
+    }
+
+
+    /**
+     * 根据用户的出生日期、性别和职业类型来计算法定退休日期
+     *
+     * @param birthdate 用户的出生日期
+     * @param gender 用户的性别,1表示男性,2表示女性
+     * @param jobType 用户的职业类型,1表示55岁退休的职业,2表示50岁退休的职业
+     * @return 用户的法定退休日期,如果输入性别无效则返回null
+     */
+    public LocalDate retirementCalculator(Date birthdate, int gender, int jobType ){
+        // 获取用户的出生日期
+        LocalDate localDate = birthdate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
+
+        // 获取年份、月份、日
+        int birthYear = localDate.getYear();
+        int birthMonth = localDate.getMonthValue();
+        int birthDay = localDate.getDayOfMonth();
+
+        LocalDate birthDate = LocalDate.of(birthYear, birthMonth, birthDay);
+        LocalDate originalRetirementDate;
+        int initialRetirementAge;
+
+        if (gender == 1) { // 男性
+            initialRetirementAge = 60;
+        } else if (gender == 2) { // 女性
+            if (jobType == 1) {
+                initialRetirementAge = 55;
+            } else {
+                initialRetirementAge = 50;
+            }
+        } else {
+            return null;
+        }
+        // 计算法定退休日期
+        originalRetirementDate = birthDate.plusYears(initialRetirementAge);
+        return originalRetirementDate;
+    }
+
+    /**
+     * 根据用户的出生日期、性别和职业类型来计算延迟退休日期
+     *
+     * @param birthdate 出生日期
+     * @param gender 性别,1表示男性,2表示女性
+     * @param jobType 职业类型,1表示55岁退休的职业,2表示50岁退休的职业
+     * @return 预计的延迟退休日期
+     */
+    public LocalDate delayedRetirementCalculator (Date birthdate,int gender,int jobType ) {
+
+        // 获取用户的出生日期
+        LocalDate localDate = birthdate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
+        // 获取年份、月份、日
+        int birthYear = localDate.getYear();
+        int birthMonth = localDate.getMonthValue();
+        int birthDay = localDate.getDayOfMonth();
+
+        //int gender = 2;
+        System.out.println("性别为:女55");
+
+        LocalDate birthDate = LocalDate.of(birthYear, birthMonth, birthDay);
+        LocalDate startDate = LocalDate.of(2025, 1, 1);
+        LocalDate originalRetirementDate;
+        int initialRetirementAge;
+        int maxDelayMonths = 0;
+
+        if (gender == 1) { // 男性
+            if (birthYear >= 1965) {
+                initialRetirementAge = 60;
+                maxDelayMonths = 36; // 最多延迟36个月至63周岁
+            }else{
+                return null;
+            }
+        } else if (gender == 2) { // 女性
+            if (jobType == 1) {
+                if (birthYear >= 1970) {
+                    initialRetirementAge = 55;
+                    maxDelayMonths = 36; // 最多延迟36个月至58周岁
+                }else{
+                    return null;
+                }
+            } else {
+                if (birthYear >= 1975) {
+                    initialRetirementAge = 50;
+                    maxDelayMonths = 60; // 最多延迟60个月至55周岁
+                }else{
+                    return null;
+                }
+            }
+        } else {
+            return null;
+        }
+
+        // 计算法定退休日期
+        originalRetirementDate = birthDate.plusYears(initialRetirementAge);
+
+        // 手动计算两个日期之间的月份数差异
+        long monthsBetween = (originalRetirementDate.getYear() - startDate.getYear()) * 12L
+                + originalRetirementDate.getMonthValue() - startDate.getMonthValue();
+
+        int delayedMonths;
+        if (gender == 1 || (gender == 2 && jobType == 1)) {
+            delayedMonths = (int)Math.floor(monthsBetween / 4.0) + 1;
+        } else {
+            delayedMonths = (int)Math.floor(monthsBetween / 2.0) + 1;
+        }
+
+        // 应用最大延迟月数限制
+        if(delayedMonths > maxDelayMonths) {
+            delayedMonths = maxDelayMonths;
+        }
+
+        // 计算延迟后的退休日期
+        LocalDate retirementDate = originalRetirementDate.plusMonths(delayedMonths);
+
+        //System.out.printf("根据计算,您的预计退休日期为:%s\n", retirementDate.toString());
+
+        return retirementDate;
+
+    }
+    /**
+     * 涉及特殊工种的计算
+     * 若员工在特殊工种累计任职时长超过该特殊工种规定的可提前退休标准,可在规定的退休年龄基础上提前5年退休。
+     * 若涉及到多段特殊工种任职,须进行折算统计,举例如下:某员工涉及三段特殊工种任职,分别是3年,6年,1年,
+     * 对应特殊工种可天退休标准为8、9、10,则3/8+6/9+1/10=1.141666667,结果大于1,符合条件,反之就不符合。
+     * /
+
+    /**
+     * 计算年龄
+     * @param startDate
+     * @param endDate
+     * @return  示例:"60岁6个月"
+     */
+
+    public String caclOldYear(Date startDate, Date endDate) {
+
+        LocalDate startLocalDate = startDate.toInstant()
+                .atZone(ZoneId.systemDefault())
+                .toLocalDate();
+
+        LocalDate endLocalDate = endDate.toInstant()
+                .atZone(ZoneId.systemDefault())
+                .toLocalDate();
+
+        // 计算时间差
+        Period period = Period.between(startLocalDate, endLocalDate);
+
+        int years = period.getYears();
+        int months = period.getMonths();
+
+        String result = years + "岁" + months + "个月";
+        return result;
+    }
+}