From 371aa3dbcb48d5cdabcee473f1972eeacdaaa410 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Wed, 2 Sep 2026 10:26:40 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../order/client/FirstlegPricingClient.java | 5 +++ .../service/OrderInboundOutboxPublisher.java | 27 +++++++++++++++ .../OrderInboundOutboxPublisherTest.java | 34 +++++++++++++++++-- 3 files changed, 63 insertions(+), 3 deletions(-) diff --git a/order/src/main/java/com/baiyue/order/client/FirstlegPricingClient.java b/order/src/main/java/com/baiyue/order/client/FirstlegPricingClient.java index de9933e8f..e866b6d6b 100644 --- a/order/src/main/java/com/baiyue/order/client/FirstlegPricingClient.java +++ b/order/src/main/java/com/baiyue/order/client/FirstlegPricingClient.java @@ -18,6 +18,11 @@ public interface FirstlegPricingClient { @RequestHeader("X-Internal-Service-Token") String token, @RequestBody OrderChannelChangeRequest request); + @PostMapping("/order-channel-changes/apply") + ApiResponse applyOrderChannelChange( + @RequestHeader("X-Internal-Service-Token") String token, + @RequestBody OrderChannelChangeRequest request); + record ChannelValidationRequest(long tenantId, long originWarehouseId, String channelCode, String destinationType, boolean requireInboundCodePrefix) {} record ChannelValidationResponse(String channelCode, String inboundCodePrefix) {} 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 64417f6a0..e933b22cc 100644 --- a/order/src/main/java/com/baiyue/order/service/OrderInboundOutboxPublisher.java +++ b/order/src/main/java/com/baiyue/order/service/OrderInboundOutboxPublisher.java @@ -4,6 +4,8 @@ import jakarta.annotation.Resource; import com.baiyue.common.core.api.ApiResponse; import com.baiyue.order.client.FirstlegInboundClient; +import com.baiyue.order.client.FirstlegPricingClient; +import com.baiyue.order.entity.OrderEntity; import com.baiyue.order.entity.OutboxEventEntity; import com.baiyue.order.dto.OrderDeclarationDtos; import com.baiyue.order.repository.OrderRepository; @@ -21,6 +23,7 @@ import java.time.LocalDateTime; public class OrderInboundOutboxPublisher { @Resource private OrderRepository repository; @Resource private FirstlegInboundClient client; + @Resource private FirstlegPricingClient pricingClient; @Resource private ObjectMapper json; @Value("${baiyue.security.internal-service-token}") private String internalToken; @@ -91,6 +94,25 @@ public class OrderInboundOutboxPublisher { failPermanently(event, "declaration event ownership does not match payload"); return; } + if (containsChannelChange(command)) { + OrderEntity order = repository.findOwned(event.getTenantId(), event.getAggregateId()); + if (order == null) { + failPermanently(event, "order snapshot is unavailable for channel change"); + return; + } + var pricingCommand = new FirstlegPricingClient.OrderChannelChangeRequest( + order.getTenantId(), order.getId(), command.version(), order.getReceivingWarehouseId(), + command.channelCode(), command.channelName(), order.getDestinationType(), + order.getDestinationWarehouseId(), order.getAmazonWarehouseCode(), order.getPricingTaxMode()); + ApiResponse pricingResponse = + pricingClient.applyOrderChannelChange(internalToken, pricingCommand); + if (pricingResponse == null || !pricingResponse.successful() || pricingResponse.data() == null + || pricingResponse.data().orderId() != order.getId() + || pricingResponse.data().orderVersion() != command.version() + || !command.channelCode().equals(pricingResponse.data().channelCode())) { + throw new IllegalStateException("头程渠道重新计价同步失败"); + } + } ApiResponse response = client.updateDeclaration(internalToken, command); if (response == null || !response.successful()) throw new IllegalStateException("头程申报同步失败"); repository.markOutboxPublished(event.getTenantId(), event.getId(), LocalDateTime.now()); @@ -101,6 +123,11 @@ public class OrderInboundOutboxPublisher { } } + private boolean containsChannelChange(OrderDeclarationDtos.UpdatedEvent command) { + return command.changes() != null && command.changes().stream() + .anyMatch(change -> "channelCode".equals(change.field()) || "channelName".equals(change.field())); + } + private boolean incomplete(FirstlegInboundClient.OrderApprovedInboundCommand command) { return command.orderNo() == null || command.orderNo().isBlank() || command.inboundCode() == null || command.inboundCode().isBlank() 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 7eaf38524..c95d1f3e8 100644 --- a/order/src/test/java/com/baiyue/order/service/OrderInboundOutboxPublisherTest.java +++ b/order/src/test/java/com/baiyue/order/service/OrderInboundOutboxPublisherTest.java @@ -2,6 +2,7 @@ package com.baiyue.order.service; import com.baiyue.common.core.api.ApiResponse; import com.baiyue.order.client.FirstlegInboundClient; +import com.baiyue.order.client.FirstlegPricingClient; import com.baiyue.order.entity.OutboxEventEntity; import com.baiyue.order.repository.OrderRepository; import com.fasterxml.jackson.databind.ObjectMapper; @@ -130,7 +131,7 @@ class OrderInboundOutboxPublisherTest { } @Test - void declarationUpdateEventIsDeliveredByVersionedContract() { + void ordinaryDeclarationUpdateDoesNotInvalidatePricing() { String payload = "{\"schemaVersion\":1,\"eventId\":\"evt-1\",\"tenantId\":11," + "\"orderId\":21,\"orderNo\":\"ORD-21\",\"inboundCode\":\"RK-21\"," + "\"version\":4,\"operatorId\":7,\"channelCode\":\"SEA\"," @@ -145,6 +146,29 @@ class OrderInboundOutboxPublisherTest { && "SEA".equals(command.channelCode()) && "RK-21".equals(command.inboundCode()))); verify(fixture.repository).markOutboxPublished(eq(11L), eq(41L), any(LocalDateTime.class)); verify(fixture.client, never()).create(any(), any()); + verify(fixture.pricingClient, never()).applyOrderChannelChange(any(), any()); + } + + @Test + void channelChangeAppliesPricingInvalidationBySameOrderVersionBeforeDelivery() { + String payload = "{\"schemaVersion\":1,\"eventId\":\"evt-2\",\"tenantId\":11," + + "\"orderId\":21,\"orderNo\":\"ORD-21\",\"inboundCode\":\"RK-21\"," + + "\"version\":4,\"operatorId\":7,\"channelCode\":\"SEA\"," + + "\"channelName\":\"海运\",\"channelChanged\":true,\"cargoes\":[]," + + "\"changes\":[{\"field\":\"channelCode\",\"beforeValue\":\"OLD\",\"afterValue\":\"SEA\"}]}"; + Fixture fixture = fixture(event("ORDER_DECLARATION_UPDATED_V1", 11L, 21L, payload)); + when(fixture.pricingClient.applyOrderChannelChange(eq("internal-token"), any())).thenReturn(ApiResponse.success( + new FirstlegPricingClient.OrderChannelChangeResponse( + 51L, 21L, 4L, "SEA", 61L, "UNPRICED", true, 71L))); + when(fixture.client.updateDeclaration(eq("internal-token"), any())).thenReturn(ApiResponse.success(null)); + + fixture.publisher.publishApprovedOrders(); + + verify(fixture.pricingClient).applyOrderChannelChange(eq("internal-token"), + org.mockito.ArgumentMatchers.argThat(command -> command.orderVersion() == 4L + && command.orderId() == 21L && "SEA".equals(command.channelCode()))); + verify(fixture.client).updateDeclaration(eq("internal-token"), any()); + verify(fixture.repository).markOutboxPublished(eq(11L), eq(41L), any(LocalDateTime.class)); } private Fixture fixture(OutboxEventEntity event) { @@ -154,6 +178,7 @@ class OrderInboundOutboxPublisherTest { private Fixture fixture(OutboxEventEntity event, String authoritativeTaxMode) { OrderRepository repository = mock(OrderRepository.class); FirstlegInboundClient client = mock(FirstlegInboundClient.class); + FirstlegPricingClient pricingClient = mock(FirstlegPricingClient.class); when(repository.pendingInboundEvents(50)).thenReturn(List.of(event)); if (event.getTenantId() != null && event.getAggregateId() != null) { var order = new com.baiyue.order.entity.OrderEntity(); @@ -165,6 +190,8 @@ class OrderInboundOutboxPublisherTest { order.setReceivingWarehouseId(31L); order.setReceivingWarehouseName("测试仓"); order.setDestinationType("OVERSEAS"); + order.setDestinationWarehouseId(41L); + order.setAmazonWarehouseCode("ONT8"); order.setTransportMode("SEA"); order.setTaxMode(authoritativeTaxMode); order.setPricingTaxMode("TAX_EXCLUDED"); @@ -179,9 +206,10 @@ class OrderInboundOutboxPublisherTest { OrderInboundOutboxPublisher publisher = new OrderInboundOutboxPublisher(); ReflectionTestUtils.setField(publisher, "repository", repository); ReflectionTestUtils.setField(publisher, "client", client); + ReflectionTestUtils.setField(publisher, "pricingClient", pricingClient); ReflectionTestUtils.setField(publisher, "json", new ObjectMapper()); ReflectionTestUtils.setField(publisher, "internalToken", "internal-token"); - return new Fixture(publisher, repository, client); + return new Fixture(publisher, repository, client, pricingClient); } private OutboxEventEntity event(String eventType, Long tenantId, Long aggregateId, String payload) { @@ -209,6 +237,6 @@ class OrderInboundOutboxPublisherTest { } private record Fixture(OrderInboundOutboxPublisher publisher, OrderRepository repository, - FirstlegInboundClient client) { + FirstlegInboundClient client, FirstlegPricingClient pricingClient) { } }