优化
This commit is contained in:
@@ -18,6 +18,11 @@ public interface FirstlegPricingClient {
|
|||||||
@RequestHeader("X-Internal-Service-Token") String token,
|
@RequestHeader("X-Internal-Service-Token") String token,
|
||||||
@RequestBody OrderChannelChangeRequest request);
|
@RequestBody OrderChannelChangeRequest request);
|
||||||
|
|
||||||
|
@PostMapping("/order-channel-changes/apply")
|
||||||
|
ApiResponse<OrderChannelChangeResponse> applyOrderChannelChange(
|
||||||
|
@RequestHeader("X-Internal-Service-Token") String token,
|
||||||
|
@RequestBody OrderChannelChangeRequest request);
|
||||||
|
|
||||||
record ChannelValidationRequest(long tenantId, long originWarehouseId, String channelCode,
|
record ChannelValidationRequest(long tenantId, long originWarehouseId, String channelCode,
|
||||||
String destinationType, boolean requireInboundCodePrefix) {}
|
String destinationType, boolean requireInboundCodePrefix) {}
|
||||||
record ChannelValidationResponse(String channelCode, String inboundCodePrefix) {}
|
record ChannelValidationResponse(String channelCode, String inboundCodePrefix) {}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import jakarta.annotation.Resource;
|
|||||||
|
|
||||||
import com.baiyue.common.core.api.ApiResponse;
|
import com.baiyue.common.core.api.ApiResponse;
|
||||||
import com.baiyue.order.client.FirstlegInboundClient;
|
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.entity.OutboxEventEntity;
|
||||||
import com.baiyue.order.dto.OrderDeclarationDtos;
|
import com.baiyue.order.dto.OrderDeclarationDtos;
|
||||||
import com.baiyue.order.repository.OrderRepository;
|
import com.baiyue.order.repository.OrderRepository;
|
||||||
@@ -21,6 +23,7 @@ import java.time.LocalDateTime;
|
|||||||
public class OrderInboundOutboxPublisher {
|
public class OrderInboundOutboxPublisher {
|
||||||
@Resource private OrderRepository repository;
|
@Resource private OrderRepository repository;
|
||||||
@Resource private FirstlegInboundClient client;
|
@Resource private FirstlegInboundClient client;
|
||||||
|
@Resource private FirstlegPricingClient pricingClient;
|
||||||
@Resource private ObjectMapper json;
|
@Resource private ObjectMapper json;
|
||||||
@Value("${baiyue.security.internal-service-token}") private String internalToken;
|
@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");
|
failPermanently(event, "declaration event ownership does not match payload");
|
||||||
return;
|
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<FirstlegPricingClient.OrderChannelChangeResponse> 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<Void> response = client.updateDeclaration(internalToken, command);
|
ApiResponse<Void> response = client.updateDeclaration(internalToken, command);
|
||||||
if (response == null || !response.successful()) throw new IllegalStateException("头程申报同步失败");
|
if (response == null || !response.successful()) throw new IllegalStateException("头程申报同步失败");
|
||||||
repository.markOutboxPublished(event.getTenantId(), event.getId(), LocalDateTime.now());
|
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) {
|
private boolean incomplete(FirstlegInboundClient.OrderApprovedInboundCommand command) {
|
||||||
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()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.baiyue.order.service;
|
|||||||
|
|
||||||
import com.baiyue.common.core.api.ApiResponse;
|
import com.baiyue.common.core.api.ApiResponse;
|
||||||
import com.baiyue.order.client.FirstlegInboundClient;
|
import com.baiyue.order.client.FirstlegInboundClient;
|
||||||
|
import com.baiyue.order.client.FirstlegPricingClient;
|
||||||
import com.baiyue.order.entity.OutboxEventEntity;
|
import com.baiyue.order.entity.OutboxEventEntity;
|
||||||
import com.baiyue.order.repository.OrderRepository;
|
import com.baiyue.order.repository.OrderRepository;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
@@ -130,7 +131,7 @@ class OrderInboundOutboxPublisherTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void declarationUpdateEventIsDeliveredByVersionedContract() {
|
void ordinaryDeclarationUpdateDoesNotInvalidatePricing() {
|
||||||
String payload = "{\"schemaVersion\":1,\"eventId\":\"evt-1\",\"tenantId\":11,"
|
String payload = "{\"schemaVersion\":1,\"eventId\":\"evt-1\",\"tenantId\":11,"
|
||||||
+ "\"orderId\":21,\"orderNo\":\"ORD-21\",\"inboundCode\":\"RK-21\","
|
+ "\"orderId\":21,\"orderNo\":\"ORD-21\",\"inboundCode\":\"RK-21\","
|
||||||
+ "\"version\":4,\"operatorId\":7,\"channelCode\":\"SEA\","
|
+ "\"version\":4,\"operatorId\":7,\"channelCode\":\"SEA\","
|
||||||
@@ -145,6 +146,29 @@ class OrderInboundOutboxPublisherTest {
|
|||||||
&& "SEA".equals(command.channelCode()) && "RK-21".equals(command.inboundCode())));
|
&& "SEA".equals(command.channelCode()) && "RK-21".equals(command.inboundCode())));
|
||||||
verify(fixture.repository).markOutboxPublished(eq(11L), eq(41L), any(LocalDateTime.class));
|
verify(fixture.repository).markOutboxPublished(eq(11L), eq(41L), any(LocalDateTime.class));
|
||||||
verify(fixture.client, never()).create(any(), any());
|
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) {
|
private Fixture fixture(OutboxEventEntity event) {
|
||||||
@@ -154,6 +178,7 @@ class OrderInboundOutboxPublisherTest {
|
|||||||
private Fixture fixture(OutboxEventEntity event, String authoritativeTaxMode) {
|
private Fixture fixture(OutboxEventEntity event, String authoritativeTaxMode) {
|
||||||
OrderRepository repository = mock(OrderRepository.class);
|
OrderRepository repository = mock(OrderRepository.class);
|
||||||
FirstlegInboundClient client = mock(FirstlegInboundClient.class);
|
FirstlegInboundClient client = mock(FirstlegInboundClient.class);
|
||||||
|
FirstlegPricingClient pricingClient = mock(FirstlegPricingClient.class);
|
||||||
when(repository.pendingInboundEvents(50)).thenReturn(List.of(event));
|
when(repository.pendingInboundEvents(50)).thenReturn(List.of(event));
|
||||||
if (event.getTenantId() != null && event.getAggregateId() != null) {
|
if (event.getTenantId() != null && event.getAggregateId() != null) {
|
||||||
var order = new com.baiyue.order.entity.OrderEntity();
|
var order = new com.baiyue.order.entity.OrderEntity();
|
||||||
@@ -165,6 +190,8 @@ class OrderInboundOutboxPublisherTest {
|
|||||||
order.setReceivingWarehouseId(31L);
|
order.setReceivingWarehouseId(31L);
|
||||||
order.setReceivingWarehouseName("测试仓");
|
order.setReceivingWarehouseName("测试仓");
|
||||||
order.setDestinationType("OVERSEAS");
|
order.setDestinationType("OVERSEAS");
|
||||||
|
order.setDestinationWarehouseId(41L);
|
||||||
|
order.setAmazonWarehouseCode("ONT8");
|
||||||
order.setTransportMode("SEA");
|
order.setTransportMode("SEA");
|
||||||
order.setTaxMode(authoritativeTaxMode);
|
order.setTaxMode(authoritativeTaxMode);
|
||||||
order.setPricingTaxMode("TAX_EXCLUDED");
|
order.setPricingTaxMode("TAX_EXCLUDED");
|
||||||
@@ -179,9 +206,10 @@ class OrderInboundOutboxPublisherTest {
|
|||||||
OrderInboundOutboxPublisher publisher = new OrderInboundOutboxPublisher();
|
OrderInboundOutboxPublisher publisher = new OrderInboundOutboxPublisher();
|
||||||
ReflectionTestUtils.setField(publisher, "repository", repository);
|
ReflectionTestUtils.setField(publisher, "repository", repository);
|
||||||
ReflectionTestUtils.setField(publisher, "client", client);
|
ReflectionTestUtils.setField(publisher, "client", client);
|
||||||
|
ReflectionTestUtils.setField(publisher, "pricingClient", pricingClient);
|
||||||
ReflectionTestUtils.setField(publisher, "json", new ObjectMapper());
|
ReflectionTestUtils.setField(publisher, "json", new ObjectMapper());
|
||||||
ReflectionTestUtils.setField(publisher, "internalToken", "internal-token");
|
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) {
|
private OutboxEventEntity event(String eventType, Long tenantId, Long aggregateId, String payload) {
|
||||||
@@ -209,6 +237,6 @@ class OrderInboundOutboxPublisherTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private record Fixture(OrderInboundOutboxPublisher publisher, OrderRepository repository,
|
private record Fixture(OrderInboundOutboxPublisher publisher, OrderRepository repository,
|
||||||
FirstlegInboundClient client) {
|
FirstlegInboundClient client, FirstlegPricingClient pricingClient) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user