fix(firstleg): retry chained remainder settlement
This commit is contained in:
@@ -13,6 +13,7 @@ import com.baiyue.firstleg.repository.InboundPalletAllocationRepository;
|
||||
import com.baiyue.firstleg.repository.LoadingPickRepository;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InOrder;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
@@ -27,6 +28,7 @@ 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.inOrder;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -243,6 +245,51 @@ class PdaPickSettlementServiceTest {
|
||||
anyLong(), anyString(), anyString(), anyInt(), anyInt(), anyString(), anyLong(), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void consumesSecondSliceBeforeCreatingSingleNextRemainderFromRootSequence() {
|
||||
LoadingPickItemEntity item = item(12L, 22L, 32L, 42L, 12, 5, "PARTIAL");
|
||||
resolvedChild(item, 32L, 42L, 62L, 52L, 12, 2);
|
||||
when(splits.nextSplitSequence(10001L, 31L)).thenReturn(2);
|
||||
childWrites(32L, 42L, 62L, 52L, 5, 2);
|
||||
when(warehouse.settle(eq("internal-token"), any())).thenReturn(ApiResponse.success(
|
||||
new WarehousePalletLocationClient.SettlementResult(List.of(), List.of(transfer(1, 501L, 7)))));
|
||||
|
||||
service.settle(1L, 9L, "operator");
|
||||
|
||||
InOrder order = inOrder(splits);
|
||||
order.verify(splits).markSourceLoaded(10001L, 32L, 5, 9L, 2);
|
||||
order.verify(splits).touchRootSplit(10001L, 31L, 9L, 4);
|
||||
order.verify(splits).cloneChildWithPurpose(eq(10001L), eq(32L), eq(31L), anyLong(),
|
||||
eq(1002L), eq("ORDER-REMAINDER"), eq("IN-001-2"), eq(7), eq(2),
|
||||
eq("REMAINDER"), eq(9L), eq("operator"));
|
||||
verify(splits).insertBatchWithChildren(anyLong(), eq(10001L), eq(31L), eq("PDA-SPLIT-1-32"),
|
||||
eq(32L), eq("pda-task-1"), eq(4), eq(12),
|
||||
org.mockito.ArgumentMatchers.isNull(), anyLong(), eq(5), eq(7), eq(701L), eq(801L),
|
||||
anyString(), eq(9L), eq("operator"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void retryAfterRemoteOrderSuccessCanFinishLocalThirdSliceWithoutNestedCode() {
|
||||
LoadingPickItemEntity item = item(13L, 23L, 33L, 43L, 9, 4, "PARTIAL");
|
||||
resolvedChild(item, 33L, 43L, 63L, 53L, 9, 3);
|
||||
when(splits.nextSplitSequence(10001L, 31L)).thenReturn(3);
|
||||
childWrites(33L, 43L, 63L, 53L, 4, 3);
|
||||
when(splits.markSourceLoaded(10001L, 33L, 4, 9L, 3)).thenReturn(0, 1);
|
||||
when(warehouse.settle(eq("internal-token"), any())).thenReturn(ApiResponse.success(
|
||||
new WarehousePalletLocationClient.SettlementResult(List.of(), List.of(transfer(1, 501L, 5)))));
|
||||
|
||||
assertThatThrownBy(() -> service.settle(1L, 9L, "operator"))
|
||||
.hasMessageContaining("来源分片状态已被并发修改");
|
||||
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")
|
||||
&& request.operationId().equals("PDA-SPLIT-1-33")));
|
||||
verify(splits).cloneChildWithPurpose(eq(10001L), eq(33L), eq(31L), anyLong(),
|
||||
eq(1002L), eq("ORDER-REMAINDER"), eq("IN-001-3"), eq(5), eq(3),
|
||||
eq("REMAINDER"), eq(9L), eq("operator"));
|
||||
}
|
||||
|
||||
private void resolved(List<LoadingPickItemEntity> items) {
|
||||
when(picks.allTaskItemsResolved(1L)).thenReturn(true);
|
||||
when(picks.items(1L)).thenReturn(items);
|
||||
@@ -255,6 +302,47 @@ class PdaPickSettlementServiceTest {
|
||||
stubOrderSplitSuccess();
|
||||
}
|
||||
|
||||
private void resolvedChild(LoadingPickItemEntity item, long inboundId, long detailId,
|
||||
long measurementId, long measurementItemId,
|
||||
int packageCount, int sourceVersion) {
|
||||
resolved(List.of(item));
|
||||
when(splits.lineage(inboundId)).thenReturn(new InboundSplitSettlementMapper.Lineage(
|
||||
inboundId, 10001L, "CHILD", 31L, "REMAINDER", "NOT_SPLIT"));
|
||||
when(splits.lockParent(31L)).thenReturn(new ParentRow(31L, 10001L, 901L, "ORDER-001",
|
||||
"IN-001", "MASTER", 31L, "LOADING", 5, 5, "FULLY_SPLIT", 4));
|
||||
when(splits.lockParent(inboundId)).thenReturn(new ParentRow(inboundId, 10001L, 902L,
|
||||
"ORDER-REMAINDER", "IN-001-" + (sourceVersion - 1), "CHILD", 31L,
|
||||
"INBOUNDED", packageCount, packageCount, "NOT_SPLIT", sourceVersion));
|
||||
when(splits.lockLatestApprovedItemsForInbound(10001L, inboundId))
|
||||
.thenReturn(List.of(new MeasurementItemRow(measurementItemId, measurementId, detailId, packageCount)));
|
||||
when(allocations.lockConfirmed(10001L, inboundId)).thenAnswer(invocation ->
|
||||
List.of(allocation(1L, 1, 501L, packageCount)));
|
||||
}
|
||||
|
||||
private void childWrites(long inboundId, long detailId, long measurementId,
|
||||
long measurementItemId, int pickedPackageCount, int sourceVersion) {
|
||||
when(splits.cloneChildWithPurpose(eq(10001L), eq(inboundId), eq(31L), anyLong(), anyLong(),
|
||||
anyString(), anyString(), anyInt(), anyInt(), eq("REMAINDER"), eq(9L), eq("operator"))).thenReturn(1);
|
||||
when(splits.insertBatchWithChildren(anyLong(), eq(10001L), eq(31L), anyString(), eq(inboundId),
|
||||
anyString(), eq(4), anyInt(), org.mockito.ArgumentMatchers.isNull(), anyLong(), anyInt(), anyInt(),
|
||||
eq(701L), eq(801L), anyString(), eq(9L), eq("operator"))).thenReturn(1);
|
||||
when(splits.cloneMeasurement(eq(10001L), eq(inboundId), eq(measurementId), anyLong(), anyLong(),
|
||||
anyLong(), anyInt(), eq(9L))).thenReturn(1);
|
||||
when(splits.insertSplitItem(anyLong(), eq(10001L), anyLong(), eq(31L), anyLong(), anyInt(),
|
||||
eq(detailId), eq(measurementId), eq(measurementItemId), anyInt())).thenReturn(1);
|
||||
when(splits.cloneMeasurementItem(eq(10001L), eq(measurementId), eq(measurementItemId), anyLong(),
|
||||
anyLong(), anyLong(), anyInt(), anyInt())).thenReturn(1);
|
||||
when(splits.markSourceLoaded(10001L, inboundId, pickedPackageCount, 9L, sourceVersion)).thenReturn(1);
|
||||
when(splits.touchRootSplit(10001L, 31L, 9L, 4)).thenReturn(1);
|
||||
when(splits.applyBatch(eq(10001L), anyLong())).thenReturn(1);
|
||||
when(splits.updateMeasurementItemForLoaded(eq(10001L), eq(measurementItemId), anyInt())).thenReturn(1);
|
||||
when(splits.updateMeasurementForLoaded(eq(10001L), eq(inboundId), eq(measurementId), anyInt())).thenReturn(1);
|
||||
when(splits.normalizeChildMetrics(eq(10001L), anyLong(), anyLong())).thenReturn(1);
|
||||
when(splits.insertHistory(anyLong(), eq(10001L), anyLong(), anyString(), anyString(), eq(9L),
|
||||
eq("operator"), anyString())).thenReturn(1);
|
||||
when(splits.convergePartialPlanItem(anyLong(), eq(9L), eq("operator"))).thenReturn(1);
|
||||
}
|
||||
|
||||
private void stubOrderSplitSuccess() {
|
||||
when(orderSplits.split(eq("internal-token"), eq(9L), any())).thenAnswer(invocation -> {
|
||||
OrderFulfillmentSplitClient.CreateRequest request = invocation.getArgument(2);
|
||||
|
||||
Reference in New Issue
Block a user