Procházet zdrojové kódy

合并订单金额功能

ZHOUTD před 2 týdny
rodič
revize
d62f916c72

+ 9 - 0
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/controller/admin/order/OtcTradeOrderController.java

@@ -169,6 +169,15 @@ public class OtcTradeOrderController {
         return otcTradeOrderService.motifyOtcOrder(agencyAuthUtils.getAgencyLoginUser(), createVO);
     }
 
+    @PostMapping("/window/merge-order-amount")
+    @Operation(summary = "合并已选订单金额")
+    @OperateLog(type = TRADE)
+    @PreAuthorize("@ss.hasPermission('trade:order:merge-amount')")
+    public CommonResult<Boolean> mergeOrderAmount(@Valid @RequestBody MergeOrderAmountReqVO reqVO) {
+        otcTradeOrderService.mergeOrderAmount(agencyAuthUtils.getAgencyLoginUser(), reqVO);
+        return success(true);
+    }
+
     @RequestDecryption
     @PostMapping("/travl/createOtcFisrtOrder")
     @Operation(summary = "win-旅行社游船订单创建-预定单")

+ 23 - 0
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/controller/admin/order/vo/order/MergeOrderAmountReqVO.java

@@ -0,0 +1,23 @@
+package com.yc.ship.module.trade.controller.admin.order.vo.order;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.util.List;
+
+@Schema(description = "管理后台 - 合并订单金额 Request VO")
+@Data
+public class MergeOrderAmountReqVO {
+
+    @Schema(description = "选中的订单 ID", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "订单不能为空")
+    @Size(min = 2, message = "至少选择两个订单")
+    private List<@NotNull(message = "订单 ID 不能为空") Long> orderIds;
+
+    @Schema(description = "接收合并金额的订单 ID", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "目标订单不能为空")
+    private Long targetOrderId;
+}

+ 3 - 1
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/service/order/impl/TradeOrderLogServiceImpl.java

@@ -49,7 +49,9 @@ public class TradeOrderLogServiceImpl implements TradeOrderLogService {
         }
         LambdaQueryWrapperX<TradeOrderLogDO> queryWrapper = new LambdaQueryWrapperX<>();
         queryWrapper.in(TradeOrderLogDO::getOrderId, orderIds);
-        queryWrapper.orderByDesc(TradeOrderLogDO::getCreateTime);
+        // create_time 的数据库精度可能只能到秒,同一批合并日志时间相同时使用雪花 ID 保证顺序稳定。
+        queryWrapper.orderByDesc(TradeOrderLogDO::getCreateTime)
+                .orderByDesc(TradeOrderLogDO::getId);
         return tradeOrderLogMapper.selectList(queryWrapper);
     }
 

+ 5 - 0
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/service/otc/OtcTradeOrderService.java

@@ -123,6 +123,11 @@ public interface OtcTradeOrderService {
 
     CommonResult<?> motifyOtcOrder(LoginUser agencyLoginUser, TradeOrderOtcCreateVO createVO);
 
+    /**
+     * 将多个订单的实际金额合并到指定订单。
+     */
+    void mergeOrderAmount(LoginUser loginUser, MergeOrderAmountReqVO reqVO);
+
     /**
      * 获取订单详情 新
      */

+ 124 - 0
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/service/otc/impl/OtcTradeOrderServiceImpl.java

@@ -189,6 +189,8 @@ import static com.yc.ship.module.trade.enums.ErrorCodeConstants.*;
 @Slf4j
 public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
 
+    private static final String MERGE_ORDER_AMOUNT_ERROR = "订单金额合并失败:";
+
     @Resource
     private StoreApi storeApi;
     @Resource
@@ -4537,6 +4539,128 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
         return tradeOrderMapper.getValidSimpleOrderInfoByVoyageId(voyageId);
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void mergeOrderAmount(LoginUser loginUser, MergeOrderAmountReqVO reqVO) {
+        LinkedHashSet<Long> orderIdSet = new LinkedHashSet<>(reqVO.getOrderIds());
+        Asserts.isTrue(orderIdSet.size() >= 2, MERGE_ORDER_AMOUNT_ERROR + "至少选择两个不同的订单");
+        Asserts.isTrue(orderIdSet.contains(reqVO.getTargetOrderId()),
+                MERGE_ORDER_AMOUNT_ERROR + "目标订单必须在已选订单中");
+
+        // 按主键顺序逐行加锁,既避免租户拦截器生成 FOR UPDATE ORDER BY 非法 SQL,也固定并发加锁顺序。
+        List<Long> sortedOrderIds = orderIdSet.stream().sorted().collect(Collectors.toList());
+        List<TradeOrderDO> orders = sortedOrderIds.stream()
+                .map(orderId -> tradeOrderMapper.selectOne(new LambdaQueryWrapper<TradeOrderDO>()
+                        .eq(TradeOrderDO::getId, orderId)
+                        .last("FOR UPDATE")))
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+        Asserts.isTrue(orders.size() == sortedOrderIds.size(), MERGE_ORDER_AMOUNT_ERROR + "部分订单不存在或无权操作");
+        Asserts.isTrue(orders.stream().allMatch(order -> order.getOrderStatus() != null && order.getOrderStatus() >= 0),
+                MERGE_ORDER_AMOUNT_ERROR + "只允许合并订单状态大于等于 0 的订单");
+        Asserts.isTrue(orders.stream().allMatch(order -> Objects.equals(order.getPayStatus(), 0)),
+                MERGE_ORDER_AMOUNT_ERROR + "只允许合并未支付订单");
+        TradeOrderDO firstOrder = orders.get(0);
+        Asserts.isTrue(orders.stream().allMatch(order -> Objects.equals(order.getVoyageId(), firstOrder.getVoyageId())),
+                MERGE_ORDER_AMOUNT_ERROR + "只允许合并同一航次的订单");
+        Asserts.isTrue(orders.stream().allMatch(order -> Objects.equals(order.getSourceId(), firstOrder.getSourceId())),
+                MERGE_ORDER_AMOUNT_ERROR + "只允许合并同一来源的订单");
+
+        List<TradeOrderTotalDO> orderTotals = sortedOrderIds.stream()
+                .map(orderId -> tradeOrderTotalMapper.selectOne(new LambdaQueryWrapper<TradeOrderTotalDO>()
+                        .eq(TradeOrderTotalDO::getOldOrderId, orderId)
+                        .last("FOR UPDATE")))
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+        Asserts.isTrue(orderTotals.size() == sortedOrderIds.size(), MERGE_ORDER_AMOUNT_ERROR + "部分订单汇总信息不存在");
+
+        Map<Long, TradeOrderDO> orderMap = CollectionUtils.convertMap(orders, TradeOrderDO::getId);
+        Map<Long, TradeOrderTotalDO> orderTotalMap = CollectionUtils.convertMap(
+                orderTotals, TradeOrderTotalDO::getOldOrderId);
+        Asserts.isTrue(orderTotalMap.size() == sortedOrderIds.size()
+                        && orderTotalMap.keySet().containsAll(sortedOrderIds),
+                MERGE_ORDER_AMOUNT_ERROR + "订单汇总信息不完整");
+        TradeOrderDO targetOrder = orderMap.get(reqVO.getTargetOrderId());
+        Asserts.isTrue(targetOrder != null, MERGE_ORDER_AMOUNT_ERROR + "目标订单不存在");
+
+        BigDecimal mergedAmount = orders.stream()
+                .map(TradeOrderDO::getAmount)
+                .filter(Objects::nonNull)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+        BigDecimal mergedPayAmount = orders.stream()
+                .map(TradeOrderDO::getPayAmount)
+                .filter(Objects::nonNull)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+        BigDecimal mergedOrderMoney = orderTotals.stream()
+                .map(TradeOrderTotalDO::getOrderMoney)
+                .filter(Objects::nonNull)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+        BigDecimal mergedRealMoney = orderTotals.stream()
+                .map(TradeOrderTotalDO::getRealMoney)
+                .filter(Objects::nonNull)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+        BigDecimal runningAmount = ObjectUtil.defaultIfNull(targetOrder.getPayAmount(), BigDecimal.ZERO);
+
+        for (Long sourceOrderId : orderIdSet) {
+            if (Objects.equals(sourceOrderId, targetOrder.getId())) {
+                continue;
+            }
+            TradeOrderDO sourceOrder = orderMap.get(sourceOrderId);
+            BigDecimal sourceAmount = ObjectUtil.defaultIfNull(sourceOrder.getPayAmount(), BigDecimal.ZERO);
+            runningAmount = runningAmount.add(sourceAmount);
+
+            TradeOrderDO sourceUpdate = new TradeOrderDO();
+            sourceUpdate.setId(sourceOrder.getId());
+            sourceUpdate.setAmount(BigDecimal.ZERO);
+            sourceUpdate.setPayAmount(BigDecimal.ZERO);
+            tradeOrderMapper.updateById(sourceUpdate);
+
+            TradeOrderTotalDO sourceTotalUpdate = new TradeOrderTotalDO();
+            sourceTotalUpdate.setId(orderTotalMap.get(sourceOrderId).getId());
+            sourceTotalUpdate.setOrderMoney(BigDecimal.ZERO);
+            sourceTotalUpdate.setRealMoney(BigDecimal.ZERO);
+            tradeOrderTotalMapper.updateById(sourceTotalUpdate);
+
+            insertMergeOrderLog(loginUser, targetOrder, sourceOrder, sourceAmount, runningAmount, true);
+            insertMergeOrderLog(loginUser, sourceOrder, targetOrder, sourceAmount, BigDecimal.ZERO, false);
+        }
+
+        TradeOrderDO targetUpdate = new TradeOrderDO();
+        targetUpdate.setId(targetOrder.getId());
+        targetUpdate.setAmount(mergedAmount);
+        targetUpdate.setPayAmount(mergedPayAmount);
+        tradeOrderMapper.updateById(targetUpdate);
+
+        TradeOrderTotalDO targetTotalUpdate = new TradeOrderTotalDO();
+        targetTotalUpdate.setId(orderTotalMap.get(targetOrder.getId()).getId());
+        targetTotalUpdate.setOrderMoney(mergedOrderMoney);
+        targetTotalUpdate.setRealMoney(mergedRealMoney);
+        tradeOrderTotalMapper.updateById(targetTotalUpdate);
+    }
+
+    private void insertMergeOrderLog(LoginUser loginUser, TradeOrderDO logOrder, TradeOrderDO relatedOrder,
+                                     BigDecimal amount, BigDecimal newAmount, boolean targetLog) {
+        TradeOrderLogDO logDO = new TradeOrderLogDO();
+        logDO.setId(IdWorker.getId());
+        logDO.setOrderId(logOrder.getId());
+        logDO.setUserId(loginUser.getId());
+        logDO.setUserType(loginUser.getUserType());
+        logDO.setBeforeStatus(logOrder.getOrderStatus());
+        logDO.setAfterStatus(logOrder.getOrderStatus());
+        logDO.setOperateType(TradeOrderOperateTypeEnum.ORDER_MERGE.getType());
+        if (targetLog) {
+            logDO.setContent(StrUtil.format(TradeOrderOperateTypeEnum.ORDER_MERGE.getContent(),
+                    MapUtil.<String, Object>builder()
+                            .put("orderId", relatedOrder.getOrderNo())
+                            .put("payAmount", amount)
+                            .put("newPayAmount", newAmount)
+                            .build()));
+        } else {
+            logDO.setContent("订单金额" + amount + "已合并至订单" + relatedOrder.getOrderNo() + ",合并后为" + newAmount);
+        }
+        tradeOrderLogMapper.insert(logDO);
+    }
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.HAND_ORDER_REFUND)