package com.tourguide.modules.order.service;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.tourguide.common.api.PageResponse;
|
import com.tourguide.common.exception.BusinessException;
|
import com.tourguide.modules.order.dto.TourOrderResponse;
|
import com.tourguide.modules.order.domain.OrderStatus;
|
import com.tourguide.modules.order.domain.RefundAmountCalculator;
|
import com.tourguide.modules.order.domain.RefundStatus;
|
import com.tourguide.modules.order.entity.TourOrder;
|
import com.tourguide.modules.order.mapper.TourOrderMapper;
|
import com.tourguide.modules.system.service.GuideNotificationService;
|
import com.tourguide.modules.review.entity.OrderReview;
|
import com.tourguide.modules.review.mapper.OrderReviewMapper;
|
import org.slf4j.Logger;
|
import org.slf4j.LoggerFactory;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.util.StringUtils;
|
import java.math.BigDecimal;
|
import java.time.LocalDate;
|
import java.util.List;
|
import java.util.stream.Collectors;
|
|
@Service
|
public class TourOrderService {
|
private static final Logger log = LoggerFactory.getLogger(TourOrderService.class);
|
|
private final TourOrderMapper orderMapper;
|
private final OrderReviewMapper reviewMapper;
|
private final GuideNotificationService notificationService;
|
private final OrderRefundService orderRefundService;
|
|
public TourOrderService(TourOrderMapper orderMapper, OrderReviewMapper reviewMapper,
|
GuideNotificationService notificationService,
|
OrderRefundService orderRefundService) {
|
this.orderMapper = orderMapper;
|
this.reviewMapper = reviewMapper;
|
this.notificationService = notificationService;
|
this.orderRefundService = orderRefundService;
|
}
|
|
public PageResponse<TourOrderResponse> page(long page, long size, String keyword, Long visitorId, Long guideId,
|
String scenicName, String status,
|
LocalDate startDate, LocalDate endDate) {
|
LambdaQueryWrapper<TourOrder> query = new LambdaQueryWrapper<TourOrder>();
|
if (StringUtils.hasText(keyword)) {
|
String searchKeyword = keyword.trim();
|
query.and(wrapper -> wrapper.like(TourOrder::getOrderNo, searchKeyword)
|
.or().like(TourOrder::getVisitorNickname, searchKeyword)
|
.or().like(TourOrder::getVisitorMobile, searchKeyword)
|
.or().like(TourOrder::getGuideName, searchKeyword)
|
.or().like(TourOrder::getGuideMobile, searchKeyword));
|
}
|
query.eq(visitorId != null, TourOrder::getVisitorId, visitorId)
|
.eq(guideId != null, TourOrder::getGuideId, guideId)
|
.eq(StringUtils.hasText(scenicName), TourOrder::getScenicName, scenicName)
|
.eq(StringUtils.hasText(status), TourOrder::getStatus, status)
|
.ge(startDate != null, TourOrder::getCreatedAt, startDate == null ? null : startDate.atStartOfDay())
|
.lt(endDate != null, TourOrder::getCreatedAt, endDate == null ? null : endDate.plusDays(1).atStartOfDay())
|
.orderByDesc(TourOrder::getId);
|
Page<TourOrder> result = orderMapper.selectPage(new Page<TourOrder>(page, size), query);
|
return new PageResponse<TourOrderResponse>(
|
result.getRecords().stream().map(TourOrderResponse::from).collect(Collectors.toList()),
|
result.getTotal(), result.getCurrent(), result.getSize());
|
}
|
|
public TourOrderResponse detail(Long id) {
|
TourOrder order = requireOrder(id);
|
OrderReview review = reviewMapper.selectOne(new LambdaQueryWrapper<OrderReview>()
|
.eq(OrderReview::getOrderId, id)
|
.eq(OrderReview::getStatus, 1)
|
.last("LIMIT 1"));
|
return TourOrderResponse.from(order, review);
|
}
|
|
public List<String> scenicOptions() {
|
return orderMapper.selectScenicOptions();
|
}
|
|
@Transactional(noRollbackFor = BusinessException.class)
|
public TourOrderResponse refund(Long id, BigDecimal refundAmount) {
|
TourOrder order = requireOrderForUpdate(id);
|
boolean notifyGuide = OrderStatus.PENDING_MEETING.matches(order.getStatus()) && order.getGuideId() != null;
|
if (OrderStatus.REFUNDED.matches(order.getStatus()) || RefundStatus.REFUNDED.matches(order.getRefundStatus())) {
|
throw new BusinessException(409, "订单已退款,请勿重复操作");
|
}
|
if (order.getPaymentTime() == null) {
|
throw new BusinessException(409, "未支付订单不能退款");
|
}
|
if (RefundStatus.REFUNDING.matches(order.getRefundStatus())) {
|
throw new BusinessException(409, "退款正在处理中,请勿重复操作");
|
}
|
if (OrderStatus.CANCELLED.matches(order.getStatus())
|
&& !RefundStatus.REFUND_FAILED.matches(order.getRefundStatus())
|
&& !RefundStatus.PARTIAL.matches(order.getRefundStatus())) {
|
throw new BusinessException(409, "已取消订单不能退款");
|
}
|
boolean followUpRefund = OrderStatus.CANCELLED.matches(order.getStatus())
|
&& (RefundStatus.REFUND_FAILED.matches(order.getRefundStatus())
|
|| RefundStatus.PARTIAL.matches(order.getRefundStatus()));
|
if (!OrderStatus.isRefundable(order.getStatus()) && !followUpRefund) {
|
throw new BusinessException(409, "当前订单状态不支持退款");
|
}
|
BigDecimal refundableAmount = RefundAmountCalculator.refundableAmount(order);
|
if (refundableAmount == null || refundableAmount.compareTo(BigDecimal.ZERO) <= 0) {
|
throw new BusinessException(409, "订单无可退金额");
|
}
|
if (refundAmount == null || refundAmount.compareTo(BigDecimal.ZERO) <= 0) {
|
throw new BusinessException(400, "退款金额必须大于0");
|
}
|
if (refundAmount.compareTo(refundableAmount) > 0) {
|
throw new BusinessException(400, "退款金额不能超过当前可退金额");
|
}
|
if (!orderRefundService.requestRefund(order, refundAmount)) {
|
throw new BusinessException(502, "支付渠道退款失败,请稍后重试");
|
}
|
if (notifyGuide) {
|
try {
|
notificationService.notifyOrderCancelled(order);
|
} catch (RuntimeException exception) {
|
log.warn("Failed to notify guide after refund submission, orderId={}, guideId={}",
|
order.getId(), order.getGuideId(), exception);
|
}
|
}
|
return TourOrderResponse.from(order);
|
}
|
|
private TourOrder requireOrder(Long id) {
|
TourOrder order = orderMapper.selectById(id);
|
if (order == null) {
|
throw new BusinessException(404, "订单不存在");
|
}
|
return order;
|
}
|
|
private TourOrder requireOrderForUpdate(Long id) {
|
TourOrder order = orderMapper.selectForUpdate(id);
|
if (order == null) {
|
throw new BusinessException(404, "订单不存在");
|
}
|
return order;
|
}
|
|
}
|