fix(order): inherit cargo bindings for remainder orders

This commit is contained in:
2026-08-11 10:11:30 +08:00
parent bceaff496e
commit 5f90bf3bc8
4 changed files with 81 additions and 10 deletions
@@ -42,11 +42,11 @@ public class InternalOrderCargoController {
if (tenantId <= 0 || orderId <= 0 || cargoId <= 0) { if (tenantId <= 0 || orderId <= 0 || cargoId <= 0) {
throw new BizException(ResultCode.BAD_REQUEST, "租户、订单和货物明细ID必须大于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) { if (cargo == null) {
throw new BizException(ResultCode.NOT_FOUND, "订单货物明细不存在或不属于该订单"); 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.getCargoName(), cargo.getPackageCount(), cargo.getGrossWeightKg(), cargo.getVolumeCbm(),
cargo.getSkuNo(), cargo.getCargoNameEn(), cargo.getUnitsPerPackage(), cargo.getTotalQuantity(), cargo.getSkuNo(), cargo.getCargoNameEn(), cargo.getUnitsPerPackage(), cargo.getTotalQuantity(),
cargo.getNetWeightKg(), cargo.getCargoRemark())); cargo.getNetWeightKg(), cargo.getCargoRemark()));
@@ -61,8 +61,8 @@ public class InternalOrderCargoController {
if (tenantId <= 0 || orderId <= 0) { if (tenantId <= 0 || orderId <= 0) {
throw new BizException(ResultCode.BAD_REQUEST, "租户和订单ID必须大于0"); throw new BizException(ResultCode.BAD_REQUEST, "租户和订单ID必须大于0");
} }
return ApiResponse.success(orders.findCargo(tenantId, orderId).stream() return ApiResponse.success(orders.findCargoInheritedByOrder(tenantId, orderId).stream()
.map(cargo -> new CargoLabelBinding(cargo.getId(), cargo.getOrderId(), cargo.getItemNo(), .map(cargo -> new CargoLabelBinding(cargo.getId(), orderId, cargo.getItemNo(),
cargo.getCargoName(), cargo.getPackageCount(), cargo.getLengthCm(), cargo.getWidthCm(), cargo.getCargoName(), cargo.getPackageCount(), cargo.getLengthCm(), cargo.getWidthCm(),
cargo.getHeightCm())) cargo.getHeightCm()))
.toList()); .toList());
@@ -262,6 +262,36 @@ public class OrderRepository {
.eq(OrderCargoEntity::getDeleted, false)); .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<OrderEntity>()
.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<OrderCargoEntity>()
.eq(OrderCargoEntity::getTenantId, tenantId)
.eq(OrderCargoEntity::getOrderId, order.getRootOrderId())
.eq(OrderCargoEntity::getId, cargoId)
.eq(OrderCargoEntity::getDeleted, false));
}
public List<OrderCargoEntity> findCargoInheritedByOrder(long tenantId, long orderId) {
List<OrderCargoEntity> direct = findCargo(tenantId, orderId);
if (!direct.isEmpty()) return direct;
OrderEntity order = orderMapper.selectOne(new LambdaQueryWrapper<OrderEntity>()
.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<OrderPartyEntity> findParties(long tenantId, long orderId) { public List<OrderPartyEntity> findParties(long tenantId, long orderId) {
return partyMapper.selectList(new LambdaQueryWrapper<OrderPartyEntity>() return partyMapper.selectList(new LambdaQueryWrapper<OrderPartyEntity>()
.eq(OrderPartyEntity::getTenantId, tenantId).eq(OrderPartyEntity::getOrderId, orderId)); .eq(OrderPartyEntity::getTenantId, tenantId).eq(OrderPartyEntity::getOrderId, orderId));
@@ -39,7 +39,7 @@ class InternalOrderCargoControllerTest {
cargo.setTotalQuantity(200); cargo.setTotalQuantity(200);
cargo.setNetWeightKg(new java.math.BigDecimal("18.500")); cargo.setNetWeightKg(new java.math.BigDecimal("18.500"));
cargo.setCargoRemark("内部货物备注"); 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); var response = controller.detail("internal-token", 11L, 21L, 31L);
@@ -52,7 +52,7 @@ class InternalOrderCargoControllerTest {
assertThat(response.data().totalQuantity()).isEqualTo(200); assertThat(response.data().totalQuantity()).isEqualTo(200);
assertThat(response.data().netWeightKg()).isEqualByComparingTo("18.500"); assertThat(response.data().netWeightKg()).isEqualByComparingTo("18.500");
assertThat(response.data().cargoRemark()).isEqualTo("内部货物备注"); assertThat(response.data().cargoRemark()).isEqualTo("内部货物备注");
verify(orders).findCargo(11L, 21L, 31L); verify(orders).findCargoInheritedByOrder(11L, 21L, 31L);
} }
@Test @Test
@@ -64,13 +64,13 @@ class InternalOrderCargoControllerTest {
@Test @Test
void cargoFromAnotherCustomerIsNotExposed() { 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)) assertThatThrownBy(() -> controller.detail("internal-token", 12L, 21L, 31L))
.isInstanceOf(BizException.class) .isInstanceOf(BizException.class)
.hasMessageContaining("不存在或不属于该订单"); .hasMessageContaining("不存在或不属于该订单");
verify(orders).findCargo(12L, 21L, 31L); verify(orders).findCargoInheritedByOrder(12L, 21L, 31L);
verifyNoMoreInteractions(orders); verifyNoMoreInteractions(orders);
} }
@@ -78,7 +78,7 @@ class InternalOrderCargoControllerTest {
void listsCargoInRepositoryOrderForStableCartonAllocation() { void listsCargoInRepositoryOrderForStableCartonAllocation() {
OrderCargoEntity first = cargo(31L, 1, 3, "60", "40", "30"); OrderCargoEntity first = cargo(31L, 1, 3, "60", "40", "30");
OrderCargoEntity second = cargo(32L, 2, 2, "50", "30", "20"); 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); var response = controller.listForCartonLabels("internal-token", 11L, 21L);
@@ -87,7 +87,21 @@ class InternalOrderCargoControllerTest {
.containsExactly(31L, 32L); .containsExactly(31L, 32L);
assertThat(response.data().get(0).packageCount()).isEqualTo(3); assertThat(response.data().get(0).packageCount()).isEqualTo(3);
assertThat(response.data().get(0).lengthCm()).isEqualByComparingTo("60"); 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, private OrderCargoEntity cargo(long id, int itemNo, int packageCount,
@@ -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");
}
}