fix: carry package count into inbound snapshot

This commit is contained in:
2026-08-08 10:16:33 +08:00
parent 71fde99ceb
commit dd8147c535
9 changed files with 68 additions and 11 deletions
@@ -55,7 +55,8 @@ public class InternalInboundOrderController {
if (command.tenantId() <= 0 || command.orderId() <= 0 || command.warehouseId() == null if (command.tenantId() <= 0 || command.orderId() <= 0 || command.warehouseId() == null
|| command.warehouseId() <= 0 || command.customerName() == null || command.customerName().isBlank() || command.warehouseId() <= 0 || command.customerName() == null || command.customerName().isBlank()
|| command.inboundCode() == null || command.inboundCode().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, "订单入库快照不完整"); throw new BizException(ResultCode.BAD_REQUEST, "订单入库快照不完整");
} }
return ApiResponse.success(service.createFromApprovedOrder(command)); return ApiResponse.success(service.createFromApprovedOrder(command));
@@ -30,8 +30,25 @@ public record OrderApprovedInboundCommand(
String destinationName, String destinationName,
String publicRemark, String publicRemark,
String expressCompanyName, 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( public OrderApprovedInboundCommand(
long tenantId, long orderId, String orderNo, String inboundCode, Long customerId, String customerName, 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, deliveryWarehouseId, deliveryWarehouseName, expectedArrivalAt, businessType, channelCode, channelName,
carrierId, carrierName, taxMode, null, containerMode, customsInspection, destinationType, carrierId, carrierName, taxMode, null, containerMode, customsInspection, destinationType,
destinationWarehouseId, destinationCode, destinationName, publicRemark, expressCompanyName, destinationWarehouseId, destinationCode, destinationName, publicRemark, expressCompanyName,
expressTrackingNo); expressTrackingNo, null);
} }
} }
@@ -68,6 +68,7 @@ public class InboundOrderCreationService {
inbound.setDestinationName(command.destinationName()); inbound.setDestinationName(command.destinationName());
inbound.setExpressCompanyName(command.expressCompanyName()); inbound.setExpressCompanyName(command.expressCompanyName());
inbound.setExpressTrackingNo(command.expressTrackingNo()); inbound.setExpressTrackingNo(command.expressTrackingNo());
inbound.setTotalPackageCount(command.totalPackageCount());
inbound.setPublicRemark(command.publicRemark()); inbound.setPublicRemark(command.publicRemark());
inbound.setCreatedBy(0L); inbound.setCreatedBy(0L);
inbound.setUpdatedBy(0L); inbound.setUpdatedBy(0L);
@@ -53,6 +53,7 @@ class InboundOrderCreationServiceTest {
assertThat(inboundCaptor.getValue().getCarrierId()).isNull(); assertThat(inboundCaptor.getValue().getCarrierId()).isNull();
assertThat(inboundCaptor.getValue().getTaxMode()).isEqualTo("BUY_DECLARATION"); assertThat(inboundCaptor.getValue().getTaxMode()).isEqualTo("BUY_DECLARATION");
assertThat(inboundCaptor.getValue().getPricingTaxMode()).isEqualTo("TAX_EXCLUDED"); assertThat(inboundCaptor.getValue().getPricingTaxMode()).isEqualTo("TAX_EXCLUDED");
assertThat(inboundCaptor.getValue().getTotalPackageCount()).isEqualTo(4);
assertThat(createdId).isEqualTo(inboundCaptor.getValue().getId()); assertThat(createdId).isEqualTo(inboundCaptor.getValue().getId());
assertThat(replayedId).isEqualTo(900L); assertThat(replayedId).isEqualTo(900L);
} }
@@ -97,6 +98,6 @@ class InboundOrderCreationServiceTest {
return new OrderApprovedInboundCommand( return new OrderApprovedInboundCommand(
10001L, 77L, "FL-77", inboundCode, 7L, "测试客户", 9L, "义乌仓", 10001L, 77L, "FL-77", inboundCode, 7L, "测试客户", 9L, "义乌仓",
null, null, null, "FBA", "AIR", "空运", null, null, 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);
} }
} }
@@ -35,7 +35,7 @@ public interface FirstlegInboundClient {
Long carrierId, String carrierName, String taxMode, String pricingTaxMode, String containerMode, Long carrierId, String carrierName, String taxMode, String pricingTaxMode, String containerMode,
Boolean customsInspection, String destinationType, Long destinationWarehouseId, Boolean customsInspection, String destinationType, Long destinationWarehouseId,
String destinationCode, String destinationName, String publicRemark, String destinationCode, String destinationName, String publicRemark,
String expressCompanyName, String expressTrackingNo String expressCompanyName, String expressTrackingNo, Integer totalPackageCount
) { ) {
} }
} }
@@ -425,7 +425,7 @@ public class OrderApplicationService {
event.setEventType(target == OrderStatus.APPROVED ? "FIRST_LEG_ORDER_APPROVED" : "FIRST_LEG_ORDER_CANCELLED"); event.setEventType(target == OrderStatus.APPROVED ? "FIRST_LEG_ORDER_APPROVED" : "FIRST_LEG_ORDER_CANCELLED");
try { try {
event.setPayloadJson(target == OrderStatus.APPROVED 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(), : json.writeValueAsString(Map.of("tenantId", order.getTenantId(), "orderId", order.getId(),
"orderNo", order.getOrderNo(), "status", target.name()))); "orderNo", order.getOrderNo(), "status", target.name())));
} catch (JsonProcessingException exception) { throw new BizException("Cannot serialize order event"); } } catch (JsonProcessingException exception) { throw new BizException("Cannot serialize order event"); }
@@ -433,6 +433,10 @@ public class OrderApplicationService {
repository.insertOutbox(event); repository.insertOutbox(event);
} }
static FirstlegInboundClient.OrderApprovedInboundCommand inboundCommand(OrderEntity order) { static FirstlegInboundClient.OrderApprovedInboundCommand inboundCommand(OrderEntity order) {
return inboundCommand(order, null);
}
static FirstlegInboundClient.OrderApprovedInboundCommand inboundCommand(OrderEntity order,
Integer totalPackageCount) {
return new FirstlegInboundClient.OrderApprovedInboundCommand( return new FirstlegInboundClient.OrderApprovedInboundCommand(
order.getTenantId(), order.getId(), order.getOrderNo(), order.getInboundCode(), order.getTenantId(), order.getId(), order.getOrderNo(), order.getInboundCode(),
order.getCustomerId(), order.getCustomerName(), order.getCustomerId(), order.getCustomerName(),
@@ -443,7 +447,18 @@ public class OrderApplicationService {
order.getTaxMode(), order.getPricingTaxMode(), order.getTaxMode(), order.getPricingTaxMode(),
order.getContainerMode(), order.getCustomsInspection(), order.getDestinationType(), order.getContainerMode(), order.getCustomsInspection(), order.getDestinationType(),
order.getDestinationWarehouseId(), order.getAmazonWarehouseCode(), order.getDestinationWarehouseName(), 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) { private String nextNo(long orderId) {
return "FL" + orderId; return "FL" + orderId;
@@ -55,7 +55,7 @@ public class OrderInboundOutboxPublisher {
failPermanently(event, "order snapshot is unavailable"); failPermanently(event, "order snapshot is unavailable");
continue; continue;
} }
command = OrderApplicationService.inboundCommand(order); command = OrderApplicationService.inboundCommand(order, totalPackageCount(order));
if (incomplete(command)) { if (incomplete(command)) {
failPermanently(event, "rebuilt order snapshot is incomplete"); failPermanently(event, "rebuilt order snapshot is incomplete");
continue; continue;
@@ -77,7 +77,17 @@ public class OrderInboundOutboxPublisher {
return command.orderNo() == null || command.orderNo().isBlank() return command.orderNo() == null || command.orderNo().isBlank()
|| command.inboundCode() == null || command.inboundCode().isBlank() || command.inboundCode() == null || command.inboundCode().isBlank()
|| command.customerName() == null || command.customerName().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) { private void failPermanently(OutboxEventEntity event, String reason) {
@@ -217,13 +217,17 @@ class OrderApplicationServiceStateTest {
order.setInboundCode("RK-1"); order.setInboundCode("RK-1");
when(repository.findOwned(10001L, 1L)).thenReturn(order); when(repository.findOwned(10001L, 1L)).thenReturn(order);
when(repository.saveOrder(10001L, order)).thenReturn(1); 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)) service(repository, mock(OrderEligibilityGuard.class), mock(OrderMasterDataGuard.class))
.transit(10001L, 2L, "u", 1L, OrderStatus.APPROVED, null); .transit(10001L, 2L, "u", 1L, OrderStatus.APPROVED, null);
assertThat(order.getStatus()).isEqualTo(OrderStatus.APPROVED); assertThat(order.getStatus()).isEqualTo(OrderStatus.APPROVED);
verify(repository).insertOutbox(argThat(event -> event.getPayloadJson().contains("\"inboundCode\":\"RK-1\"") 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 @Test
@@ -290,10 +294,11 @@ class OrderApplicationServiceStateTest {
order.setTaxMode("BUY_DECLARATION"); order.setTaxMode("BUY_DECLARATION");
order.setPricingTaxMode("TAX_EXCLUDED"); order.setPricingTaxMode("TAX_EXCLUDED");
var command = OrderApplicationService.inboundCommand(order); var command = OrderApplicationService.inboundCommand(order, 4);
assertThat(command.taxMode()).isEqualTo("BUY_DECLARATION"); assertThat(command.taxMode()).isEqualTo("BUY_DECLARATION");
assertThat(command.pricingTaxMode()).isEqualTo("TAX_EXCLUDED"); assertThat(command.pricingTaxMode()).isEqualTo("TAX_EXCLUDED");
assertThat(command.totalPackageCount()).isEqualTo(4);
} }
@Test @Test
@@ -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().warehouseId()).isEqualTo(31L);
org.assertj.core.api.Assertions.assertThat(command.getValue().taxMode()).isEqualTo("BUY_DECLARATION"); 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().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).markOutboxPublished(eq(11L), eq(41L), any(LocalDateTime.class));
verify(fixture.repository, never()).markOutboxRetry(anyLong(), anyLong(), any()); verify(fixture.repository, never()).markOutboxRetry(anyLong(), anyLong(), any());
verify(fixture.repository, never()).markOutboxFailed(any(), anyLong()); verify(fixture.repository, never()).markOutboxFailed(any(), anyLong());
@@ -114,6 +115,12 @@ class OrderInboundOutboxPublisherTest {
order.setTaxMode("BUY_DECLARATION"); order.setTaxMode("BUY_DECLARATION");
order.setPricingTaxMode("TAX_EXCLUDED"); order.setPricingTaxMode("TAX_EXCLUDED");
when(repository.findOwned(event.getTenantId(), event.getAggregateId())).thenReturn(order); 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(); OrderInboundOutboxPublisher publisher = new OrderInboundOutboxPublisher();
ReflectionTestUtils.setField(publisher, "repository", repository); ReflectionTestUtils.setField(publisher, "repository", repository);