diff --git a/firstleg/src/main/java/com/baiyue/firstleg/controller/InternalInboundOrderController.java b/firstleg/src/main/java/com/baiyue/firstleg/controller/InternalInboundOrderController.java index 1e71e89d9..3fe2aab35 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/controller/InternalInboundOrderController.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/controller/InternalInboundOrderController.java @@ -55,7 +55,8 @@ public class InternalInboundOrderController { if (command.tenantId() <= 0 || command.orderId() <= 0 || command.warehouseId() == null || command.warehouseId() <= 0 || command.customerName() == null || command.customerName().isBlank() || command.inboundCode() == null || command.inboundCode().isBlank() - || command.inboundCode().length() > 40) { + || command.inboundCode().length() > 40 + || command.totalPackageCount() != null && command.totalPackageCount() < 1) { throw new BizException(ResultCode.BAD_REQUEST, "订单入库快照不完整"); } return ApiResponse.success(service.createFromApprovedOrder(command)); diff --git a/firstleg/src/main/java/com/baiyue/firstleg/dto/OrderApprovedInboundCommand.java b/firstleg/src/main/java/com/baiyue/firstleg/dto/OrderApprovedInboundCommand.java index bac1cbc30..e9835b181 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/dto/OrderApprovedInboundCommand.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/dto/OrderApprovedInboundCommand.java @@ -30,8 +30,25 @@ public record OrderApprovedInboundCommand( String destinationName, String publicRemark, String expressCompanyName, - String expressTrackingNo + String expressTrackingNo, + Integer totalPackageCount ) { + /** 兼容尚未携带订单总箱数的历史事件。 */ + public OrderApprovedInboundCommand( + long tenantId, long orderId, String orderNo, String inboundCode, Long customerId, String customerName, + Long warehouseId, String warehouseName, Long deliveryWarehouseId, String deliveryWarehouseName, + LocalDateTime expectedArrivalAt, String businessType, String channelCode, String channelName, + Long carrierId, String carrierName, String taxMode, String pricingTaxMode, String containerMode, + Boolean customsInspection, String destinationType, Long destinationWarehouseId, String destinationCode, + String destinationName, String publicRemark, String expressCompanyName, String expressTrackingNo + ) { + this(tenantId, orderId, orderNo, inboundCode, customerId, customerName, warehouseId, warehouseName, + deliveryWarehouseId, deliveryWarehouseName, expectedArrivalAt, businessType, channelCode, channelName, + carrierId, carrierName, taxMode, pricingTaxMode, containerMode, customsInspection, destinationType, + destinationWarehouseId, destinationCode, destinationName, publicRemark, expressCompanyName, + expressTrackingNo, null); + } + /** 兼容未携带报价包税维度的历史调用方。 */ public OrderApprovedInboundCommand( long tenantId, long orderId, String orderNo, String inboundCode, Long customerId, String customerName, @@ -45,6 +62,6 @@ public record OrderApprovedInboundCommand( deliveryWarehouseId, deliveryWarehouseName, expectedArrivalAt, businessType, channelCode, channelName, carrierId, carrierName, taxMode, null, containerMode, customsInspection, destinationType, destinationWarehouseId, destinationCode, destinationName, publicRemark, expressCompanyName, - expressTrackingNo); + expressTrackingNo, null); } } diff --git a/firstleg/src/main/java/com/baiyue/firstleg/service/InboundOrderCreationService.java b/firstleg/src/main/java/com/baiyue/firstleg/service/InboundOrderCreationService.java index c8ce95b51..3f62d85ba 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/service/InboundOrderCreationService.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/service/InboundOrderCreationService.java @@ -68,6 +68,7 @@ public class InboundOrderCreationService { inbound.setDestinationName(command.destinationName()); inbound.setExpressCompanyName(command.expressCompanyName()); inbound.setExpressTrackingNo(command.expressTrackingNo()); + inbound.setTotalPackageCount(command.totalPackageCount()); inbound.setPublicRemark(command.publicRemark()); inbound.setCreatedBy(0L); inbound.setUpdatedBy(0L); diff --git a/firstleg/src/test/java/com/baiyue/firstleg/service/InboundOrderCreationServiceTest.java b/firstleg/src/test/java/com/baiyue/firstleg/service/InboundOrderCreationServiceTest.java index 7995676b0..1e7c607fa 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/service/InboundOrderCreationServiceTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/service/InboundOrderCreationServiceTest.java @@ -53,6 +53,7 @@ class InboundOrderCreationServiceTest { assertThat(inboundCaptor.getValue().getCarrierId()).isNull(); assertThat(inboundCaptor.getValue().getTaxMode()).isEqualTo("BUY_DECLARATION"); assertThat(inboundCaptor.getValue().getPricingTaxMode()).isEqualTo("TAX_EXCLUDED"); + assertThat(inboundCaptor.getValue().getTotalPackageCount()).isEqualTo(4); assertThat(createdId).isEqualTo(inboundCaptor.getValue().getId()); assertThat(replayedId).isEqualTo(900L); } @@ -97,6 +98,6 @@ class InboundOrderCreationServiceTest { return new OrderApprovedInboundCommand( 10001L, 77L, "FL-77", inboundCode, 7L, "测试客户", 9L, "义乌仓", null, null, null, "FBA", "AIR", "空运", null, null, - "BUY_DECLARATION", "TAX_EXCLUDED", null, null, null, null, null, null, null, null, null); + "BUY_DECLARATION", "TAX_EXCLUDED", null, null, null, null, null, null, null, null, null, 4); } } diff --git a/order/src/main/java/com/baiyue/order/client/FirstlegInboundClient.java b/order/src/main/java/com/baiyue/order/client/FirstlegInboundClient.java index e0f6053fa..085b8a38d 100644 --- a/order/src/main/java/com/baiyue/order/client/FirstlegInboundClient.java +++ b/order/src/main/java/com/baiyue/order/client/FirstlegInboundClient.java @@ -35,7 +35,7 @@ public interface FirstlegInboundClient { Long carrierId, String carrierName, String taxMode, String pricingTaxMode, String containerMode, Boolean customsInspection, String destinationType, Long destinationWarehouseId, String destinationCode, String destinationName, String publicRemark, - String expressCompanyName, String expressTrackingNo + String expressCompanyName, String expressTrackingNo, Integer totalPackageCount ) { } } diff --git a/order/src/main/java/com/baiyue/order/service/OrderApplicationService.java b/order/src/main/java/com/baiyue/order/service/OrderApplicationService.java index 0811d4696..4d3621c4b 100644 --- a/order/src/main/java/com/baiyue/order/service/OrderApplicationService.java +++ b/order/src/main/java/com/baiyue/order/service/OrderApplicationService.java @@ -425,7 +425,7 @@ public class OrderApplicationService { event.setEventType(target == OrderStatus.APPROVED ? "FIRST_LEG_ORDER_APPROVED" : "FIRST_LEG_ORDER_CANCELLED"); try { event.setPayloadJson(target == OrderStatus.APPROVED - ? json.writeValueAsString(inboundCommand(order)) + ? json.writeValueAsString(inboundCommand(order, totalPackageCount(order))) : json.writeValueAsString(Map.of("tenantId", order.getTenantId(), "orderId", order.getId(), "orderNo", order.getOrderNo(), "status", target.name()))); } catch (JsonProcessingException exception) { throw new BizException("Cannot serialize order event"); } @@ -433,6 +433,10 @@ public class OrderApplicationService { repository.insertOutbox(event); } static FirstlegInboundClient.OrderApprovedInboundCommand inboundCommand(OrderEntity order) { + return inboundCommand(order, null); + } + static FirstlegInboundClient.OrderApprovedInboundCommand inboundCommand(OrderEntity order, + Integer totalPackageCount) { return new FirstlegInboundClient.OrderApprovedInboundCommand( order.getTenantId(), order.getId(), order.getOrderNo(), order.getInboundCode(), order.getCustomerId(), order.getCustomerName(), @@ -443,7 +447,18 @@ public class OrderApplicationService { order.getTaxMode(), order.getPricingTaxMode(), order.getContainerMode(), order.getCustomsInspection(), order.getDestinationType(), order.getDestinationWarehouseId(), order.getAmazonWarehouseCode(), order.getDestinationWarehouseName(), - order.getCustomerRemark(), order.getExpressCompany(), order.getExpressTrackingNo()); + order.getCustomerRemark(), order.getExpressCompany(), order.getExpressTrackingNo(), totalPackageCount); + } + private int totalPackageCount(OrderEntity order) { + long total = repository.findCargo(order.getTenantId(), order.getId()).stream() + .map(com.baiyue.order.entity.OrderCargoEntity::getPackageCount) + .filter(java.util.Objects::nonNull) + .mapToLong(Integer::longValue) + .sum(); + if (total < 1 || total > 1_000_000) { + throw new BizException("订单货物总箱数必须为1至1000000箱"); + } + return (int) total; } private String nextNo(long orderId) { return "FL" + orderId; diff --git a/order/src/main/java/com/baiyue/order/service/OrderInboundOutboxPublisher.java b/order/src/main/java/com/baiyue/order/service/OrderInboundOutboxPublisher.java index 5c9f13331..9839cad77 100644 --- a/order/src/main/java/com/baiyue/order/service/OrderInboundOutboxPublisher.java +++ b/order/src/main/java/com/baiyue/order/service/OrderInboundOutboxPublisher.java @@ -55,7 +55,7 @@ public class OrderInboundOutboxPublisher { failPermanently(event, "order snapshot is unavailable"); continue; } - command = OrderApplicationService.inboundCommand(order); + command = OrderApplicationService.inboundCommand(order, totalPackageCount(order)); if (incomplete(command)) { failPermanently(event, "rebuilt order snapshot is incomplete"); continue; @@ -77,7 +77,17 @@ public class OrderInboundOutboxPublisher { return command.orderNo() == null || command.orderNo().isBlank() || command.inboundCode() == null || command.inboundCode().isBlank() || command.customerName() == null || command.customerName().isBlank() - || command.warehouseId() == null || command.warehouseId() <= 0; + || command.warehouseId() == null || command.warehouseId() <= 0 + || command.totalPackageCount() == null || command.totalPackageCount() < 1; + } + + private int totalPackageCount(com.baiyue.order.entity.OrderEntity order) { + long total = repository.findCargo(order.getTenantId(), order.getId()).stream() + .map(com.baiyue.order.entity.OrderCargoEntity::getPackageCount) + .filter(java.util.Objects::nonNull) + .mapToLong(Integer::longValue) + .sum(); + return total > 0 && total <= 1_000_000 ? (int) total : 0; } private void failPermanently(OutboxEventEntity event, String reason) { diff --git a/order/src/test/java/com/baiyue/order/service/OrderApplicationServiceStateTest.java b/order/src/test/java/com/baiyue/order/service/OrderApplicationServiceStateTest.java index e8ccd14de..aa492f2de 100644 --- a/order/src/test/java/com/baiyue/order/service/OrderApplicationServiceStateTest.java +++ b/order/src/test/java/com/baiyue/order/service/OrderApplicationServiceStateTest.java @@ -217,13 +217,17 @@ class OrderApplicationServiceStateTest { order.setInboundCode("RK-1"); when(repository.findOwned(10001L, 1L)).thenReturn(order); when(repository.saveOrder(10001L, order)).thenReturn(1); + OrderCargoEntity cargo = new OrderCargoEntity(); + cargo.setPackageCount(4); + when(repository.findCargo(10001L, 1L)).thenReturn(List.of(cargo)); service(repository, mock(OrderEligibilityGuard.class), mock(OrderMasterDataGuard.class)) .transit(10001L, 2L, "u", 1L, OrderStatus.APPROVED, null); assertThat(order.getStatus()).isEqualTo(OrderStatus.APPROVED); verify(repository).insertOutbox(argThat(event -> event.getPayloadJson().contains("\"inboundCode\":\"RK-1\"") - && event.getPayloadJson().contains("\"carrierId\":null"))); + && event.getPayloadJson().contains("\"carrierId\":null") + && event.getPayloadJson().contains("\"totalPackageCount\":4"))); } @Test @@ -290,10 +294,11 @@ class OrderApplicationServiceStateTest { order.setTaxMode("BUY_DECLARATION"); order.setPricingTaxMode("TAX_EXCLUDED"); - var command = OrderApplicationService.inboundCommand(order); + var command = OrderApplicationService.inboundCommand(order, 4); assertThat(command.taxMode()).isEqualTo("BUY_DECLARATION"); assertThat(command.pricingTaxMode()).isEqualTo("TAX_EXCLUDED"); + assertThat(command.totalPackageCount()).isEqualTo(4); } @Test diff --git a/order/src/test/java/com/baiyue/order/service/OrderInboundOutboxPublisherTest.java b/order/src/test/java/com/baiyue/order/service/OrderInboundOutboxPublisherTest.java index 332e3534a..9d30968f6 100644 --- a/order/src/test/java/com/baiyue/order/service/OrderInboundOutboxPublisherTest.java +++ b/order/src/test/java/com/baiyue/order/service/OrderInboundOutboxPublisherTest.java @@ -36,6 +36,7 @@ class OrderInboundOutboxPublisherTest { org.assertj.core.api.Assertions.assertThat(command.getValue().warehouseId()).isEqualTo(31L); org.assertj.core.api.Assertions.assertThat(command.getValue().taxMode()).isEqualTo("BUY_DECLARATION"); org.assertj.core.api.Assertions.assertThat(command.getValue().pricingTaxMode()).isEqualTo("TAX_EXCLUDED"); + org.assertj.core.api.Assertions.assertThat(command.getValue().totalPackageCount()).isEqualTo(4); verify(fixture.repository).markOutboxPublished(eq(11L), eq(41L), any(LocalDateTime.class)); verify(fixture.repository, never()).markOutboxRetry(anyLong(), anyLong(), any()); verify(fixture.repository, never()).markOutboxFailed(any(), anyLong()); @@ -114,6 +115,12 @@ class OrderInboundOutboxPublisherTest { order.setTaxMode("BUY_DECLARATION"); order.setPricingTaxMode("TAX_EXCLUDED"); when(repository.findOwned(event.getTenantId(), event.getAggregateId())).thenReturn(order); + var firstCargo = new com.baiyue.order.entity.OrderCargoEntity(); + firstCargo.setPackageCount(1); + var secondCargo = new com.baiyue.order.entity.OrderCargoEntity(); + secondCargo.setPackageCount(3); + when(repository.findCargo(event.getTenantId(), event.getAggregateId())) + .thenReturn(List.of(firstCargo, secondCargo)); } OrderInboundOutboxPublisher publisher = new OrderInboundOutboxPublisher(); ReflectionTestUtils.setField(publisher, "repository", repository);