diff --git a/evo-admin/src/main/java/com/evo/attendance/processor/PunchTheClockStrategyExchangeProcessor.java b/evo-admin/src/main/java/com/evo/attendance/processor/PunchTheClockStrategyExchangeProcessor.java index 2112b4c..e2b55c6 100644 --- a/evo-admin/src/main/java/com/evo/attendance/processor/PunchTheClockStrategyExchangeProcessor.java +++ b/evo-admin/src/main/java/com/evo/attendance/processor/PunchTheClockStrategyExchangeProcessor.java @@ -76,7 +76,14 @@ public interface PunchTheClockStrategyExchangeProcessor { return ""; } - default String initMessage(Integer result, String meg){ + + + default String initMessage(Integer result, String meg){ + //需要返回的对象, 默认失败 + return initStaticMessage(result, meg); + } + + static String initStaticMessage(Integer result, String meg){ //需要返回的对象, 默认失败 return JSONObject.toJSONString(new RzAttendanceVo(result, meg, null)); } diff --git a/evo-admin/src/main/java/com/evo/attendance/service/IRzAttendanceService.java b/evo-admin/src/main/java/com/evo/attendance/service/IRzAttendanceService.java index acfca3e..b9d7131 100644 --- a/evo-admin/src/main/java/com/evo/attendance/service/IRzAttendanceService.java +++ b/evo-admin/src/main/java/com/evo/attendance/service/IRzAttendanceService.java @@ -1,9 +1,11 @@ package com.evo.attendance.service; import com.baomidou.mybatisplus.extension.service.IService; +import com.evo.attendance.domain.RzAttendance; import com.evo.attendance.domain.vo.RzAttendanceDetailVO; import com.evo.common.core.domain.AjaxResult; -import com.evo.attendance.domain.RzAttendance; +import com.evo.ding.domain.DingShiftGroup; +import com.evo.system.domain.SysStaff; import java.util.Date; import java.util.List; @@ -64,4 +66,8 @@ public interface IRzAttendanceService extends IService public void sendAbnormalAttendance(); public void dingDing(String dingUserId, String bizId); + + void addRzOverTime(String result, SysStaff sysStaff, DingShiftGroup dingsShiftGroup, String checkType, Date formatWorkDate, Date formatUserCheckTime, String timeResult, String remark ); + + public RzAttendance queryNowDayAttendanceByStatisticalIdAndDate(Long staffId, Date date); } diff --git a/evo-admin/src/main/java/com/evo/attendance/service/IRzSpecialAttendanceService.java b/evo-admin/src/main/java/com/evo/attendance/service/IRzSpecialAttendanceService.java index 0e9a2da..7be6ba1 100644 --- a/evo-admin/src/main/java/com/evo/attendance/service/IRzSpecialAttendanceService.java +++ b/evo-admin/src/main/java/com/evo/attendance/service/IRzSpecialAttendanceService.java @@ -1,7 +1,10 @@ package com.evo.attendance.service; +import com.baomidou.mybatisplus.extension.service.IService; import com.evo.attendance.domain.RzSpecialAttendance; +import org.apache.ibatis.annotations.Param; +import java.util.Date; import java.util.List; /** @@ -10,7 +13,7 @@ import java.util.List; * @author evo * @date 2025-04-15 */ -public interface IRzSpecialAttendanceService +public interface IRzSpecialAttendanceService extends IService { /** * 查询加班考勤记录 @@ -51,4 +54,6 @@ public interface IRzSpecialAttendanceService * @return 结果 */ public int deleteRzSpecialAttendanceById(Long id); + + public RzSpecialAttendance selectRzSpecialAttendanceByUserIdAndDate(Long staffId, Date date); } diff --git a/evo-admin/src/main/java/com/evo/attendance/service/impl/RzAttendanceServiceImpl.java b/evo-admin/src/main/java/com/evo/attendance/service/impl/RzAttendanceServiceImpl.java index 86407d0..4f99a8a 100644 --- a/evo-admin/src/main/java/com/evo/attendance/service/impl/RzAttendanceServiceImpl.java +++ b/evo-admin/src/main/java/com/evo/attendance/service/impl/RzAttendanceServiceImpl.java @@ -16,13 +16,17 @@ import com.evo.attendance.service.RzAttendanceDetailService; import com.evo.common.annotation.DataScope; import com.evo.common.core.domain.AjaxResult; import com.evo.common.core.domain.entity.SysDept; -import com.evo.common.utils.*; +import com.evo.common.utils.Collections; +import com.evo.common.utils.DateUtils; +import com.evo.common.utils.SecurityUtils; +import com.evo.common.utils.StringUtils; import com.evo.ding.DingUtils; import com.evo.ding.domain.DingShift; import com.evo.ding.domain.DingShiftGroup; import com.evo.ding.mapper.DingShiftGroupMapper; import com.evo.ding.mapper.DingShiftMapper; import com.evo.equipment.service.IEqImagesService; +import com.evo.personnelMatters.service.IRzOverTimeService; import com.evo.system.domain.SysStaff; import com.evo.system.mapper.SysDeptMapper; import com.evo.system.mapper.SysStaffMapper; @@ -55,6 +59,8 @@ public class RzAttendanceServiceImpl extends ServiceImpl"+result); JSONObject resultJson = JSON.parseObject(result); - if(Integer.valueOf(0).equals(resultJson.getInteger("errcode"))) try { + if(Integer.valueOf(0).equals(resultJson.getInteger("errcode"))) + try { { JSONArray jsonArray = resultJson.getJSONArray("recordresult"); for (int i = 0; i < jsonArray.size(); i++) { @@ -538,13 +545,33 @@ public class RzAttendanceServiceImpl extends ServiceImpl().eq(DingShift::getDingId, dingsShiftGroup.getShiftId())); + DingShift dingsShift = dingsShiftMapper.selectByDingId(dingsShiftGroup.getShiftId()); //通过workDate 获取哪一天的卡 - RzAttendance attendance = getBaseMapper().queryNowDayAttendanceByStatisticalIdAndDate(sysStaff.getUserId(),formatWorkDate); + RzAttendance attendance = queryNowDayAttendanceByStatisticalIdAndDate(sysStaff.getUserId(),formatWorkDate); //生成打卡记录 rzAttendanceDetailService.addDetail(attendance, ("OnDuty".equals(checkType) ? "上班卡" : "下班卡"), "/", formatUserCheckTime, "钉钉打卡", ""); //上班 @@ -599,12 +626,15 @@ public class RzAttendanceServiceImpl extends ServiceImpl>>>> {}", result); - } + }*/ break; } } @@ -616,6 +646,64 @@ public class RzAttendanceServiceImpl extends ServiceImpl>>>>>>>>>>>>>>>>> 出现未知打卡类型, 当前打卡类型"); + //终止继续执行 + return; + } + //判断打卡时间, 添加夜班次数 打卡时间在晚上7点以后 + if(dingsShift.getWorkHour() == 12 && attendance.getWorkSum().intValue() >= 12 && attendance.getWorkStartTime()!= null && attendance.getWorkStartTime().getHours() > 12){ + attendance.setNightNumber(1); + //必须是8小时倒班制or单班制, 并且工作时长>=8小时, 并且 上班打开时间在12点至21点 算作中班 -- 2026-02-04 因1月份张克龙中班考勤问题, 刘杰面谈 + }else if(dingsShift.getWorkHour() == 8 && attendance.getWorkSum().intValue() >= 8 && attendance.getWorkStartTime() != null && attendance.getWorkStartTime().getHours() >= 12 && attendance.getWorkStartTime().getHours() < 21){ + attendance.setMiddleShiftNumber(1); + } + + getBaseMapper().updateRzAttendance(attendance); + //检查是否开启加班 + if(dingsShift.getOvertimeWork()){ + //使用排班下班时间作为加班开始时间 + rzOverTimeService.overTimeCard(sysStaff, DateUtils.parseDate(DateUtils.parseDateToStr("yyyy-MM-dd", formatWorkDate)+" "+ dingsShiftGroup.getCheckTime())); + //员工打卡时间作为加班下班时间 + rzOverTimeService.overTimeOffDutyCard(sysStaff, formatUserCheckTime, "/"); + } + }else{ + log.info("钉钉回调打卡信息=====>>>>> {}", result); + } + } + + @Override + public RzAttendance queryNowDayAttendanceByStatisticalIdAndDate(Long staffId, Date date) { + return getBaseMapper().queryNowDayAttendanceByStatisticalIdAndDate(staffId,date); + } + @Async protected void sendAbnormalAttendance(SysStaff sysStaff, Date date){ diff --git a/evo-admin/src/main/java/com/evo/attendance/service/impl/RzSpecialAttendanceServiceImpl.java b/evo-admin/src/main/java/com/evo/attendance/service/impl/RzSpecialAttendanceServiceImpl.java index dcdd65a..1df5206 100644 --- a/evo-admin/src/main/java/com/evo/attendance/service/impl/RzSpecialAttendanceServiceImpl.java +++ b/evo-admin/src/main/java/com/evo/attendance/service/impl/RzSpecialAttendanceServiceImpl.java @@ -4,6 +4,7 @@ import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.evo.attendance.domain.RzAttendanceStatistical; import com.evo.attendance.domain.RzSpecialAttendance; import com.evo.attendance.domain.RzSpecialOverTime; @@ -23,7 +24,7 @@ import javax.annotation.Resource; * @date 2025-04-15 */ @Service -public class RzSpecialAttendanceServiceImpl implements IRzSpecialAttendanceService +public class RzSpecialAttendanceServiceImpl extends ServiceImpl implements IRzSpecialAttendanceService { @Resource private RzSpecialAttendanceMapper rzSpecialAttendanceMapper; @@ -131,4 +132,9 @@ public class RzSpecialAttendanceServiceImpl implements IRzSpecialAttendanceServi rzAttendanceStatistical.setOverTimeHours(rzAttendanceStatistical.getOverTimeHours().subtract(rzSpecialAttendance.getWorkHours())); return rzAttendanceStatisticalMapper.updateRzAttendanceStatistical(rzAttendanceStatistical); } + + @Override + public RzSpecialAttendance selectRzSpecialAttendanceByUserIdAndDate(Long staffId, Date date) { + return getBaseMapper().selectRzSpecialAttendanceByUserIdAndDate(staffId, date); + } } diff --git a/evo-admin/src/main/java/com/evo/common/controller/CallbackController.java b/evo-admin/src/main/java/com/evo/common/controller/CallbackController.java index 4eac824..dd36a58 100644 --- a/evo-admin/src/main/java/com/evo/common/controller/CallbackController.java +++ b/evo-admin/src/main/java/com/evo/common/controller/CallbackController.java @@ -1,23 +1,21 @@ package com.evo.common.controller; import com.alibaba.fastjson2.JSON; -import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; -import com.evo.attendance.processor.impl.KQDeviceExchangeProcessor; import com.evo.attendance.service.IRzAttendanceService; import com.evo.common.core.controller.BaseController; -import com.evo.common.utils.StringUtils; import com.evo.ding.DingGlobalParams; import com.evo.ding.DingCallbackCrypto; +import com.evo.ding.processor.instance.InstanceChangeExchangeProcessor; +import com.evo.ding.processor.callback.DingCallBackExchangeProcessor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.scheduling.annotation.Async; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import javax.annotation.Resource; +import java.util.List; import java.util.Map; /** @@ -36,7 +34,10 @@ public class CallbackController extends BaseController { @Autowired IRzAttendanceService rzAttendanceService; - + @Autowired + List instanceChangeExchangeProcessorList; + @Autowired + List dingCallBackExchangeProcessorList; @PostMapping("/ding/clock") public Map dingDingClock(@RequestBody Map params){ System.out.println(JSONObject.toJSONString(params)); @@ -53,34 +54,52 @@ public class CallbackController extends BaseController { // 3. 反序列化回调事件json数据 JSONObject eventJson = JSON.parseObject(decryptMsg); String eventType = eventJson.getString("EventType"); + log.info("发生了:" + eventType + "事件"); + for (DingCallBackExchangeProcessor processor : dingCallBackExchangeProcessorList) { + if (processor.accept(eventType)) { + processor.exchange(eventJson); + } + } // 4. 根据EventType分类处理 - if ("check_url".equals(eventType)) { - // 测试回调url的正确性 - log.info("测试回调url的正确性"); - } else if ("attendance_check_record".equals(eventType)) { //打卡推送 - JSONArray dataList = eventJson.getJSONArray("DataList"); - if(dataList!= null && dataList.size() > 0){ - JSONObject jsonObject = dataList.getJSONObject(0); - String userId = jsonObject.getString("userId"); - if(StringUtils.isNotEmpty(userId)){ - rzAttendanceService.dingDing(userId, jsonObject.getString("bizId")); - } - } - //加班审批订阅 - }else if ("bpms_instance_change".equals(eventType)) { - - - // 处理通讯录用户增加事件 - }else if ("user_add_org".equals(eventType)) { - // 处理通讯录用户增加事件 - log.info("发生了:" + eventType + "事件"); - - - } else { - // 添加其他已注册的 - log.info("发生了:" + eventType + "事件"); - } +// if ("check_url".equals(eventType)) { +// log.info("打卡回调"); +// } else if ("attendance_check_record".equals(eventType)) { //打卡推送 +// JSONArray dataList = eventJson.getJSONArray("DataList"); +// if(dataList!= null && dataList.size() > 0){ +// JSONObject jsonObject = dataList.getJSONObject(0); +// String userId = jsonObject.getString("userId"); +// if(StringUtils.isNotEmpty(userId)){ +// rzAttendanceService.dingDing(userId, jsonObject.getString("bizId")); +// } +// } +// //审批实例订阅 +// }else if ("bpms_instance_change".equals(eventType)) { +// log.info("审批实例订阅回调"); +// for (InstanceChangeExchangeProcessor processor : instanceChangeExchangeProcessorList) { +// if (processor.accept(eventJson.getString("processCode"))) { +// processor.exchange(eventJson); +// } +// } +// // 审批任务订阅 +// }else if ("bpms_task_change".equals(eventType)) { +// log.info("审批任务订阅回调"); +// // 处理通讯录用户增加事件 +// }else if ("attendance_approve_status_change".equals(eventType)) { +// log.info("请假、加班、出差、外出状态变更事件"); +// +// +// +// // 处理通讯录用户增加事件 +// }else if ("user_add_org".equals(eventType)) { +// // 处理通讯录用户增加事件 +// log.info("发生了:" + eventType + "事件"); +// +// +// } else { +// // 添加其他已注册的 +// log.info("发生了:" + eventType + "事件"); +// } // 5. 返回success的加密数据 Map successMap = callbackCrypto.getEncryptedMap("success"); diff --git a/evo-admin/src/main/java/com/evo/common/utils/ParamUtils.java b/evo-admin/src/main/java/com/evo/common/utils/ParamUtils.java index e74b24b..b4c271c 100644 --- a/evo-admin/src/main/java/com/evo/common/utils/ParamUtils.java +++ b/evo-admin/src/main/java/com/evo/common/utils/ParamUtils.java @@ -215,6 +215,9 @@ public class ParamUtils { public static JSONObject getDeviceOverTimeRules(String sn){ if(StringUtils.isEmpty(sn)) return null; RzSysParam param= paramService.getRzSysParam("特殊加班的考勤规则", "device_over_time_rules","{\"ET74336\":{\"minHour\":2, \"maxHour\":3,\"endMealTime\":\"19:00\"}}","特殊加班的考勤规则, minHour:最小加班时长. maxHour: 最大加班时长, endMealTime: 餐厅闭厅时间"); + if(param == null){ + return getDefaultMaxOverTime(); + } JSONObject jsonObject = JSONObject.parseObject(param.getParamValue()); JSONObject rules = jsonObject.getJSONObject(sn); return (ObjectUtils.isEmpty(rules) ? getDefaultMaxOverTime(): rules); diff --git a/evo-admin/src/main/java/com/evo/ding/DingUtils.java b/evo-admin/src/main/java/com/evo/ding/DingUtils.java index ff0cc0f..1bfc872 100644 --- a/evo-admin/src/main/java/com/evo/ding/DingUtils.java +++ b/evo-admin/src/main/java/com/evo/ding/DingUtils.java @@ -1,6 +1,5 @@ package com.evo.ding; -import com.aliyun.dingtalkoauth2_1_0.Client; import com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenRequest; import com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenResponse; import com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceResponseBody; @@ -38,17 +37,23 @@ public class DingUtils { this.redisCache = redisCache; } - private static Client createClient() throws Exception { + public static com.aliyun.teaopenapi.Client client(Class t) throws Exception { com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config(); config.protocol = "https"; config.regionId = "central"; - return new Client(config); + if(t == com.aliyun.dingtalkoauth2_1_0.Client.class){ + return new com.aliyun.dingtalkoauth2_1_0.Client(config); + }else if(t == com.aliyun.dingtalkworkflow_1_0.Client.class){ + return new com.aliyun.dingtalkworkflow_1_0.Client(config); + } + return null; } + public static String getAccessToken() throws Exception { String token = redisCache.getCacheObject(ACCESS_TOKEN_KEY); if(StringUtils.isEmpty(token)){ - Client client = createClient(); + com.aliyun.dingtalkoauth2_1_0.Client client = (com.aliyun.dingtalkoauth2_1_0.Client) client(com.aliyun.dingtalkoauth2_1_0.Client.class); GetAccessTokenRequest getAccessTokenRequest = new GetAccessTokenRequest().setAppKey(DingGlobalParams.getClientId()).setAppSecret(DingGlobalParams.getClientSecret()); try { GetAccessTokenResponse res = client.getAccessToken(getAccessTokenRequest); @@ -168,40 +173,58 @@ public class DingUtils { return ""; } - public static com.aliyun.dingtalkworkflow_1_0.Client createClient1() throws Exception { - com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config(); - config.protocol = "https"; - config.regionId = "central"; - return new com.aliyun.dingtalkworkflow_1_0.Client(config); - } + /*** * 获取审批详情 * @param processInstanceId 审批示例id */ - public static GetProcessInstanceResponseBody.GetProcessInstanceResponseBodyResult getProcessInstanceInfo(String processInstanceId) throws Exception { - - com.aliyun.dingtalkworkflow_1_0.Client client = DingUtils.createClient1(); - com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceHeaders getProcessInstanceHeaders = new com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceHeaders(); - getProcessInstanceHeaders.xAcsDingtalkAccessToken = getAccessToken(); - com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceRequest getProcessInstanceRequest = new com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceRequest(); - getProcessInstanceRequest.setProcessInstanceId(processInstanceId); + public static GetProcessInstanceResponseBody.GetProcessInstanceResponseBodyResult getProcessInstanceInfo(String processInstanceId){ try { - return client.getProcessInstanceWithOptions(getProcessInstanceRequest, getProcessInstanceHeaders, new com.aliyun.teautil.models.RuntimeOptions()).getBody().getResult(); - } catch (TeaException err) { - if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) { - // err 中含有 code 和 message 属性,可帮助开发定位问题 - log.error("获取token出现 TeaException 异常, 异常编码{}, 异常原因{}", err.getCode(), err.getMessage()); - } - } catch (Exception _err) { - TeaException err = new TeaException(_err.getMessage(), _err); - if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) { - // err 中含有 code 和 message 属性,可帮助开发定位问题 - log.error("获取token出现 Exception 异常, 异常编码{}, 异常原因{}", err.getCode(), err.getMessage()); - } + com.aliyun.dingtalkworkflow_1_0.Client client = (com.aliyun.dingtalkworkflow_1_0.Client) client(com.aliyun.dingtalkworkflow_1_0.Client.class); + com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceHeaders getProcessInstanceHeaders = new com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceHeaders(); + getProcessInstanceHeaders.xAcsDingtalkAccessToken = getAccessToken(); + com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceRequest getProcessInstanceRequest = new com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceRequest(); + getProcessInstanceRequest.setProcessInstanceId(processInstanceId); + try { + return client.getProcessInstanceWithOptions(getProcessInstanceRequest, getProcessInstanceHeaders, new com.aliyun.teautil.models.RuntimeOptions()).getBody().getResult(); + } catch (TeaException err) { + if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) { + // err 中含有 code 和 message 属性,可帮助开发定位问题 + log.error("获取token出现 TeaException 异常, 异常编码{}, 异常原因{}", err.getCode(), err.getMessage()); + } + } catch (Exception _err) { + TeaException err = new TeaException(_err.getMessage(), _err); + if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) { + // err 中含有 code 和 message 属性,可帮助开发定位问题 + log.error("获取token出现 Exception 异常, 异常编码{}, 异常原因{}", err.getCode(), err.getMessage()); + } + } + } catch (Exception e) { + log.error("=============>>> 获取审批实例出现异常, 异常原因为: {}", e.getMessage()); + e.printStackTrace(); } return null; } + /*** + * 获取排班详情 + * @param userId + * @param planId + * @return + */ + public static String getPlanInfo(String userId, String planId){ + try { + DingTalkClient client = new DefaultDingTalkClient(DingGlobalParams.getBaseUrl()+"attendance/schedule/result/listbyids"); + OapiAttendanceScheduleResultListbyidsRequest req = new OapiAttendanceScheduleResultListbyidsRequest(); + req.setOpUserId(userId); + req.setScheduleIds(planId); + OapiAttendanceScheduleResultListbyidsResponse rsp = client.execute(req, getAccessToken()); + return rsp.getBody(); + } catch (Exception e) { + e.printStackTrace(); + } + return ""; + } } diff --git a/evo-admin/src/main/java/com/evo/ding/domain/DingShift.java b/evo-admin/src/main/java/com/evo/ding/domain/DingShift.java index 677c5f5..8709f8d 100644 --- a/evo-admin/src/main/java/com/evo/ding/domain/DingShift.java +++ b/evo-admin/src/main/java/com/evo/ding/domain/DingShift.java @@ -1,5 +1,6 @@ package com.evo.ding.domain; +import com.baomidou.mybatisplus.annotation.TableId; import lombok.Data; import java.io.Serializable; @@ -16,6 +17,7 @@ import java.io.Serializable; public class DingShift implements Serializable { /** 主键 */ + @TableId private Long id; /** 班次名称 */ public String name; diff --git a/evo-admin/src/main/java/com/evo/ding/mapper/DingShiftMapper.java b/evo-admin/src/main/java/com/evo/ding/mapper/DingShiftMapper.java index 975b3d3..edb0d5f 100644 --- a/evo-admin/src/main/java/com/evo/ding/mapper/DingShiftMapper.java +++ b/evo-admin/src/main/java/com/evo/ding/mapper/DingShiftMapper.java @@ -2,6 +2,8 @@ package com.evo.ding.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.evo.ding.domain.DingShift; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; /** * DingsShiftGroupMapper @@ -13,5 +15,5 @@ import com.evo.ding.domain.DingShift; */ public interface DingShiftMapper extends BaseMapper { - + public DingShift selectByDingId(@Param("dingId") String dingId); } diff --git a/evo-admin/src/main/java/com/evo/ding/processor/DingBasicExchangeProcessor.java b/evo-admin/src/main/java/com/evo/ding/processor/DingBasicExchangeProcessor.java new file mode 100644 index 0000000..929bd43 --- /dev/null +++ b/evo-admin/src/main/java/com/evo/ding/processor/DingBasicExchangeProcessor.java @@ -0,0 +1,108 @@ +package com.evo.ding.processor; + +import com.evo.attendance.service.IRzAttendanceService; +import com.evo.attendance.service.RzAttendanceDetailService; +import com.evo.common.utils.Collections; +import com.evo.common.utils.spring.SpringUtils; +import com.evo.ding.service.DingShiftGroupService; +import com.evo.ding.service.DingShiftService; +import com.evo.personnelMatters.service.IRzLeaveDetailService; +import com.evo.personnelMatters.service.IRzLeaveService; +import com.evo.personnelMatters.service.IRzOverTimeDetailService; +import com.evo.personnelMatters.service.IRzOverTimeService; +import com.evo.system.domain.SysStaff; +import com.evo.system.service.ISysStaffService; + +import java.util.Map; + +/** + * 钉钉回调基础类型 DingBaseExchangeProcessor + * + * @author andy.shi + * @ClassName:DingBaseExchangeProcessor + * @date: 2026年03月12日 11:05 + * @remark: 开发人员联系方式 1042025947@qq.com/微信同步 + */ +public interface DingBasicExchangeProcessor { + + final Map p = Collections.emptyMap(); + //审批状态 + String APPROVE_STATUS = "COMPLETED"; + + String APPROVE_RESULT = "AGREE"; + + default ISysStaffService getISysStaffService(){ + if(!p.containsKey("sysStaffService")){ + p.put("sysStaffService", SpringUtils.getBean(ISysStaffService.class)); + } + return (ISysStaffService)p.get("sysStaffService"); + } + + default IRzAttendanceService getIRzAttendanceService(){ + if(!p.containsKey("rzAttendanceService")){ + p.put("rzAttendanceService", SpringUtils.getBean(IRzAttendanceService.class)); + } + return (IRzAttendanceService)p.get("rzAttendanceService"); + } + + default RzAttendanceDetailService getRzAttendanceDetailService(){ + if(!p.containsKey("rzAttendanceDetailService")){ + p.put("rzAttendanceDetailService", SpringUtils.getBean(RzAttendanceDetailService.class)); + } + return (RzAttendanceDetailService)p.get("rzAttendanceDetailService"); + } + + default IRzOverTimeService getIRzOverTimeService(){ + if(!p.containsKey("rzOverTimeService")){ + p.put("rzOverTimeService", SpringUtils.getBean(IRzOverTimeService.class)); + } + return (IRzOverTimeService)p.get("rzOverTimeService"); + } + + default IRzOverTimeDetailService getIRzOverTimeDetailService(){ + if(!p.containsKey("rzOverTimeDetailService")){ + p.put("rzOverTimeDetailService", SpringUtils.getBean(IRzOverTimeDetailService.class)); + } + return (IRzOverTimeDetailService)p.get("rzOverTimeDetailService"); + } + + default DingShiftService getDingShiftService(){ + if(!p.containsKey("dingShiftService")){ + p.put("dingShiftService", SpringUtils.getBean(DingShiftService.class)); + } + return (DingShiftService)p.get("dingShiftService"); + } + + default DingShiftGroupService getDingShiftGroupService(){ + if(!p.containsKey("dingShiftGroupService")){ + p.put("dingShiftGroupService", SpringUtils.getBean(DingShiftGroupService.class)); + } + return (DingShiftGroupService)p.get("dingShiftGroupService"); + } + + default IRzLeaveService getIRzLeaveService(){ + if(!p.containsKey("rzLeaveService")){ + p.put("rzLeaveService", SpringUtils.getBean(IRzLeaveService.class)); + } + return (IRzLeaveService)p.get("rzLeaveService"); + } + + default IRzLeaveDetailService getIRzLeaveDetailService(){ + if(!p.containsKey("rzLeaveDetailService")){ + p.put("rzLeaveDetailService", SpringUtils.getBean(IRzLeaveDetailService.class)); + } + return (IRzLeaveDetailService)p.get("rzLeaveDetailService"); + } + + + + + + default SysStaff getSysStaffByDingUserId(String dingUserId){ + SysStaff sysStaff = getISysStaffService().selectSysStaffByDingUserId(dingUserId); + if(sysStaff == null){ + throw new RuntimeException("未查询到用户信息, 用户的DingUserId为:"+dingUserId); + } + return sysStaff; + } +} diff --git a/evo-admin/src/main/java/com/evo/ding/processor/approveStatus/AttendanceApproveStatusChangeExchangeProcessor.java b/evo-admin/src/main/java/com/evo/ding/processor/approveStatus/AttendanceApproveStatusChangeExchangeProcessor.java new file mode 100644 index 0000000..39fffd6 --- /dev/null +++ b/evo-admin/src/main/java/com/evo/ding/processor/approveStatus/AttendanceApproveStatusChangeExchangeProcessor.java @@ -0,0 +1,46 @@ +package com.evo.ding.processor.approveStatus; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceResponseBody; +import com.evo.ding.processor.DingBasicExchangeProcessor; +import com.evo.system.domain.SysStaff; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +/** + * 请假、加班、出差、外出状态变更事件 回调 InstanceChangeExchangeProcessor + * + * @author andy.shi + * @ClassName:InstanceChangeExchangeProcessor + * @date: 2026年03月09日 15:25 + * @remark: 开发人员联系方式 1042025947@qq.com/微信同步 + */ + +public interface AttendanceApproveStatusChangeExchangeProcessor extends DingBasicExchangeProcessor { + + Logger logger = LoggerFactory.getLogger(AttendanceApproveStatusChangeExchangeProcessor.class); + //请假 + String APPROVE_TYPE_LEAVE = "LEAVE"; + //加班 + String APPROVE_TYPE_OVERTIME = "OVERTIME"; + + boolean accept(String approveType, String result, String status); + + void exchange(GetProcessInstanceResponseBody.GetProcessInstanceResponseBodyResult json); + + default JSONObject getExtValue(GetProcessInstanceResponseBody.GetProcessInstanceResponseBodyResult json, String componentType) { + List formComponentValueList = json.getFormComponentValues(); + GetProcessInstanceResponseBody.GetProcessInstanceResponseBodyResultFormComponentValues formComponentValue = formComponentValueList.stream().filter(data -> componentType.equals(data.getComponentType())).findAny().orElse(null); + if(formComponentValue != null){ + return JSON.parseObject(formComponentValue.getExtValue()); + } + throw new RuntimeException("未查询到相关审批信息, 审批类型为:"+componentType+"; 参数信息为: "+ json.toString()); + } + + + +} diff --git a/evo-admin/src/main/java/com/evo/ding/processor/approveStatus/impl/ApproveStatusLeaveExchangeProcessor.java b/evo-admin/src/main/java/com/evo/ding/processor/approveStatus/impl/ApproveStatusLeaveExchangeProcessor.java new file mode 100644 index 0000000..15df17e --- /dev/null +++ b/evo-admin/src/main/java/com/evo/ding/processor/approveStatus/impl/ApproveStatusLeaveExchangeProcessor.java @@ -0,0 +1,89 @@ +package com.evo.ding.processor.approveStatus.impl; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceResponseBody; +import com.evo.common.core.domain.entity.SysDictData; +import com.evo.common.utils.StringUtils; +import com.evo.ding.processor.approveStatus.AttendanceApproveStatusChangeExchangeProcessor; +import com.evo.personnelMatters.domain.RzLeave; +import com.evo.personnelMatters.domain.RzLeaveDetail; +import com.evo.system.domain.SysStaff; +import com.evo.system.service.ISysDictDataService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Date; + +/** + * 请假事件处理 ApproveStatusLeaveExchangeProcessor + * + * @author andy.shi + * @ClassName:ApproveStatusLeaveExchangeProcessor + * @date: 2026年03月12日 13:32 + * @remark: 开发人员联系方式 1042025947@qq.com/微信同步 + */ +@Slf4j +@Service +public class ApproveStatusLeaveExchangeProcessor implements AttendanceApproveStatusChangeExchangeProcessor { + + @Autowired + ISysDictDataService sysDictDataService; + + @Override + public boolean accept(String approveType, String result, String status) { + return APPROVE_TYPE_LEAVE.equals(approveType.toUpperCase()) && APPROVE_STATUS.equals(status.toUpperCase()) && APPROVE_RESULT.equals(result.toUpperCase()); + } + + @Override + public void exchange(GetProcessInstanceResponseBody.GetProcessInstanceResponseBodyResult json) { + try { + SysStaff sysStaff = getSysStaffByDingUserId(json.getOriginatorUserId()); + JSONObject extValueJson = getExtValue(json, "DDHolidayField"); + //解析假期类型 + JSONObject extension = JSON.parseObject(extValueJson.getString("extension")); + //查询假期类型 + String tag = extension.getString("tag"); + SysDictData sysDictData = sysDictDataService.selectDictDataListByType("holidays_type").stream().filter(data-> StringUtils.equals(data.getDictLabel(),tag)).findFirst().orElse(null); + if(sysDictData != null){ + //假期类型的code + Long dictCode = sysDictData.getDictCode(); + JSONArray detailList = extValueJson.getJSONArray("detailList"); + Date beginTime = null; + Date endTime = null; + //如果大于1 证明是多天请假, 需要排序 + if(detailList.size() > 1){ + detailList.sort((a,b)->{ + return ((JSONObject) a).getLong("workDate").compareTo(((JSONObject) b).getLong("workDate")); + }); + //获取第一天 + beginTime = new Date(detailList.getJSONObject(0).getJSONObject("approveInfo").getLong("fromTime")); + //获取最后一天 + endTime = new Date(detailList.getJSONObject(detailList.size()-1).getJSONObject("approveInfo").getLong("toTime")); + }else{ + //只请一天假 + JSONObject approveInfo = detailList.getJSONObject(0).getJSONObject("approveInfo"); + beginTime = new Date(approveInfo.getLong("fromTime")); + //获取最后一天 + endTime = new Date(approveInfo.getLong("toTime")); + } + RzLeave rzLeave = getIRzLeaveService().selectRzLeaveByUserId(sysStaff.getUserId()); + RzLeaveDetail rzLeaveDetail = new RzLeaveDetail(); + rzLeaveDetail.setName(sysStaff.getName()); + rzLeaveDetail.setLeaveStartTime(beginTime); + rzLeaveDetail.setLeaveEndTime(endTime); + rzLeaveDetail.setLeaveId(rzLeave.getId()); + rzLeaveDetail.setType(dictCode); + rzLeaveDetail.setLeaveHour(extValueJson.getInteger("durationInHour")); + rzLeaveDetail.setRemarks("钉钉"+tag); + getIRzLeaveDetailService().insertRzLeaveDetail(rzLeaveDetail); + } + } catch (Exception e) { + log.error("执行钉钉 -[请假审批]-回调出现异常, 异常原因为:{}", e.getMessage()); + e.printStackTrace(); + } + + } +} diff --git a/evo-admin/src/main/java/com/evo/ding/processor/approveStatus/impl/ApproveStatusOvertimeExchangeProcessor.java b/evo-admin/src/main/java/com/evo/ding/processor/approveStatus/impl/ApproveStatusOvertimeExchangeProcessor.java new file mode 100644 index 0000000..133c421 --- /dev/null +++ b/evo-admin/src/main/java/com/evo/ding/processor/approveStatus/impl/ApproveStatusOvertimeExchangeProcessor.java @@ -0,0 +1,118 @@ +package com.evo.ding.processor.approveStatus.impl; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceResponseBody; +import com.evo.attendance.domain.RzAttendanceStatistical; +import com.evo.attendance.domain.RzSpecialAttendance; +import com.evo.attendance.domain.RzSpecialOverTime; +import com.evo.attendance.mapper.RzAttendanceStatisticalMapper; +import com.evo.attendance.mapper.RzSpecialAttendanceMapper; +import com.evo.attendance.mapper.RzSpecialOverTimeMapper; +import com.evo.attendance.service.IRzAttendanceStatisticalService; +import com.evo.attendance.service.IRzSpecialAttendanceService; +import com.evo.common.core.domain.entity.SysDictData; +import com.evo.common.utils.DataUtils; +import com.evo.common.utils.DateUtils; +import com.evo.common.utils.StringUtils; +import com.evo.ding.processor.approveStatus.AttendanceApproveStatusChangeExchangeProcessor; +import com.evo.personnelMatters.domain.RzLeave; +import com.evo.personnelMatters.domain.RzLeaveDetail; +import com.evo.system.domain.SysStaff; +import com.evo.system.service.ISysDictDataService; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; + +/** + *加班事件处理 ApproveStatusOvertimeExchangeProcessor + * + * @author andy.shi + * @ClassName:ApproveStatusLeaveExchangeProcessor + * @date: 2026年03月12日 13:32 + * @remark: 开发人员联系方式 1042025947@qq.com/微信同步 + */ +@Slf4j +@Service +public class ApproveStatusOvertimeExchangeProcessor implements AttendanceApproveStatusChangeExchangeProcessor { + + @Autowired + IRzSpecialAttendanceService rzSpecialAttendanceService; + @Resource + private RzSpecialOverTimeMapper rzSpecialOverTimeMapper; + @Resource + private RzAttendanceStatisticalMapper rzAttendanceStatisticalMapper; + + @Override + public boolean accept(String approveType, String result, String status) { + return APPROVE_TYPE_OVERTIME.equals(approveType.toUpperCase()) && APPROVE_STATUS.equals(status.toUpperCase()) && APPROVE_RESULT.equals(result.toUpperCase()); + } + + @Override + @Transactional + public void exchange(GetProcessInstanceResponseBody.GetProcessInstanceResponseBodyResult result) { + try { + SysStaff sysStaff = getSysStaffByDingUserId(result.getOriginatorUserId()); + List formComponentValueList = result.getFormComponentValues(); + GetProcessInstanceResponseBody.GetProcessInstanceResponseBodyResultFormComponentValues startComponentValue = formComponentValueList.stream().filter(data -> "开始时间".equals(data.getName())).findAny().orElse(null); + GetProcessInstanceResponseBody.GetProcessInstanceResponseBodyResultFormComponentValues endValue = formComponentValueList.stream().filter(data -> "结束时间".equals(data.getName())).findAny().orElse(null); + GetProcessInstanceResponseBody.GetProcessInstanceResponseBodyResultFormComponentValues durationValue = formComponentValueList.stream().filter(data -> "时长".equals(data.getName())).findAny().orElse(null); + if(startComponentValue != null && endValue != null && durationValue != null){ + Date startDate = DateUtils.parseDate(startComponentValue.getValue(), "yyyy-MM-dd HH:mm"); + Date endDate = DateUtils.parseDate(endValue.getValue(), "yyyy-MM-dd HH:mm"); + BigDecimal duration = new BigDecimal(durationValue.getValue()); + //获取加班详情数据 + RzSpecialAttendance rzSpecialAttendance = rzSpecialAttendanceService.selectRzSpecialAttendanceByUserIdAndDate(sysStaff.getUserId(), startDate); + //如果没有, 则新增 + if(rzSpecialAttendance == null){ + rzSpecialAttendance = new RzSpecialAttendance(); + rzSpecialAttendance.setAttendanceDate(startDate); + rzSpecialAttendance.setStaffId(sysStaff.getUserId()); + rzSpecialAttendance.setName(sysStaff.getName()); + rzSpecialAttendance.setDeptId(sysStaff.getDeptId()); + rzSpecialAttendance.setDelFlag("0"); + rzSpecialAttendance.setCreateBy("admin"); + rzSpecialAttendance.setCreateTime(new Date()); + } + rzSpecialAttendance.setWorkStartTime(startDate); + rzSpecialAttendance.setWorkEndTime(endDate); + rzSpecialAttendance.setWorkHours(duration); + rzSpecialAttendanceService.saveOrUpdate(rzSpecialAttendance); + + //获取加班统计数据 + RzSpecialOverTime rzSpecialOverTime = rzSpecialOverTimeMapper.selectRzSpecialOverTimeByUserIdAndDate(rzSpecialAttendance.getStaffId(),rzSpecialAttendance.getAttendanceDate()); + if(ObjectUtils.isEmpty(rzSpecialOverTime)){ + rzSpecialOverTime = new RzSpecialOverTime(); + rzSpecialOverTime.setUserId(sysStaff.getUserId()); + rzSpecialOverTime.setName(sysStaff.getName()); + rzSpecialOverTime.setDeptId(sysStaff.getDeptId()); + rzSpecialOverTime.setDelFlag("0"); + rzSpecialOverTime.setOverDate(startDate); + rzSpecialOverTime.setCreateTime(new Date()); + rzSpecialOverTime.setCreateBy("admin"); + rzSpecialOverTimeMapper.insertRzSpecialOverTime(rzSpecialOverTime); + } + if(DataUtils.DEFAULT_VALUE.compareTo(rzSpecialAttendance.getWorkHours()) != 0){ + rzSpecialOverTime.setSickHours(DataUtils.findDefaultValue(rzSpecialOverTime.getSickHours(), new BigDecimal(0)).add(rzSpecialAttendance.getWorkHours())); + rzSpecialOverTimeMapper.updateRzSpecialOverTime(rzSpecialOverTime); + //更新加班统计 + RzAttendanceStatistical rzAttendanceStatistical = rzAttendanceStatisticalMapper.getRzAttendanceStatisticalByDateAndName(rzSpecialAttendance.getStaffId(),rzSpecialAttendance.getAttendanceDate()); + rzAttendanceStatistical.setOverTimeHours(DataUtils.findDefaultValue(rzAttendanceStatistical.getOverTimeHours(), new BigDecimal(0)).add(rzSpecialAttendance.getWorkHours())); + rzAttendanceStatisticalMapper.updateRzAttendanceStatistical(rzAttendanceStatistical); + } + } + } catch (Exception e) { + log.error("执行钉钉 -[加班审批]-回调出现异常, 异常原因为:{}", e.getMessage()); + e.printStackTrace(); + } + + } +} diff --git a/evo-admin/src/main/java/com/evo/ding/processor/callback/DingCallBackExchangeProcessor.java b/evo-admin/src/main/java/com/evo/ding/processor/callback/DingCallBackExchangeProcessor.java new file mode 100644 index 0000000..f08c20d --- /dev/null +++ b/evo-admin/src/main/java/com/evo/ding/processor/callback/DingCallBackExchangeProcessor.java @@ -0,0 +1,27 @@ +package com.evo.ding.processor.callback; + +import com.alibaba.fastjson2.JSONObject; + +/** + * 钉钉回调入口 DingCallBackExchangeProcessor + * + * @author andy.shi + * @ClassName:DingCallBackExchanageProcessor + * @date: 2026年03月12日 8:49 + * @remark: 开发人员联系方式 1042025947@qq.com/微信同步 + */ +public interface DingCallBackExchangeProcessor { + + //打卡 + static final String EVENT_TYPE_CHECK_RECORD = "attendance_check_record"; + //审批实例订阅 + static final String EVENT_TYPE_INSTANCE_CHANGE = "bpms_instance_change"; + //请假、加班、出差、外出状态变更事件 + static final String EVENT_TYPE_APPROVE_STATUS_CHANGE = "attendance_approve_status_change"; + + boolean accept(String eventType); + + void exchange(JSONObject json); + + +} diff --git a/evo-admin/src/main/java/com/evo/ding/processor/callback/impl/DingCallBackApproveStatusChangeExchangeProcessor.java b/evo-admin/src/main/java/com/evo/ding/processor/callback/impl/DingCallBackApproveStatusChangeExchangeProcessor.java new file mode 100644 index 0000000..b0212bf --- /dev/null +++ b/evo-admin/src/main/java/com/evo/ding/processor/callback/impl/DingCallBackApproveStatusChangeExchangeProcessor.java @@ -0,0 +1,48 @@ +package com.evo.ding.processor.callback.impl; + +import com.alibaba.fastjson2.JSONObject; +import com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceResponseBody; +import com.evo.ding.DingUtils; +import com.evo.ding.processor.approveStatus.AttendanceApproveStatusChangeExchangeProcessor; +import com.evo.ding.processor.instance.InstanceChangeExchangeProcessor; +import com.evo.ding.processor.callback.DingCallBackExchangeProcessor; +import com.evo.system.domain.SysStaff; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 请假、加班、出差、外出状态变更事件 DingCallBackInstanceChangeExchangeProcessor + * + * @author andy.shi + * @ClassName:DingCallBackCheckRecordExchangeProcessor + * @date: 2026年03月12日 9:02 + * @remark: 开发人员联系方式 1042025947@qq.com/微信同步 + */ +@Slf4j +@Service +public class DingCallBackApproveStatusChangeExchangeProcessor implements DingCallBackExchangeProcessor { + + @Autowired + List approveStatusChangeExchangeProcessorList; + + @Override + public boolean accept(String eventType) { + return EVENT_TYPE_APPROVE_STATUS_CHANGE.equals(eventType); + } + + @Override + public void exchange(JSONObject json) { + log.info("请假、加班、出差、外出状态变更事件 回调 ========================>>>{}", json.toString()); + //获取审批示例id + String processInstanceId = json.getString("processInstanceId"); + GetProcessInstanceResponseBody.GetProcessInstanceResponseBodyResult result = DingUtils.getProcessInstanceInfo(processInstanceId); + for (AttendanceApproveStatusChangeExchangeProcessor processor : approveStatusChangeExchangeProcessorList) { + if (processor.accept(json.getString("approveType"), result.getResult(), result.getStatus())) { + processor.exchange(result); + } + } + } +} diff --git a/evo-admin/src/main/java/com/evo/ding/processor/callback/impl/DingCallBackCheckRecordExchangeProcessor.java b/evo-admin/src/main/java/com/evo/ding/processor/callback/impl/DingCallBackCheckRecordExchangeProcessor.java new file mode 100644 index 0000000..7dea73f --- /dev/null +++ b/evo-admin/src/main/java/com/evo/ding/processor/callback/impl/DingCallBackCheckRecordExchangeProcessor.java @@ -0,0 +1,44 @@ +package com.evo.ding.processor.callback.impl; + +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.evo.attendance.service.IRzAttendanceService; +import com.evo.common.utils.StringUtils; +import com.evo.ding.processor.callback.DingCallBackExchangeProcessor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * 员工打开回调 DingCallBackCheckRecordExchangeProcessor + * + * @author andy.shi + * @ClassName:DingCallBackCheckRecordExchangeProcessor + * @date: 2026年03月12日 9:02 + * @remark: 开发人员联系方式 1042025947@qq.com/微信同步 + */ +@Slf4j +@Service +public class DingCallBackCheckRecordExchangeProcessor implements DingCallBackExchangeProcessor { + + @Autowired + IRzAttendanceService rzAttendanceService; + + @Override + public boolean accept(String eventType) { + return EVENT_TYPE_CHECK_RECORD.equals(eventType); + } + + @Override + public void exchange(JSONObject json) { + log.info("钉钉 打卡回调========================>>>{}", json.toString()); + JSONArray dataList = json.getJSONArray("DataList"); + if(dataList!= null && dataList.size() > 0){ + JSONObject jsonObject = dataList.getJSONObject(0); + String userId = jsonObject.getString("userId"); + if(StringUtils.isNotEmpty(userId)){ + rzAttendanceService.dingDing(userId, jsonObject.getString("bizId")); + } + } + } +} diff --git a/evo-admin/src/main/java/com/evo/ding/processor/callback/impl/DingCallBackInstanceChangeExchangeProcessor.java b/evo-admin/src/main/java/com/evo/ding/processor/callback/impl/DingCallBackInstanceChangeExchangeProcessor.java new file mode 100644 index 0000000..a401827 --- /dev/null +++ b/evo-admin/src/main/java/com/evo/ding/processor/callback/impl/DingCallBackInstanceChangeExchangeProcessor.java @@ -0,0 +1,41 @@ +package com.evo.ding.processor.callback.impl; + +import com.alibaba.fastjson2.JSONObject; +import com.evo.ding.processor.instance.InstanceChangeExchangeProcessor; +import com.evo.ding.processor.callback.DingCallBackExchangeProcessor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 审批实例订阅 DingCallBackInstanceChangeExchangeProcessor + * + * @author andy.shi + * @ClassName:DingCallBackCheckRecordExchangeProcessor + * @date: 2026年03月12日 9:02 + * @remark: 开发人员联系方式 1042025947@qq.com/微信同步 + */ +@Slf4j +@Service +public class DingCallBackInstanceChangeExchangeProcessor implements DingCallBackExchangeProcessor { + + @Autowired + List instanceChangeExchangeProcessorList; + + @Override + public boolean accept(String eventType) { + return EVENT_TYPE_INSTANCE_CHANGE.equals(eventType); + } + + @Override + public void exchange(JSONObject json) { + log.info("审批实例订阅回调========================>>>{}", json.toString()); + for (InstanceChangeExchangeProcessor processor : instanceChangeExchangeProcessorList) { + if (processor.accept(json.getString("processCode"))) { + processor.exchange(json); + } + } + } +} diff --git a/evo-admin/src/main/java/com/evo/ding/processor/instance/InstanceChangeExchangeProcessor.java b/evo-admin/src/main/java/com/evo/ding/processor/instance/InstanceChangeExchangeProcessor.java new file mode 100644 index 0000000..3b2af57 --- /dev/null +++ b/evo-admin/src/main/java/com/evo/ding/processor/instance/InstanceChangeExchangeProcessor.java @@ -0,0 +1,32 @@ +package com.evo.ding.processor.instance; + +import com.alibaba.fastjson2.JSONObject; +import com.evo.ding.processor.DingBasicExchangeProcessor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 钉钉审批实例回调处理接口 InstanceChangeExchangeProcessor + * + * @author andy.shi + * @ClassName:InstanceChangeExchangeProcessor + * @date: 2026年03月09日 15:25 + * @remark: 开发人员联系方式 1042025947@qq.com/微信同步 + */ + +public interface InstanceChangeExchangeProcessor extends DingBasicExchangeProcessor { + + Logger logger = LoggerFactory.getLogger(InstanceChangeExchangeProcessor.class); + + //补卡 + static final String PROCESS_CODE_RECHARGE_CARD = "PROC-19617D02-F632-402E-9ACD-134CC47E462F"; + + boolean accept(String processCode); + + void exchange(JSONObject json); + + + + + +} diff --git a/evo-admin/src/main/java/com/evo/ding/processor/instance/impl/InstanceChangeRechargeCardExchangeProcessor.java b/evo-admin/src/main/java/com/evo/ding/processor/instance/impl/InstanceChangeRechargeCardExchangeProcessor.java new file mode 100644 index 0000000..63e7fb2 --- /dev/null +++ b/evo-admin/src/main/java/com/evo/ding/processor/instance/impl/InstanceChangeRechargeCardExchangeProcessor.java @@ -0,0 +1,77 @@ +package com.evo.ding.processor.instance.impl; + +import com.alibaba.fastjson2.JSONObject; +import com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceResponseBody; +import com.evo.common.utils.DateUtils; +import com.evo.common.utils.StringUtils; +import com.evo.ding.DingUtils; +import com.evo.ding.domain.DingShiftGroup; +import com.evo.ding.processor.instance.InstanceChangeExchangeProcessor; +import com.evo.system.domain.SysStaff; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.text.ParseException; +import java.util.Date; +import java.util.List; + +/** + * 钉钉补卡接口回调 InstanceChangeRechargeCardExchangeProcessor + * + * @author andy.shi + * @ClassName:InstanceChangeRechargeCardExchangeProcessor + * @date: 2026年03月10日 10:24 + * @remark: 开发人员联系方式 1042025947@qq.com/微信同步 + */ +@Slf4j +@Service +public class InstanceChangeRechargeCardExchangeProcessor implements InstanceChangeExchangeProcessor { + + @Override + public boolean accept(String processCode) { + return InstanceChangeExchangeProcessor.PROCESS_CODE_RECHARGE_CARD.equals(processCode); + } + @Override + public void exchange(JSONObject json) { + //获取审批示例id + String processInstanceId = json.getString("processInstanceId"); + GetProcessInstanceResponseBody.GetProcessInstanceResponseBodyResult result = DingUtils.getProcessInstanceInfo(processInstanceId); + //根据操作人id, 获取user + SysStaff sysStaff = getSysStaffByDingUserId(result.getOriginatorUserId()); + if(sysStaff != null){ + List resultFormValues = result.getFormComponentValues(); + //补卡时间 + String bkTime = resultFormValues.stream().filter(data-> "补卡时间".equals(data.getName()) && StringUtils.isNotEmpty(data.getValue())).findFirst().get().getValue(); + String extValue = resultFormValues.stream().filter(data-> "补卡时间".equals(data.getName()) && StringUtils.isNotEmpty(data.getExtValue())).findFirst().get().getExtValue(); + //补卡类型 + String bkType = resultFormValues.stream().filter(data-> "补卡类型".equals(data.getName()) && StringUtils.isNotEmpty(data.getValue())).findFirst().get().getValue(); + //补卡原因 + String bkYi = resultFormValues.stream().filter(data-> "补卡原因".equals(data.getName()) && StringUtils.isNotEmpty(data.getValue())).findFirst().get().getValue(); + //获取补卡班次 + String checkType = "上班卡".equals(bkType) ? "OnDuty" : "OffDuty"; + DingShiftGroup dingsShiftGroup = null; + try { + if(StringUtils.isNotEmpty(extValue)){ + JSONObject js = JSONObject.parseObject(extValue); + String planId = js.getString("planId"); + String s = DingUtils.getPlanInfo(result.getOriginatorUserId(), planId); + JSONObject planResult = JSONObject.parseObject(s); + if(Integer.valueOf(0).equals(planResult.getInteger("errcode"))){ + JSONObject planResultObject = planResult.getJSONArray("result").getJSONObject(0); + String formatPlanCheckTime = DateUtils.parseDateToStr("HH:mm:ss", new Date(planResultObject.getString("plan_check_time"))); + dingsShiftGroup = getDingShiftGroupService().selectByGroupIdAndTypeAndCheckTime(planResultObject.getString("group_id"), checkType, formatPlanCheckTime); + Date date= DateUtils.parseDate(bkTime, "yyyy-MM-dd HH:mm"); + //生成补卡记录信息 + getIRzAttendanceService().addRzOverTime(json.toString(), sysStaff, dingsShiftGroup, checkType, date, date, "/", "钉钉补卡-"+bkYi); + } + } + } catch (ParseException e) { + log.info("补卡出现错误, 为在本地找到班次数据, 错误信息为:{}, 回调信息为: {}", e.getMessage(), json.toString()); + e.printStackTrace(); + } + } + } +} + + + diff --git a/evo-admin/src/main/java/com/evo/ding/service/DingShiftGroupService.java b/evo-admin/src/main/java/com/evo/ding/service/DingShiftGroupService.java new file mode 100644 index 0000000..6128a32 --- /dev/null +++ b/evo-admin/src/main/java/com/evo/ding/service/DingShiftGroupService.java @@ -0,0 +1,17 @@ +package com.evo.ding.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.evo.ding.domain.DingShiftGroup; + +/** + * DingShiftGroupService + * + * @author andy.shi + * @ClassName:DingShiftGroupService + * @date: 2026年03月11日 15:48 + * @remark: 开发人员联系方式 1042025947@qq.com/微信同步 + */ +public interface DingShiftGroupService extends IService { + + DingShiftGroup selectByGroupIdAndTypeAndCheckTime(String groupId, String type, String checkTime); +} diff --git a/evo-admin/src/main/java/com/evo/ding/service/DingShiftService.java b/evo-admin/src/main/java/com/evo/ding/service/DingShiftService.java new file mode 100644 index 0000000..cc40ef0 --- /dev/null +++ b/evo-admin/src/main/java/com/evo/ding/service/DingShiftService.java @@ -0,0 +1,17 @@ +package com.evo.ding.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.evo.ding.domain.DingShift; + +/** + * DingsShiftService + * + * @author andy.shi + * @ClassName:DingsShiftService + * @date: 2026年03月11日 15:55 + * @remark: 开发人员联系方式 1042025947@qq.com/微信同步 + */ +public interface DingShiftService extends IService { + + public DingShift selectByDingId(String dingId); +} diff --git a/evo-admin/src/main/java/com/evo/ding/service/impl/DingShiftGroupServiceImpl.java b/evo-admin/src/main/java/com/evo/ding/service/impl/DingShiftGroupServiceImpl.java new file mode 100644 index 0000000..97edfa9 --- /dev/null +++ b/evo-admin/src/main/java/com/evo/ding/service/impl/DingShiftGroupServiceImpl.java @@ -0,0 +1,23 @@ +package com.evo.ding.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.evo.ding.domain.DingShiftGroup; +import com.evo.ding.mapper.DingShiftGroupMapper; +import com.evo.ding.service.DingShiftGroupService; +import org.springframework.stereotype.Service; + +/** + * DingShiftGroupServiceImpl + * + * @author andy.shi + * @ClassName:DingShiftGroupServiceImpl + * @date: 2026年03月11日 15:49 + * @remark: 开发人员联系方式 1042025947@qq.com/微信同步 + */ +@Service +public class DingShiftGroupServiceImpl extends ServiceImpl implements DingShiftGroupService { + @Override + public DingShiftGroup selectByGroupIdAndTypeAndCheckTime(String groupId, String type, String checkTime) { + return getBaseMapper().selectByGroupIdAndTypeAndCheckTime(groupId, type, checkTime); + } +} diff --git a/evo-admin/src/main/java/com/evo/ding/service/impl/DingShiftServiceImpl.java b/evo-admin/src/main/java/com/evo/ding/service/impl/DingShiftServiceImpl.java new file mode 100644 index 0000000..ba30bb8 --- /dev/null +++ b/evo-admin/src/main/java/com/evo/ding/service/impl/DingShiftServiceImpl.java @@ -0,0 +1,26 @@ +package com.evo.ding.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.evo.ding.domain.DingShift; +import com.evo.ding.domain.DingShiftGroup; +import com.evo.ding.mapper.DingShiftGroupMapper; +import com.evo.ding.mapper.DingShiftMapper; +import com.evo.ding.service.DingShiftGroupService; +import com.evo.ding.service.DingShiftService; +import org.springframework.stereotype.Service; + +/** + * DingShiftGroupServiceImpl + * + * @author andy.shi + * @ClassName:DingShiftGroupServiceImpl + * @date: 2026年03月11日 15:49 + * @remark: 开发人员联系方式 1042025947@qq.com/微信同步 + */ +@Service +public class DingShiftServiceImpl extends ServiceImpl implements DingShiftService { + @Override + public DingShift selectByDingId(String dingId) { + return getBaseMapper().selectByDingId(dingId); + } +} diff --git a/evo-admin/src/main/java/com/evo/personnelMatters/mapper/RzLeaveMapper.java b/evo-admin/src/main/java/com/evo/personnelMatters/mapper/RzLeaveMapper.java index ec4bbc4..6eec94d 100644 --- a/evo-admin/src/main/java/com/evo/personnelMatters/mapper/RzLeaveMapper.java +++ b/evo-admin/src/main/java/com/evo/personnelMatters/mapper/RzLeaveMapper.java @@ -51,4 +51,10 @@ public interface RzLeaveMapper extends BaseMapper * @return */ public RzLeave queryRzLeaveByDateAndUserId(@Param("userId") Long userId, @Param("date") Date date); + + /** + * 根据员工ID和时间查询请假统计 + * @return + */ + public RzLeave queryRzLeaveByUserId(@Param("userId") Long userId); } diff --git a/evo-admin/src/main/java/com/evo/personnelMatters/service/IRzLeaveService.java b/evo-admin/src/main/java/com/evo/personnelMatters/service/IRzLeaveService.java index ba69bc1..7cce1ea 100644 --- a/evo-admin/src/main/java/com/evo/personnelMatters/service/IRzLeaveService.java +++ b/evo-admin/src/main/java/com/evo/personnelMatters/service/IRzLeaveService.java @@ -59,4 +59,12 @@ public interface IRzLeaveService public List listLeaveDetails(RzLeave rzLeave); List selectRzLeaveListByDate(RzLeave rzLeave); + + /** + * 根据员工 + * + * @param userId 员工id + * @return 请假管理 + */ + public RzLeave selectRzLeaveByUserId(Long userId); } diff --git a/evo-admin/src/main/java/com/evo/personnelMatters/service/IRzOverTimeDetailService.java b/evo-admin/src/main/java/com/evo/personnelMatters/service/IRzOverTimeDetailService.java index f28c8e3..3852f15 100644 --- a/evo-admin/src/main/java/com/evo/personnelMatters/service/IRzOverTimeDetailService.java +++ b/evo-admin/src/main/java/com/evo/personnelMatters/service/IRzOverTimeDetailService.java @@ -1,5 +1,6 @@ package com.evo.personnelMatters.service; +import com.baomidou.mybatisplus.extension.service.IService; import com.evo.common.core.domain.AjaxResult; import com.evo.personnelMatters.domain.RzOverTimeDetail; @@ -11,7 +12,7 @@ import java.util.List; * @author chenyj * @date 2024-09-09 */ -public interface IRzOverTimeDetailService +public interface IRzOverTimeDetailService extends IService { /** * 查询加班详情 diff --git a/evo-admin/src/main/java/com/evo/personnelMatters/service/IRzOverTimeService.java b/evo-admin/src/main/java/com/evo/personnelMatters/service/IRzOverTimeService.java index fee067e..aa71bb2 100644 --- a/evo-admin/src/main/java/com/evo/personnelMatters/service/IRzOverTimeService.java +++ b/evo-admin/src/main/java/com/evo/personnelMatters/service/IRzOverTimeService.java @@ -1,9 +1,13 @@ package com.evo.personnelMatters.service; +import com.baomidou.mybatisplus.extension.service.IService; import com.evo.common.core.domain.AjaxResult; import com.evo.personnelMatters.domain.RzOverTime; import com.evo.personnelMatters.domain.RzOverTimeDetail; +import com.evo.system.domain.SysStaff; +import org.apache.ibatis.annotations.Param; +import java.util.Date; import java.util.List; /** @@ -12,7 +16,7 @@ import java.util.List; * @author chenyj * @date 2024-09-03 */ -public interface IRzOverTimeService +public interface IRzOverTimeService extends IService { /** * 查询加班管理 @@ -60,4 +64,10 @@ public interface IRzOverTimeService * @return */ public List selectRzOverTimeDetailListByUserIdAndMonth(RzOverTime rzOverTime); + + public RzOverTime selectRzOverTimeByNameAndMonth(Long userId, Date overTimeMonth); + + public String overTimeCard(SysStaff sysStaff, Date overTimeMonth); + + public String overTimeOffDutyCard(SysStaff sysStaff , Date date, String sn); } diff --git a/evo-admin/src/main/java/com/evo/personnelMatters/service/impl/RzLeaveServiceImpl.java b/evo-admin/src/main/java/com/evo/personnelMatters/service/impl/RzLeaveServiceImpl.java index 3340a27..356d8b4 100644 --- a/evo-admin/src/main/java/com/evo/personnelMatters/service/impl/RzLeaveServiceImpl.java +++ b/evo-admin/src/main/java/com/evo/personnelMatters/service/impl/RzLeaveServiceImpl.java @@ -2,6 +2,7 @@ package com.evo.personnelMatters.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.evo.common.annotation.DataScope; import com.evo.common.constant.Constants; import com.evo.common.core.domain.AjaxResult; @@ -29,7 +30,7 @@ import java.util.List; * @date 2024-08-03 */ @Service -public class RzLeaveServiceImpl implements IRzLeaveService +public class RzLeaveServiceImpl extends ServiceImpl implements IRzLeaveService { @Resource private RzLeaveMapper rzLeaveMapper; @@ -160,4 +161,9 @@ public class RzLeaveServiceImpl implements IRzLeaveService wa.like(StringUtils.isNotBlank(rzLeave.getDeptName()),RzLeave::getDeptName,rzLeave.getDeptName()); return rzLeaveMapper.selectList(wa); } + + @Override + public RzLeave selectRzLeaveByUserId(Long userId) { + return getBaseMapper().queryRzLeaveByUserId(userId); + } } diff --git a/evo-admin/src/main/java/com/evo/personnelMatters/service/impl/RzOverTimeDetailServiceImpl.java b/evo-admin/src/main/java/com/evo/personnelMatters/service/impl/RzOverTimeDetailServiceImpl.java index 93f9dfa..f0f9c07 100644 --- a/evo-admin/src/main/java/com/evo/personnelMatters/service/impl/RzOverTimeDetailServiceImpl.java +++ b/evo-admin/src/main/java/com/evo/personnelMatters/service/impl/RzOverTimeDetailServiceImpl.java @@ -1,5 +1,6 @@ package com.evo.personnelMatters.service.impl; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.evo.attendance.domain.RzAttendance; import com.evo.attendance.domain.RzAttendanceDetail; import com.evo.attendance.mapper.RzAttendanceDetailMapper; @@ -34,7 +35,7 @@ import java.util.List; * @date 2024-09-09 */ @Service -public class RzOverTimeDetailServiceImpl implements IRzOverTimeDetailService +public class RzOverTimeDetailServiceImpl extends ServiceImpl implements IRzOverTimeDetailService { @Resource private RzOverTimeDetailMapper rzOverTimeDetailMapper; diff --git a/evo-admin/src/main/java/com/evo/personnelMatters/service/impl/RzOverTimeServiceImpl.java b/evo-admin/src/main/java/com/evo/personnelMatters/service/impl/RzOverTimeServiceImpl.java index 7c0d477..bab685e 100644 --- a/evo-admin/src/main/java/com/evo/personnelMatters/service/impl/RzOverTimeServiceImpl.java +++ b/evo-admin/src/main/java/com/evo/personnelMatters/service/impl/RzOverTimeServiceImpl.java @@ -1,9 +1,12 @@ package com.evo.personnelMatters.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.evo.attendance.processor.KqUtils; +import com.evo.attendance.processor.PunchTheClockStrategyExchangeProcessor; import com.evo.common.annotation.DataScope; import com.evo.common.constant.Constants; import com.evo.common.core.domain.AjaxResult; +import com.evo.common.utils.DataUtils; import com.evo.common.utils.DateUtils; import com.evo.common.utils.SecurityUtils; import com.evo.common.utils.StringUtils; @@ -15,10 +18,12 @@ import com.evo.personnelMatters.service.IRzOverTimeService; import com.evo.system.domain.SysStaff; import com.evo.system.mapper.SysDeptMapper; import com.evo.system.mapper.SysStaffMapper; +import org.apache.commons.lang3.ObjectUtils; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Date; import java.util.List; /** @@ -151,5 +156,77 @@ public class RzOverTimeServiceImpl extends ServiceImpl selectDictDataListByType(String type); } diff --git a/evo-admin/src/main/java/com/evo/system/service/ISysStaffService.java b/evo-admin/src/main/java/com/evo/system/service/ISysStaffService.java index 3884ae2..751247e 100644 --- a/evo-admin/src/main/java/com/evo/system/service/ISysStaffService.java +++ b/evo-admin/src/main/java/com/evo/system/service/ISysStaffService.java @@ -108,13 +108,18 @@ public interface ISysStaffService extends IService * @param userId 重新入职员工Id */ AjaxResult reEmployment(Long userId); - - - /*** * 数据导出 * @param response * @param sysStaff */ void exportInfo(HttpServletResponse response, SysStaff sysStaff); + + /** + * 根据钉钉id 查询员工信息 + * + * @param dingUserId 钉钉userId + * @return 员工管理 + */ + public SysStaff selectSysStaffByDingUserId(String dingUserId); } diff --git a/evo-admin/src/main/java/com/evo/system/service/impl/SysDictDataServiceImpl.java b/evo-admin/src/main/java/com/evo/system/service/impl/SysDictDataServiceImpl.java index b1368dd..71479d5 100644 --- a/evo-admin/src/main/java/com/evo/system/service/impl/SysDictDataServiceImpl.java +++ b/evo-admin/src/main/java/com/evo/system/service/impl/SysDictDataServiceImpl.java @@ -117,4 +117,9 @@ public class SysDictDataServiceImpl extends ServiceImpl selectDictDataListByType(String type) { + return getBaseMapper().selectDictDataByType(type); + } } diff --git a/evo-admin/src/main/java/com/evo/system/service/impl/SysStaffServiceImpl.java b/evo-admin/src/main/java/com/evo/system/service/impl/SysStaffServiceImpl.java index 4e19e43..19cedb4 100644 --- a/evo-admin/src/main/java/com/evo/system/service/impl/SysStaffServiceImpl.java +++ b/evo-admin/src/main/java/com/evo/system/service/impl/SysStaffServiceImpl.java @@ -838,4 +838,9 @@ public class SysStaffServiceImpl extends ServiceImpl i ExcelUtil1 util = new ExcelUtil1(SysStaffVo.class); util.exportExcel(response, res_list, "员工信息"); } + + @Override + public SysStaff selectSysStaffByDingUserId(String dingUserId) { + return getBaseMapper().selectSysStaffByDingUserId(dingUserId); + } } diff --git a/evo-admin/src/main/resources/mapper/ding/DingShiftMapper.xml b/evo-admin/src/main/resources/mapper/ding/DingShiftMapper.xml new file mode 100644 index 0000000..90dea11 --- /dev/null +++ b/evo-admin/src/main/resources/mapper/ding/DingShiftMapper.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + select id, name, ding_Id, work_Hour, overtime_Work, deduction_Hour from ding_shift + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/evo-admin/src/main/resources/mapper/personnelMatters/RzLeaveMapper.xml b/evo-admin/src/main/resources/mapper/personnelMatters/RzLeaveMapper.xml index 5a863be..34e0a1c 100644 --- a/evo-admin/src/main/resources/mapper/personnelMatters/RzLeaveMapper.xml +++ b/evo-admin/src/main/resources/mapper/personnelMatters/RzLeaveMapper.xml @@ -116,4 +116,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" where del_flag = '0' and DATE_FORMAT( leave_date, '%Y%m' ) = DATE_FORMAT( #{date} , '%Y%m' ) and user_id = #{userId} + + +