diff --git a/cash-api/market-server/src/main/java/com/czg/controller/admin/AttendanceController.java b/cash-api/market-server/src/main/java/com/czg/controller/admin/AttendanceController.java index 312d9e1e6..04e31ce57 100644 --- a/cash-api/market-server/src/main/java/com/czg/controller/admin/AttendanceController.java +++ b/cash-api/market-server/src/main/java/com/czg/controller/admin/AttendanceController.java @@ -62,6 +62,20 @@ public class AttendanceController { public CzgResult> getList( @RequestParam(required = false) String name, @RequestParam(required = false) String startTime, @RequestParam(required = false) String endTime) { - return CzgResult.success(attendanceService.list(StpKit.USER.getShopId(), name, startTime, endTime)); + return CzgResult.success(attendanceService.list(StpKit.USER.getShopId(), name, startTime, endTime, null)); + } + + + /** + * 用户打卡详情 + * @param userId 返回的userId + * @param startTime 开始时间 + * @param endTime 结束时间 + */ + @GetMapping("/detail") + public CzgResult> detail( + @RequestParam String userId, @RequestParam(required = false) String startTime, + @RequestParam(required = false) String endTime, @RequestParam(required = false, defaultValue = "0") Integer weekNum) { + return CzgResult.success(attendanceService.detail(StpKit.USER.getShopId(), userId, startTime, endTime, weekNum)); } } diff --git a/cash-common/cash-common-service/src/main/java/com/czg/market/service/AttendanceService.java b/cash-common/cash-common-service/src/main/java/com/czg/market/service/AttendanceService.java index eae89aeb0..e055cc43f 100644 --- a/cash-common/cash-common-service/src/main/java/com/czg/market/service/AttendanceService.java +++ b/cash-common/cash-common-service/src/main/java/com/czg/market/service/AttendanceService.java @@ -3,7 +3,10 @@ package com.czg.market.service; import com.czg.market.vo.DingAttendanceStatsVO; import java.util.ArrayList; +import java.util.Map; public interface AttendanceService { - ArrayList list(Long shopId, String name, String startTime, String endTime); + ArrayList list(Long shopId, String name, String startTime, String endTime, String userId); + + Map detail(Long shopId, String userId, String startTime, String endTime, Integer weekNum); } diff --git a/cash-common/cash-common-service/src/main/java/com/czg/market/vo/DingAttendanceStatsVO.java b/cash-common/cash-common-service/src/main/java/com/czg/market/vo/DingAttendanceStatsVO.java index fe69c8400..766e8d5f2 100644 --- a/cash-common/cash-common-service/src/main/java/com/czg/market/vo/DingAttendanceStatsVO.java +++ b/cash-common/cash-common-service/src/main/java/com/czg/market/vo/DingAttendanceStatsVO.java @@ -1,5 +1,6 @@ package com.czg.market.vo; +import com.alibaba.fastjson2.JSONArray; import lombok.Data; import java.math.BigDecimal; @@ -33,6 +34,12 @@ public class DingAttendanceStatsVO { * 用户是否有有效考勤数据 */ private boolean isActive; + /** + * 旷工天数 + */ + private String absenceDays; + + private JSONArray report; public void setValByName(String name, String val) { switch (name) { @@ -54,6 +61,10 @@ public class DingAttendanceStatsVO { case "应出勤天数": this.shouldAttendDays = val; break; + case "旷工天数": + this.absenceDays = val; + break; + } } diff --git a/cash-service/market-service/src/main/java/com/czg/service/market/service/impl/AttendanceServiceImpl.java b/cash-service/market-service/src/main/java/com/czg/service/market/service/impl/AttendanceServiceImpl.java index 1e188d2b9..1700c0f01 100644 --- a/cash-service/market-service/src/main/java/com/czg/service/market/service/impl/AttendanceServiceImpl.java +++ b/cash-service/market-service/src/main/java/com/czg/service/market/service/impl/AttendanceServiceImpl.java @@ -3,6 +3,8 @@ package com.czg.service.market.service.impl; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; import com.czg.account.entity.ShopConfig; import com.czg.account.service.ShopConfigService; import com.czg.market.service.AttendanceService; @@ -10,8 +12,13 @@ import com.czg.market.vo.DingAttendanceStatsVO; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; +import java.math.BigDecimal; +import java.time.DayOfWeek; import java.util.ArrayList; import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentLinkedDeque; @Service public class AttendanceServiceImpl implements AttendanceService { @@ -21,18 +28,18 @@ public class AttendanceServiceImpl implements AttendanceService { private ShopConfigService shopConfigService; @Override - public ArrayList list(Long shopId, String name, String startTime, String endTime) { + public ArrayList list(Long shopId, String name, String startTime, String endTime, String userId) { DateTime start; DateTime end; if (StrUtil.isBlank(startTime)) { start = DateUtil.beginOfMonth(new Date()); - }else { + } else { start = DateUtil.parse(startTime); } if (StrUtil.isBlank(endTime)) { end = DateUtil.date(); - }else { + } else { end = DateUtil.parse(endTime); } @@ -41,6 +48,62 @@ public class AttendanceServiceImpl implements AttendanceService { return new ArrayList<>(); } - return dingService.getUserReport(shopId, config.getDingAppKey(), config.getDingAppSecret(), start, end, name); + return dingService.getUserReport(shopId, config.getDingAppKey(), config.getDingAppSecret(), start, end, name, userId); + } + + // 自定义规则:1-5 → 数字, 6 → 六, 7 → 日 + private static String formatWeek(String week) { + return switch (week) { + case "星期一" -> "1"; + case "星期二" -> "2"; + case "星期三" -> "3"; + case "星期四" -> "4"; + case "星期五" -> "5"; + case "星期六" -> "六"; + case "星期日" -> "日"; + default -> week; + }; + } + + @Override + public Map detail(Long shopId, String userId, String startTime, String endTime, Integer weekNum) { + ArrayList listed = list(shopId, null, startTime, endTime, userId); + + ArrayList> weekList = new ArrayList<>(); + DateTime dateTime = StrUtil.isBlank(endTime) ? DateUtil.date() : DateUtil.parse(endTime); + for (int i = 7 * weekNum; i < 15 + 7 * weekNum; i++) { + DateTime date = DateUtil.offsetDay(dateTime, (15 - i + 1) * -1); + String week = formatWeek(DateUtil.dayOfWeekEnum(date).toChinese()); + weekList.add(new HashMap<>(){{ + put("week", week); + put("date", date); + }}); + } + + if (listed.isEmpty()) { + return new HashMap() {{ + put("attendanceSummary", null); + put("weekList", weekList); + put("statusList", null); + }}; + } + ShopConfig config = shopConfigService.getById(shopId); + if (config == null || StrUtil.isBlank(config.getDingAppSecret())) { + return new HashMap() {{ + put("attendanceSummary", null); + put("weekList", weekList); + put("statusList", null); + }}; + } + ConcurrentLinkedDeque statusList = new ConcurrentLinkedDeque<>(); + weekList.parallelStream().forEach(week -> { + statusList.add(dingService.getAttendanceStatus(config.getDingAppKey(), config.getDingAppSecret(), shopId, (Date) week.get("date"), userId)); + }); + + return Map.of( + "attendanceSummary", listed.getFirst(), + "weekList", weekList, + "statusList", statusList + ); } } diff --git a/cash-service/market-service/src/main/java/com/czg/service/market/service/impl/DingService.java b/cash-service/market-service/src/main/java/com/czg/service/market/service/impl/DingService.java index e222782bb..44df7d2f0 100644 --- a/cash-service/market-service/src/main/java/com/czg/service/market/service/impl/DingService.java +++ b/cash-service/market-service/src/main/java/com/czg/service/market/service/impl/DingService.java @@ -5,6 +5,7 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import com.czg.market.vo.DingAttendanceStatsVO; import com.czg.market.vo.DingUserVO; @@ -15,19 +16,33 @@ import com.dingtalk.api.request.*; import com.dingtalk.api.response.*; import com.taobao.api.ApiException; import com.taobao.api.TaobaoResponse; +import com.taobao.api.internal.util.StringUtils; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.util.*; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; import java.util.stream.Collectors; @Service public class DingService { + public static final Map STATUS_MAP = new HashMap<>(){{ + put("Normal", "正常"); + put("Early", "早退"); + put("Late", "迟到"); + put("SeriousLate", "严重迟到"); + put("Absenteeism", "旷工迟到"); + put("NotSigned", "未打卡"); + }}; + @Resource private RedisService redisService; + public String getToken(String key, String secret, Long shopId) { Object accessToken = redisService.get("ding_access_token:" + shopId); if (accessToken instanceof String && StrUtil.isNotBlank((String) accessToken)) { @@ -53,7 +68,25 @@ public class DingService { return data.getString("access_token"); } + public String getColumIdByName(String key, String secret, Long shopId, String name) { + Map columns = getColumns(key, secret, shopId); + String columId = null; + for (Map.Entry entry : columns.entrySet()) { + String key1 = entry.getKey(); + String value = entry.getValue(); + if (value.equals(name)) { + columId = key1; + break; + } + } + return columId; + } + public Map getColumns(String key, String secret, Long shopId) { + Object value = redisService.get("ding_columns:" + shopId); + if (value instanceof String && StrUtil.isNotBlank((String) value)) { + return JSONObject.parseObject((String) value, Map.class); + } DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/attendance/getattcolumns"); OapiAttendanceGetattcolumnsRequest req = new OapiAttendanceGetattcolumnsRequest(); OapiAttendanceGetattcolumnsResponse rsp = null; @@ -72,10 +105,11 @@ public class DingService { Map columMap = jsonObject.getJSONObject("result").getJSONArray("columns").stream() .filter(item -> { JSONObject item1 = (JSONObject) item; - return item1.containsKey("id") && CollUtil.newArrayList("出勤天数", "休息天数", "工作时长", "迟到次数", "迟到时长", "应出勤天数").contains(item1.getString("name")); + return item1.containsKey("id") && CollUtil.newArrayList("出勤天数", "休息天数", "工作时长", "迟到次数", "迟到时长", "应出勤天数", "旷工迟到次数").contains(item1.getString("name")); }) .collect(Collectors.toMap(item -> ((JSONObject) item).getLong("id").toString(), item -> ((JSONObject) item).getString("name"))); + redisService.set("ding_columns:" + shopId, JSONObject.toJSONString(columMap)); return columMap; } @@ -125,32 +159,83 @@ public class DingService { return data == null ? null : data.getJSONObject("result"); } - public ArrayList getUserList(String name,String key, String secret, Long shopId) { + public String getAttendanceStatus(String key, String secret, Long shopId, Date date, String userId) { + String status = ""; + try { + DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/attendance/getupdatedata"); + OapiAttendanceGetupdatedataRequest req = new OapiAttendanceGetupdatedataRequest(); + req.setUserid(userId); + req.setWorkDate(date); + OapiAttendanceGetupdatedataResponse rsp = client.execute(req, getToken(key, secret, shopId)); + JSONArray resultList = JSONObject.parseObject(rsp.getBody()).getJSONObject("result").getJSONArray("attendance_result_list"); + if (resultList.isEmpty()) { + status = "休息日"; + } + for (Object item : resultList) { + JSONObject jsonObject = (JSONObject) item; + String timeResult = jsonObject.getString("time_result"); + if (!"正常".equals(status)) { + status = STATUS_MAP.get(timeResult); + } + } + + return status; + } catch (ApiException e) { + throw new RuntimeException(e); + } + } + + public ArrayList getUserList(String name, String key, String secret, Long shopId, String userId) { try { DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/v2/department/listsub"); OapiV2DepartmentListsubRequest req = new OapiV2DepartmentListsubRequest(); req.setDeptId(1L); req.setLanguage("zh_CN"); - OapiV2DepartmentListsubResponse rsp = client.execute(req, getToken(key, secret, shopId)); - Set deptIdList = getData(rsp, true).getJSONArray("result").stream().map(item -> ((JSONObject) item) - .getLong("dept_id")).collect(Collectors.toSet()); - ArrayList dingUserVOS = new ArrayList<>(); - for (Long dptId : deptIdList) { - dingUserVOS.addAll(getUserByDeptId(dptId, key, secret, shopId)); + OapiV2DepartmentListsubResponse rsp = client.execute(req, getToken(key, secret, shopId)); + + Set deptIdList = getData(rsp, true).getJSONArray("result") + .stream() + .map(item -> ((JSONObject) item).getLong("dept_id")) + .collect(Collectors.toSet()); + + // 线程池:按 CPU 核数 * 2 + ExecutorService pool = Executors.newFixedThreadPool( + Math.min(deptIdList.size(), Runtime.getRuntime().availableProcessors() * 2) + ); + + List>> futures = new ArrayList<>(); + + // 并发拉取 + for (Long deptId : deptIdList) { + futures.add(pool.submit(() -> getUserByDeptId(deptId, key, secret, shopId))); } - if (StrUtil.isNotBlank(name)) { - return dingUserVOS.stream().filter(item -> item.getName().contains(name)).collect(Collectors.toCollection(ArrayList::new)); + + // 汇总结果 + List allUsers = new ArrayList<>(); + for (Future> f : futures) { + allUsers.addAll(f.get()); } - return dingUserVOS; - }catch (Exception e) { + + pool.shutdown(); + + // 正确过滤逻辑 + return allUsers.stream().filter(item -> { + if (StrUtil.isNotBlank(userId) && !item.getUserid().equals(userId)) { + return false; + } + return !StrUtil.isNotBlank(name) || item.getName().contains(name); + }).collect(Collectors.toCollection(ArrayList::new)); + + } catch (Exception e) { throw new RuntimeException(e); } } - public ArrayList getUserReport(Long shopId, String key, String secret, DateTime startTime, DateTime endTime, String name) { + + public ArrayList getUserReport(Long shopId, String key, String secret, DateTime startTime, DateTime endTime, String name, String userId) { Map columns = getColumns(key, secret, shopId); - ArrayList userList = getUserList(name, key, secret, shopId); + ArrayList userList = getUserList(name, key, secret, shopId, userId); ArrayList statsVOS = new ArrayList<>(); for (DingUserVO item : userList) { JSONObject repost = null; @@ -166,6 +251,7 @@ public class DingService { statsVO.setName(item.getName()); if (repost != null) { statsVO.setActive(true); +// statsVO.setReport(repost.getJSONArray("column_vals")); repost.getJSONArray("column_vals").forEach(info -> { JSONObject infoObj = (JSONObject) info; Long id = infoObj.getJSONObject("column_vo").getLong("id"); diff --git a/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/OrderInfoCustomServiceImpl.java b/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/OrderInfoCustomServiceImpl.java index 4787da354..6f42b02e7 100644 --- a/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/OrderInfoCustomServiceImpl.java +++ b/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/OrderInfoCustomServiceImpl.java @@ -1771,7 +1771,7 @@ public class OrderInfoCustomServiceImpl implements OrderInfoCustomService { .set(OrderDetail::getSubStatus, TableValueConstant.OrderDetail.SubStatus.SENT_OUT.getCode()) .set(OrderDetail::getFoodServeTime, DateUtil.date().toLocalDateTime()) .eq(OrderDetail::getId, detailStatusDTO.getOrderDetailId()).update(); - AssertUtil.isTrue(!update, "已出菜"); + AssertUtil.isTrue(!update, "已出菜,请勿重复出菜"); }else { if (detailStatusDTO.getOrderId() != null) {