|
|
@@ -37,6 +37,7 @@ import com.ylx.order.domain.vo.merchant.MerchantCancelOrderDTO;
|
|
|
import com.ylx.order.domain.vo.merchant.OrderCustomerPhoneVO;
|
|
|
import com.ylx.order.domain.vo.merchant.OrderPageVO;
|
|
|
import com.ylx.order.enums.AfterSaleServiceStatusEnum;
|
|
|
+import com.ylx.order.enums.CustomerTagEnum;
|
|
|
import com.ylx.order.enums.OrderStatusEnum;
|
|
|
import com.ylx.order.enums.PaymentMethodEnum;
|
|
|
import com.ylx.order.mapper.TOrderMapper;
|
|
|
@@ -59,12 +60,16 @@ import javax.annotation.Resource;
|
|
|
import java.math.BigDecimal;
|
|
|
import java.math.RoundingMode;
|
|
|
import java.text.SimpleDateFormat;
|
|
|
+import java.time.LocalDate;
|
|
|
import java.time.LocalDateTime;
|
|
|
import java.time.LocalTime;
|
|
|
import java.time.format.DateTimeFormatter;
|
|
|
+import java.time.temporal.ChronoUnit;
|
|
|
import java.util.*;
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
+import static com.ylx.order.constant.OrderConstant.*;
|
|
|
+
|
|
|
|
|
|
/**
|
|
|
* 订单表 服务实现类
|
|
|
@@ -109,12 +114,9 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
private MaProjectMapper maProjectMapper;
|
|
|
@Resource
|
|
|
private IAfterSalesServiceService afterSalesServiceService;
|
|
|
- private static final String DICT_TYPE = "unit_type";
|
|
|
- private static final int BUFFER_MINUTES = 30; // 30分钟缓冲期
|
|
|
@Resource
|
|
|
private IMessageService messageService;
|
|
|
|
|
|
- private static final int NOT_DELETE = 0;
|
|
|
@Resource
|
|
|
private TGeoFenceService geoFenceService;
|
|
|
|
|
|
@@ -283,10 +285,7 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
}
|
|
|
|
|
|
// 5. 获取商户地址信息
|
|
|
- List<TAddress> merchantAddressList = this.addressService.list(new LambdaQueryWrapper<TAddress>()
|
|
|
- .eq(TAddress::getMerchantId, dto.getMerchantId())
|
|
|
- .eq(TAddress::getUserType, 2)
|
|
|
- .eq(TAddress::getIsDelete, 0));
|
|
|
+ List<TAddress> merchantAddressList = this.addressService.list(new LambdaQueryWrapper<TAddress>().eq(TAddress::getMerchantId, dto.getMerchantId()).eq(TAddress::getUserType, 2).eq(TAddress::getIsDelete, 0));
|
|
|
if (CollUtil.isEmpty(merchantAddressList)) {
|
|
|
throw new ServiceException("商户地址不存在");
|
|
|
}
|
|
|
@@ -304,12 +303,10 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
}
|
|
|
|
|
|
// 实付金额 = 商品原价 - 优惠券优惠 + 车费
|
|
|
- BigDecimal finalAmount = basePrice.subtract(couponDiscount).add(trafficFee)
|
|
|
- .setScale(2, RoundingMode.HALF_UP);
|
|
|
+ BigDecimal finalAmount = basePrice.subtract(couponDiscount).add(trafficFee).setScale(2, RoundingMode.HALF_UP);
|
|
|
|
|
|
// 7. 组装订单对象
|
|
|
- TOrder order = buildOrder(dto, project, maTechnician, address, merchantAddressList,
|
|
|
- basePrice, trafficFee, couponDiscount, finalAmount, userId);
|
|
|
+ TOrder order = buildOrder(dto, project, maTechnician, address, merchantAddressList, basePrice, trafficFee, couponDiscount, finalAmount, userId);
|
|
|
|
|
|
// 8. 计算商户、平台分佣金额
|
|
|
this.calculateOrderIncome(order, finalAmount, project.getMerchantShareRatio());
|
|
|
@@ -376,9 +373,7 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
Page<TOrder> orderPage = baseMapper.selectPage(page, wrapper);
|
|
|
|
|
|
// 4. 转换 VO
|
|
|
- List<OrderDateQueryVo> voList = orderPage.getRecords().stream()
|
|
|
- .map(this::convertToVo)
|
|
|
- .collect(Collectors.toList());
|
|
|
+ List<OrderDateQueryVo> voList = orderPage.getRecords().stream().map(this::convertToVo).collect(Collectors.toList());
|
|
|
|
|
|
|
|
|
// 5. 返回分页结果
|
|
|
@@ -408,7 +403,7 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
}
|
|
|
|
|
|
// 4. 执行逻辑删除
|
|
|
- order.setIsDelete(1);
|
|
|
+ order.setIsDelete(DELETED);
|
|
|
order.setDeletedTime(LocalDateTime.now());
|
|
|
boolean updated = updateById(order);
|
|
|
if (!updated) {
|
|
|
@@ -421,10 +416,7 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
public void processOrderPayment(WxPayOrderNotifyV3Result.DecryptNotifyResult result, TWxUser wxUser, TOrder order) {
|
|
|
// 更新订单状态
|
|
|
- this.lambdaUpdate()
|
|
|
- .set(TOrder::getStatus, OrderStatusEnum.PENDING_DISPATCH.getCode())
|
|
|
- .eq(TOrder::getId, order.getId())
|
|
|
- .update();
|
|
|
+ this.lambdaUpdate().set(TOrder::getStatus, OrderStatusEnum.PENDING_DISPATCH.getCode()).eq(TOrder::getId, order.getId()).update();
|
|
|
|
|
|
// 插入订单流转记录
|
|
|
OrderUpdateStatusDTO dto = new OrderUpdateStatusDTO();
|
|
|
@@ -521,10 +513,7 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
/**
|
|
|
* 组装订单对象
|
|
|
*/
|
|
|
- private TOrder buildOrder(OrderSubmitDTO dto, Project project, MaTechnician maTechnician,
|
|
|
- TAddress address, List<TAddress> merchantAddressList,
|
|
|
- BigDecimal basePrice, BigDecimal trafficFee, BigDecimal couponDiscount,
|
|
|
- BigDecimal finalAmount, Long userId) {
|
|
|
+ private TOrder buildOrder(OrderSubmitDTO dto, Project project, MaTechnician maTechnician, TAddress address, List<TAddress> merchantAddressList, BigDecimal basePrice, BigDecimal trafficFee, BigDecimal couponDiscount, BigDecimal finalAmount, Long userId) {
|
|
|
TOrder order = new TOrder();
|
|
|
order.setOrderNo(orderNumberGenerator.generateNextOrderNumber(""));
|
|
|
order.setUserId(userId);
|
|
|
@@ -584,12 +573,9 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
*/
|
|
|
private void handleBalancePayment(Long userId, BigDecimal finalAmount, TOrder order) {
|
|
|
// 1. 数据库层面原子扣减余额,并校验余额是否充足
|
|
|
- boolean deductSuccess = this.wxUserService.lambdaUpdate()
|
|
|
- .setSql("d_balance = d_balance - " + finalAmount)
|
|
|
- .eq(TWxUser::getId, userId)
|
|
|
+ boolean deductSuccess = this.wxUserService.lambdaUpdate().setSql("d_balance = d_balance - " + finalAmount).eq(TWxUser::getId, userId)
|
|
|
// 防止余额超扣的核心:当前余额必须大于等于扣减金额
|
|
|
- .apply("d_balance >= {0}", finalAmount)
|
|
|
- .update();
|
|
|
+ .apply("d_balance >= {0}", finalAmount).update();
|
|
|
|
|
|
if (!deductSuccess) {
|
|
|
throw new ServiceException("余额不足,扣款失败");
|
|
|
@@ -619,13 +605,7 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
*/
|
|
|
private Map<String, Object> createWxPayOrder(TOrder order, WxLoginUser wxLoginUser) {
|
|
|
try {
|
|
|
- return wxPayV3Service.createV3JsapiOrder(
|
|
|
- order.getOrderNo(),
|
|
|
- order.getFinalAmount(),
|
|
|
- "购买情绪价值商品",
|
|
|
- wxLoginUser.getCOpenid(),
|
|
|
- WxPayTypeEnum.EMOTION_GOODS.getCode()
|
|
|
- );
|
|
|
+ return wxPayV3Service.createV3JsapiOrder(order.getOrderNo(), order.getFinalAmount(), "购买情绪价值商品", wxLoginUser.getCOpenid(), WxPayTypeEnum.EMOTION_GOODS.getCode());
|
|
|
} catch (Exception e) {
|
|
|
log.error("微信支付下单失败,订单号: {}", order.getOrderNo(), e);
|
|
|
throw new ServiceException("支付服务异常,请稍后重试");
|
|
|
@@ -643,14 +623,12 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
|
|
|
// 1. 根据ID查询订单主表
|
|
|
TOrder order = this.baseMapper.selectById(orderId);
|
|
|
- if (order == null || order.getIsDelete() == 1) {
|
|
|
+ if (order == null || order.getIsDelete() == DELETED) {
|
|
|
throw new ServiceException("订单不存在,订单ID:" + orderId);
|
|
|
}
|
|
|
|
|
|
// 2. 查询订单状态流转记录
|
|
|
- LambdaQueryWrapper<OrderStatusFlow> flowWrapper = Wrappers.<OrderStatusFlow>lambdaQuery()
|
|
|
- .eq(OrderStatusFlow::getOrderId, order.getId())
|
|
|
- .orderByAsc(OrderStatusFlow::getCreateTime);
|
|
|
+ LambdaQueryWrapper<OrderStatusFlow> flowWrapper = Wrappers.<OrderStatusFlow>lambdaQuery().eq(OrderStatusFlow::getOrderId, order.getId()).orderByAsc(OrderStatusFlow::getCreateTime);
|
|
|
List<OrderStatusFlow> flowList = orderStatusFlowService.getBaseMapper().selectList(flowWrapper);
|
|
|
|
|
|
// 3. 组装VO
|
|
|
@@ -670,10 +648,7 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
// 1. 获取服务次数(从项目表获取 projectUsersNum)
|
|
|
Integer serviceNum = null;
|
|
|
if (order.getProjectId() != null && order.getMerchantId() != null) {
|
|
|
- MaProject project = maProjectMapper.selectByProjectIdAndMerchantId(
|
|
|
- order.getProjectId(),
|
|
|
- order.getMerchantId()
|
|
|
- );
|
|
|
+ MaProject project = maProjectMapper.selectByProjectIdAndMerchantId(order.getProjectId(), order.getMerchantId());
|
|
|
if (project != null && project.getProjectUsersNum() != null) {
|
|
|
serviceNum = project.getProjectUsersNum().intValue();
|
|
|
}
|
|
|
@@ -742,10 +717,7 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
this.fillCurrentAfterSaleInfo(vo, order.getId());
|
|
|
|
|
|
// 订单状态流转列表转换
|
|
|
- List<OrderStatusFlowVO> flowVOList = flowList.stream()
|
|
|
- .map(this::convertToFlowVO)
|
|
|
- .sorted(Comparator.comparing(OrderStatusFlowVO::getCreateTime))
|
|
|
- .collect(Collectors.toList());
|
|
|
+ List<OrderStatusFlowVO> flowVOList = flowList.stream().map(this::convertToFlowVO).sorted(Comparator.comparing(OrderStatusFlowVO::getCreateTime)).collect(Collectors.toList());
|
|
|
vo.setOrderStatusFlowList(flowVOList);
|
|
|
|
|
|
return vo;
|
|
|
@@ -862,18 +834,13 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
// 2. 组装目标时间段
|
|
|
LocalDateTime newStart = LocalDateTime.of(dto.getAppointmentDate(), dto.getStartTime());
|
|
|
LocalDateTime actualEnd = newStart.plusMinutes(duration); // 实际服务结束时间
|
|
|
- LocalDateTime newEndWithBuffer = actualEnd.plusMinutes(BUFFER_MINUTES); // 含30分钟缓冲的结束时间
|
|
|
+ LocalDateTime newEndWithBuffer = actualEnd.plusMinutes(APPOINT_BUFFER_MINUTES); // 含30分钟缓冲的结束时间
|
|
|
|
|
|
// 3. 查询该商户当天的【有效占用】订单
|
|
|
LocalDateTime dayStart = dto.getAppointmentDate().atStartOfDay();
|
|
|
LocalDateTime dayEnd = dto.getAppointmentDate().atTime(LocalTime.MAX);
|
|
|
|
|
|
- List<TOrder> validOrders = this.baseMapper.selectValidOrdersByMerchantAndDate(
|
|
|
- dto.getMerchantId(),
|
|
|
- dto.getProjectId(),
|
|
|
- dayStart,
|
|
|
- dayEnd
|
|
|
- );
|
|
|
+ List<TOrder> validOrders = this.baseMapper.selectValidOrdersByMerchantAndDate(dto.getMerchantId(), dto.getProjectId(), dayStart, dayEnd);
|
|
|
|
|
|
// 4. 内存中遍历判断重叠
|
|
|
for (TOrder order : validOrders) {
|
|
|
@@ -898,99 +865,36 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
|
|
|
// 2.获取当前登录用户
|
|
|
WxLoginUser wxLoginUser = getCurrentWxLoginUser();
|
|
|
- Long userId = Long.parseLong(wxLoginUser.getId());
|
|
|
- dto.setMerchantId(userId);
|
|
|
-
|
|
|
- // 3. 获取当前用户的默认地址
|
|
|
- List<TAddress> merchantAddressList = this.addressService.list(new LambdaQueryWrapper<TAddress>()
|
|
|
- .eq(TAddress::getMerchantId, userId)
|
|
|
- .eq(TAddress::getUserType, 2)
|
|
|
- .eq(TAddress::getIsDefault, 1)
|
|
|
- .eq(TAddress::getIsDelete, NOT_DELETE));
|
|
|
- if (CollUtil.isEmpty(merchantAddressList)) {
|
|
|
- throw new ServiceException("商户地址不存在,请先完善商户地址");
|
|
|
- }
|
|
|
-
|
|
|
- TAddress merchantAddress = CollUtil.getFirst(merchantAddressList);
|
|
|
- BigDecimal merchantLat = merchantAddress.getLatitude();
|
|
|
- BigDecimal merchantLon = merchantAddress.getLongitude();
|
|
|
-
|
|
|
- // 商户坐标为空拦截
|
|
|
- if (ObjectUtil.hasNull(merchantLat, merchantLon)) {
|
|
|
- log.warn("商户id:{} 默认地址经纬度为空,无法计算围栏距离", userId);
|
|
|
- dto.setLatitude(null);
|
|
|
- dto.setLongitude(null);
|
|
|
- } else {
|
|
|
- dto.setLatitude(merchantLat);
|
|
|
- dto.setLongitude(merchantLon);
|
|
|
- }
|
|
|
-
|
|
|
- page = this.baseMapper.queryMerchantOrderList(page, dto);
|
|
|
- List<OrderPageVO> records = page.getRecords();
|
|
|
- if (CollUtil.isEmpty(records)) {
|
|
|
+ Long merchantId = Long.parseLong(wxLoginUser.getId());
|
|
|
+ dto.setMerchantId(merchantId);
|
|
|
+
|
|
|
+ // 3. 查询商户默认地址
|
|
|
+ TAddress merchantAddress = getMerchantDefaultAddress(merchantId);
|
|
|
+ // 填充商户坐标到查询DTO,用于SQL距离筛选(如有)
|
|
|
+ fillMerchantLonLatToDto(dto, merchantAddress);
|
|
|
+
|
|
|
+ // 4. Mapper分页查询订单基础数据
|
|
|
+ page = baseMapper.queryMerchantOrderList(page, dto);
|
|
|
+ List<OrderPageVO> voRecords = page.getRecords();
|
|
|
+ // 无订单直接返回,跳过围栏、标签计算逻辑
|
|
|
+ if (CollUtil.isEmpty(voRecords)) {
|
|
|
return page;
|
|
|
}
|
|
|
|
|
|
- // 4. 获取所有有效的地理围栏
|
|
|
+ // 5. 查询当前城市有效地理围栏,提前过滤非法围栏
|
|
|
List<TGeoFence> validFenceList = geoFenceService.selectValidFences(dto.getCityCode());
|
|
|
- if (CollUtil.isEmpty(validFenceList)) {
|
|
|
- log.info("城市编码{}无有效地理围栏,全部订单非高风险", dto.getCityCode());
|
|
|
- for (OrderPageVO vo : records) {
|
|
|
- vo.setIsHighRiskArea(false);
|
|
|
- }
|
|
|
- return page;
|
|
|
- }
|
|
|
-
|
|
|
- // 5. 遍历订单,在内存中进行空间计算
|
|
|
- for (OrderPageVO orderVO : records) {
|
|
|
- BigDecimal userLat = orderVO.getUserLatitude();
|
|
|
- BigDecimal userLon = orderVO.getUserLongitude();
|
|
|
- // 用户坐标为空,跳过计算
|
|
|
- if (ObjectUtil.hasNull(userLat, userLon)) {
|
|
|
- orderVO.setIsHighRiskArea(false);
|
|
|
- continue;
|
|
|
- }
|
|
|
+ List<TGeoFence> usableFence = filterUsableFence(validFenceList);
|
|
|
|
|
|
- // 存储最小距离、对应围栏、是否命中风险圈
|
|
|
- BigDecimal minDistanceKm = null;
|
|
|
- TGeoFence nearestFence = null;
|
|
|
-
|
|
|
- for (TGeoFence fence : validFenceList) {
|
|
|
- BigDecimal fenceLat = fence.getLatitude();
|
|
|
- BigDecimal fenceLon = fence.getLongitude();
|
|
|
- BigDecimal radiusKm = fence.getRadiusKm();
|
|
|
- // 围栏坐标/半径非法则跳过当前围栏
|
|
|
- if (ObjectUtil.hasNull(fenceLat, fenceLon, radiusKm)
|
|
|
- || radiusKm.compareTo(BigDecimal.ZERO) <= 0) {
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- // 【修复】传参顺序:用户纬度、用户经度,围栏纬度、围栏经度
|
|
|
- BigDecimal distKm = DistanceUtil.formatDistanceInKilometers(userLat, userLon, fenceLat, fenceLon);
|
|
|
- // 距离非法跳过
|
|
|
- if (ObjectUtil.isNull(distKm)) {
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- // 更新全局最小距离围栏
|
|
|
- if (ObjectUtil.isNull(minDistanceKm) || distKm.compareTo(minDistanceKm) < 0) {
|
|
|
- minDistanceKm = distKm;
|
|
|
- nearestFence = fence;
|
|
|
- }
|
|
|
- }
|
|
|
|
|
|
- // 赋值VO
|
|
|
- if (ObjectUtil.isNull(minDistanceKm) || ObjectUtil.isNull(nearestFence)) {
|
|
|
- orderVO.setIsHighRiskArea(false);
|
|
|
- orderVO.setDistanceKm(null);
|
|
|
- orderVO.setFenceIntro(null);
|
|
|
- } else {
|
|
|
- orderVO.setDistanceKm(minDistanceKm);
|
|
|
- // 判断是否在围栏半径内
|
|
|
- int compare = minDistanceKm.compareTo(nearestFence.getRadiusKm());
|
|
|
- orderVO.setIsHighRiskArea(compare <= 0);
|
|
|
- orderVO.setFenceIntro(nearestFence.getFenceIntro());
|
|
|
- }
|
|
|
+ // 6. 批量遍历订单,计算风险区域+客户标签
|
|
|
+ LocalDateTime now = DateUtils.getNowLocalDateTime();
|
|
|
+ for (OrderPageVO vo : voRecords) {
|
|
|
+ // 计算高风险围栏信息
|
|
|
+ calcOrderGeoFenceInfo(vo, usableFence);
|
|
|
+ // 计算客户标签
|
|
|
+ Integer tagCode = calcCustomerTagCode(vo, merchantId, now);
|
|
|
+ vo.setCustomerTagCode(tagCode);
|
|
|
+ vo.setCustomerTagDesc(CustomerTagEnum.getDescByCode(tagCode));
|
|
|
}
|
|
|
|
|
|
return page;
|
|
|
@@ -1059,11 +963,7 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
Long orderId = dto.getOrderId();
|
|
|
|
|
|
// 2. 查询订单:只查归属商户、未删除、手机号字段
|
|
|
- TOrder order = baseMapper.selectOne(Wrappers.lambdaQuery(TOrder.class)
|
|
|
- .select(TOrder::getId, TOrder::getMerchantId, TOrder::getContactPhoneNumber)
|
|
|
- .eq(TOrder::getId, orderId)
|
|
|
- .eq(TOrder::getMerchantId, merchantId)
|
|
|
- .eq(TOrder::getIsDelete, 0));
|
|
|
+ TOrder order = baseMapper.selectOne(Wrappers.lambdaQuery(TOrder.class).select(TOrder::getId, TOrder::getMerchantId, TOrder::getContactPhoneNumber).eq(TOrder::getId, orderId).eq(TOrder::getMerchantId, merchantId).eq(TOrder::getIsDelete, NOT_DELETE));
|
|
|
|
|
|
// 3. 校验订单
|
|
|
if (ObjectUtil.isNull(order)) {
|
|
|
@@ -1090,12 +990,7 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
String cancelReason = dto.getCancelReason();
|
|
|
|
|
|
// 2. 查询订单基础信息
|
|
|
- TOrder order = baseMapper.selectOne(Wrappers.lambdaQuery(TOrder.class)
|
|
|
- .select(TOrder::getId, TOrder::getMerchantId, TOrder::getStatus,
|
|
|
- TOrder::getUserId, TOrder::getFinalAmount, TOrder::getProjectId)
|
|
|
- .eq(TOrder::getId, orderId)
|
|
|
- .eq(TOrder::getMerchantId, merchantId)
|
|
|
- .eq(TOrder::getIsDelete, NOT_DELETE));
|
|
|
+ TOrder order = baseMapper.selectOne(Wrappers.lambdaQuery(TOrder.class).select(TOrder::getId, TOrder::getMerchantId, TOrder::getStatus, TOrder::getUserId, TOrder::getFinalAmount, TOrder::getProjectId).eq(TOrder::getId, orderId).eq(TOrder::getMerchantId, merchantId).eq(TOrder::getIsDelete, NOT_DELETE));
|
|
|
|
|
|
// 订单不存在/不属于当前商户
|
|
|
if (ObjectUtil.isNull(order)) {
|
|
|
@@ -1105,9 +1000,7 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
// 3. 状态校验:仅【商户已接单】允许取消转售后
|
|
|
Integer statusCode = order.getStatus();
|
|
|
OrderStatusEnum orderStatus = OrderStatusEnum.fromCode(statusCode);
|
|
|
- if (orderStatus == null
|
|
|
- || (!OrderStatusEnum.PENDING_SERVICE.getCode().equals(statusCode)
|
|
|
- && !OrderStatusEnum.IN_SERVICE.getCode().equals(statusCode))) {
|
|
|
+ if (orderStatus == null || (!OrderStatusEnum.PENDING_SERVICE.getCode().equals(statusCode) && !OrderStatusEnum.IN_SERVICE.getCode().equals(statusCode))) {
|
|
|
String currentText = OrderStatusEnum.getInfoByCode(statusCode);
|
|
|
throw new ServiceException("仅【待服务、服务中】订单可操作,当前订单状态:" + currentText);
|
|
|
}
|
|
|
@@ -1115,14 +1008,7 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
// 4. 更新订单为售后状态,并发乐观锁校验
|
|
|
if (OrderStatusEnum.PENDING_SERVICE.getCode().equals(statusCode)) {
|
|
|
// ========== 分支1:待服务 → 退回待派单,无售后、无退款 ==========
|
|
|
- LambdaUpdateWrapper<TOrder> updateWrapper = Wrappers.lambdaUpdate(TOrder.class)
|
|
|
- .set(TOrder::getStatus, OrderStatusEnum.PENDING_DISPATCH.getCode())
|
|
|
- .set(TOrder::getCancelledTime,LocalDateTime.now())
|
|
|
- .set(TOrder::getCancelledReason, cancelReason)
|
|
|
- .set(TOrder::getUpdateTime, LocalDateTime.now())
|
|
|
- .set(TOrder::getUpdateBy,loginUser.getCNickName())
|
|
|
- .eq(TOrder::getId, orderId)
|
|
|
- .eq(TOrder::getStatus, statusCode);
|
|
|
+ LambdaUpdateWrapper<TOrder> updateWrapper = Wrappers.lambdaUpdate(TOrder.class).set(TOrder::getStatus, OrderStatusEnum.PENDING_DISPATCH.getCode()).set(TOrder::getCancelledTime, LocalDateTime.now()).set(TOrder::getCancelledReason, cancelReason).set(TOrder::getUpdateTime, LocalDateTime.now()).set(TOrder::getUpdateBy, loginUser.getCNickName()).eq(TOrder::getId, orderId).eq(TOrder::getStatus, statusCode);
|
|
|
int row = baseMapper.update(null, updateWrapper);
|
|
|
if (row <= 0) {
|
|
|
throw new ServiceException("订单状态已变更,请刷新重试");
|
|
|
@@ -1130,13 +1016,7 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
} else if (OrderStatusEnum.IN_SERVICE.getCode().equals(statusCode)) {
|
|
|
// ========== 分支2:服务中 → 转售后、生成售后单、发起退款 ==========
|
|
|
// 1. 更新订单信息
|
|
|
- LambdaUpdateWrapper<TOrder> updateWrapper = Wrappers.lambdaUpdate(TOrder.class)
|
|
|
- .set(TOrder::getCancelledTime,LocalDateTime.now())
|
|
|
- .set(TOrder::getCancelledReason, cancelReason)
|
|
|
- .set(TOrder::getUpdateTime, LocalDateTime.now())
|
|
|
- .set(TOrder::getUpdateBy,loginUser.getCNickName())
|
|
|
- .eq(TOrder::getId, orderId)
|
|
|
- .eq(TOrder::getStatus, statusCode);
|
|
|
+ LambdaUpdateWrapper<TOrder> updateWrapper = Wrappers.lambdaUpdate(TOrder.class).set(TOrder::getCancelledTime, LocalDateTime.now()).set(TOrder::getCancelledReason, cancelReason).set(TOrder::getUpdateTime, LocalDateTime.now()).set(TOrder::getUpdateBy, loginUser.getCNickName()).eq(TOrder::getId, orderId).eq(TOrder::getStatus, statusCode);
|
|
|
int row = baseMapper.update(null, updateWrapper);
|
|
|
if (row <= 0) {
|
|
|
throw new ServiceException("订单状态已变更,请刷新重试");
|
|
|
@@ -1160,12 +1040,7 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
Long merchantId = Long.parseLong(loginUser.getId());
|
|
|
Long orderId = dto.getOrderId();
|
|
|
|
|
|
- TOrder order = baseMapper.selectOne(Wrappers.lambdaQuery(TOrder.class)
|
|
|
- .select(TOrder::getId, TOrder::getMerchantId, TOrder::getStatus,
|
|
|
- TOrder::getArrivedTime)
|
|
|
- .eq(TOrder::getId, orderId)
|
|
|
- .eq(TOrder::getMerchantId, merchantId)
|
|
|
- .eq(TOrder::getIsDelete, NOT_DELETE));
|
|
|
+ TOrder order = baseMapper.selectOne(Wrappers.lambdaQuery(TOrder.class).select(TOrder::getId, TOrder::getMerchantId, TOrder::getStatus, TOrder::getArrivedTime).eq(TOrder::getId, orderId).eq(TOrder::getMerchantId, merchantId).eq(TOrder::getIsDelete, NOT_DELETE));
|
|
|
if (ObjectUtil.isNull(order)) {
|
|
|
throw new ServiceException("订单不存在或不属于当前商户");
|
|
|
}
|
|
|
@@ -1181,13 +1056,7 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
}
|
|
|
|
|
|
// 更新状态+服务开始时间
|
|
|
- LambdaUpdateWrapper<TOrder> update = Wrappers.lambdaUpdate(TOrder.class)
|
|
|
- .set(TOrder::getStatus, OrderStatusEnum.IN_SERVICE.getCode())
|
|
|
- .set(TOrder::getStartTime, LocalDateTime.now())
|
|
|
- .set(TOrder::getUpdateTime, LocalDateTime.now())
|
|
|
- .eq(TOrder::getId, orderId)
|
|
|
- .set(TOrder::getUpdateBy,loginUser.getCNickName())
|
|
|
- .eq(TOrder::getStatus, status);
|
|
|
+ LambdaUpdateWrapper<TOrder> update = Wrappers.lambdaUpdate(TOrder.class).set(TOrder::getStatus, OrderStatusEnum.IN_SERVICE.getCode()).set(TOrder::getStartTime, LocalDateTime.now()).set(TOrder::getUpdateTime, LocalDateTime.now()).eq(TOrder::getId, orderId).set(TOrder::getUpdateBy, loginUser.getCNickName()).eq(TOrder::getStatus, status);
|
|
|
int count = baseMapper.update(null, update);
|
|
|
if (count <= 0) {
|
|
|
throw new ServiceException("订单状态已变更,请刷新重试");
|
|
|
@@ -1203,12 +1072,7 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
Long merchantId = Long.parseLong(loginUser.getId());
|
|
|
Long orderId = dto.getOrderId();
|
|
|
|
|
|
- TOrder order = baseMapper.selectOne(Wrappers.lambdaQuery(TOrder.class)
|
|
|
- .select(TOrder::getId, TOrder::getMerchantId, TOrder::getStatus,
|
|
|
- TOrder::getStartTime)
|
|
|
- .eq(TOrder::getId, orderId)
|
|
|
- .eq(TOrder::getMerchantId, merchantId)
|
|
|
- .eq(TOrder::getIsDelete, NOT_DELETE));
|
|
|
+ TOrder order = baseMapper.selectOne(Wrappers.lambdaQuery(TOrder.class).select(TOrder::getId, TOrder::getMerchantId, TOrder::getStatus, TOrder::getStartTime).eq(TOrder::getId, orderId).eq(TOrder::getMerchantId, merchantId).eq(TOrder::getIsDelete, NOT_DELETE));
|
|
|
if (ObjectUtil.isNull(order)) {
|
|
|
throw new ServiceException("订单不存在或不属于当前商户");
|
|
|
}
|
|
|
@@ -1224,13 +1088,7 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
}
|
|
|
|
|
|
// 更新状态、结束时间
|
|
|
- LambdaUpdateWrapper<TOrder> update = Wrappers.lambdaUpdate(TOrder.class)
|
|
|
- .set(TOrder::getStatus, OrderStatusEnum.COMPLETED.getCode())
|
|
|
- .set(TOrder::getCompletedTime, LocalDateTime.now())
|
|
|
- .set(TOrder::getUpdateTime, LocalDateTime.now())
|
|
|
- .eq(TOrder::getId, orderId)
|
|
|
- .set(TOrder::getUpdateBy,loginUser.getCNickName())
|
|
|
- .eq(TOrder::getStatus, status);
|
|
|
+ LambdaUpdateWrapper<TOrder> update = Wrappers.lambdaUpdate(TOrder.class).set(TOrder::getStatus, OrderStatusEnum.COMPLETED.getCode()).set(TOrder::getCompletedTime, LocalDateTime.now()).set(TOrder::getUpdateTime, LocalDateTime.now()).eq(TOrder::getId, orderId).set(TOrder::getUpdateBy, loginUser.getCNickName()).eq(TOrder::getStatus, status);
|
|
|
int count = baseMapper.update(null, update);
|
|
|
if (count <= 0) {
|
|
|
throw new ServiceException("订单状态已变更,请刷新重试");
|
|
|
@@ -1258,11 +1116,7 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
}
|
|
|
|
|
|
// 查询订单
|
|
|
- TOrder order = baseMapper.selectOne(Wrappers.lambdaQuery(TOrder.class)
|
|
|
- .select(TOrder::getId, TOrder::getMerchantId, TOrder::getStatus)
|
|
|
- .eq(TOrder::getId, orderId)
|
|
|
- .eq(TOrder::getMerchantId, merchantId)
|
|
|
- .eq(TOrder::getIsDelete, NOT_DELETE));
|
|
|
+ TOrder order = baseMapper.selectOne(Wrappers.lambdaQuery(TOrder.class).select(TOrder::getId, TOrder::getMerchantId, TOrder::getStatus).eq(TOrder::getId, orderId).eq(TOrder::getMerchantId, merchantId).eq(TOrder::getIsDelete, NOT_DELETE));
|
|
|
if (ObjectUtil.isNull(order)) {
|
|
|
throw new ServiceException("订单不存在或不属于当前商户");
|
|
|
}
|
|
|
@@ -1274,12 +1128,7 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
}
|
|
|
|
|
|
// 更新到达信息
|
|
|
- LambdaUpdateWrapper<TOrder> update = Wrappers.lambdaUpdate(TOrder.class)
|
|
|
- .set(TOrder::getArrivedTime, LocalDateTime.now())
|
|
|
- .set(TOrder::getUpdateTime, LocalDateTime.now())
|
|
|
- .set(TOrder::getUpdateBy,loginUser.getCNickName())
|
|
|
- .eq(TOrder::getId, orderId)
|
|
|
- .eq(TOrder::getStatus, status);
|
|
|
+ LambdaUpdateWrapper<TOrder> update = Wrappers.lambdaUpdate(TOrder.class).set(TOrder::getArrivedTime, LocalDateTime.now()).set(TOrder::getUpdateTime, LocalDateTime.now()).set(TOrder::getUpdateBy, loginUser.getCNickName()).eq(TOrder::getId, orderId).eq(TOrder::getStatus, status);
|
|
|
int count = baseMapper.update(null, update);
|
|
|
if (count <= 0) {
|
|
|
throw new ServiceException("订单状态已变更,请刷新重试");
|
|
|
@@ -1288,9 +1137,7 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
|
|
|
private void fillCurrentAfterSaleInfo(IAfterSaleDisplay vo, Long orderId) {
|
|
|
LambdaQueryWrapper<AfterSalesService> wrapper = new LambdaQueryWrapper<>();
|
|
|
- wrapper.eq(AfterSalesService::getOrderId, orderId)
|
|
|
- .orderByDesc(AfterSalesService::getCreateTime)
|
|
|
- .last("LIMIT 1");
|
|
|
+ wrapper.eq(AfterSalesService::getOrderId, orderId).orderByDesc(AfterSalesService::getCreateTime).last("LIMIT 1");
|
|
|
|
|
|
AfterSalesService afterSalesService = this.afterSalesServiceService.getOne(wrapper);
|
|
|
if (ObjectUtil.isNull(afterSalesService)) {
|
|
|
@@ -1322,7 +1169,7 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
}
|
|
|
|
|
|
// 2. 从字典缓存中获取数据(若依的 DictUtils 读取成本极低,无需自己做 DCL 锁)
|
|
|
- List<SysDictData> dictList = DictUtils.getSortedDictCache(DICT_TYPE);
|
|
|
+ List<SysDictData> dictList = DictUtils.getSortedDictCache(DICT_UNIT_TYPE);
|
|
|
if (CollUtil.isEmpty(dictList)) {
|
|
|
return duration;
|
|
|
}
|
|
|
@@ -1365,8 +1212,7 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
BigDecimal hundred = new BigDecimal("100");
|
|
|
|
|
|
// 1. 计算商户收益 = 实付金额 * 商户比例 / 100,保留两位小数,四舍五入
|
|
|
- BigDecimal merchantIncome = finalAmount.multiply(ratio)
|
|
|
- .divide(hundred, 2, RoundingMode.HALF_UP);
|
|
|
+ BigDecimal merchantIncome = finalAmount.multiply(ratio).divide(hundred, 2, RoundingMode.HALF_UP);
|
|
|
|
|
|
// 2. 计算平台收益 = 实付金额 - 商户收益
|
|
|
BigDecimal platformIncome = finalAmount.subtract(merchantIncome);
|
|
|
@@ -1428,4 +1274,126 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 计算用户客户标签编码
|
|
|
+ *
|
|
|
+ * @param vo 当前订单VO
|
|
|
+ * @param merchantId 当前商户ID
|
|
|
+ * @return CustomerTagEnum.code
|
|
|
+ */
|
|
|
+ private Integer calcCustomerTagCode(OrderPageVO vo, Long merchantId, LocalDateTime currentTime) {
|
|
|
+ Long userId = vo.getUserId();
|
|
|
+ Long categoryId = vo.getCategoryId();
|
|
|
+ // 关键参数缺失,返回null
|
|
|
+ if (ObjectUtil.hasNull(userId, categoryId, currentTime)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ // 查询该用户、商户、分类下,当前订单之前最近一笔下单时间
|
|
|
+ LocalDateTime lastOrderTime = this.baseMapper.getLastOrderDateBeforeCurrent(userId, merchantId, categoryId, currentTime);
|
|
|
+ // 1. 无历史订单 = 新客收单 code=0
|
|
|
+ if (ObjectUtil.isNull(lastOrderTime)) {
|
|
|
+ return CustomerTagEnum.NEW_CUSTOMER.getCode();
|
|
|
+ }
|
|
|
+ // 计算自然日间隔
|
|
|
+ LocalDate currentDate = currentTime.toLocalDate();
|
|
|
+ LocalDate lastDate = lastOrderTime.toLocalDate();
|
|
|
+ long dayDiff = ChronoUnit.DAYS.between(lastDate, currentDate);
|
|
|
+
|
|
|
+ // 2. 间隔≤14天:再次复购 code=1
|
|
|
+ if (dayDiff <= REPURCHASE_DAY_LIMIT) {
|
|
|
+ return CustomerTagEnum.REPURCHASE.getCode();
|
|
|
+ }
|
|
|
+ // 3. 间隔>15天:老客户 code=2
|
|
|
+ return CustomerTagEnum.OLD_CUSTOMER.getCode();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取商户默认地址,无地址直接抛业务异常
|
|
|
+ */
|
|
|
+ private TAddress getMerchantDefaultAddress(Long merchantId) {
|
|
|
+ LambdaQueryWrapper<TAddress> addrWrapper = Wrappers.lambdaQuery(TAddress.class)
|
|
|
+ .eq(TAddress::getMerchantId, merchantId)
|
|
|
+ .eq(TAddress::getUserType, USER_TYPE_MERCHANT)
|
|
|
+ .eq(TAddress::getIsDefault, ADDR_DEFAULT_YES)
|
|
|
+ .eq(TAddress::getIsDelete, NOT_DELETE);
|
|
|
+ List<TAddress> merchantAddressList = addressService.list(addrWrapper);
|
|
|
+ if (CollUtil.isEmpty(merchantAddressList)) {
|
|
|
+ throw new ServiceException("商户地址不存在,请先完善商户地址");
|
|
|
+ }
|
|
|
+ return CollUtil.getFirst(merchantAddressList);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 将商户经纬度填充至查询DTO,坐标为空置null
|
|
|
+ */
|
|
|
+ private void fillMerchantLonLatToDto(OrderDateQueryDTO dto, TAddress merchantAddress) {
|
|
|
+ BigDecimal lat = merchantAddress.getLatitude();
|
|
|
+ BigDecimal lon = merchantAddress.getLongitude();
|
|
|
+ if (ObjectUtil.hasNull(lat, lon)) {
|
|
|
+ log.warn("商户id:{} 默认地址经纬度为空,无法计算围栏距离", dto.getMerchantId());
|
|
|
+ dto.setLatitude(null);
|
|
|
+ dto.setLongitude(null);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ dto.setLatitude(lat);
|
|
|
+ dto.setLongitude(lon);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 过滤可用围栏:剔除坐标/半径为空、半径<=0的无效围栏,减少内层循环次数
|
|
|
+ */
|
|
|
+ private List<TGeoFence> filterUsableFence(List<TGeoFence> allFence) {
|
|
|
+ if (CollUtil.isEmpty(allFence)) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+ return allFence.stream()
|
|
|
+ .filter(fence -> !ObjectUtil.hasNull(fence.getLatitude(), fence.getLongitude(), fence.getRadiusKm()))
|
|
|
+ .filter(fence -> fence.getRadiusKm().compareTo(BigDecimal.ZERO) > 0)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算单条订单围栏距离、是否高风险区域、围栏备注
|
|
|
+ */
|
|
|
+ private void calcOrderGeoFenceInfo(OrderPageVO orderVO, List<TGeoFence> usableFence) {
|
|
|
+ // 用户坐标为空直接标记非风险
|
|
|
+ BigDecimal userLat = orderVO.getUserLatitude();
|
|
|
+ BigDecimal userLon = orderVO.getUserLongitude();
|
|
|
+ if (ObjectUtil.hasNull(userLat, userLon) || CollUtil.isEmpty(usableFence)) {
|
|
|
+ orderVO.setIsHighRiskArea(false);
|
|
|
+ orderVO.setDistanceKm(null);
|
|
|
+ orderVO.setFenceIntro(null);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ BigDecimal minDistanceKm = null;
|
|
|
+ TGeoFence nearestFence = null;
|
|
|
+ // 遍历可用围栏,计算最小距离围栏
|
|
|
+ for (TGeoFence fence : usableFence) {
|
|
|
+ BigDecimal distKm = DistanceUtil.formatDistanceInKilometers(userLat, userLon, fence.getLatitude(), fence.getLongitude());
|
|
|
+ if (ObjectUtil.isNull(distKm)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ // 更新最近围栏
|
|
|
+ if (ObjectUtil.isNull(minDistanceKm) || distKm.compareTo(minDistanceKm) < 0) {
|
|
|
+ minDistanceKm = distKm;
|
|
|
+ nearestFence = fence;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 赋值围栏相关信息
|
|
|
+ if (ObjectUtil.isNull(minDistanceKm) || ObjectUtil.isNull(nearestFence)) {
|
|
|
+ orderVO.setIsHighRiskArea(false);
|
|
|
+ orderVO.setDistanceKm(null);
|
|
|
+ orderVO.setFenceIntro(null);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ orderVO.setDistanceKm(minDistanceKm);
|
|
|
+ // 判断是否在围栏半径内
|
|
|
+ boolean isHighRisk = minDistanceKm.compareTo(nearestFence.getRadiusKm()) <= 0;
|
|
|
+ orderVO.setIsHighRiskArea(isHighRisk);
|
|
|
+ orderVO.setFenceIntro(nearestFence.getFenceIntro());
|
|
|
+ }
|
|
|
+
|
|
|
}
|