package com.xiaopeng.maintenance.service.impl;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.conditions.query.QueryChainWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xiaopeng.api.feignClient.account.XpOperationUserClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.XpFleetOperationsClient;
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.XpSchedulingDetail;
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.*;
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.service.IXpSchedulingDetailService;
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;
@Resource
private IXpSchedulingDetailService xpSchedulingDetailService;
@Resource
private XpFleetOperationsClient fleetOperationsClient;
@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.setClockOutResult(2);
} else {
xpClockIn.setClockOutResult(1);
}
xpClockIn.setAppUserId(null);
this.updateById(xpClockIn);
return R.ok();
}
@Override
public Integer checkWorkStatus(Long appUserId) {
if (appUserId == null) {
log.warn("检查上班状态失败: appUserId 为空");
return 1;
}
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. 查询当天打卡记录
XpClockIn xpClockIn = baseMapper.selectOne(new LambdaQueryWrapper()
.eq(XpClockIn::getAppUserId, appUserId)
.between(XpClockIn::getClockInTime, startTime, endTime)
.last("limit 1"));
// 3. 判断上班状态
// 有上班打卡状态,无下班打卡状态
if (Objects.isNull(xpClockIn)) {
return 1;
} else if (Objects.nonNull(xpClockIn.getClockInResult()) && Objects.isNull(xpClockIn.getClockOutResult())) {
return 2;
} else {
return 3;
}
} catch (Exception e) {
log.error("检查用户上班状态异常, appUserId={}", appUserId, e);
// 异常情况返回 false
return 1;
}
}
/**
* 获取打卡统计
*
* @param query
* @return
*/
@Override
public PageInfo getClockInStatistics(ClockInStatisticsVo query) {
PageInfo pageInfo = new PageInfo<>(query.getPageNum(), query.getPageSize());
List appUserIds1 = new ArrayList<>();
List appUserIds2 = new ArrayList<>();
List appUserIds3 = 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());
appUserIds1.addAll(collect);
}
appUserIds1.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());
appUserIds2.addAll(collect);
}
appUserIds2.add(0L);
}
if(StringUtils.isNotEmpty(query.getName()) && null != query.getRoleId()){
for (Long l : appUserIds1) {
if(appUserIds2.contains(l)){
appUserIds3.add(l);
}
}
}else{
appUserIds3.addAll(appUserIds1);
appUserIds3.addAll(appUserIds2);
}
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, appUserIds3, 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);
}
}
}
pageInfo.setRecords(clockInStatistics);
return pageInfo;
}
/**
* 导出打卡统计
* @param query
* @param response
*/
@Override
public void exportClockInStatistics(ClockInStatisticsVo query, HttpServletResponse response) {
PageInfo pageInfo = new PageInfo<>(0, 10000);
List appUserIds1 = new ArrayList<>();
List appUserIds2 = new ArrayList<>();
List appUserIds3 = 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());
appUserIds1.addAll(collect);
}
appUserIds1.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());
appUserIds2.addAll(collect);
}
appUserIds2.add(0L);
}
if(StringUtils.isNotEmpty(query.getName()) && null != query.getRoleId()){
for (Long l : appUserIds1) {
if(appUserIds2.contains(l)){
appUserIds3.add(l);
}
}
}else{
appUserIds3.addAll(appUserIds1);
appUserIds3.addAll(appUserIds2);
}
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, appUserIds3, 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);
}
}
}
ExcelUtil excel = new ExcelUtil(ClockInStatisticsDto.class);
excel.exportExcel(response, clockInStatistics, "打卡统计");
}
/**
* 获取打卡统计明细
* @param query
* @return
*/
@Override
public GetClockInStatisticsDetailsDto getClockInStatisticsDetails(ClockInStatisticsDetailsVo query) {
GetClockInStatisticsDetailsDto dto = new GetClockInStatisticsDetailsDto();
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);
dto.setList(pageInfo);
XpOperationsUser xpOperationsUser = xpOperationUserClient.queryById(query.getAppUserId()).getData();
List longList = fleetOperationsClient.getFleetIdsByUserId(xpOperationsUser.getId()).getData();
List list = xpSchedulingDetailService.list(new QueryWrapper().eq("fleet_id", longList.get(0)).between(StringUtils.isNotEmpty(startTime), "plan_time", startTime, endTime));
dto.setShouldClockInCount(list.size() * 2);
dto.setNormalClockInCount(this.baseMapper.getClockInStatisticsDetailsCount(1, query.getAppUserId(), startTime, endTime));
dto.setLateClockInCount(this.baseMapper.getClockInStatisticsDetailsCount(2, query.getAppUserId(), startTime, endTime));
dto.setMissingClockInCount(this.baseMapper.getClockInStatisticsDetailsCount(3, query.getAppUserId(), startTime, endTime));
return dto;
}
/**
* 导出打卡统计明细
* @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, "打卡统计明细");
}
}