Przeglądaj źródła

烟台年假完善使用假期后年中更改职级/工龄逻辑(在原有余额基础上新增而不是覆盖) 更新假期接口做去重处理

wzy 1 rok temu
rodzic
commit
4fa1a48b30

+ 67 - 4
mjava-kaiyue_cd/src/main/java/com/malk/kaiyue_cd/controller/KYCDController.java

@@ -1,17 +1,80 @@
 package com.malk.kaiyue_cd.controller;
 
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.malk.controller.DDCallbackController;
+import com.malk.kaiyue_cd.service.KYCDService;
 import com.malk.server.common.McR;
+import com.malk.server.dingtalk.DDConf;
+import com.malk.server.dingtalk.crypto.DingCallbackCrypto;
+import lombok.SneakyThrows;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
 @Slf4j
 @RestController
 @RequestMapping
-public class KYCDController {
+public class KYCDController extends DDCallbackController {
+    @Autowired
+    private KYCDService kycdService;
+
+    @Autowired
+    private DDConf ddConf;
+
     @PostMapping("/test")
     McR test(){
         log.info("11111");
         return McR.success();
     }
+
+
+    @SneakyThrows
+    public Map<String, String> invokeCallback(@RequestParam(value = "signature", required = false) String signature,
+                                              @RequestParam(value = "timestamp", required = false) String timestamp,
+                                              @RequestParam(value = "nonce", required = false) String nonce,
+                                              @RequestBody(required = false) JSONObject json) {
+        DingCallbackCrypto callbackCrypto = new DingCallbackCrypto(ddConf.getToken(), ddConf.getAesKey(), ddConf.getAppKey());
+        // 处理回调消息,得到回调事件decryptMsg...
+        final String decryptMsg = callbackCrypto.getDecryptMsg(signature, timestamp, nonce, json.getString("encrypt"));
+        JSONObject eventJson = JSON.parseObject(decryptMsg);
+        Map success = callbackCrypto.getEncryptedMap(DDConf.CALLBACK_RESPONSE, System.currentTimeMillis(), DingCallbackCrypto.Utils.getRandomStr(8));
+
+        // 业务处理代码...
+        String eventType = eventJson.getString("EventType");
+        if (DDConf.CALLBACK_CHECK.equals(eventType)) {
+            log.info("----- [DD]验证注册 -----");
+            return success;
+        }
+        // [回调任务执行逻辑: 异步] 钉钉超时3s未返回会被记录为失败, 可通过失败接口获取记录
+        if (Arrays.asList(DDConf.HRM_USER_RECORD_CHANGE).contains(eventType)) {
+            log.info("[DD]人事档案变动回调, eventType:{}, eventJson:{}",eventType, eventJson);
+            //获取员工userId
+            String userId = "";
+            if (Objects.nonNull(eventJson.get("staffId"))){
+                //人事档案返回的userId
+                userId = eventJson.get("staffId").toString();
+            }else {
+                log.error("[DD]人事档案变动回调, 未获取到userId");
+                return success;
+            }
+
+            log.info("员工userId:"+userId);
+            Map<String, Object> map = new HashMap();
+            map.put("userid_list", userId);
+            //更新员工年假余额
+            log.info("----- [DD]更新员工年假余额 -----");
+
+            return success;
+        }
+        log.info("----- [DD]已注册, 未处理的其它回调 -----, eventType:{}, eventJson:{}",eventType, eventJson);
+        return success;
+    }
+
+
 }

+ 4 - 0
mjava-kaiyue_cd/src/main/java/com/malk/kaiyue_cd/service/KYCDService.java

@@ -0,0 +1,4 @@
+package com.malk.kaiyue_cd.service;
+
+public interface KYCDService {
+}

+ 8 - 0
mjava-kaiyue_cd/src/main/java/com/malk/kaiyue_cd/service/impl/KYCDServiceImpl.java

@@ -0,0 +1,8 @@
+package com.malk.kaiyue_cd.service.impl;
+
+import com.malk.kaiyue_cd.service.KYCDService;
+import org.springframework.stereotype.Service;
+
+@Service
+public class KYCDServiceImpl implements KYCDService {
+}

+ 6 - 6
mjava-kaiyue_cd/src/main/resources/application-dev.yml

@@ -49,12 +49,12 @@ logging:
 
 # dingtalk
 dingtalk:
-  agentId: 2999477924
-  appKey: dingzorik72leqm5qgpj
-  appSecret: csxfrSOZy02aXvc4IkqM9dFqz7cEDgUogvJaBIq_rtIbvjZLDKiVkHdVgKeNfoVQ
-  corpId: ding43bb7be8e7bdc63224f2f5cc6abecb85
-  aesKey: 7txhFmSyWIXIrEvwlNfcuMfOQe19K6hqCdIaXMHLO2K
-  token: 24VR2Bnu
+  agentId: 3047759151
+  appKey: dingl3bjoikl5x6lg3cr
+  appSecret: 68vsttGMji5GBOK81hR-BlYcmF6DROnmnB20LRmCuXES5FvKvXfuhJb6x8DNrf9l
+  corpId: ding3d180832e53201ee35c2f4657eb6378f
+  aesKey: 2z3Xuekm3uPRCc3nLGEISf33OIo0r5pSdB9bLDdT7DL
+  token: 4DRvfMdccRp4xnOdoRpK3vymMqJF7WO
   operator: ""   # OA管理员账号 [0开头需要转一下字符串]
 
   #poc

+ 0 - 3
mjava-kaiyue_yt/src/main/java/com/malk/kaiyue/controller/KYController.java

@@ -1,6 +1,5 @@
 package com.malk.kaiyue.controller;
 
-import cn.hutool.core.util.StrUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 import com.malk.controller.DDCallbackController;
@@ -8,7 +7,6 @@ import com.malk.kaiyue.service.KYService;
 import com.malk.server.common.McR;
 import com.malk.server.dingtalk.DDConf;
 import com.malk.server.dingtalk.crypto.DingCallbackCrypto;
-import com.malk.service.dingtalk.DDClient_Event;
 import lombok.SneakyThrows;
 import lombok.Synchronized;
 import lombok.extern.slf4j.Slf4j;
@@ -19,7 +17,6 @@ import java.util.*;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
-import java.util.stream.Collectors;
 
 @Slf4j
 @RestController

+ 93 - 24
mjava-kaiyue_yt/src/main/java/com/malk/kaiyue/service/impl/KYServiceImpl.java

@@ -2,12 +2,11 @@ package com.malk.kaiyue.service.impl;
 
 import cn.hutool.core.date.DateTime;
 import cn.hutool.core.date.DateUtil;
-import cn.hutool.core.util.StrUtil;
-import cn.hutool.http.HttpUtil;
 import com.malk.kaiyue.service.KYService;
 import com.malk.server.common.McR;
 import com.malk.server.dingtalk.DDConf;
 import com.malk.server.dingtalk.DDR;
+import com.malk.server.dingtalk.DDR_New;
 import com.malk.service.dingtalk.DDClient;
 import com.malk.utils.UtilHttp;
 import lombok.extern.slf4j.Slf4j;
@@ -15,6 +14,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
 import java.util.*;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 @Slf4j
@@ -59,12 +59,16 @@ public class KYServiceImpl implements KYService {
         return (Map)ddr.getResult();
     }
 
+    //保存5s内已处理的更新假期余额事件
+    private Map<String, Long> bodyList = new HashMap<>();
 
     public McR getEmployeeAnnualLeaveNum(Map<String, Object> map) {
         //获取accessToken
         String access_token = ddClient.getAccessToken();
         //获取agentId
         String agentId = ddConf.getAgentId().toString();
+        //获取OA管理员账号
+        String opUserId = ddConf.getOperator();
         //查询接口body添加参数
         //field_filter_list:要查询字段(花名册字段信息参考:https://open.dingtalk.com/document/orgapp/roster-custom-field-business-code)
         //agentid:企业内部应用AgentId
@@ -110,15 +114,23 @@ public class KYServiceImpl implements KYService {
                     }
 
                 }
+                //假期有效开始日期为当年1月1日
+                DateTime beginDate = DateUtil.beginOfYear(new Date());
+                //假期有效截至日期为当年12月31日
+                DateTime endDate = DateUtil.endOfYear(new Date());
                 //工龄(年) 计算规则:首次工作时间至当年一月一日 数值向下取整
-                int workAge =(int) (DateUtil.betweenDay(DateUtil.parse(joinWorkingTime), DateUtil.beginOfYear(new Date()), true) / 365);
-                System.out.println(workAge + "年");
+                int workAge =(int) (DateUtil.betweenYear(DateUtil.parse(joinWorkingTime), beginDate, true));
+                if (DateUtil.dayOfYear(DateUtil.parse(joinWorkingTime)) != 1){
+                    workAge --;
+                }
+
+                System.out.println("截至今年1月1日,工龄为:"+workAge + "年");
                 //法定年假
                 int legalAnnualLeave = 0;
                 //福利年假
                 int welfareAnnualLeave = 0;
                 //根据职级、工龄和合同续签数计算年假基数
-                if (positionLevel.equals("5")){
+                if (positionLevel.equals("5") || positionLevel.equals("副主任医师")){
                     if (workAge < 10){
                         legalAnnualLeave = 5;
                         welfareAnnualLeave = 15;
@@ -129,35 +141,35 @@ public class KYServiceImpl implements KYService {
                         legalAnnualLeave = 15;
                         welfareAnnualLeave = 5;
                     }
-                }else if (positionLevel.equals("4") || positionLevel.equals("3")){
+                }else if (positionLevel.equals("4") || positionLevel.equals("3") || positionLevel.equals("技术专员")){
                     if (workAge < 10){
                         legalAnnualLeave = 5;
-                        welfareAnnualLeave = 7 + 2 * contractRenewCount;
+                        welfareAnnualLeave = 7 + 2 * (Math.min(contractRenewCount, 2));
                     }else if (workAge >= 10 && workAge < 20){
                         legalAnnualLeave = 10;
-                        welfareAnnualLeave = 2 + 2 * contractRenewCount;
+                        welfareAnnualLeave = 2 + 2 * (Math.min(contractRenewCount, 2));
                     }else if (workAge >= 20){
                         legalAnnualLeave = 15;
                         welfareAnnualLeave = 1;
                     }
-                }else if (positionLevel.equals("2")){
+                }else if (positionLevel.equals("2") || positionLevel.equals("高级管理员")){
                     if (workAge < 10){
                         legalAnnualLeave = 5;
-                        welfareAnnualLeave = 5 + 2 * contractRenewCount;
+                        welfareAnnualLeave = 5 + 2 * (Math.min(contractRenewCount, 2));
                     }else if (workAge >= 10 && workAge < 20){
                         legalAnnualLeave = 10;
-                        welfareAnnualLeave = 0 + 2 * contractRenewCount;
+                        welfareAnnualLeave = 0 + 2 * (Math.min(contractRenewCount, 2));
                     }else if (workAge >= 20){
                         legalAnnualLeave = 15;
                         welfareAnnualLeave = 0;
                     }
-                }else if (positionLevel.equals("1")){
+                }else if (positionLevel.equals("1") || positionLevel.equals("技术专家")){
                     if (workAge < 10){
                         legalAnnualLeave = 5;
-                        welfareAnnualLeave = 3  + 2 * contractRenewCount;
+                        welfareAnnualLeave = 3  + 2 * (Math.min(contractRenewCount, 2));
                     }else if (workAge >= 10 && workAge < 20){
                         legalAnnualLeave = 10;
-                        welfareAnnualLeave = 0  + 2 * contractRenewCount;
+                        welfareAnnualLeave = 0  + 2 * (Math.min(contractRenewCount, 1));
                     }else if (workAge >= 20){
                         legalAnnualLeave = 15;
                         welfareAnnualLeave = 0;
@@ -166,10 +178,9 @@ public class KYServiceImpl implements KYService {
                     legalAnnualLeave = 0;
                     welfareAnnualLeave = 0;
                 }
-                //开始日期为当年1月1日
-                DateTime beginDate = DateUtil.beginOfYear(new Date());
-                //有效日期至当年12月31日
-                DateTime endDate = DateUtil.offsetDay(DateUtil.endOfYear(new Date()),-1);
+
+                //当年天数
+                int yearDays = DateUtil.dayOfYear(endDate);
 
                 result.add("姓名:"+name+",职级:"+positionLevel+",工龄:"+workAge+"年,合同续签数"+contractRenewCount+",法定年假:" + legalAnnualLeave + "天,福利年假:" + welfareAnnualLeave + "天"+",截止日期:"+endDate);
                 //年假基数
@@ -177,8 +188,8 @@ public class KYServiceImpl implements KYService {
                 double yearLeaveDecimalPart = 0;
                 //判断员工是否当年新入职
                 if (DateUtil.year(DateUtil.parse(confirmJoinTime)) == DateUtil.year(new Date())){
-                    int workDay = (int) DateUtil.betweenDay(DateUtil.parse(confirmJoinTime),endDate,true);
-                    yearLeave = workDay * yearLeave / 365.0;
+                    int workDay = (int) DateUtil.betweenDay(DateUtil.parse(confirmJoinTime),endDate,true) + 1;
+                    yearLeave = workDay * yearLeave / yearDays;
                     yearLeaveDecimalPart = yearLeave - (int) yearLeave;
                     if (yearLeaveDecimalPart < 0.25){
                         yearLeave = (int) yearLeave;
@@ -188,6 +199,34 @@ public class KYServiceImpl implements KYService {
                         yearLeave = (int) yearLeave + 1;
                     }
                 }
+
+                //查询出用户消费年假记录 当返回leaveRecords中calType为null或不返回该字段则为请假消耗 将计算出的年假数减去请假消耗的数量
+                Map body = new HashMap();
+                body.put("opUserId",opUserId);
+                body.put("leaveCode","f9240c02-8fe7-4535-af2c-ca6740e1c654");//测试年假wzy
+                body.put("userIds",new String[]{userId});
+                body.put("pageNumber",0);
+                body.put("pageSize",50);
+                DDR_New useDdr = (DDR_New) UtilHttp.doPost("https://api.dingtalk.com/v1.0/attendance/vacations/records/query", DDConf.initTokenHeader(access_token), null, body, DDR_New.class);
+                Map useResult = (Map) useDdr.getResult();
+                List<Map> useList = (List<Map>) useResult.get("leaveRecords");
+                Double useLeaveNum = 0d;
+                for (Map use : useList) {
+                    //判断是否为今年请假
+                    DateTime gmtCreate = DateUtil.date((long) use.get("gmtCreate"));
+                    if (DateUtil.year(gmtCreate) == DateUtil.year(new Date())){
+                        //判断是否为请假消耗
+                        if (!use.containsKey("calType") || Objects.isNull(use.get("calType"))){
+                            //判断是否为正常请假而不是接口测试
+                            if (!"接口测试修改".equals(use.get("leaveReason").toString())){
+                                useLeaveNum += (int) use.get("recordNumPerDay") / 100;
+                            }
+                        }
+                    }
+                }
+                //实际年假数
+                double realYearLeave = (yearLeave - useLeaveNum) < 0 ? 0 : (yearLeave - useLeaveNum);
+
                 //更新假期余额接口的body
                 Map<String,Object> updateBody = new HashMap<>();
                 Map<String,Object> leave_quotas = new HashMap<>();
@@ -198,19 +237,33 @@ public class KYServiceImpl implements KYService {
                 //操作原因
                 leave_quotas.put("reason","接口测试修改");
                 //以天计算的额度总数 假期类型按天计算时,该值不为空且按百分之一天折算。 例如:1000=10天。
-                leave_quotas.put("quota_num_per_day",(int) (yearLeave * 100) );
+                leave_quotas.put("quota_num_per_day",(int) (realYearLeave * 100) );
                 //以小时计算的额度总数 假期类型按小时,计算该值不为空且按百分之一小时折算。例如:1000=10小时。
                 leave_quotas.put("quota_num_per_hour",0);
                 //额度所对应的周期,格式必须是"yyyy",例如"2021"
                 leave_quotas.put("quota_cycle",DateUtil.year(new Date())+"");
                 //自定义添加的假期类型:年假开发测试的leave_code
-                leave_quotas.put("leave_code","28abb7bf-e1b6-4387-9e2a-e1b2ae983e7a");
+                //leave_quotas.put("leave_code","28abb7bf-e1b6-4387-9e2a-e1b2ae983e7a");
+                leave_quotas.put("leave_code","f9240c02-8fe7-4535-af2c-ca6740e1c654");//测试
                 //要更新的员工的userId
                 leave_quotas.put("userid",userId);
 
                 updateBody.put("leave_quotas",leave_quotas);
-                //当前企业内拥有OA审批应用权限的管理员的userId(飞超)
-                updateBody.put("op_userid","02421908021891243060");
+                //当前企业内拥有OA审批应用权限的管理员的userId
+                updateBody.put("op_userid",opUserId);
+
+                String bodyStr = yearLeave + userId;
+
+                // 检查更新事件是否已经处理过,如果是,则忽略该更新
+                if (isUpdateLeave(bodyStr)) {
+                    log.info("更新事件已处理,忽略该回调...");
+                    return null;
+                }
+
+
+                // 将更新和当前时间戳添加到已处理集合中
+                long currentTime = System.currentTimeMillis();
+                bodyList.put(bodyStr, currentTime);
 
                 //更新假期余额
                 UtilHttp.doPost("https://oapi.dingtalk.com/topapi/attendance/vacation/quota/update", null, DDConf.initTokenParams(access_token), updateBody, DDR.class);
@@ -219,4 +272,20 @@ public class KYServiceImpl implements KYService {
         log.info(result.stream().collect(Collectors.joining(",")));
         return McR.success(result);
     }
+
+    /**
+     * 检查该更新事件在5s内是否处理过,应对钉钉瞬间重复回调
+     *
+     * @param msg 回调事件
+     * @return 是否处理过
+     */
+    private boolean isUpdateLeave(String msg) {
+        // 清理超过10s的回调事件
+        long currentTime = System.currentTimeMillis();
+        long expirationTime = currentTime - TimeUnit.SECONDS.toMillis(5);
+        bodyList.entrySet().removeIf(entry -> entry.getValue() < expirationTime);
+
+        return bodyList.containsKey(msg);
+    }
+
 }

+ 14 - 14
mjava-kaiyue_yt/src/main/resources/application-dev.yml

@@ -49,22 +49,22 @@ logging:
 
 # dingtalk
 dingtalk:
-  agentId: 2999477924
-  appKey: dingzorik72leqm5qgpj
-  appSecret: csxfrSOZy02aXvc4IkqM9dFqz7cEDgUogvJaBIq_rtIbvjZLDKiVkHdVgKeNfoVQ
-  corpId: ding43bb7be8e7bdc63224f2f5cc6abecb85
-  aesKey: 7txhFmSyWIXIrEvwlNfcuMfOQe19K6hqCdIaXMHLO2K
-  token: 24VR2Bnu
-  operator: ""   # OA管理员账号 [0开头需要转一下字符串]
+  #agentId: 2999477924
+  #appKey: dingzorik72leqm5qgpj
+  #appSecret: csxfrSOZy02aXvc4IkqM9dFqz7cEDgUogvJaBIq_rtIbvjZLDKiVkHdVgKeNfoVQ
+  #corpId: ding43bb7be8e7bdc63224f2f5cc6abecb85
+  #aesKey: 7txhFmSyWIXIrEvwlNfcuMfOQe19K6hqCdIaXMHLO2K
+  #token: 24VR2Bnu
+  #operator: "02421908021891243060"   # OA管理员账号 [0开头需要转一下字符串]
 
   #poc
-  #agentId: 2995824312
-  #appKey: ding3ap1jk1tg44tz3s2
-  #appSecret: PaWTDG-FiX-RW5fnV9r8CzEmR-9QlJpubC88txhprL_Z_iREO62B-iRW6w7gkA_K
-  #corpId: ding321c72787fffc78b35c2f4657eb6378f
-  #aesKey: LSIc7r5uHAP0dd6v23J3LWRmjECMNzbkIcxAwdx63RE
-  #token: yqXHMHaK4oHYvjyQshU4zFqgrHFq7PcBxVSqGo1BAQk0
-  #operator: ""   # OA管理员账号 [0开头需要转一下字符串]
+  agentId: 3047931226
+  appKey: dingzwmsgsh53i0vntbu
+  appSecret: E2ro4owsTwPxba_4ntLg01kYn4-W_Ti6IU3MaALj-f_xQkXhnSDccEE5Iad-fnuk
+  corpId: dingc905d8a60b6a641b24f2f5cc6abecb85
+  aesKey: hEWViCdQbFxRoRwPDi64tRegzAkuoIpp5Oq3JSngWXi
+  token: wKJlvqvPrEkNd849
+  operator: "344749020127590108"   # OA管理员账号 [0开头需要转一下字符串]
 
 
 # aliwork