package com.xiaopeng.order.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.ImmutableMap;
import com.xiaopeng.api.dto.automaticPilot.PushOrderDto;
import com.xiaopeng.api.dto.map.RouteAndFareDto;
import com.xiaopeng.api.dto.operation.LonLatDto;
import com.xiaopeng.api.dto.order.GetOrderNumDto;
import com.xiaopeng.api.feignClient.account.UserResourcesClient;
import com.xiaopeng.api.feignClient.account.XpAppUserCouponClient;
import com.xiaopeng.api.feignClient.automaticPilot.OrderMissionClient;
import com.xiaopeng.api.feignClient.maintenance.XpTaskRuleClient;
import com.xiaopeng.api.feignClient.map.XpMapClient;
import com.xiaopeng.api.feignClient.operation.*;
import com.xiaopeng.api.feignClient.pushMessage.XpPushMessageClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.UserVehicleClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.XpFleetClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.XpFleetVehicleClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.XpVehicleClient;
import com.xiaopeng.api.model.map.XpTestPoint;
import com.xiaopeng.api.model.operation.*;
import com.xiaopeng.api.model.order.*;
import com.xiaopeng.api.model.vehicleConvoy.XpFleet;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.api.service.BootCommonService;
import com.xiaopeng.api.vo.OrderType;
import com.xiaopeng.api.vo.automaticPilot.VehiclePosition;
import com.xiaopeng.api.vo.automaticPilot.protobuf.OrderOperationType;
import com.xiaopeng.api.vo.operation.RouteVo;
import com.xiaopeng.api.vo.order.FareBreakdownDetailVo;
import com.xiaopeng.api.vo.order.XpPricingCardVo;
import com.xiaopeng.common.core.constant.CacheConstants;
import com.xiaopeng.common.core.constant.HttpStatus;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.exception.ServiceException;
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.web.domain.BaseModel;
import com.xiaopeng.common.core.web.page.PageInfo;
import com.xiaopeng.common.redis.service.RedisService;
import com.xiaopeng.common.security.utils.SecurityUtils;
import com.xiaopeng.order.constant.I18nKeyConstant;
import com.xiaopeng.order.job.base.QuartzManager;
import com.xiaopeng.order.job.base.TimeJobType;
import com.xiaopeng.order.job.jobs.EndTripJob;
import com.xiaopeng.order.job.jobs.OrderCancelJob;
import com.xiaopeng.order.mangodb.model.OrderTrajectory;
import com.xiaopeng.order.mapper.XpCruiseOrderMapper;
import com.xiaopeng.order.mapper.XpTestOrderDetailMapper;
import com.xiaopeng.order.mapper.XpTestOrderMapper;
import com.xiaopeng.order.model.query.XpTestOrderPageQuery;
import com.xiaopeng.order.model.vo.AddTestOrderVo;
import com.xiaopeng.order.model.vo.XpTestOrderDetailVO;
import com.xiaopeng.order.model.vo.XpTestOrderPageVO;
import com.xiaopeng.order.service.IXpOrderEvaluateService;
import com.xiaopeng.order.service.IXpOrderReassignService;
import com.xiaopeng.order.service.IXpTestOrderCancelService;
import com.xiaopeng.order.service.IXpTestOrderService;
import com.xiaopeng.order.utils.GDMapGeocodingUtil;
import com.xiaopeng.order.utils.TaxiPricingDemoService;
import com.xiaopeng.order.utils.XpPricingUtil;
import com.xiaopeng.order.utils.holiday.HolidayUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.transaction.annotation.ShardingSphereTransactionType;
import org.apache.shardingsphere.transaction.core.TransactionType;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneOffset;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
*
* 订单 服务实现类
*
*
* @author mitao
* @since 2026-02-03
*/
@Slf4j
@Service
@RequiredArgsConstructor(onConstructor_ = @Lazy)
public class XpTestOrderServiceImpl extends ServiceImpl implements IXpTestOrderService {
private final UserResourcesClient userResourcesClient;
private final XpTestPointClient xpTestPointClient;
private final XpVehicleClient xpVehicleClient;
private final XpAppUserCouponClient xpAppUserCouponClient;
private final XpFleetClient xpFleetClient;
private final XpFleetVehicleClient xpFleetVehicleClient;
private final UserVehicleClient userVehicleClient;
private final IXpOrderEvaluateService xpOrderEvaluateService;
private final IXpTestOrderCancelService xpTestOrderCancelService;
private final IXpOrderReassignService xpOrderReassignService;
private final RedisService redisService;
private final XpPricingClient xpPricingClient;
private final XpSystemSettingClient xpSystemSettingClient;
private final XpPushMessageClient xpPushMessageClient;
private final OrderMissionClient orderMissionClient;
private final XpTestOrderDetailMapper xpTestOrderDetailMapper;
private final XpCruiseOrderMapper xpCruiseOrderMapper;
private final XpTaskRuleClient xpTaskRuleClient;
private final XpCruisePlanClient xpCruisePlanClient;
private final XpPricingUtil xpPricingUtil;
private final GDMapGeocodingUtil gdMapGeocodingUtil;
private final XpOpenCityClient openCityClient;
private final XpMapClient xpMapClient;
private final XpRoutePlanClient xpRoutePlanClient;
private final XpTestPlanClient xpTestPlanClient;
private final HolidayUtil holidayUtil;
private final MongoTemplate mongoTemplate;
private final XpElectronicFenceClient xpElectronicFenceClient;
private final XpElectronicClient xpElectronicClient;
private final XpTestPointClient testPointClient;
@Resource
private BootCommonService bootCommonService;
/**
* 添加测试单
* @param query
*/
@Override
public void addTestOrder(AddTestOrderVo query) {
Long startTestPointId = query.getStartTestPointId();
Long endTestPointId = query.getEndTestPointId();
XpTestPoint xpPoint = testPointClient.queryById(startTestPointId).getData();
XpTestPoint endXpPoint = testPointClient.queryById(endTestPointId).getData();
if(xpPoint ==null || xpPoint.getOperationStatus()!=2){
throw new ServiceException("起站点已停止运营", 200,I18nKeyConstant.NO_CURRENT_POINT);
}
if(endXpPoint ==null || endXpPoint.getOperationStatus()!=2){
throw new ServiceException("终站点已停止运营", 200,I18nKeyConstant.NO_CURRENT_POINT);
}
// 是否在运营区域时间内
XpElectronicFenceArea xpElectronicFenceArea = xpElectronicClient.queryElectronicFenceAreaById(xpPoint.getElectronicAreaId()).getData();
XpElectronicFence xpElectronicFence = xpElectronicFenceClient.selectById(xpElectronicFenceArea.getElectronicId().toString()).getData();
String operationStrategy = xpElectronicFence.getOperationStrategy();
Boolean inOperationTime = isInOperationTime(operationStrategy);
if (!inOperationTime && xpElectronicFence.getFailureType()!=1) {
throw new ServiceException("当前区域未开通服务", I18nKeyConstant.NO_CURRENT_REGION);
}
// 判断点位是否在运营区域内
boolean inTime = xpTestPointClient.nowInTimePoint(startTestPointId).getData();
if (!inTime) {
throw new ServiceException("当前点位未开通服务", I18nKeyConstant.NO_CURRENT_REGION);
}
inTime = xpTestPointClient.nowInTimePoint(endTestPointId).getData();
if (!inTime) {
throw new ServiceException("当前点位未开通服务", I18nKeyConstant.NO_CURRENT_REGION);
}
Map geocode = gdMapGeocodingUtil.geocode(xpPoint.getPointLon().toString(), xpPoint.getPointLat().toString());
String districtCode = geocode.get("districtCode");
String cityCode = geocode.get("cityCode");
String provinceCode = geocode.get("provinceCode");
XpOpenCity openCity = openCityClient.getXpOpenCity(provinceCode, cityCode, districtCode).getData();
if (openCity == null) {
throw new ServiceException("当前区域未开通服务", I18nKeyConstant.NO_CURRENT_REGION);
}
XpPricing xpPricing = bootCommonService.getXpPricing(openCity.getId(),null);
if (xpPricing == null) {
throw new ServiceException("当前区域未开通服务", I18nKeyConstant.NO_CURRENT_REGION);
}
// 算距离
RouteAndFareDto routeAndFareDto = new RouteAndFareDto();
LonLatDto lonLatDto = new LonLatDto();
lonLatDto.setLongitude(xpPoint.getPointLon());
lonLatDto.setLatitude(xpPoint.getPointLat());
routeAndFareDto.setOrigin(lonLatDto);
LonLatDto lonLatDto1 = new LonLatDto();
lonLatDto1.setLongitude(endXpPoint.getPointLon());
lonLatDto1.setLatitude(endXpPoint.getPointLat());
routeAndFareDto.setDestination(lonLatDto1);
routeAndFareDto.setOpenCityId(openCity.getId());
XpRoutePlan xpRoutePlan = xpRoutePlanClient.getXpRoutePlanOpenCityId(openCity.getId()).getData();
if(xpRoutePlan!=null&& !"null".equals(xpRoutePlan.getRoutePlanRule())){
routeAndFareDto.setRoutePlanRule(Integer.valueOf(xpRoutePlan.getRoutePlanRule()));
}
// 调用地图 获取时间
R distanceAndDuration = xpMapClient.getDistanceAndDuration(routeAndFareDto);
if (distanceAndDuration.getCode() != 200) {
throw new ServiceException("获取导肮信息失败", I18nKeyConstant.PATH_NOT_FOUND);
}
RouteVo routeVo = distanceAndDuration.getData();
// 生成订单数据
XpTestOrder xpOrder = new XpTestOrder();
xpOrder.setTestPlanId(query.getTestPlanId());
xpOrder.setOrderType(query.getOrderType());
xpOrder.setMileageKilometers(0.0);
xpOrder.setOrderNumber("XP" + System.currentTimeMillis());
xpOrder.setCityId(openCity.getId());
xpOrder.setPlacementLat(xpPoint.getPointLat());
xpOrder.setPlacementLon(xpPoint.getPointLon());
xpOrder.setPlacementAddress(xpPoint.getPointAddress());
xpOrder.setStartLon(xpPoint.getPointLon());
xpOrder.setStartLat(xpPoint.getPointLat());
xpOrder.setBoardingLon(xpPoint.getPointLon());
xpOrder.setBoardingLat(xpPoint.getPointLat());
xpOrder.setBoardingAddress(xpPoint.getPointAddress());
xpOrder.setEndLon(endXpPoint.getPointLon());
xpOrder.setEndLat(endXpPoint.getPointLat());
xpOrder.setStartAddress(xpPoint.getPointAddress());
xpOrder.setEndAddress(endXpPoint.getPointAddress());
xpOrder.setXpElectronicFenceAreaId(xpPoint.getElectronicAreaId());
xpOrder.setStartTestPointId(startTestPointId);
xpOrder.setEndTestPointId(endTestPointId);
xpOrder.setStartTestPointName(xpPoint.getPointName());
xpOrder.setEndTestPointName(endXpPoint.getPointName());
xpOrder.setStatus(1);
xpOrder.setOrderSource(1);
xpOrder.setOpenTrunk(2);
// 当前时段
String currentTimeSlot = DateUtils.getCurrentTimeSlot();
xpOrder.setTimeSlot(currentTimeSlot);
xpOrder.setCreateTime(LocalDateTime.now());
xpOrder.setCityCode(openCity.getCityCode());
List vehicleIds = query.getVehicleIds();
for (Long vehicleId : vehicleIds) {
xpOrder.setId(null);
XpVehicle xpVehicle = xpVehicleClient.queryById(vehicleId).getData();
xpOrder.setVehicleId(vehicleId);
xpOrder.setFleetId(xpVehicle.getFleetId());
this.save(xpOrder);
redisService.setCacheObject("TEST_ORDER_STATUS:" + xpOrder.getId(), 1, 24L, TimeUnit.HOURS);
//缓存导航数据,用于下单后推送导航给车端和乘客端
redisService.setCacheObject("TEST_ORDER_NAVIGATION_END_DATA:" + vehicleId, JSON.toJSONString(routeVo), 24L, TimeUnit.HOURS);
//下发XPU消息
PushOrderDto pushOrderDto = new PushOrderDto();
pushOrderDto.setVin(xpVehicle.getVehicleVin());
pushOrderDto.setOrderId("TEST" + xpOrder.getId());
pushOrderDto.setStartLatitude(xpOrder.getStartLat());
pushOrderDto.setStartLongitude(xpOrder.getStartLon());
pushOrderDto.setEndLatitude(xpOrder.getEndLat());
pushOrderDto.setEndLongitude(xpOrder.getEndLon());
pushOrderDto.setEndPoiName(xpOrder.getEndTestPointName());
pushOrderDto.setOrderType(xpOrder.getOrderType() == 1 ? OrderType.TEST_ORDER : OrderType.TEST_CRUISE_ORDER);
pushOrderDto.setStage(1);
// 调用推送服务 orderId + carVin
orderMissionClient.pushOrder(pushOrderDto);
}
}
private Boolean isInOperationTime(String operationStrategy) {
JSONArray jsonArray = JSONArray.parseArray(operationStrategy);
Integer holidayToday = holidayUtil.isHolidayToday();
for (Object o : jsonArray) {
JSONObject jsonObject = JSONObject.parseObject(o.toString());
String type = jsonObject.getString("type");
if (type.equals(holidayToday.toString())) {
JSONArray time = jsonObject.getJSONArray("time");
// 判断当前时间 是否在time时间段中
for (Object object : time) {
String[] timeArray = object.toString().split("-");
String startTime = timeArray[0];
String endTime = timeArray[1];
boolean currentTimeInRange = DateUtils.isCurrentTimeInRange(startTime, endTime);
if (currentTimeInRange) {
return true;
}
}
} else if (type.equals("3")) {
// 判断是什么类型 0每天 1-7 想起
String typeStr = jsonObject.getString("typeStr");
// 查看今天周几
Integer week = DateUtils.getWeek();
// 包含每天 或者 包含当天
if (typeStr.contains("0") || typeStr.contains(week.toString())) {
JSONArray time = jsonObject.getJSONArray("time");
// 判断当前时间 是否在time时间段中
for (Object object : time) {
String[] timeArray = object.toString().split("-");
String startTime = timeArray[0];
String endTime = timeArray[1];
boolean currentTimeInRange = DateUtils.isCurrentTimeInRange(startTime, endTime);
if (currentTimeInRange) {
return true;
}
}
}
}
}
return false;
}
@Override
public PageInfo pageList(XpTestOrderPageQuery query) {
// 0. 车牌号筛选:先查车辆服务获取车辆ID列表
List vehicleIdsFilter = null;
if (StringUtils.isNotBlank(query.getPlateNumber())) {
R> vehicleResult = xpVehicleClient.listByPlateNumber(query.getPlateNumber());
if (vehicleResult == null || CollectionUtils.isEmpty(vehicleResult.getData())) {
return buildEmptyOrderPage(query);
}
vehicleIdsFilter = vehicleResult.getData().stream()
.map(XpVehicle::getId)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(vehicleIdsFilter)) {
return buildEmptyOrderPage(query);
}
}
// 1. 构建查询条件
LambdaQueryWrapper wrapper = buildQueryWrapper(query);
wrapper.eq(XpTestOrder::getTestPlanId, query.getTestPlanId());
if (CollUtil.isNotEmpty(vehicleIdsFilter)) {
wrapper.in(XpTestOrder::getVehicleId, vehicleIdsFilter);
}
wrapper.orderByDesc(XpTestOrder::getCreateTime);
// 2. 分页查询
Page page = new Page<>(query.getPageNum(), query.getPageSize());
Page pageResult = this.page(page, wrapper);
if (CollectionUtils.isEmpty(pageResult.getRecords())) {
return buildEmptyOrderPage(query);
}
// 3. 收集关联ID
Set vehicleIds = new HashSet<>();
Set fleetIds = new HashSet<>();
for (XpTestOrder order : pageResult.getRecords()) {
if (Objects.nonNull(order.getVehicleId())) {
vehicleIds.add(order.getVehicleId());
}
if (Objects.nonNull(order.getFleetId())) {
fleetIds.add(order.getFleetId());
}
}
// 4. 批量查询关联数据
Map vehicleMap = batchGetVehicles(vehicleIds);
Map fleetMap = batchGetFleets(fleetIds);
// 5. 组装 VO
List voList = pageResult.getRecords().stream()
.map(order -> convertToPageVO(order, vehicleMap, fleetMap))
.collect(Collectors.toList());
// 6. 返回分页结果
PageInfo resultPage = new PageInfo<>(query.getPageNum(), query.getPageSize());
resultPage.setTotal(pageResult.getTotal());
resultPage.setRecords(voList);
return resultPage;
}
private PageInfo buildEmptyOrderPage(XpTestOrderPageQuery query) {
PageInfo resultPage = new PageInfo<>(query.getPageNum(), query.getPageSize());
resultPage.setTotal(0L);
resultPage.setRecords(new ArrayList<>());
return resultPage;
}
@Override
public XpTestOrderDetailVO getOrderDetail(Long id) {
// 1. 查询订单基础信息
XpTestOrder order = this.getById(id);
if (order == null) {
throw new ServiceException("订单不存在");
}
// 2. 转换为详情 VO
XpTestOrderDetailVO vo = BeanUtil.toBean(order, XpTestOrderDetailVO.class);
// 4. 获取车辆信息
if (Objects.nonNull(order.getVehicleId())) {
Map vehicleMap = batchGetVehicles(Collections.singleton(order.getVehicleId()));
XpVehicle vehicle = vehicleMap.get(order.getVehicleId());
if (Objects.nonNull(vehicle)) {
vo.setPlateNumber(vehicle.getPlateNumber());
vo.setVehicleModel(vehicle.getVehicleModel());
}
}
// 5. 获取车队信息
if (Objects.nonNull(order.getFleetId())) {
Map fleetMap = batchGetFleets(Collections.singleton(order.getFleetId()));
XpFleet fleet = fleetMap.get(order.getFleetId());
if (Objects.nonNull(fleet)) {
vo.setFleetName(fleet.getFleetName());
}
}
// 6. 获取评价信息
LambdaQueryWrapper evaluateWrapper = new LambdaQueryWrapper<>();
evaluateWrapper.eq(XpOrderEvaluate::getOrderId, id);
XpOrderEvaluate evaluate = xpOrderEvaluateService.getOne(evaluateWrapper.orderByDesc(XpOrderEvaluate::getCreateTime).last(" limit 0,1"));
if (Objects.nonNull(evaluate)) {
vo.setOrderRating(evaluate.getOrderRating());
}
// 7. 查询mongodb 该订单上传的经纬度轨迹信息(按订单id)
List trajectories = queryOrderTrajectories(id);
vo.setOrderPositionList(convertTrajectoriesToPositions(trajectories));
// 8. 起步价、里程费、时长费、远途费:按订单费用明细聚合
Map feeMap = sumOrderDetailMoneyByDetailType(id);
vo.setStartMoney(feeMap.getOrDefault(1, BigDecimal.ZERO));
vo.setMileageMoney(feeMap.getOrDefault(2, BigDecimal.ZERO));
vo.setDurationMoney(feeMap.getOrDefault(3, BigDecimal.ZERO));
vo.setLongDistancemoney(feeMap.getOrDefault(4, BigDecimal.ZERO));
return vo;
}
@Override
public void deleteOrder(Long id) {
// 1. 校验订单是否存在
XpTestOrder order = this.getById(id);
if (Objects.isNull(order)) {
throw new ServiceException("订单不存在", I18nKeyConstant.ORDER_NOT_EXISTS);
}
this.removeById(id);
}
/**
* 构建查询条件
*/
private LambdaQueryWrapper buildQueryWrapper(XpTestOrderPageQuery query) {
LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
wrapper.like(StringUtils.isNotBlank(query.getOrderNumber()), XpTestOrder::getOrderNumber, query.getOrderNumber())
.eq(Objects.nonNull(query.getStatus()), XpTestOrder::getStatus, query.getStatus());
if (Objects.nonNull(query.getStartTime())) {
wrapper.ge(XpTestOrder::getCreateTime, LocalDateTime.of(query.getStartTime(), LocalTime.MIN));
}
if (Objects.nonNull(query.getEndTime())) {
wrapper.le(XpTestOrder::getCreateTime, LocalDateTime.of(query.getEndTime(), LocalTime.MAX));
}
wrapper.orderByDesc(XpTestOrder::getCreateTime);
return wrapper;
}
/**
* 批量获取车辆信息
*/
private Map batchGetVehicles(Set vehicleIds) {
if (CollectionUtils.isEmpty(vehicleIds)) {
return new HashMap<>();
}
try {
R> result = xpVehicleClient.listByIds(new ArrayList<>(vehicleIds));
if (Objects.nonNull(result) && Objects.nonNull(result.getData())) {
return result.getData().stream()
.collect(Collectors.toMap(XpVehicle::getId, Function.identity(), (a, b) -> a));
}
} catch (Exception e) {
log.error("批量获取车辆信息失败: {}", e.getMessage());
}
return new HashMap<>();
}
/**
* 批量获取车队信息
*/
private Map batchGetFleets(Set fleetIds) {
if (CollectionUtils.isEmpty(fleetIds)) {
return new HashMap<>();
}
try {
R> result = xpFleetClient.listByIds(new ArrayList<>(fleetIds));
if (Objects.nonNull(result) && Objects.nonNull(result.getData())) {
return result.getData().stream()
.collect(Collectors.toMap(XpFleet::getId, Function.identity(), (a, b) -> a));
}
} catch (Exception e) {
log.error("批量获取车队信息失败: {}", e.getMessage());
}
return new HashMap<>();
}
@Override
@Transactional
@ShardingSphereTransactionType(TransactionType.BASE)
public void cancelOrCloseOrder(Long orderId, Integer type) {
// 1. 校验订单是否存在
XpTestOrder order = this.getById(orderId);
if (Objects.isNull(order)) {
throw new ServiceException("订单不存在");
}
// 2. 校验操作类型
if (type != 1 && type != 2) {
throw new ServiceException("操作类型错误,type只能为1或2");
}
// 3. 校验订单状态是否允许操作
Integer currentStatus = order.getStatus();
//取消订单:在未接到乘客前都可以,关闭订单,在送达目的地可以操作关闭
if (type == 1) {
// 取消订单:只有待接单(1)、待出发(2)、接驾中(3)状态可以取消
if (currentStatus != 1 && currentStatus != 2 && currentStatus != 3 && currentStatus != 4) {
throw new ServiceException("当前订单状态不允许取消,只有待接单、待出发、接驾中状态的订单可以取消", I18nKeyConstant.ORDER_CANCEL_FAILED);
}
} else {
// 关闭订单:0=已取消,1=待接单,2=待出发,3=接驾中,4=待客上车,5=乘客已上车,6=服务中 状态可以关闭
if (currentStatus == 7 || currentStatus == 8 || currentStatus == 9 || currentStatus == 10) {
throw new ServiceException("当前订单状态不允许关闭", I18nKeyConstant.ORDER_CLOSE_FAILED);
}
}
// 4. 执行操作
if (type == 1) {
// 取消订单
cancelOrder(order);
} else {
// 关闭订单
closeOrder(order);
}
}
/**
* 转换为分页列表 VO
*/
private XpTestOrderPageVO convertToPageVO(XpTestOrder order,
Map vehicleMap,
Map fleetMap) {
XpTestOrderPageVO vo = new XpTestOrderPageVO();
vo.setId(order.getId());
vo.setOrderType(order.getOrderType());
vo.setOrderNumber(order.getOrderNumber());
vo.setCreateTime(order.getCreateTime());
vo.setOrderSource(order.getOrderSource());
vo.setStartAddress(order.getStartAddress());
vo.setEndAddress(order.getEndAddress());
vo.setStatus(order.getStatus());
// 车辆信息
if (Objects.nonNull(order.getVehicleId()) && vehicleMap.containsKey(order.getVehicleId())) {
XpVehicle vehicle = vehicleMap.get(order.getVehicleId());
vo.setPlateNumber(vehicle.getPlateNumber());
}
// 车队信息
if (Objects.nonNull(order.getFleetId()) && fleetMap.containsKey(order.getFleetId())) {
XpFleet fleet = fleetMap.get(order.getFleetId());
vo.setFleetName(fleet.getFleetName());
}
return vo;
}
/**
* 取消订单
*/
private void cancelOrder(XpTestOrder order) {
//判断还原车辆状态
if (null == order.getVehicleId()) {
Object cacheObject1 = redisService.getCacheObject("CREATE_ORDER:" + order.getId().toString());
if (null != cacheObject1) {
JSONObject jsonObject = JSON.parseObject(cacheObject1.toString());
String vin = jsonObject.getString("vin");
XpVehicle vehicle = xpVehicleClient.findByVehicleVin(vin).getData();
if (null != vehicle && 0 != vehicle.getState()) {
XpVehicle vehicle1 = new XpVehicle();
vehicle1.setId(vehicle.getId());
vehicle1.setState(0);
xpVehicleClient.updateVehicleById(vehicle1);
log.info("管理后台取消订单,车辆状态修改0 {}", vehicle.getPlateNumber());
}
}
}
// 1. 更新订单状态为已取消(0)
lambdaUpdate().set(XpTestOrder::getStatus, 0)
.eq(XpTestOrder::getId, order.getId()).update();
// 2. 保存取消记录
XpTestOrderCancel orderCancel = new XpTestOrderCancel();
orderCancel.setOrderId(order.getId());
orderCancel.setCancelSource(3); // 3=运营人员(后台管理接口固定为运营人员)
orderCancel.setUserId(SecurityUtils.getUserId()); // 当前登录用户ID
orderCancel.setCancelReason("运营人员取消订单");
xpTestOrderCancelService.save(orderCancel);
//查询车辆信息
R xpVehicleR = xpVehicleClient.queryById(order.getVehicleId());
if (xpVehicleR.getCode() == HttpStatus.SUCCESS && Objects.nonNull(xpVehicleR.getData())) {
XpVehicle xpVehicle = xpVehicleR.getData();
//还原车辆状态
if(1 == xpVehicle.getState()){
XpVehicle vehicle1 = new XpVehicle();
vehicle1.setId(xpVehicle.getId());
vehicle1.setState(0);
xpVehicleClient.updateVehicleById(vehicle1);
log.info("管理后台取消订单,车辆状态修改0 {}", xpVehicle.getPlateNumber());
}
// 下发命令
PushOrderDto pushOrderDto = new PushOrderDto();
pushOrderDto.setOrderId("TEST" + order.getId().toString());
pushOrderDto.setVin(xpVehicle.getVehicleVin());
pushOrderDto.setOperation_type(OrderOperationType.CLOUD_CANCEL_ORDER);
orderMissionClient.cancelOrder(pushOrderDto);
log.info("运营人员取消订单成功,开始执行巡游订单 {}", order.getId());
} else {
log.info("查询车辆信息失败,订单ID: {}, 下发取消订单命令失败", order.getId());
}
log.info("订单取消成功,订单ID: {}, 操作人: {}", order.getId(), SecurityUtils.getUsername());
}
/**
* 关闭订单
*/
private void closeOrder(XpTestOrder order) {
XpVehicle xpVehicle = xpVehicleClient.queryById(order.getVehicleId()).getData();
order.setStatus(7);
order.setGetoffTime(LocalDateTime.now());
// 解析下车地点
VehiclePosition vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("vin").is(xpVehicle.getVehicleVin())), VehiclePosition.class);
if(Objects.nonNull(vehiclePosition)){
GeoJsonPoint point = vehiclePosition.getPoint();
if(Objects.nonNull(point)){
double x = point.getX();
double y = point.getY();
Map geocode = gdMapGeocodingUtil.geocode(String.valueOf(x), String.valueOf(y));
order.setGetoffAddress(geocode.get("address"));
}
}
//计算行驶总距离和时长
List orderTrajectoryList = mongoTemplate.find(new Query().addCriteria(Criteria.where("orderId").is(order.getId())), OrderTrajectory.class);
Double reduce = orderTrajectoryList.stream().map(OrderTrajectory::getDistance).reduce(0.0, Double::sum);
order.setMileageKilometers(new BigDecimal(reduce).divide(new BigDecimal(1000)).setScale(4, RoundingMode.HALF_EVEN).doubleValue());
long duration = LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8")) - order.getTripBegin().toEpochSecond(ZoneOffset.of("+8"));
order.setDuration(new BigDecimal(duration).divide(new BigDecimal(60), 2, RoundingMode.HALF_EVEN).doubleValue());
order.setStartTestPointId(null);
this.updateById(order);
redisService.setCacheObject("TEST_ORDER_STATUS:" + order.getId(), 7, 24L, TimeUnit.HOURS);
//给XPU推送订单取消的报文
PushOrderDto pushOrderDto = new PushOrderDto();
pushOrderDto.setOrderId("TEST" + order.getId().toString());
pushOrderDto.setVin(xpVehicle.getVehicleVin());
pushOrderDto.setOperation_type(OrderOperationType.CLOUD_CANCEL_ORDER);
orderMissionClient.cancelOrder(pushOrderDto);
//结束行程,修改状态和计算费用
endTrip(order.getId());
}
/**
* 改派订单
*/
@Override
@Transactional
@ShardingSphereTransactionType(TransactionType.BASE)
public void reassignOrder(Long orderId, Long newVehicleId, String reassignReason) {
// 1. 校验订单是否存在
XpTestOrder order = this.getById(orderId);
if (Objects.isNull(order)) {
throw new ServiceException("订单不存在");
}
// 2. 校验订单状态(只有待出发、接驾中、待乘客上车状态可以改派)
Integer currentStatus = order.getStatus();
if (currentStatus != 2 && currentStatus != 3 && currentStatus != 4) {
throw new ServiceException("当前订单状态不允许改派,只有待出发、接驾中、待乘客上车状态的订单可以改派", I18nKeyConstant.ORDER_REASSIGN_FAILED);
}
// 3. 校验新旧车辆是否相同
if (Objects.equals(order.getVehicleId(), newVehicleId)) {
throw new ServiceException("新车辆与原车辆相同,无需改派", I18nKeyConstant.ORDER_REASSIGN_VEHICLE_FAILED);
}
// 4. 查询新车辆信息并校验
R> vehicleResult = xpVehicleClient.listByIds(Collections.singletonList(newVehicleId));
if (Objects.isNull(vehicleResult) || Objects.isNull(vehicleResult.getData()) || vehicleResult.getData().isEmpty()) {
throw new ServiceException("新车辆不存在", I18nKeyConstant.ORDER_REASSIGN_NEW_VEHICLE_NOT_EXISTS);
}
XpVehicle newVehicle = vehicleResult.getData().get(0);
// 5. 校验新车辆的运营状态
// 车辆状态:1=待完善 2=待运营 3=保险/年检过期
// 运营状态:1=运营中 2=维修中 3=已收车 4=车辆异常
// 当前模式:1=接单 2=测试
// 模式状态:1=开始接单/开始测试 2=停止接单/停止测试
// if (Objects.isNull(newVehicle.getStatus()) || newVehicle.getStatus() == 3) {
// throw new ServiceException("新车辆保险或年检已过期,无法接单", I18nKeyConstant.ORDER_REASSIGN_NEW_VEHICLE_STATUS_INVALID);
// }
if (Objects.isNull(newVehicle.getOperatingStatus()) || newVehicle.getOperatingStatus() != 1) {
throw new ServiceException("新车辆运营状态异常,无法接单", I18nKeyConstant.ORDER_REASSIGN_NEW_VEHICLE_OPERATING_STATUS_INVALID);
}
if (Objects.isNull(newVehicle.getAcceptOrTest()) || newVehicle.getAcceptOrTest() != 1) {
throw new ServiceException("新车辆当前不在接单模式", I18nKeyConstant.ORDER_REASSIGN_NEW_VEHICLE_NOT_IN_ACCEPT_MODE);
}
if (Objects.isNull(newVehicle.getAcceptOrTestStatus()) || newVehicle.getAcceptOrTestStatus() != 1) {
throw new ServiceException("新车辆未开始接单", I18nKeyConstant.ORDER_REASSIGN_NEW_VEHICLE_NOT_START_ACCEPT);
}
if (Objects.isNull(newVehicle.getState()) || newVehicle.getState() != 0) {
throw new ServiceException("新车辆已接单,请重新选择", I18nKeyConstant.ORDER_REASSIGN_NEW_VEHICLE_NOT_FREE);
}
// 6. 保存原车辆和车队信息
Long oldVehicleId = order.getVehicleId();
Long oldFleetId = order.getFleetId();
// 6.1 处理原车辆:取消订单指令、解绑乘客、更新状态、创建返程巡游订单
if (Objects.nonNull(oldVehicleId)) {
try {
R oldVehicleResult = xpVehicleClient.queryById(oldVehicleId);
if (oldVehicleResult != null && oldVehicleResult.getCode() == HttpStatus.SUCCESS && oldVehicleResult.getData() != null) {
XpVehicle oldVehicle = oldVehicleResult.getData();
// 6.1.1 取消原订单指令
PushOrderDto cancelDto = new PushOrderDto();
cancelDto.setOrderId("TEST" + order.getId().toString());
cancelDto.setVin(oldVehicle.getVehicleVin());
cancelDto.setOperation_type(OrderOperationType.CLOUD_CANCEL_ORDER);
orderMissionClient.cancelOrder(cancelDto);
log.info("已取消原车辆订单指令,订单ID: {}, VIN: {}", orderId, oldVehicle.getVehicleVin());
// 6.1.3 修改原车辆状态为空闲
XpVehicle vehicle1 = new XpVehicle();
vehicle1.setId(oldVehicle.getId());
vehicle1.setState(0);
xpVehicleClient.updateVehicleById(vehicle1);
log.info("改派订单,车辆状态修改0 {}", oldVehicle.getPlateNumber());
log.info("已将原车辆状态设为空闲,车辆ID: {}", oldVehicleId);
} else {
log.warn("查询原车辆信息失败,车辆ID: {}", oldVehicleId);
}
} catch (Exception e) {
log.error("处理原车辆失败,车辆ID: {},错误: {}", oldVehicleId, e.getMessage());
}
}
// 7. 查询新车辆关联的车队ID
Long newFleetId = null;
try {
R fleetIdResult = xpFleetVehicleClient.getFleetIdByVehicleId(newVehicleId);
if (Objects.nonNull(fleetIdResult) && Objects.nonNull(fleetIdResult.getData())) {
newFleetId = fleetIdResult.getData();
log.info("查询到新车辆ID: {} 关联的车队ID: {}", newVehicleId, newFleetId);
} else {
log.warn("新车辆ID: {} 未关联任何车队,将使用原车队ID: {}", newVehicleId, oldFleetId);
newFleetId = oldFleetId;
}
} catch (Exception e) {
log.error("查询新车辆关联的车队ID失败,车辆ID: {},错误: {}", newVehicleId, e.getMessage());
newFleetId = oldFleetId;
}
// 8. 如果旧订单的车队ID为空,尝试查询旧车辆的车队ID
if (Objects.isNull(oldFleetId) && Objects.nonNull(oldVehicleId)) {
try {
R oldFleetIdResult = xpFleetVehicleClient.getFleetIdByVehicleId(oldVehicleId);
if (Objects.nonNull(oldFleetIdResult) && Objects.nonNull(oldFleetIdResult.getData())) {
oldFleetId = oldFleetIdResult.getData();
log.info("补充查询到旧车辆ID: {} 关联的车队ID: {}", oldVehicleId, oldFleetId);
}
} catch (Exception e) {
log.warn("查询旧车辆关联的车队ID失败,车辆ID: {},错误: {}", oldVehicleId, e.getMessage());
}
}
// 9. 更新订单的车辆和车队信息
order.setVehicleId(newVehicleId);
if (Objects.nonNull(newFleetId)) {
order.setFleetId(newFleetId);
}
order.setStartTestPointId(null);
this.updateById(order);
// 10. 保存改派记录
XpOrderReassign orderReassign = new XpOrderReassign();
orderReassign.setOrderId(orderId);
orderReassign.setUserId(SecurityUtils.getUserId());
orderReassign.setOperationTime(LocalDateTime.now());
orderReassign.setOldVehicleId(oldVehicleId);
orderReassign.setNewVehicleId(newVehicleId);
orderReassign.setOldFleetId(oldFleetId);
orderReassign.setNewFleetId(newFleetId);
orderReassign.setReassignReason(
StringUtils.isNotBlank(reassignReason) ? reassignReason : "运营人员改派订单"
);
orderReassign.setCreateBy(SecurityUtils.getLoginUser().getSysUser().getNickName());
xpOrderReassignService.save(orderReassign);
log.info("订单改派成功,订单ID: {}, 原车辆ID: {}, 新车辆ID: {}, 原车队ID: {}, 新车队ID: {}, 操作人: {}",
orderId, oldVehicleId, newVehicleId, oldFleetId, newFleetId, SecurityUtils.getUsername());
// 11. 处理新车辆:绑定乘客、下发订单、更新状态
try {
// 11.2 下发订单到新车辆
PushOrderDto pushOrderDto = new PushOrderDto();
pushOrderDto.setVin(newVehicle.getVehicleVin());
pushOrderDto.setOrderId("TEST" + order.getId());
pushOrderDto.setStartLatitude(order.getStartLat());
pushOrderDto.setStartLongitude(order.getStartLon());
pushOrderDto.setEndLatitude(order.getEndLat());
pushOrderDto.setEndLongitude(order.getEndLon());
pushOrderDto.setEndPoiName(order.getEndTestPointName());
pushOrderDto.setOrderType(OrderType.USER_ORDER);
pushOrderDto.setStage(1);
// 调用推送服务 orderId + carVin
orderMissionClient.pushOrder(pushOrderDto);
log.info("已下发订单到新车辆,订单ID: {}, VIN: {}", orderId, newVehicle.getVehicleVin());
// 11.3 更新新车辆状态为已接单
XpVehicle vehicle1 = new XpVehicle();
vehicle1.setId(newVehicle.getId());
vehicle1.setState(1);
xpVehicleClient.updateVehicleById(vehicle1);
log.info("改派订单,车辆状态修改1 {}", newVehicle.getPlateNumber());
log.info("已将新车辆状态设为已接单,车辆ID: {}", newVehicleId);
} catch (Exception e) {
log.error("处理新车辆失败,车辆ID: {},错误: {}", newVehicleId, e.getMessage());
}
}
@Override
public R updateOrderStatus(Long orderId, Integer status) {
XpTestOrder xpOrder = this.getById(orderId);
if (Objects.isNull(xpOrder)) {
return R.fail("订单不存在");
}
if (xpOrder.getStatus() == 0) {
return R.fail("订单已取消");
}
if(xpOrder.getStatus() >= 8){
return R.fail("订单已结束");
}
// 查询车辆
XpVehicle xpVehicle = xpVehicleClient.queryById(xpOrder.getVehicleId()).getData();
if(xpOrder.getOrderType() == 1){
switch (status) {
case 4:
// 查询起点点位
Long startPointId = xpOrder.getStartTestPointId();
XpTestPoint xpPoint = xpTestPointClient.queryById(startPointId).getData();
// 添加取消订单定时任务job 时间为点位限停时长
Map cancelJobMaps =
new ImmutableMap.Builder().
put("id", xpOrder.getId())
.build();
QuartzManager.addJob(
OrderCancelJob.class,
(OrderCancelJob.name + xpOrder.getId()).toUpperCase(),
TimeJobType.ORDER_CANCEL,
DateUtils.localDateTimeToDate(LocalDateTime.now().plusMinutes(xpPoint.getParkingTimeLimit())),
cancelJobMaps
);
// 车辆到达上车点时间
xpOrder.setArrivalTime(LocalDateTime.now());
break;
case 5:
LocalDateTime now = LocalDateTime.now();
xpOrder.setBoardingTime(now);
xpOrder.setRideTime(now);
// 删除定时任务job
QuartzManager.removeJob((OrderCancelJob.name + xpOrder.getId()).toUpperCase(), TimeJobType.ORDER_CANCEL);
break;
case 6:
// 行程开始时间
xpOrder.setTripBegin(LocalDateTime.now());
redisService.deleteObject("TEST_ORDER_NAVIGATION_START_DATA:" + xpOrder.getVehicleId());
break;
case 7:
xpOrder.setGetoffTime(LocalDateTime.now());
// 解析下车地点
VehiclePosition vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("vin").is(xpVehicle.getVehicleVin())), VehiclePosition.class);
if(Objects.nonNull(vehiclePosition)){
GeoJsonPoint point = vehiclePosition.getPoint();
if(Objects.nonNull(point)){
double x = point.getX();
double y = point.getY();
Map geocode = gdMapGeocodingUtil.geocode(String.valueOf(x), String.valueOf(y));
xpOrder.setGetoffAddress(geocode.get("address"));
}
}
//计算行驶总距离和时长
List orderTrajectoryList = mongoTemplate.find(new Query().addCriteria(Criteria.where("orderId").is(xpOrder.getId())), OrderTrajectory.class);
Double reduce = orderTrajectoryList.stream().map(OrderTrajectory::getDistance).reduce(0.0, Double::sum);
xpOrder.setMileageKilometers(new BigDecimal(reduce).divide(new BigDecimal(1000)).setScale(4, RoundingMode.HALF_EVEN).doubleValue());
long duration = LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8")) - xpOrder.getTripBegin().toEpochSecond(ZoneOffset.of("+8"));
xpOrder.setDuration(new BigDecimal(duration).divide(new BigDecimal(60), 2, RoundingMode.HALF_EVEN).doubleValue());
//创建定时结束订单和等待结束后下发巡游单的定时任务
Integer arriveTime = xpSystemSettingClient.getOrderFinishTime().getData();
// 添加自动结束行程定时任务job
Map endTripJobMaps =
new ImmutableMap.Builder()
.put("id", xpOrder.getId().toString())
.put("orderType", OrderType.TEST_ORDER)
.build();
QuartzManager.addJob(
EndTripJob.class,
(EndTripJob.name + xpOrder.getId()).toUpperCase(),
TimeJobType.END_TRIP,
DateUtils.localDateTimeToDate(LocalDateTime.now().plusMinutes(arriveTime)),
endTripJobMaps
);
break;
}
xpOrder.setStatus(status);
xpOrder.setStartTestPointId(null);
this.updateById(xpOrder);
redisService.setCacheObject("TEST_ORDER_STATUS:" + xpOrder.getId(), status, 24L, TimeUnit.HOURS);
}else{
//测试巡游单,只需要修改状态
if(status == 10){
xpOrder.setStatus(status);
xpOrder.setStartTestPointId(null);
this.updateById(xpOrder);
redisService.setCacheObject("TEST_ORDER_STATUS:" + xpOrder.getId(), status, 24L, TimeUnit.HOURS);
}
}
return R.ok();
}
@Override
public Boolean endTrip(Long orderId) {
List xpOrderDetails = new ArrayList<>();
// 起步价格明细
List start = new ArrayList<>();
// 里程费价格明细
List mileage = new ArrayList<>();
// 时长费价格明细
List duration = new ArrayList<>();
// 远途费价格明细
List longDistance = new ArrayList<>();
XpTestOrder order = this.getById(orderId);
if (order == null) {
throw new RuntimeException("订单不存在");
}
if (order.getStatus() != 7) {
throw new RuntimeException("订单状态错误");
}
order.setStatus(8);
order.setStartTestPointId(null);
order.setTripEnd(LocalDateTime.now());
baseMapper.updateById(order);
redisService.setCacheObject("TEST_ORDER_STATUS:" + order.getId(), 8, 24L, TimeUnit.HOURS);
// 根据订单所属开通城市id 查询定价
XpPricing pricing = bootCommonService.getXpPricing(order.getCityId(),order.getVehicleId());
if (pricing == null) {
throw new ServiceException("当前城市未配置定价规则", I18nKeyConstant.ORDER_PRICING_NOT_EXISTS);
}
// 费用
// 查询订单轨迹
List trajectories = mongoTemplate.find(
new Query(Criteria.where("orderId").is(orderId))
.with(Sort.by(Sort.Direction.ASC, "locatetime")),
OrderTrajectory.class
);
List gpsPoints = new ArrayList<>();
for (OrderTrajectory trajectory : trajectories) {
TaxiPricingDemoService.GpsPoint point = new TaxiPricingDemoService.GpsPoint(trajectory.getDistance(), trajectory.getLocatetime());
gpsPoints.add(point);
}
TaxiPricingDemoService.PricingConfig config = new TaxiPricingDemoService.PricingConfig();
JSONArray startingFareJsonArray = JSONArray.parseArray(pricing.getStartingFareJson());
LocalDateTime boardingTime = order.getBoardingTime();
Date date = DateUtils.localDateTimeToDate(boardingTime);
double paramNum4 = 0.0;
double paramNum5 = 0.0;
//一口价
if (pricing.getPricingType() == 2) {
Double mileageKilometers = order.getMileageKilometers();
Double duration1 = order.getDuration();
JSONObject basicPricingJson = JSONObject.parseObject(pricing.getBasicPricingJson());
XpPricingCardVo xpPricingCardVo = xpPricingUtil.calculationFixedPrice(order.getVehicleId().toString(), basicPricingJson, null, mileageKilometers, duration1, 0, 0.0, pricing.getIsDynamicPrice(), order.getCityId(), pricing.getDynamicPricingJson());
XpTestOrderDetail xpOrderDetail = new XpTestOrderDetail();
xpOrderDetail.setDetailType(1);
xpOrderDetail.setOrderId(orderId);
xpOrderDetail.setType(1);
xpOrderDetail.setMileage(new BigDecimal(mileageKilometers).setScale(2, RoundingMode.HALF_UP));
String currentTimeSlot = DateUtils.getCurrentTimeSlot();
xpOrderDetail.setTime(currentTimeSlot);
xpOrderDetail.setDuration(duration1);
xpOrderDetail.setMoney(new BigDecimal(xpPricingCardVo.getStartPrice()).setScale(2, RoundingMode.HALF_UP));
xpOrderDetails.add(xpOrderDetail);
xpOrderDetail = new XpTestOrderDetail();
xpOrderDetail.setDetailType(2);
xpOrderDetail.setOrderId(orderId);
xpOrderDetail.setType(1);
xpOrderDetail.setMoney(new BigDecimal(xpPricingCardVo.getDistancePrice()).setScale(2,RoundingMode.HALF_UP));
xpOrderDetails.add(xpOrderDetail);
xpOrderDetail = new XpTestOrderDetail();
xpOrderDetail.setDetailType(3);
xpOrderDetail.setOrderId(orderId);
xpOrderDetail.setType(1);
xpOrderDetail.setMoney(new BigDecimal(xpPricingCardVo.getDurationPrice()).setScale(2, RoundingMode.HALF_UP));
xpOrderDetails.add(xpOrderDetail);
BigDecimal totalPrice =BigDecimal.ZERO;
for (XpTestOrderDetail xpOrderDetail1 : xpOrderDetails) {
totalPrice = totalPrice.add(xpOrderDetail1.getMoney());
xpTestOrderDetailMapper.insert(xpOrderDetail1);
}
order.setOrderMoney(totalPrice);
order.setStartTestPointId(null);
baseMapper.updateById(order);
} else {
//动态计费
if (pricing.getPricingType() == 1) {
for (int i = 0; i < startingFareJsonArray.size(); i++) {
JSONObject jsonObject = startingFareJsonArray.getJSONObject(i);
String num1 = jsonObject.getString("num1");
String num2 = jsonObject.getString("num2");
String num3 = jsonObject.getString("num3");
String num4 = jsonObject.getString("num4");
String num5 = jsonObject.getString("num5");
String num6 = jsonObject.getString("num6");
String num7 = jsonObject.getString("num7");
config.normals.add(new TaxiPricingDemoService.PricingRule("普通时段", num1, num2, Double.parseDouble(num3), Double.parseDouble(num4), Double.parseDouble(num5), Double.parseDouble(num6), Double.parseDouble(num7)));
// 判断当前是那个时段
Calendar calendarStart = Calendar.getInstance();
calendarStart.setTime(date);
calendarStart.set(Calendar.HOUR_OF_DAY, Integer.valueOf(jsonObject.getString("num1").split(":")[0]));
calendarStart.set(Calendar.MINUTE, Integer.valueOf(jsonObject.getString("num1").split(":")[1]));
Calendar calendarEnd = Calendar.getInstance();
calendarEnd.setTime(date);
calendarEnd.set(Calendar.HOUR_OF_DAY, Integer.valueOf(jsonObject.getString("num2").split(":")[0]));
calendarEnd.set(Calendar.MINUTE, Integer.valueOf(jsonObject.getString("num2").split(":")[1]));
// 找到符合条件的直接跳出循环
if (date.getTime() > calendarStart.getTimeInMillis() && date.getTime() < calendarEnd.getTimeInMillis()) {
paramNum4 = jsonObject.getDouble("num4");
paramNum5 = jsonObject.getDouble("num5");
break;
}
}
JSONObject basicPricingJson = JSONObject.parseObject(pricing.getBasicPricingJson());
config.peaks.add(new TaxiPricingDemoService.PricingRule("高峰期", basicPricingJson.getString("num21").split(" - ")[0], basicPricingJson.getString("num21").split(" - ")[1], Double.parseDouble(basicPricingJson.getString("num23")), paramNum4, paramNum5, Double.parseDouble(basicPricingJson.getString("num24")), Double.parseDouble(basicPricingJson.getString("num25"))));
config.peaks.add(new TaxiPricingDemoService.PricingRule("高峰期", basicPricingJson.getString("num22").split(" - ")[0], basicPricingJson.getString("num22").split(" - ")[1], Double.parseDouble(basicPricingJson.getString("num23")), paramNum4, paramNum5, Double.parseDouble(basicPricingJson.getString("num24")), Double.parseDouble(basicPricingJson.getString("num25"))));
config.night = new TaxiPricingDemoService.PricingRule("夜间行驶", basicPricingJson.getString("num14").split(" - ")[0], basicPricingJson.getString("num14").split(" - ")[1], Double.parseDouble(basicPricingJson.getString("num15")), paramNum4, paramNum5, Double.parseDouble(basicPricingJson.getString("num16")), Double.parseDouble(basicPricingJson.getString("num17")));
config.holiday = new TaxiPricingDemoService.PricingRule("节假日", "00:00", "23:59", Double.parseDouble(basicPricingJson.getString("num29")), paramNum4, paramNum5, Double.parseDouble(basicPricingJson.getString("num30")), Double.parseDouble(basicPricingJson.getString("num31")));
// --- 远途加价区间配置 (关键修改) ---
// 1. 超过 num1(12) 不足 num2(20), 加收 num3(1.2)
config.longSteps.add(new TaxiPricingDemoService.LongRangeStep(paramNum4, Double.parseDouble(basicPricingJson.getString("num7")), Double.parseDouble(basicPricingJson.getString("num8"))));
// 2. 超过 num4(20) 不足 num5(35), 加收 num6(2.0)
config.longSteps.add(new TaxiPricingDemoService.LongRangeStep(Double.parseDouble(basicPricingJson.getString("num9")), Double.parseDouble(basicPricingJson.getString("num10")), Double.parseDouble(basicPricingJson.getString("num11"))));
// 3. 超过 num7(35) 以后, 加收 num8(3.5) -> numEnd 传个极大值
config.longSteps.add(new TaxiPricingDemoService.LongRangeStep(Double.parseDouble(basicPricingJson.getString("num12")), 999999, Double.parseDouble(basicPricingJson.getString("num13"))));
}
TaxiPricingDemoService.TaxiPricingService service = new TaxiPricingDemoService.TaxiPricingService();
Integer holidayToday = holidayUtil.isHolidayToday();
Map bill = service.calculateOrderPrice(DateUtils.toDate(order.getBoardingTime()), order.getMileageKilometers(), gpsPoints, config, holidayToday);
log.info("======= 订单计费账单 =======");
bill.forEach((detail, price) -> log.info(detail + ": " + price + " 元"));
BigDecimal totalAmount = bill.values().stream().reduce(BigDecimal.ZERO, BigDecimal::add);
log.info("--------------------------");
log.info("应付总额: " + totalAmount + " 元");
BigDecimal startMoney = BigDecimal.ZERO;
// 默认费率1
double priceRate = 1;
if (pricing.getIsDynamicPrice() == 1) {
// 先判断是否已经调价过了
Object userDynamicPriceObj = redisService.getCacheObject("USER_DYNAMIC_PRICE_" + order.getCityId() + "_" + order.getVehicleId());
if (userDynamicPriceObj != null) {
// 是否调价过了
priceRate = Double.parseDouble(userDynamicPriceObj.toString());
} else {
// 没有调过
//是否触发动态调价 redis获取
Object dynamicPriceObj = redisService.getCacheObject("DYNAMIC_PRICE_" + order.getCityId());
if (dynamicPriceObj != null) {
JSONObject jsonObject = JSONObject.parseObject(pricing.getDynamicPricingJson());
// 触发动态调价
// value=时间
String timeStr = dynamicPriceObj.toString();
// 判断当前距离这个时间范围的新增订单数(排除取消) / 上浮数量 算出应该增加的价格率
Integer num = this.getOrderNum(new GetOrderNumDto(timeStr, order.getCityId()));
// X单上升一次调价
Integer orderNum = getOrderNum(jsonObject, 4);
// 需要调价的次数
int count = num / orderNum;
if (count > 0) {
// 计算每次上升的费率
Integer upRate = getOrderNum(jsonObject, 5);
BigDecimal divide = new BigDecimal(upRate).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
for (int i = count; i > 0; i--) {
// 当前费率+(费率*上升百分比)
priceRate = new BigDecimal(priceRate).multiply(divide).add(new BigDecimal(priceRate)).setScale(2, RoundingMode.HALF_UP).doubleValue();
}
// 冷却时间
Integer time = getOrderNum(jsonObject, 6);
// 触发调价了
redisService.setCacheObject("USER_DYNAMIC_PRICE_" + order.getCityId() + "_" + order.getVehicleId(), priceRate, time.longValue(), TimeUnit.MINUTES);
}
}
}
}
// 封装费用明细数据
for (Map.Entry stringBigDecimalEntry : bill.entrySet()) {
// 封装起步价
FareBreakdownDetailVo startDetail = new FareBreakdownDetailVo();
String key = stringBigDecimalEntry.getKey();
if (key.contains("起步价")) {
startDetail.setTime(key.split("\\|")[1]);
startDetail.setMoney(stringBigDecimalEntry.getValue());
startMoney = startMoney.add(stringBigDecimalEntry.getValue());
if (key.contains("普通时段")) {
startDetail.setType(1);
} else if (key.contains("夜间")) {
startDetail.setType(2);
} else if (key.contains("高峰")) {
startDetail.setType(3);
} else if (key.contains("节假")) {
startDetail.setType(4);
}
String durationAndMileage = key.split("\\|")[2];
startDetail.setDuration(Double.valueOf(durationAndMileage.split("&")[0]));
startDetail.setMileage(new BigDecimal(durationAndMileage.split("&")[1]).multiply(BigDecimal.valueOf(1000)));
start.add(startDetail);
}
// 封装里程费
if (!key.contains("起步") && !key.contains("时长") && !key.contains("远途") && !key.contains("distance")) {
FareBreakdownDetailVo mileageDetail = new FareBreakdownDetailVo();
if (key.contains("普通时段")) {
mileageDetail.setType(1);
String key1 = "distance普通时段|" + key.split("\\|")[1];
String key2 = "普通时段|" + key.split("\\|")[1];
BigDecimal distance = bill.get(key1);
BigDecimal money = bill.get(key2);
mileageDetail.setMileage(distance);
order.setStartMoney(money.multiply(BigDecimal.valueOf(priceRate)));
mileageDetail.setMoney(money.multiply(BigDecimal.valueOf(priceRate)));
} else if (key.contains("夜间行驶")) {
mileageDetail.setType(2);
String key1 = "distance夜间行驶|" + key.split("\\|")[1];
String key2 = "夜间行驶|" + key.split("\\|")[1];
BigDecimal distance = bill.get(key1);
BigDecimal money = bill.get(key2);
mileageDetail.setMileage(distance);
order.setStartMoney(money.multiply(BigDecimal.valueOf(priceRate)));
mileageDetail.setMoney(money.multiply(BigDecimal.valueOf(priceRate)));
} else if (key.contains("高峰期")) {
mileageDetail.setType(3);
String key1 = "distance高峰期|" + key.split("\\|")[1];
String key2 = "高峰期|" + key.split("\\|")[1];
BigDecimal distance = bill.get(key1);
BigDecimal money = bill.get(key2);
mileageDetail.setMileage(distance);
order.setStartMoney(money.multiply(BigDecimal.valueOf(priceRate)));
mileageDetail.setMoney(money.multiply(BigDecimal.valueOf(priceRate)));
} else if (key.contains("节假")) {
mileageDetail.setType(4);
String key1 = "distance节假日|" + key.split("\\|")[1];
String key2 = "节假日|" + key.split("\\|")[1];
BigDecimal distance = bill.get(key1);
BigDecimal money = bill.get(key2);
mileageDetail.setMileage(distance);
order.setStartMoney(money.multiply(BigDecimal.valueOf(priceRate)));
mileageDetail.setMoney(money.multiply(BigDecimal.valueOf(priceRate)));
}
mileage.add(mileageDetail);
}
// 封装时长费
FareBreakdownDetailVo timeDetail = new FareBreakdownDetailVo();
if (key.contains("时长费")) {
if(key.contains("普通时段")){
timeDetail.setType(1);
}
if(key.contains("夜间行驶")){
timeDetail.setType(2);
}
if(key.contains("高峰")){
timeDetail.setType(3);
}
if(key.contains("节假")){
timeDetail.setType(4);
}
timeDetail.setMoney(stringBigDecimalEntry.getValue());
timeDetail.setDuration(Double.valueOf(key.split("&")[1]));
duration.add(timeDetail);
}
// 封装远途费
FareBreakdownDetailVo longDetail = new FareBreakdownDetailVo();
if (key.contains("远途费")) {
longDetail.setType(startDetail.getType());
longDetail.setMoney(stringBigDecimalEntry.getValue());
// longDetail.setMileage(new BigDecimal(key.split("&")[1]));
longDistance.add(longDetail);
}
}
for (FareBreakdownDetailVo fareBreakdownDetailVo : start) {
XpTestOrderDetail xpOrderDetail = new XpTestOrderDetail();
BeanUtils.copyProperties(fareBreakdownDetailVo, xpOrderDetail);
xpOrderDetail.setOrderId(orderId);
xpOrderDetail.setDetailType(1);
xpOrderDetail.setCreateTime(LocalDateTime.now());
xpOrderDetails.add(xpOrderDetail);
}
BigDecimal mileageMoney = BigDecimal.ZERO;
for (FareBreakdownDetailVo fareBreakdownDetailVo : mileage) {
XpTestOrderDetail xpOrderDetail = new XpTestOrderDetail();
BeanUtils.copyProperties(fareBreakdownDetailVo, xpOrderDetail);
xpOrderDetail.setOrderId(orderId);
xpOrderDetail.setDetailType(2);
xpOrderDetail.setCreateTime(LocalDateTime.now());
mileageMoney = mileageMoney.add(fareBreakdownDetailVo.getMoney());
xpOrderDetails.add(xpOrderDetail);
}
order.setMileageMoney(mileageMoney);
for (FareBreakdownDetailVo fareBreakdownDetailVo : longDistance) {
XpTestOrderDetail xpOrderDetail = new XpTestOrderDetail();
BeanUtils.copyProperties(fareBreakdownDetailVo, xpOrderDetail);
xpOrderDetail.setOrderId(orderId);
xpOrderDetail.setDetailType(4);
xpOrderDetail.setCreateTime(LocalDateTime.now());
mileageMoney = mileageMoney.add(fareBreakdownDetailVo.getMoney());
xpOrderDetails.add(xpOrderDetail);
}
for (FareBreakdownDetailVo fareBreakdownDetailVo : duration) {
XpTestOrderDetail xpOrderDetail = new XpTestOrderDetail();
BeanUtils.copyProperties(fareBreakdownDetailVo, xpOrderDetail);
xpOrderDetail.setOrderId(orderId);
xpOrderDetail.setDetailType(3);
xpOrderDetail.setCreateTime(LocalDateTime.now());
mileageMoney = mileageMoney.add(fareBreakdownDetailVo.getMoney());
xpOrderDetails.add(xpOrderDetail);
}
for (XpTestOrderDetail xpOrderDetail : xpOrderDetails) {
xpTestOrderDetailMapper.insert(xpOrderDetail);
}
for (FareBreakdownDetailVo fareBreakdownDetailVo : duration) {
if (order.getDurationMoney() == null) {
order.setDurationMoney(BigDecimal.ZERO);
}
BigDecimal add = order.getDurationMoney().add(fareBreakdownDetailVo.getMoney());
order.setDurationMoney(add);
}
BigDecimal orderMoney = startMoney.add(mileageMoney).add(order.getDurationMoney() != null ? order.getDurationMoney() : BigDecimal.ZERO)
.add(order.getLongDistancemoney() != null ? order.getLongDistancemoney() : BigDecimal.ZERO)
.add(order.getWaitMoney() != null ? order.getWaitMoney() : BigDecimal.ZERO);
order.setOrderMoney(orderMoney);
order.setStartTestPointId(null);
baseMapper.updateById(order);
}
order.setStartTestPointId(null);
baseMapper.updateById(order);
return true;
}
/**
* 获取配置 4每X单 5上升X% 6冷却
*
* @param rule
* @param num
* @return
*/
private Integer getOrderNum(JSONObject rule, int num) {
Integer param = rule.getInteger("num" + num);
return param;
}
/**
* 获取订单数量
*
* @param dto
* @return
*/
@Override
public Integer getOrderNum(GetOrderNumDto dto) {
Long num = this.baseMapper.selectCount(new LambdaQueryWrapper().eq(XpTestOrder::getCityId, dto.getOpenCityId()).between(BaseModel::getCreateTime, dto.getTimeStr(), new Date()));
return num.intValue();
}
@Override
public void saveVehicleLocation(OrderTrajectory orderTrajectory) {
// 查询订单信息
Integer status = redisService.getCacheObject("TEST_ORDER_STATUS:" + orderTrajectory.getOrderId());
if(status < 6 || status > 8){
return;
}
// 计算距离
// 获取上一次定位
String oldLocation = redisService.getCacheObject(CacheConstants.ORDER_OLD_LOCATION + orderTrajectory.getOrderId());
if (StringUtils.isEmpty(oldLocation)) {
orderTrajectory.setDistance(0D);
// 存储mongo数据
mongoTemplate.save(orderTrajectory);
// 存储首次定位
redisService.setCacheObject(CacheConstants.ORDER_OLD_LOCATION + orderTrajectory.getOrderId(), orderTrajectory.getLocation(), 1L, TimeUnit.HOURS);
} else {
Map distance = GeodesyUtil.getDistance(orderTrajectory.getLocation(), oldLocation);
if (null != distance) {
Double distance1 = distance.get("WGS84");
// 存储本次距离
orderTrajectory.setDistance(distance1);
// 存储mongo数据
mongoTemplate.save(orderTrajectory);
// 更新定位
redisService.setCacheObject(CacheConstants.ORDER_OLD_LOCATION + orderTrajectory.getOrderId(), orderTrajectory.getLocation(), 1L, TimeUnit.HOURS);
} else {
log.error("调用高德计算距离出错");
}
}
}
/**
* 查询订单轨迹(mongodb)
*
* @param orderId 订单id
* @return 轨迹列表(按 locatetime 升序)
*/
private List queryOrderTrajectories(Long orderId) {
if (orderId == null) {
return new ArrayList<>();
}
List trajectories = mongoTemplate.find(
new Query(Criteria.where("orderId").is(orderId))
.with(Sort.by(Sort.Direction.ASC, "locatetime")),
OrderTrajectory.class
);
return trajectories;
}
/**
* 订单费用明细聚合:按 detail_type 汇总 money
*
* detail_type:1=起步价 2=里程费 3=时长费 4=远途费
*/
private Map sumOrderDetailMoneyByDetailType(Long orderId) {
if (orderId == null) {
return Collections.emptyMap();
}
List detailList = xpTestOrderDetailMapper.selectList(
new LambdaQueryWrapper()
.eq(XpTestOrderDetail::getOrderId, orderId)
.ne(XpTestOrderDetail::getIsDeleted, true)
);
if (CollectionUtils.isEmpty(detailList)) {
return Collections.emptyMap();
}
Map sumMap = new HashMap<>();
for (XpTestOrderDetail detail : detailList) {
if (detail == null || detail.getDetailType() == null) {
continue;
}
BigDecimal money = detail.getMoney() == null ? BigDecimal.ZERO : detail.getMoney();
sumMap.put(detail.getDetailType(), sumMap.getOrDefault(detail.getDetailType(), BigDecimal.ZERO).add(money));
}
return sumMap;
}
/**
* 将轨迹点转换为订单经纬度列表
*/
private List convertTrajectoriesToPositions(List trajectories) {
if (CollectionUtils.isEmpty(trajectories)) {
return new ArrayList<>();
}
List positions = new ArrayList<>();
for (OrderTrajectory trajectory : trajectories) {
if (trajectory == null || StringUtils.isBlank(trajectory.getLocation())) {
continue;
}
String[] parts = trajectory.getLocation().split(",");
if (parts.length != 2) {
continue;
}
try {
double longitude = Double.parseDouble(parts[0].trim());
double latitude = Double.parseDouble(parts[1].trim());
XpTestOrderDetailVO.OrderPosition position = new XpTestOrderDetailVO.OrderPosition();
position.setLongitude(longitude);
position.setLatitude(latitude);
positions.add(position);
} catch (Exception e) {
// ignore invalid point
}
}
return positions;
}
@Override
public void changeAmount(Long id, BigDecimal amount) {
if (Objects.isNull(id)) {
throw new ServiceException("订单ID不能为空", I18nKeyConstant.ORDER_CHANGE_AMOUNT_ID_REQUIRED);
}
if (Objects.isNull(amount)) {
throw new ServiceException("订单金额不能为空", I18nKeyConstant.ORDER_CHANGE_AMOUNT_AMOUNT_REQUIRED);
}
if (amount.compareTo(BigDecimal.ZERO) < 0) {
throw new ServiceException("订单金额不能小于0", I18nKeyConstant.ORDER_CHANGE_AMOUNT_AMOUNT_INVALID);
}
XpTestOrder order = this.getById(id);
if (Objects.isNull(order)) {
throw new ServiceException("订单不存在", I18nKeyConstant.ORDER_NOT_EXISTS);
}
final Integer pendingPaymentStatus = 8;
if (!Objects.equals(order.getStatus(), pendingPaymentStatus)) {
throw new ServiceException("当前订单状态不允许改价,只有待付款状态的订单可以改价", I18nKeyConstant.ORDER_CHANGE_AMOUNT_STATUS_INVALID);
}
if (Objects.nonNull(order.getOrderMoney()) && order.getOrderMoney().compareTo(amount) == 0) {
return;
}
boolean updated = this.lambdaUpdate()
.set(XpTestOrder::getOrderMoney, amount)
.set(XpTestOrder::getChangeMoneyFlag, Boolean.TRUE)
.eq(XpTestOrder::getId, id)
.eq(XpTestOrder::getStatus, pendingPaymentStatus)
.update();
if (!updated) {
throw new ServiceException("订单改价失败,请稍后重试", I18nKeyConstant.ORDER_CHANGE_AMOUNT_FAILED);
}
}
@Override
public R> cancelTestOrderById(Long orderId) {
XpTestOrder xpTestOrder = this.getById(orderId);
if (xpTestOrder != null) {
xpTestOrder.setStatus(0);
xpTestOrder.setStartTestPointId(null);
this.updateById(xpTestOrder);
XpVehicle xpVehicle = xpVehicleClient.queryById(xpTestOrder.getVehicleId()).getData();
PushOrderDto pushOrderDto = new PushOrderDto();
pushOrderDto.setVin(xpVehicle.getVehicleVin());
pushOrderDto.setOrderId("TEST" + xpTestOrder.getId());
pushOrderDto.setOperation_type(OrderOperationType.CLOUD_CANCEL_ORDER);
R booleanR = orderMissionClient.cancelOrder(pushOrderDto);
if(booleanR==null || !booleanR.getData()){
log.error("XPU取消测试巡游订单失败");
return R.fail("XPU取消测试巡游订单失败");
}
return R.ok();
}
return R.fail("获取订单失败");
}
}