Merge remote-tracking branch 'origin/test' into test

This commit is contained in:
2025-12-01 15:36:13 +08:00
6 changed files with 198 additions and 21 deletions

View File

@@ -62,6 +62,20 @@ public class AttendanceController {
public CzgResult<ArrayList<DingAttendanceStatsVO>> 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<Map<String, Object>> 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));
}
}

View File

@@ -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<DingAttendanceStatsVO> list(Long shopId, String name, String startTime, String endTime);
ArrayList<DingAttendanceStatsVO> list(Long shopId, String name, String startTime, String endTime, String userId);
Map<String, Object> detail(Long shopId, String userId, String startTime, String endTime, Integer weekNum);
}

View File

@@ -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;
}
}

View File

@@ -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<DingAttendanceStatsVO> list(Long shopId, String name, String startTime, String endTime) {
public ArrayList<DingAttendanceStatsVO> 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<String, Object> detail(Long shopId, String userId, String startTime, String endTime, Integer weekNum) {
ArrayList<DingAttendanceStatsVO> listed = list(shopId, null, startTime, endTime, userId);
ArrayList<Map<String, Object>> 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<String, Object>() {{
put("attendanceSummary", null);
put("weekList", weekList);
put("statusList", null);
}};
}
ShopConfig config = shopConfigService.getById(shopId);
if (config == null || StrUtil.isBlank(config.getDingAppSecret())) {
return new HashMap<String, Object>() {{
put("attendanceSummary", null);
put("weekList", weekList);
put("statusList", null);
}};
}
ConcurrentLinkedDeque<String> 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
);
}
}

View File

@@ -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<String, String> 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<String, String> columns = getColumns(key, secret, shopId);
String columId = null;
for (Map.Entry<String, String> entry : columns.entrySet()) {
String key1 = entry.getKey();
String value = entry.getValue();
if (value.equals(name)) {
columId = key1;
break;
}
}
return columId;
}
public Map<String, String> 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<String, String> 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<DingUserVO> 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<DingUserVO> 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<Long> deptIdList = getData(rsp, true).getJSONArray("result").stream().map(item -> ((JSONObject) item)
.getLong("dept_id")).collect(Collectors.toSet());
ArrayList<DingUserVO> dingUserVOS = new ArrayList<>();
for (Long dptId : deptIdList) {
dingUserVOS.addAll(getUserByDeptId(dptId, key, secret, shopId));
OapiV2DepartmentListsubResponse rsp = client.execute(req, getToken(key, secret, shopId));
Set<Long> 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<Future<List<DingUserVO>>> 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<DingUserVO> allUsers = new ArrayList<>();
for (Future<List<DingUserVO>> 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<DingAttendanceStatsVO> getUserReport(Long shopId, String key, String secret, DateTime startTime, DateTime endTime, String name) {
public ArrayList<DingAttendanceStatsVO> getUserReport(Long shopId, String key, String secret, DateTime startTime, DateTime endTime, String name, String userId) {
Map<String, String> columns = getColumns(key, secret, shopId);
ArrayList<DingUserVO> userList = getUserList(name, key, secret, shopId);
ArrayList<DingUserVO> userList = getUserList(name, key, secret, shopId, userId);
ArrayList<DingAttendanceStatsVO> 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");

View File

@@ -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) {