feat(phase3): connect PDA settlement to real child orders
This commit is contained in:
+25
@@ -0,0 +1,25 @@
|
||||
package com.baiyue.firstleg.client;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class OrderFulfillmentSplitClientContractTest {
|
||||
@Test
|
||||
void usesAuthenticatedOrderInternalSplitContract() throws Exception {
|
||||
FeignClient client = OrderFulfillmentSplitClient.class.getAnnotation(FeignClient.class);
|
||||
var method = OrderFulfillmentSplitClient.class.getMethod("split", String.class, long.class,
|
||||
OrderFulfillmentSplitClient.CreateRequest.class);
|
||||
|
||||
assertThat(client.path()).isEqualTo("/internal/order/v1/fulfillment-splits");
|
||||
assertThat(method.getAnnotation(PostMapping.class)).isNotNull();
|
||||
assertThat(method.getParameterAnnotations()[0][0]).isInstanceOf(RequestHeader.class);
|
||||
assertThat(((RequestHeader) method.getParameterAnnotations()[0][0]).value())
|
||||
.isEqualTo("X-Internal-Service-Token");
|
||||
assertThat(((RequestHeader) method.getParameterAnnotations()[1][0]).value())
|
||||
.isEqualTo("X-Operator-Id");
|
||||
}
|
||||
}
|
||||
+5
-2
@@ -68,9 +68,12 @@ class PdaSplitSettlementSqlContractTest {
|
||||
.contains("#{containerBookingId}");
|
||||
|
||||
String clone = InboundSplitSettlementMapper.class.getMethod("cloneChildWithPurpose",
|
||||
long.class, long.class, long.class, long.class, String.class, int.class,
|
||||
long.class, long.class, long.class, long.class, long.class, String.class,
|
||||
String.class, int.class,
|
||||
int.class, String.class, long.class, String.class)
|
||||
.getAnnotation(Insert.class).value()[0];
|
||||
assertThat(clone).contains("CASE WHEN #{splitPurpose}='LOADED' THEN 'LOADING' ELSE 'INBOUNDED' END");
|
||||
assertThat(clone)
|
||||
.contains("#{childOrderId},#{childOrderNo}")
|
||||
.contains("CASE WHEN #{splitPurpose}='LOADED' THEN 'LOADING' ELSE 'INBOUNDED' END");
|
||||
}
|
||||
}
|
||||
|
||||
+71
-8
@@ -2,6 +2,7 @@ package com.baiyue.firstleg.service;
|
||||
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.firstleg.client.WarehousePalletLocationClient;
|
||||
import com.baiyue.firstleg.client.OrderFulfillmentSplitClient;
|
||||
import com.baiyue.firstleg.entity.InboundPalletAllocationEntity;
|
||||
import com.baiyue.firstleg.entity.LoadingPickItemEntity;
|
||||
import com.baiyue.firstleg.entity.LoadingPickTaskEntity;
|
||||
@@ -19,12 +20,14 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@@ -35,6 +38,7 @@ class PdaPickSettlementServiceTest {
|
||||
@MockBean private InboundSplitSettlementMapper splits;
|
||||
@MockBean private InboundPalletAllocationRepository allocations;
|
||||
@MockBean private WarehousePalletLocationClient warehouse;
|
||||
@MockBean private OrderFulfillmentSplitClient orderSplits;
|
||||
@Resource private PdaPickSettlementService service;
|
||||
|
||||
@Test
|
||||
@@ -48,8 +52,8 @@ class PdaPickSettlementServiceTest {
|
||||
.thenReturn(List.of(new MeasurementItemRow(51L, 61L, 41L, 20)));
|
||||
when(splits.lockLatestApprovedItemsForInbound(10001L, 31L))
|
||||
.thenReturn(List.of(new MeasurementItemRow(51L, 61L, 41L, 20)));
|
||||
when(allocations.lockConfirmed(10001L, 31L))
|
||||
.thenReturn(List.of(allocation(1L, 1, 501L, 10), allocation(2L, 2, 502L, 10)));
|
||||
when(allocations.lockConfirmed(10001L, 31L)).thenAnswer(invocation ->
|
||||
List.of(allocation(1L, 1, 501L, 10), allocation(2L, 2, 502L, 10)));
|
||||
when(warehouse.settle(eq("internal-token"), any())).thenReturn(ApiResponse.success(
|
||||
new WarehousePalletLocationClient.SettlementResult(List.of(), List.of(
|
||||
transfer(1, 501L, 2), transfer(2, 502L, 10)))));
|
||||
@@ -57,9 +61,11 @@ class PdaPickSettlementServiceTest {
|
||||
|
||||
service.settle(1L, 9L, "operator");
|
||||
|
||||
verify(splits).cloneChildWithPurpose(eq(10001L), eq(31L), eq(31L), anyLong(), anyString(), eq(8),
|
||||
verify(splits).cloneChildWithPurpose(eq(10001L), eq(31L), eq(31L), anyLong(),
|
||||
eq(1001L), eq("ORDER-LOADED"), anyString(), eq(8),
|
||||
anyInt(), eq("LOADED"), eq(9L), eq("operator"));
|
||||
verify(splits).cloneChildWithPurpose(eq(10001L), eq(31L), eq(31L), anyLong(), anyString(), eq(12),
|
||||
verify(splits).cloneChildWithPurpose(eq(10001L), eq(31L), eq(31L), anyLong(),
|
||||
eq(1002L), eq("ORDER-REMAINDER"), anyString(), eq(12),
|
||||
anyInt(), eq("REMAINDER"), eq(9L), eq("operator"));
|
||||
verify(allocations).consume(10001L, 31L, 1L, 8);
|
||||
verify(warehouse).settle(eq("internal-token"), org.mockito.ArgumentMatchers.argThat(request ->
|
||||
@@ -93,8 +99,8 @@ class PdaPickSettlementServiceTest {
|
||||
when(splits.lockParent(31L)).thenReturn(parent(20));
|
||||
when(splits.lockLatestApprovedItemsForInbound(10001L, 31L))
|
||||
.thenReturn(List.of(new MeasurementItemRow(51L, 61L, 41L, 20)));
|
||||
when(allocations.lockConfirmed(10001L, 31L))
|
||||
.thenReturn(List.of(allocation(1L, 1, 501L, 10), allocation(2L, 2, 502L, 10)));
|
||||
when(allocations.lockConfirmed(10001L, 31L)).thenAnswer(invocation ->
|
||||
List.of(allocation(1L, 1, 501L, 10), allocation(2L, 2, 502L, 10)));
|
||||
when(warehouse.settle(eq("internal-token"), any())).thenReturn(ApiResponse.success(
|
||||
new WarehousePalletLocationClient.SettlementResult(List.of(released(91L, 501L)),
|
||||
List.of(transfer(2, 502L, 10)))));
|
||||
@@ -126,7 +132,8 @@ class PdaPickSettlementServiceTest {
|
||||
|
||||
verify(allocations).consume(10001L, 31L, 1L, 10);
|
||||
verify(allocations).consume(10001L, 31L, 2L, 10);
|
||||
verify(splits).cloneChildWithPurpose(eq(10001L), eq(31L), eq(31L), anyLong(), anyString(), eq(20),
|
||||
verify(splits).cloneChildWithPurpose(eq(10001L), eq(31L), eq(31L), anyLong(),
|
||||
eq(1001L), eq("ORDER-LOADED"), anyString(), eq(20),
|
||||
anyInt(), eq("LOADED"), eq(9L), eq("operator"));
|
||||
verify(warehouse).settle(eq("internal-token"), org.mockito.ArgumentMatchers.argThat(request ->
|
||||
request.targetBusinessId() == 0L && request.targetPackageCount() == 0
|
||||
@@ -155,6 +162,44 @@ class PdaPickSettlementServiceTest {
|
||||
assertThat(resolution.skippedPlanItemIds()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void orderSplitFailureStopsLocalChildCreationAndCanBeRetriedWithSameKey() {
|
||||
LoadingPickItemEntity item = item(11L, 21L, 31L, 41L, 20, 20, "PICKED");
|
||||
resolved(List.of(item));
|
||||
when(splits.lockParent(31L)).thenReturn(parent(20));
|
||||
when(splits.lockLatestApprovedItemsForInbound(10001L, 31L))
|
||||
.thenReturn(List.of(new MeasurementItemRow(51L, 61L, 41L, 20)));
|
||||
when(allocations.lockConfirmed(10001L, 31L)).thenAnswer(invocation ->
|
||||
List.of(allocation(1L, 1, 501L, 10), allocation(2L, 2, 502L, 10)));
|
||||
when(orderSplits.split(eq("internal-token"), eq(9L), any())).thenReturn(
|
||||
ApiResponse.failure(com.baiyue.common.core.api.ResultCode.SERVICE_UNAVAILABLE,
|
||||
"order unavailable", null, null));
|
||||
|
||||
assertThatThrownBy(() -> service.settle(1L, 9L, "operator"))
|
||||
.hasMessageContaining("order unavailable");
|
||||
|
||||
verify(orderSplits).split(eq("internal-token"), eq(9L),
|
||||
org.mockito.ArgumentMatchers.argThat(request ->
|
||||
request.idempotencyKey().equals("pda-task-1")
|
||||
&& request.operationId().equals("PDA-SPLIT-1-31")));
|
||||
verify(splits, never()).cloneChildWithPurpose(anyLong(), anyLong(), anyLong(), anyLong(),
|
||||
anyLong(), anyString(), anyString(), anyInt(), anyInt(), anyString(), anyLong(), anyString());
|
||||
|
||||
stubOrderSplitSuccess();
|
||||
writes(0, 20);
|
||||
when(warehouse.settle(eq("internal-token"), any())).thenReturn(ApiResponse.success(
|
||||
new WarehousePalletLocationClient.SettlementResult(
|
||||
List.of(released(91L, 501L), released(92L, 502L)), List.of())));
|
||||
|
||||
service.settle(1L, 9L, "operator");
|
||||
|
||||
verify(orderSplits, times(2)).split(eq("internal-token"), eq(9L),
|
||||
org.mockito.ArgumentMatchers.argThat(request -> request.idempotencyKey().equals("pda-task-1")));
|
||||
verify(splits).cloneChildWithPurpose(eq(10001L), eq(31L), eq(31L), anyLong(),
|
||||
eq(1001L), eq("ORDER-LOADED"), anyString(), eq(20), anyInt(), eq("LOADED"),
|
||||
eq(9L), eq("operator"));
|
||||
}
|
||||
|
||||
private void resolved(List<LoadingPickItemEntity> items) {
|
||||
when(picks.allTaskItemsResolved(1L)).thenReturn(true);
|
||||
when(picks.items(1L)).thenReturn(items);
|
||||
@@ -164,11 +209,29 @@ class PdaPickSettlementServiceTest {
|
||||
task.setId(1L); task.setLoadingPlanId(701L);
|
||||
when(picks.task(1L)).thenReturn(task);
|
||||
when(splits.containerBookingId(701L)).thenReturn(801L);
|
||||
stubOrderSplitSuccess();
|
||||
}
|
||||
|
||||
private void stubOrderSplitSuccess() {
|
||||
when(orderSplits.split(eq("internal-token"), eq(9L), any())).thenAnswer(invocation -> {
|
||||
OrderFulfillmentSplitClient.CreateRequest request = invocation.getArgument(2);
|
||||
OrderFulfillmentSplitClient.Child loaded = new OrderFulfillmentSplitClient.Child(
|
||||
1001L, "ORDER-LOADED", 1, "LOADED", request.loadedPackageCount(),
|
||||
request.sourceOrderId(), request.loadingPlanId(), request.containerBookingId());
|
||||
OrderFulfillmentSplitClient.Child remainder = request.remainingPackageCount() == 0 ? null
|
||||
: new OrderFulfillmentSplitClient.Child(1002L, "ORDER-REMAINDER", 2, "REMAINDER",
|
||||
request.remainingPackageCount(), request.sourceOrderId(), request.loadingPlanId(),
|
||||
request.containerBookingId());
|
||||
return ApiResponse.success(new OrderFulfillmentSplitClient.Response(
|
||||
request.parentOrderId(), "ORDER-001", 2, loaded, remainder,
|
||||
request.sourcePackageCount(), request.loadedPackageCount(), request.remainingPackageCount(),
|
||||
request.loadingPlanId(), request.containerBookingId(), request.idempotencyKey()));
|
||||
});
|
||||
}
|
||||
|
||||
private void writes(int childCount, int pickedCount) {
|
||||
when(splits.nextSplitSequence(10001L, 31L)).thenReturn(1);
|
||||
when(splits.cloneChildWithPurpose(eq(10001L), eq(31L), eq(31L), anyLong(), anyString(), anyInt(),
|
||||
when(splits.cloneChildWithPurpose(eq(10001L), eq(31L), eq(31L), anyLong(), anyLong(), anyString(), anyString(), anyInt(),
|
||||
anyInt(), anyString(), eq(9L), eq("operator"))).thenReturn(1);
|
||||
when(splits.insertBatchWithChildren(anyLong(), eq(10001L), eq(31L), anyString(), eq(31L), anyString(), eq(3),
|
||||
anyInt(), anyLong(), any(), anyInt(), anyInt(), eq(701L), eq(801L), anyString(), eq(9L), eq("operator"))).thenReturn(1);
|
||||
|
||||
Reference in New Issue
Block a user