From 77a91b65535217620b2e3e9ffbbb9aea714f4e96 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Tue, 11 Aug 2026 10:09:09 +0800 Subject: [PATCH] fix(order): inherit cargo bindings for remainder orders --- .../InternalOrderCargoController.java | 8 ++--- .../order/repository/OrderRepository.java | 30 +++++++++++++++++++ .../InternalOrderCargoControllerTest.java | 26 ++++++++++++---- .../OrderCargoInheritanceContractTest.java | 27 +++++++++++++++++ 4 files changed, 81 insertions(+), 10 deletions(-) create mode 100644 order/src/test/java/com/baiyue/order/repository/OrderCargoInheritanceContractTest.java diff --git a/order/src/main/java/com/baiyue/order/controller/InternalOrderCargoController.java b/order/src/main/java/com/baiyue/order/controller/InternalOrderCargoController.java index 36e7a3651..43b1f2b83 100644 --- a/order/src/main/java/com/baiyue/order/controller/InternalOrderCargoController.java +++ b/order/src/main/java/com/baiyue/order/controller/InternalOrderCargoController.java @@ -42,11 +42,11 @@ public class InternalOrderCargoController { if (tenantId <= 0 || orderId <= 0 || cargoId <= 0) { throw new BizException(ResultCode.BAD_REQUEST, "租户、订单和货物明细ID必须大于0"); } - OrderCargoEntity cargo = orders.findCargo(tenantId, orderId, cargoId); + OrderCargoEntity cargo = orders.findCargoInheritedByOrder(tenantId, orderId, cargoId); if (cargo == null) { throw new BizException(ResultCode.NOT_FOUND, "订单货物明细不存在或不属于该订单"); } - return ApiResponse.success(new CargoBinding(cargo.getId(), cargo.getOrderId(), cargo.getItemNo(), + return ApiResponse.success(new CargoBinding(cargo.getId(), orderId, cargo.getItemNo(), cargo.getCargoName(), cargo.getPackageCount(), cargo.getGrossWeightKg(), cargo.getVolumeCbm(), cargo.getSkuNo(), cargo.getCargoNameEn(), cargo.getUnitsPerPackage(), cargo.getTotalQuantity(), cargo.getNetWeightKg(), cargo.getCargoRemark())); @@ -61,8 +61,8 @@ public class InternalOrderCargoController { if (tenantId <= 0 || orderId <= 0) { throw new BizException(ResultCode.BAD_REQUEST, "租户和订单ID必须大于0"); } - return ApiResponse.success(orders.findCargo(tenantId, orderId).stream() - .map(cargo -> new CargoLabelBinding(cargo.getId(), cargo.getOrderId(), cargo.getItemNo(), + return ApiResponse.success(orders.findCargoInheritedByOrder(tenantId, orderId).stream() + .map(cargo -> new CargoLabelBinding(cargo.getId(), orderId, cargo.getItemNo(), cargo.getCargoName(), cargo.getPackageCount(), cargo.getLengthCm(), cargo.getWidthCm(), cargo.getHeightCm())) .toList()); diff --git a/order/src/main/java/com/baiyue/order/repository/OrderRepository.java b/order/src/main/java/com/baiyue/order/repository/OrderRepository.java index a8e1e885b..925338837 100644 --- a/order/src/main/java/com/baiyue/order/repository/OrderRepository.java +++ b/order/src/main/java/com/baiyue/order/repository/OrderRepository.java @@ -262,6 +262,36 @@ public class OrderRepository { .eq(OrderCargoEntity::getDeleted, false)); } + /** + * 子订单尚未物化独立货物行时,按同客户根订单继承货物明细;返回实体的 orderId + * 仍是根单,调用方必须用请求中的子订单 ID 生成绑定快照,避免跨客户或跨根单借用。 + */ + public OrderCargoEntity findCargoInheritedByOrder(long tenantId, long orderId, long cargoId) { + OrderCargoEntity direct = findCargo(tenantId, orderId, cargoId); + if (direct != null) return direct; + OrderEntity order = orderMapper.selectOne(new LambdaQueryWrapper() + .eq(OrderEntity::getTenantId, tenantId) + .eq(OrderEntity::getId, orderId) + .eq(OrderEntity::getDeleted, false)); + if (order == null || !"CHILD".equals(order.getOrderRole()) || order.getRootOrderId() == null) return null; + return cargoMapper.selectOne(new LambdaQueryWrapper() + .eq(OrderCargoEntity::getTenantId, tenantId) + .eq(OrderCargoEntity::getOrderId, order.getRootOrderId()) + .eq(OrderCargoEntity::getId, cargoId) + .eq(OrderCargoEntity::getDeleted, false)); + } + + public List findCargoInheritedByOrder(long tenantId, long orderId) { + List direct = findCargo(tenantId, orderId); + if (!direct.isEmpty()) return direct; + OrderEntity order = orderMapper.selectOne(new LambdaQueryWrapper() + .eq(OrderEntity::getTenantId, tenantId) + .eq(OrderEntity::getId, orderId) + .eq(OrderEntity::getDeleted, false)); + if (order == null || !"CHILD".equals(order.getOrderRole()) || order.getRootOrderId() == null) return List.of(); + return findCargo(tenantId, order.getRootOrderId()); + } + public List findParties(long tenantId, long orderId) { return partyMapper.selectList(new LambdaQueryWrapper() .eq(OrderPartyEntity::getTenantId, tenantId).eq(OrderPartyEntity::getOrderId, orderId)); diff --git a/order/src/test/java/com/baiyue/order/controller/InternalOrderCargoControllerTest.java b/order/src/test/java/com/baiyue/order/controller/InternalOrderCargoControllerTest.java index 83cc76d23..c29974a56 100644 --- a/order/src/test/java/com/baiyue/order/controller/InternalOrderCargoControllerTest.java +++ b/order/src/test/java/com/baiyue/order/controller/InternalOrderCargoControllerTest.java @@ -39,7 +39,7 @@ class InternalOrderCargoControllerTest { cargo.setTotalQuantity(200); cargo.setNetWeightKg(new java.math.BigDecimal("18.500")); cargo.setCargoRemark("内部货物备注"); - when(orders.findCargo(11L, 21L, 31L)).thenReturn(cargo); + when(orders.findCargoInheritedByOrder(11L, 21L, 31L)).thenReturn(cargo); var response = controller.detail("internal-token", 11L, 21L, 31L); @@ -52,7 +52,7 @@ class InternalOrderCargoControllerTest { assertThat(response.data().totalQuantity()).isEqualTo(200); assertThat(response.data().netWeightKg()).isEqualByComparingTo("18.500"); assertThat(response.data().cargoRemark()).isEqualTo("内部货物备注"); - verify(orders).findCargo(11L, 21L, 31L); + verify(orders).findCargoInheritedByOrder(11L, 21L, 31L); } @Test @@ -64,13 +64,13 @@ class InternalOrderCargoControllerTest { @Test void cargoFromAnotherCustomerIsNotExposed() { - when(orders.findCargo(12L, 21L, 31L)).thenReturn(null); + when(orders.findCargoInheritedByOrder(12L, 21L, 31L)).thenReturn(null); assertThatThrownBy(() -> controller.detail("internal-token", 12L, 21L, 31L)) .isInstanceOf(BizException.class) .hasMessageContaining("不存在或不属于该订单"); - verify(orders).findCargo(12L, 21L, 31L); + verify(orders).findCargoInheritedByOrder(12L, 21L, 31L); verifyNoMoreInteractions(orders); } @@ -78,7 +78,7 @@ class InternalOrderCargoControllerTest { void listsCargoInRepositoryOrderForStableCartonAllocation() { OrderCargoEntity first = cargo(31L, 1, 3, "60", "40", "30"); OrderCargoEntity second = cargo(32L, 2, 2, "50", "30", "20"); - when(orders.findCargo(11L, 21L)).thenReturn(List.of(first, second)); + when(orders.findCargoInheritedByOrder(11L, 21L)).thenReturn(List.of(first, second)); var response = controller.listForCartonLabels("internal-token", 11L, 21L); @@ -87,7 +87,21 @@ class InternalOrderCargoControllerTest { .containsExactly(31L, 32L); assertThat(response.data().get(0).packageCount()).isEqualTo(3); assertThat(response.data().get(0).lengthCm()).isEqualByComparingTo("60"); - verify(orders).findCargo(11L, 21L); + verify(orders).findCargoInheritedByOrder(11L, 21L); + } + + @Test + void childOrderCanInheritRootCargoBindingWithoutCrossCustomerLeak() { + OrderCargoEntity cargo = cargo(31L, 1, 3, "60", "40", "30"); + cargo.setOrderId(21L); + when(orders.findCargoInheritedByOrder(11L, 201L, 31L)).thenReturn(cargo); + + var response = controller.detail("internal-token", 11L, 201L, 31L); + + assertThat(response.successful()).isTrue(); + assertThat(response.data().cargoId()).isEqualTo(31L); + assertThat(response.data().orderId()).isEqualTo(201L); + verify(orders).findCargoInheritedByOrder(11L, 201L, 31L); } private OrderCargoEntity cargo(long id, int itemNo, int packageCount, diff --git a/order/src/test/java/com/baiyue/order/repository/OrderCargoInheritanceContractTest.java b/order/src/test/java/com/baiyue/order/repository/OrderCargoInheritanceContractTest.java new file mode 100644 index 000000000..68e153040 --- /dev/null +++ b/order/src/test/java/com/baiyue/order/repository/OrderCargoInheritanceContractTest.java @@ -0,0 +1,27 @@ +package com.baiyue.order.repository; + +import org.junit.jupiter.api.Test; + +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.assertj.core.api.Assertions.assertThat; + +class OrderCargoInheritanceContractTest { + @Test + void childCargoLookupIsRootScopedAndTenantScoped() throws Exception { + Path source = Path.of("src/main/java/com/baiyue/order/repository/OrderRepository.java"); + if (!Files.exists(source)) source = Path.of("order/src/main/java/com/baiyue/order/repository/OrderRepository.java"); + String text = Files.readString(source, StandardCharsets.UTF_8); + + assertThat(text) + .contains("findCargoInheritedByOrder") + .contains("OrderEntity::getTenantId") + .contains("OrderEntity::getId") + .contains("!\"CHILD\".equals(order.getOrderRole())") + .contains("order.getRootOrderId()") + .contains("OrderCargoEntity::getTenantId") + .contains("OrderCargoEntity::getOrderId"); + } +}