package com.xiaopeng.maintenance.service.impl;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xiaopeng.api.feignClient.account.XpOperationUserClient;
import com.xiaopeng.api.model.account.SysRole;
import com.xiaopeng.api.model.account.XpOperationsRole;import com.xiaopeng.api.model.account.XpOperationsUser;import com.xiaopeng.api.model.maintenance.XpCheckLocation;
import com.xiaopeng.api.model.maintenance.XpClockIn;
import com.xiaopeng.api.model.maintenance.XpShift;
import com.xiaopeng.api.vo.account.XpOperationsUserVO;
import com.xiaopeng.api.vo.account.XpOperationsUserWithRoleVO;import com.xiaopeng.common.core.constant.Constants;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.utils.DateUtils;
import com.xiaopeng.common.core.utils.GeodesyUtil;
import com.xiaopeng.common.core.utils.StringUtils;
import com.xiaopeng.common.core.utils.poi.ExcelUtil;
import com.xiaopeng.common.core.web.page.PageInfo;
import com.xiaopeng.maintenance.dto.ClockDTO;
import com.xiaopeng.maintenance.dto.ClockInStatisticsDetailsDto;
import com.xiaopeng.maintenance.dto.ClockInStatisticsDto;
import com.xiaopeng.maintenance.dto.VerifyRangeDTO;
import com.xiaopeng.maintenance.mapper.XpCheckLocationMapper;
import com.xiaopeng.maintenance.mapper.XpClockInMapper;
import com.xiaopeng.maintenance.mapper.XpShiftMapper;
import com.xiaopeng.maintenance.service.IXpClockInService;
import com.xiaopeng.maintenance.vo.ClockInStatisticsDetailsVo;
import com.xiaopeng.maintenance.vo.ClockInStatisticsVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
*
* 打卡明细 服务实现类
*
*
* @author mitao
* @since 2026-02-03
*/
@Slf4j
@Service
public class XpClockInServiceImpl extends ServiceImpl implements IXpClockInService {
@Resource
private XpCheckLocationMapper xpCheckLocationMapper;
@Resource
private XpShiftMapper xpShiftMapper;
@Resource
private XpClockInMapper xpClockInMapper;
@Resource
private XpOperationUserClient xpOperationUserClient;
@Override
public Boolean verifyRange(VerifyRangeDTO dto) {
XpCheckLocation xpCheckLocation = xpCheckLocationMapper.selectById(dto.getLocationId());
Map distance = GeodesyUtil.getDistance(dto.getUserLon() + "," + dto.getUserLat(), xpCheckLocation.getLocationLon() + "," + xpCheckLocation.getLocationLat());
if (null != distance) {
Double distance1 = distance.get("WGS84");
if (BigDecimal.valueOf(distance1).compareTo(xpCheckLocation.getLocationRange()) > 0) {
return false;
} else {
return true;
}
} else {
log.error("调用高德计算距离出错");
return false;
}
}
@Override
public R clockIn(ClockDTO dto) {
XpCheckLocation xpCheckLocation = xpCheckLocationMapper.selectById(dto.getLocationId());
Map distance = GeodesyUtil.getDistance(dto.getUserLon() + "," + dto.getUserLat(), xpCheckLocation.getLocationLon() + "," + xpCheckLocation.getLocationLat());
if (null != distance) {
Double distance1 = distance.get("WGS84");
if (BigDecimal.valueOf(distance1).compareTo(xpCheckLocation.getLocationRange()) > 0) {
return R.fail("不在打卡范围内");
}
} else {
return R.fail("打卡失败");
}
// 查询班次
XpShift xpShift = xpShiftMapper.selectById(dto.getShiftId());
if (null == xpShift) {
return R.fail("班次不存在");
}
// 开始打卡时间
String startTime = xpShift.getStartTime();
LocalDateTime startLocalDateTime = DateUtils.stringToLocalDateTime(LocalDate.now() + " " + startTime + ":00");
XpClockIn xpClockIn = new XpClockIn();
xpClockIn.setAppUserId(dto.getOperationsUserId());
xpClockIn.setClockInTime(LocalDateTime.now());
xpClockIn.setClockInLon(dto.getUserLon());
xpClockIn.setClockInLat(dto.getUserLat());
xpClockIn.setShiftId(dto.getShiftId());
xpClockIn.setClockInAddress(dto.getClockInAddress());
if (xpClockIn.getClockInTime().isAfter(startLocalDateTime)) {
xpClockIn.setClockInResult(2);
} else {
xpClockIn.setClockInResult(1);
}
this.save(xpClockIn);
return R.ok();
}
@Override
public R clockOut(ClockDTO dto) {
XpCheckLocation xpCheckLocation = xpCheckLocationMapper.selectById(dto.getLocationId());
Map distance = GeodesyUtil.getDistance(dto.getUserLon() + "," + dto.getUserLat(), xpCheckLocation.getLocationLon() + "," + xpCheckLocation.getLocationLat());
if (null != distance) {
Double distance1 = distance.get("WGS84");
if (BigDecimal.valueOf(distance1).compareTo(xpCheckLocation.getLocationRange()) > 0) {
return R.fail("不在打卡范围内");
}
} else {
return R.fail("打卡失败");
}
// 查询上班打卡信息
XpClockIn xpClockIn = xpClockInMapper.selectOne(Wrappers.lambdaQuery(XpClockIn.class)
.eq(XpClockIn::getAppUserId, dto.getOperationsUserId())
.likeRight(XpClockIn::getClockInTime, LocalDate.now())
.last("LIMIT 1"));
if (Objects.isNull(xpClockIn)) {
return R.fail("请先上班打卡");
}
// 查询班次
XpShift xpShift = xpShiftMapper.selectById(xpClockIn.getShiftId());
if (null == xpShift) {
return R.fail("班次不存在");
}
// 开始打卡时间
String endTime = xpShift.getEndTime();
LocalDateTime endLocalDateTime = DateUtils.stringToLocalDateTime(LocalDate.now() + " " + endTime + ":00");
xpClockIn.setClockOutTime(LocalDateTime.now());
xpClockIn.setClockOutLon(dto.getUserLon());
xpClockIn.setClockOutLat(dto.getUserLat());
xpClockIn.setClockOutAddress(dto.getClockOutAddress());
if (xpClockIn.getClockOutTime().isBefore(endLocalDateTime)) {
xpClockIn.setClockInResult(2);
} else {
xpClockIn.setClockInResult(1);
}
xpClockIn.setAppUserId(null);
this.updateById(xpClockIn);
return R.ok();
}
@Override
public Boolean checkWorkStatus(Long appUserId) {
if (appUserId == null) {
log.warn("检查上班状态失败: appUserId 为空");
return false;
}
try {
// 1. 计算当天时间范围
LocalDateTime startTime = LocalDateTime.of(LocalDate.now(), LocalTime.MIN); // 00:00:00
LocalDateTime endTime = LocalDateTime.of(LocalDate.now(), LocalTime.MAX); // 23:59:59
// 2. 查询当天打卡记录
List records = baseMapper.selectTodayClockInRecords(
appUserId, startTime, endTime
);
// 3. 判断上班状态
if (records == null || records.isEmpty()) {
// 无打卡记录 = 未上班
log.debug("用户 {} 今日无打卡记录, workFlag=false", appUserId);
return false;
} else if (records.size() == 1) {
// 1条记录 = 已上班未下班
log.debug("用户 {} 今日打卡1次, workFlag=true", appUserId);
return true;
} else {
// 2条及以上记录 = 已下班
log.debug("用户 {} 今日打卡{}次, workFlag=false", appUserId, records.size());
return false;
}
} catch (Exception e) {
log.error("检查用户上班状态异常, appUserId={}", appUserId, e);
// 异常情况返回 false
return false;
}
}
/**
* 获取打卡统计
*
* @param query
* @return
*/
@Override
public PageInfo getClockInStatistics(ClockInStatisticsVo query) {
PageInfo pageInfo = new PageInfo<>(query.getPageNum(), query.getPageSize());
List appUserIds = new ArrayList<>();
//处理查询条件
if(StringUtils.isNotEmpty(query.getName())){
R> listR = xpOperationUserClient.listByName(query.getName());
if(listR.getCode() == Constants.SUCCESS){
List collect = listR.getData().stream().map(XpOperationsUser::getId).collect(Collectors.toList());
appUserIds.addAll(collect);
}
appUserIds.add(0L);
}
if(null != query.getRoleId()){
R> roleByRoleId = xpOperationUserClient.getXpOperationsRoleByRoleId(query.getRoleId());
if(roleByRoleId.getCode() == Constants.SUCCESS){
List collect = roleByRoleId.getData().stream().map(XpOperationsRole::getOperationsUserId).collect(Collectors.toList());
appUserIds.addAll(collect);
}
appUserIds.add(0L);
}
String startTime = "";
String endTime = "";
if(StringUtils.isNotEmpty(query.getStatisticDate())){
String statisticDate = query.getStatisticDate();
startTime = statisticDate.split(" - ")[0].split(" ")[0];
endTime = statisticDate.split(" - ")[1].split(" ")[0];
}
//查询数据
List clockInStatistics = this.baseMapper.getClockInStatistics(pageInfo, appUserIds, startTime, endTime);
List appUserIdList = clockInStatistics.stream().map(ClockInStatisticsDto::getAppUserId).collect(Collectors.toList());
List ids = appUserIdList.stream().map(Long::parseLong).collect(Collectors.toList());
List data = xpOperationUserClient.listByIdsWithRole(ids).getData();
Map userMap = data.stream().collect(Collectors.toMap(XpOperationsUserWithRoleVO::getId, Function.identity()));
//将查询结果组装成map对象,用于循环中提取数据
for (ClockInStatisticsDto clockInStatistic : clockInStatistics) {
String appUserId = clockInStatistic.getAppUserId();
//运维账号可以拥有多个角色
if(CollUtil.isNotEmpty(userMap)){
XpOperationsUserWithRoleVO vo = userMap.get(Long.parseLong(appUserId));
if (Objects.nonNull(vo)) {
clockInStatistic.setName(vo.getNickName());
String roleName = vo.getRoles().stream().map(SysRole::getRoleName).collect(Collectors.joining(";"));
clockInStatistic.setRoleName(roleName);
}
}
Integer shouldClockInDays = clockInStatistic.getShouldClockInDays();
Integer clockInDays = clockInStatistic.getClockInDays();
clockInStatistic.setNotClockInDays(shouldClockInDays - clockInDays);
}
pageInfo.setRecords(clockInStatistics);
return pageInfo;
}
/**
* 导出打卡统计
* @param query
* @param response
*/
@Override
public void exportClockInStatistics(ClockInStatisticsVo query, HttpServletResponse response) {
PageInfo pageInfo = new PageInfo<>(0, 10000);
List appUserIds = new ArrayList<>();
//处理查询条件
if(StringUtils.isNotEmpty(query.getName())){
R> listR = xpOperationUserClient.listByName(query.getName());
if(listR.getCode() == Constants.SUCCESS){
List collect = listR.getData().stream().map(XpOperationsUser::getId).collect(Collectors.toList());
appUserIds.addAll(collect);
}
appUserIds.add(0L);
}
if(null != query.getRoleId()){
R> roleByRoleId = xpOperationUserClient.getXpOperationsRoleByRoleId(query.getRoleId());
if(roleByRoleId.getCode() == Constants.SUCCESS){
List collect = roleByRoleId.getData().stream().map(XpOperationsRole::getOperationsUserId).collect(Collectors.toList());
appUserIds.addAll(collect);
}
appUserIds.add(0L);
}
String startTime = "";
String endTime = "";
if(StringUtils.isNotEmpty(query.getStatisticDate())){
String statisticDate = query.getStatisticDate();
startTime = statisticDate.split(" - ")[0].split(" ")[0];
endTime = statisticDate.split(" - ")[1].split(" ")[0];
}
//查询数据
List clockInStatistics = this.baseMapper.getClockInStatistics(pageInfo, appUserIds, startTime, endTime);
List appUserIdList = clockInStatistics.stream().map(ClockInStatisticsDto::getAppUserId).collect(Collectors.toList());
List ids = appUserIdList.stream().map(Long::parseLong).collect(Collectors.toList());
List data = xpOperationUserClient.listByIdsWithRole(ids).getData();
Map userMap = data.stream().collect(Collectors.toMap(XpOperationsUserWithRoleVO::getId, Function.identity()));
for (ClockInStatisticsDto clockInStatistic : clockInStatistics) {
String appUserId = clockInStatistic.getAppUserId();
//运维账号可以拥有多个角色
if(CollUtil.isNotEmpty(userMap)){
XpOperationsUserWithRoleVO vo = userMap.get(Long.parseLong(appUserId));
if (Objects.nonNull(vo)) {
clockInStatistic.setName(vo.getNickName());
String roleName = vo.getRoles().stream().map(SysRole::getRoleName).collect(Collectors.joining(";"));
clockInStatistic.setRoleName(roleName);
}
}
Integer shouldClockInDays = clockInStatistic.getShouldClockInDays();
Integer clockInDays = clockInStatistic.getClockInDays();
clockInStatistic.setNotClockInDays(shouldClockInDays - clockInDays);
}
ExcelUtil excel = new ExcelUtil(ClockInStatisticsDto.class);
excel.exportExcel(response, clockInStatistics, "打卡统计");
}
/**
* 获取打卡统计明细
* @param query
* @return
*/
@Override
public PageInfo getClockInStatisticsDetails(ClockInStatisticsDetailsVo query) {
PageInfo pageInfo = new PageInfo<>(query.getPageNum(), query.getPageSize());
String startTime = "";
String endTime = "";
if(StringUtils.isNotEmpty(query.getStatisticDate())){
String statisticDate = query.getStatisticDate();
startTime = statisticDate.split(" - ")[0].split(" ")[0];
endTime = statisticDate.split(" - ")[1].split(" ")[0];
}
List clockInStatisticsDetails = this.baseMapper.getClockInStatisticsDetails(pageInfo, query.getAppUserId(), startTime, endTime);
List data = xpOperationUserClient.listByIdsWithRole(Arrays.asList(query.getAppUserId())).getData();
XpOperationsUserWithRoleVO xpOperationsUserWithRoleVO = data.get(0);
//将查询结果组装成map对象,用于循环中提取数据
for (ClockInStatisticsDetailsDto clockInStatistic : clockInStatisticsDetails) {
clockInStatistic.setName(xpOperationsUserWithRoleVO.getNickName());
clockInStatistic.setRoleName(xpOperationsUserWithRoleVO.getRoles().stream().map(SysRole::getRoleName).collect(Collectors.joining(";")));
}
pageInfo.setRecords(clockInStatisticsDetails);
return pageInfo;
}
/**
* 导出打卡统计明细
* @param query
* @param response
*/
@Override
public void exportClockInStatisticsDetails(ClockInStatisticsDetailsVo query, HttpServletResponse response) {
PageInfo pageInfo = new PageInfo<>(0, 10000);
String startTime = "";
String endTime = "";
if(StringUtils.isNotEmpty(query.getStatisticDate())){
String statisticDate = query.getStatisticDate();
startTime = statisticDate.split(" - ")[0].split(" ")[0];
endTime = statisticDate.split(" - ")[1].split(" ")[0];
}
List clockInStatisticsDetails = this.baseMapper.getClockInStatisticsDetails(pageInfo, query.getAppUserId(), startTime, endTime);
List data = xpOperationUserClient.listByIdsWithRole(Arrays.asList(query.getAppUserId())).getData();
XpOperationsUserWithRoleVO xpOperationsUserWithRoleVO = data.get(0);
//将查询结果组装成map对象,用于循环中提取数据
for (ClockInStatisticsDetailsDto clockInStatistic : clockInStatisticsDetails) {
clockInStatistic.setName(xpOperationsUserWithRoleVO.getNickName());
clockInStatistic.setRoleName(xpOperationsUserWithRoleVO.getRoles().stream().map(SysRole::getRoleName).collect(Collectors.joining(";")));
}
ExcelUtil excel = new ExcelUtil(ClockInStatisticsDetailsDto.class);
excel.exportExcel(response, clockInStatisticsDetails, "打卡统计明细");
}
}