|
|
@@ -171,134 +171,143 @@ public class OrderDeliverServiceImpl extends ServiceImpl<OrderDeliverMapper, Ord
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
@Override
|
|
|
public Boolean insertByBo(OrderDeliverBo bo) {
|
|
|
+ // 建议使用 @Transactional 注解在方法上,确保整个流程原子性
|
|
|
try {
|
|
|
+ Long orderId = bo.getOrderId();
|
|
|
+
|
|
|
// 1. 生成发货单号
|
|
|
bo.setDeliverCode(SequenceUtils.nextIdDateTimePadded(""));
|
|
|
|
|
|
- Long orderId = bo.getOrderId();
|
|
|
+ // 2. 计算本次发货总数
|
|
|
+ long currentDeliverQty = bo.getOrderDeliverProducts().stream()
|
|
|
+ .mapToLong(OrderDeliverProductBo::getDeliverNum)
|
|
|
+ .sum();
|
|
|
+
|
|
|
+ if (currentDeliverQty <= 0) {
|
|
|
+ throw new RuntimeException("发货数量必须大于0");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 使用 SQL 原子更新来计算新状态和累加数量
|
|
|
|
|
|
- // 2. 查询订单主信息(用于后续状态更新)
|
|
|
OrderMain orderMain = orderMainMapper.selectById(orderId);
|
|
|
if (orderMain == null) {
|
|
|
throw new RuntimeException("订单不存在: " + orderId);
|
|
|
}
|
|
|
|
|
|
- // 3. 获取 订单总数量 和 已发货总数量
|
|
|
- OrderQuantitySummary summary = orderProductMapper.selectOrderAndDeliveredQuantity(orderId);
|
|
|
- long orderTotalQty = Optional.ofNullable(summary.getOrderTotalQty()).orElse(0L);
|
|
|
- long deliveredTotalQty = Optional.ofNullable(summary.getDeliveredTotalQty()).orElse(0L);
|
|
|
+ // 获取当前的已发货数量 (假设订单主表有 delivered_qty 字段)
|
|
|
+ // 如果你的架构是查 OrderProduct 汇总表,请确保那个汇总表也是实时准确的
|
|
|
+ // 强烈建议:订单主表 OrderMain 应该有一个 delivered_total_qty 字段
|
|
|
+ Long currentDeliveredQty = orderMain.getQuantityShipped().longValue();
|
|
|
+ if (currentDeliveredQty == null) currentDeliveredQty = 0L;
|
|
|
+
|
|
|
+ Long orderTotalQty = orderMain.getProductQuantity(); // 假设主表也有总数量字段,否则查明细
|
|
|
+ if (orderTotalQty == null) {
|
|
|
+ // 如果主表没存总数量,才去查明细,但要注意明细也可能并发变
|
|
|
+ OrderQuantitySummary summary = orderProductMapper.selectOrderAndDeliveredQuantity(orderId);
|
|
|
+ orderTotalQty = Optional.ofNullable(summary.getOrderTotalQty()).orElse(0L);
|
|
|
+ // 注意:这里如果依赖明细表统计总数,在高并发下依然有风险,最好主表冗余总数量
|
|
|
+ }
|
|
|
|
|
|
- // 4. 新增本次发货数量(从 BO 中累加)
|
|
|
- long currentDeliverQty = bo.getOrderDeliverProducts().stream()
|
|
|
- .mapToLong(OrderDeliverProductBo::getDeliverNum)
|
|
|
- .sum();
|
|
|
+ long newDeliveredTotalQty = currentDeliveredQty + currentDeliverQty;
|
|
|
|
|
|
- // 5. 计算发货后总量
|
|
|
- long newDeliveredTotalQty = deliveredTotalQty + currentDeliverQty;
|
|
|
+ // 4. 校验是否超发
|
|
|
+ if (newDeliveredTotalQty > orderTotalQty) {
|
|
|
+ throw new RuntimeException("发货数量超过订单总数!订单总数:" + orderTotalQty + ", 当前尝试发货后累计:" + newDeliveredTotalQty);
|
|
|
+ }
|
|
|
|
|
|
- // 6. 判断新状态
|
|
|
+ // 5. 确定新状态
|
|
|
String newStatus;
|
|
|
if (newDeliveredTotalQty >= orderTotalQty) {
|
|
|
- newStatus = OrderStatus.SHIPPED.getCode(); // 已全部发货
|
|
|
- } else if (newDeliveredTotalQty > 0) {
|
|
|
- newStatus = OrderStatus.PARTIALLY_SHIPPED.getCode(); // 部分发货
|
|
|
+ newStatus = OrderStatus.SHIPPED.getCode();
|
|
|
} else {
|
|
|
- newStatus = orderMain.getOrderStatus(); // 无发货,保持原状态
|
|
|
+ newStatus = OrderStatus.PARTIALLY_SHIPPED.getCode();
|
|
|
}
|
|
|
|
|
|
- // 7. 插入发货单主表
|
|
|
+ // 6. 插入发货单主表和明细 (略,保持你原有的逻辑)
|
|
|
OrderDeliver deliver = MapstructUtils.convert(bo, OrderDeliver.class);
|
|
|
validEntityBeforeSave(deliver);
|
|
|
boolean inserted = baseMapper.insert(deliver) > 0;
|
|
|
- if (!inserted) {
|
|
|
- return false;
|
|
|
- }
|
|
|
+ if (!inserted) return false;
|
|
|
bo.setId(deliver.getId());
|
|
|
-
|
|
|
- // 8. 插入发货商品明细
|
|
|
saveOrderDeliverProducts(deliver.getId(), bo.getOrderDeliverProducts(), false);
|
|
|
|
|
|
- // 9. 更新订单主表状态
|
|
|
-
|
|
|
-
|
|
|
- OrderMain currentOrder = orderMainMapper.selectById(orderId);
|
|
|
-
|
|
|
- if (currentOrder == null) {
|
|
|
- throw new RuntimeException("订单不存在");
|
|
|
- }
|
|
|
-
|
|
|
- // 2. 更新当前订单状态
|
|
|
+ // 7. 【核心修复】更新订单主表
|
|
|
+ // 必须同时更新:1. 已发货数量 2. 订单状态
|
|
|
+ // 并且要带上版本号或条件判断,防止并发覆盖
|
|
|
LambdaUpdateWrapper<OrderMain> updateWrapper = new LambdaUpdateWrapper<OrderMain>()
|
|
|
.eq(OrderMain::getId, orderId)
|
|
|
+ // 乐观锁检查:确保更新时的基础数量和我们计算时的一致,防止中间被别人改了
|
|
|
+ // 如果你的表有 version 字段,优先用 version
|
|
|
+ // .eq(OrderMain::getVersion, orderMain.getVersion())
|
|
|
+ // 如果没有 version,至少校验当前已发货数量是否变动(防止重复提交或并发)
|
|
|
+ .eq(OrderMain::getQuantityShipped, currentDeliveredQty)
|
|
|
+ .set(OrderMain::getQuantityShipped, newDeliveredTotalQty)
|
|
|
.set(OrderMain::getOrderStatus, newStatus);
|
|
|
|
|
|
- orderMainMapper.update(null, updateWrapper);
|
|
|
-
|
|
|
- // 3. 【新增逻辑】如果存在父订单,根据策略同步更新父订单状态
|
|
|
- if (currentOrder.getParentOrderId() != null) {
|
|
|
- Long parentId = currentOrder.getParentOrderId();
|
|
|
- boolean shouldUpdateParent = false;
|
|
|
- String targetParentStatus = null;
|
|
|
-
|
|
|
- // 场景 1: 当前子单变为 "已发货" (SHIPPED)
|
|
|
- if (OrderStatus.SHIPPED.getCode().equals(newStatus)) {
|
|
|
- // 逻辑:查询该父订单下所有的子订单
|
|
|
- List<OrderMain> siblingOrders = orderMainMapper.selectList(
|
|
|
- new LambdaQueryWrapper<OrderMain>()
|
|
|
- .eq(OrderMain::getParentOrderId, parentId)
|
|
|
- .select(OrderMain::getOrderStatus) // 只查状态字段,优化性能
|
|
|
- );
|
|
|
-
|
|
|
- // 校验:是否所有子订单都已经是 "已发货"
|
|
|
- if (!siblingOrders.isEmpty()) {
|
|
|
- boolean allShipped = siblingOrders.stream()
|
|
|
- .allMatch(order -> OrderStatus.SHIPPED.getCode().equals(order.getOrderStatus()));
|
|
|
-
|
|
|
- if (allShipped) {
|
|
|
- shouldUpdateParent = true;
|
|
|
- targetParentStatus = OrderStatus.SHIPPED.getCode();
|
|
|
- }
|
|
|
- // 如果并非全部发货,则父订单不应更新为 SHIPPED (保持原状或维持 PARTIALLY_SHIPPED)
|
|
|
- }
|
|
|
+ // 如果使用了 version,这里也要 +1
|
|
|
+ // .set(OrderMain::getVersion, orderMain.getVersion() + 1)
|
|
|
|
|
|
- }
|
|
|
- // 场景 2: 当前子单变为 "部分发货" (PARTIALLY_SHIPPED)
|
|
|
- else if (OrderStatus.PARTIALLY_SHIPPED.getCode().equals(newStatus)) {
|
|
|
- // 逻辑:子单部分发货,父单必然也是部分发货,直接标记需要更新
|
|
|
- shouldUpdateParent = true;
|
|
|
- targetParentStatus = OrderStatus.PARTIALLY_SHIPPED.getCode();
|
|
|
- }
|
|
|
- // 场景 3: 状态无变化 (保持原状态)
|
|
|
- else if (currentOrder.getOrderStatus().equals(newStatus)) {
|
|
|
- // 逻辑:状态没变,不需要处理父订单
|
|
|
- shouldUpdateParent = false;
|
|
|
- }
|
|
|
- // [可选扩展] 场景 4: 其他状态 (如取消 CANCELLED, 关闭 CLOSED 等)
|
|
|
- // 如果你的业务要求子单取消父单也必须取消,可以在这里添加 else if 分支
|
|
|
- // else if (OrderStatus.CANCELLED.getCode().equals(newStatus)) { ... }
|
|
|
-
|
|
|
- // 执行父订单更新
|
|
|
- if (shouldUpdateParent && targetParentStatus != null) {
|
|
|
- LambdaUpdateWrapper<OrderMain> parentUpdateWrapper = new LambdaUpdateWrapper<OrderMain>()
|
|
|
- .eq(OrderMain::getId, parentId)
|
|
|
- // 优化:只有当父订单当前状态不等于目标状态时才更新,避免无效写操作
|
|
|
- .ne(OrderMain::getOrderStatus, targetParentStatus)
|
|
|
- .set(OrderMain::getOrderStatus, targetParentStatus);
|
|
|
-
|
|
|
- // 再次确认父订单存在且状态不同再执行,ne 条件已经涵盖了大部分,但为了严谨可以执行 update
|
|
|
- // 如果 ne 条件不满足,update 返回 0,不会报错
|
|
|
- orderMainMapper.update(null, parentUpdateWrapper);
|
|
|
+ int updateCount = orderMainMapper.update(null, updateWrapper);
|
|
|
+ if (updateCount == 0) {
|
|
|
+ // 更新失败,说明数据被其他人修改了(并发冲突),抛出异常让事务回滚,前端重试
|
|
|
+ throw new RuntimeException("订单状态更新失败,可能存在并发操作,请刷新后重试");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 8. 处理父订单状态同步
|
|
|
+ // 只有当当前子单成功更新为 SHIPPED 时,才需要检查父订单
|
|
|
+ if (OrderStatus.SHIPPED.getCode().equals(newStatus)) {
|
|
|
+ syncParentOrderStatus(orderId, orderMain.getParentOrderId());
|
|
|
+ } else if (OrderStatus.PARTIALLY_SHIPPED.getCode().equals(newStatus)) {
|
|
|
+ // 部分发货时,父订单理论上至少是部分发货
|
|
|
+ if (orderMain.getParentOrderId() != null) {
|
|
|
+ LambdaUpdateWrapper<OrderMain> parentPartialWrapper = new LambdaUpdateWrapper<OrderMain>()
|
|
|
+ .eq(OrderMain::getId, orderMain.getParentOrderId())
|
|
|
+ .ne(OrderMain::getOrderStatus, OrderStatus.PARTIALLY_SHIPPED.getCode())
|
|
|
+ // 只有当父订单状态是“未发货”或其他非部分/全发状态时才升级
|
|
|
+ .set(OrderMain::getOrderStatus, OrderStatus.PARTIALLY_SHIPPED.getCode());
|
|
|
+ orderMainMapper.update(null, parentPartialWrapper);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- log.info("新增订单发货单成功:{}", bo.getId());
|
|
|
+ log.info("新增订单发货单成功:{}, 订单新状态:{}", bo.getId(), newStatus);
|
|
|
return true;
|
|
|
- } catch (RuntimeException e) {
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
log.error("新增订单发货主失败:{}", e.getMessage(), e);
|
|
|
throw new RuntimeException(e);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 单独提取父订单同步逻辑,增加健壮性
|
|
|
+ */
|
|
|
+ private void syncParentOrderStatus(Long currentOrderId, Long parentId) {
|
|
|
+ if (parentId == null) return;
|
|
|
+
|
|
|
+ // 使用数据库锁或者再次查询确认
|
|
|
+ // 这里的逻辑:查询该父订单下所有子单的状态
|
|
|
+ List<OrderMain> siblingOrders = orderMainMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<OrderMain>()
|
|
|
+ .eq(OrderMain::getParentOrderId, parentId)
|
|
|
+ .select(OrderMain::getId, OrderMain::getOrderStatus)
|
|
|
+ );
|
|
|
+
|
|
|
+ if (siblingOrders.isEmpty()) return;
|
|
|
+
|
|
|
+ boolean allShipped = siblingOrders.stream()
|
|
|
+ .allMatch(o -> OrderStatus.SHIPPED.getCode().equals(o.getOrderStatus()));
|
|
|
+
|
|
|
+ if (allShipped) {
|
|
|
+ // 所有子单都发货了,更新父单
|
|
|
+ LambdaUpdateWrapper<OrderMain> parentUpdateWrapper = new LambdaUpdateWrapper<OrderMain>()
|
|
|
+ .eq(OrderMain::getId, parentId)
|
|
|
+ .ne(OrderMain::getOrderStatus, OrderStatus.SHIPPED.getCode()) // 避免无效更新
|
|
|
+ .set(OrderMain::getOrderStatus, OrderStatus.SHIPPED.getCode());
|
|
|
+
|
|
|
+ orderMainMapper.update(null, parentUpdateWrapper);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
public TableDataInfo<OrderDeliverProductVo> getCustomerOrderProductList(Set<Long> orderIdList) {
|
|
|
// 1. 空值与空集合校验
|