fix: allocate pallet locations on weight confirmation
This commit is contained in:
+8
@@ -20,6 +20,14 @@ class InboundMeasurementEvidenceOpenApiContractTest {
|
||||
Map<String, Object> detailPath = stringMap(paths.get(
|
||||
"/api/platform/v1/first-leg/inbounds/{inboundId}/measurement"));
|
||||
assertThat(detailPath).containsKey("get");
|
||||
Map<String, Object> confirmOperation = stringMap(stringMap(paths.get(
|
||||
"/api/platform/v1/first-leg/inbounds/{inboundId}/weight-results/{resultId}/confirm")).get("post"));
|
||||
assertThat(confirmOperation.get("description").toString())
|
||||
.contains("首张图片确认成功时", "原子扩容原块", "确认响应立即返回");
|
||||
Map<String, Object> submitOperation = stringMap(stringMap(paths.get(
|
||||
"/api/platform/v1/inbounds/{inboundId}/measurements/submit")).get("post"));
|
||||
assertThat(submitOperation.get("description").toString())
|
||||
.contains("只校验并复用", "不会首次分配");
|
||||
|
||||
Map<String, Object> schemas = stringMap(stringMap(document.get("components")).get("schemas"));
|
||||
Map<String, Object> detailProperties = properties(schemas, "InboundMeasurementDetail");
|
||||
|
||||
+11
-3
@@ -61,6 +61,7 @@ class InboundMeasurementServiceTest {
|
||||
@MockBean private MeasurementImageClient imageClient;
|
||||
@MockBean private WarehousePalletLocationClient warehousePallets;
|
||||
@MockBean private InboundPalletAllocationRepository palletAllocations;
|
||||
@MockBean private MeasurementLocationOccupationService locationOccupation;
|
||||
@Resource private InboundMeasurementService service;
|
||||
|
||||
@BeforeEach
|
||||
@@ -555,7 +556,7 @@ class InboundMeasurementServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void confirmingAWeightBindsItToTheNextEmptySlotWhenTheTicketAlreadyHasABlock() {
|
||||
void confirmingAWeightReturnsTheAuthoritativeSlotBoundByTheLocationOrchestrator() {
|
||||
InboundOrderEntity inbound = inbound("MEASURE_REJECTED");
|
||||
InboundWeightResultEntity weight = weight(20L, "30.000", "KG", "PENDING_CONFIRM", 0);
|
||||
when(repository.findInbound(1L, 10L)).thenReturn(inbound);
|
||||
@@ -564,11 +565,18 @@ class InboundMeasurementServiceTest {
|
||||
when(repository.listWeights(1L, 10L)).thenReturn(List.of(weight));
|
||||
when(palletAllocations.list(1L, 10L)).thenReturn(List.of(
|
||||
locationSlot(1, 801L, "A01", "RESERVED_EMPTY", null)));
|
||||
when(palletAllocations.findByWeightResult(1L, 10L, 20L)).thenReturn(
|
||||
locationSlot(1, 801L, "A01", "BOUND", 20L));
|
||||
|
||||
service.confirmWeightResult(1L, 10L, 20L, 9L, "操作员",
|
||||
InboundMeasurementResponses.WeightResult result = service.confirmWeightResult(
|
||||
1L, 10L, 20L, 9L, "操作员",
|
||||
new InboundMeasurementRequests.ConfirmWeightResultRequest(0, null, null, null));
|
||||
|
||||
verify(palletAllocations).bindNextEmpty(1L, 10L, weight);
|
||||
verify(locationOccupation).ensureForConfirmedWeight(1L, 10L, 9L, 20L, 1);
|
||||
assertThat(result.blockId()).isEqualTo(700L);
|
||||
assertThat(result.slotNo()).isEqualTo(1);
|
||||
assertThat(result.locationCode()).isEqualTo("A01");
|
||||
assertThat(result.slotStatus()).isEqualTo("BOUND");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+75
-12
@@ -57,7 +57,7 @@ class MeasurementLocationOccupationServiceTest {
|
||||
when(warehouse.confirmBlock(org.mockito.ArgumentMatchers.eq("internal-token"),
|
||||
org.mockito.ArgumentMatchers.any())).thenReturn(ApiResponse.success(block("OCCUPIED", 3)));
|
||||
|
||||
service.occupy(BUSINESS_TENANT, INBOUND, OPERATOR, MEASUREMENT, 3);
|
||||
service.ensureForConfirmedWeight(BUSINESS_TENANT, INBOUND, OPERATOR, MEASUREMENT, 3);
|
||||
|
||||
ArgumentCaptor<WarehousePalletLocationClient.BlockReserveRequest> reserve =
|
||||
ArgumentCaptor.forClass(WarehousePalletLocationClient.BlockReserveRequest.class);
|
||||
@@ -74,14 +74,14 @@ class MeasurementLocationOccupationServiceTest {
|
||||
assertThat(confirm.getValue().idempotencyKey())
|
||||
.isEqualTo(reserve.getValue().idempotencyKey() + "-confirm");
|
||||
|
||||
verify(allocations, times(2)).replaceBlock(
|
||||
verify(allocations).replaceBlock(
|
||||
org.mockito.ArgumentMatchers.eq(BUSINESS_TENANT),
|
||||
org.mockito.ArgumentMatchers.eq(INBOUND),
|
||||
org.mockito.ArgumentMatchers.eq(WAREHOUSE),
|
||||
org.mockito.ArgumentMatchers.eq(20),
|
||||
org.mockito.ArgumentMatchers.eq(new BigDecimal("1.0000")),
|
||||
org.mockito.ArgumentMatchers.any());
|
||||
verify(allocations, times(2)).bindConfirmedWeights(
|
||||
verify(allocations).bindConfirmedWeights(
|
||||
org.mockito.ArgumentMatchers.eq(BUSINESS_TENANT),
|
||||
org.mockito.ArgumentMatchers.eq(INBOUND), org.mockito.ArgumentMatchers.anyList());
|
||||
}
|
||||
@@ -96,7 +96,7 @@ class MeasurementLocationOccupationServiceTest {
|
||||
when(warehouse.confirmBlock(org.mockito.ArgumentMatchers.eq("internal-token"),
|
||||
org.mockito.ArgumentMatchers.any())).thenReturn(ApiResponse.success(block("OCCUPIED", 3)));
|
||||
|
||||
service.occupy(BUSINESS_TENANT, INBOUND, OPERATOR, MEASUREMENT, 3);
|
||||
service.ensureForConfirmedWeight(BUSINESS_TENANT, INBOUND, OPERATOR, MEASUREMENT, 3);
|
||||
|
||||
ArgumentCaptor<WarehousePalletLocationClient.BlockReserveRequest> reserve =
|
||||
ArgumentCaptor.forClass(WarehousePalletLocationClient.BlockReserveRequest.class);
|
||||
@@ -108,7 +108,7 @@ class MeasurementLocationOccupationServiceTest {
|
||||
verify(warehouse).confirmBlock(org.mockito.ArgumentMatchers.eq("internal-token"), confirm.capture());
|
||||
assertThat(confirm.getValue().businessId()).isEqualTo(INBOUND);
|
||||
|
||||
verify(allocations, times(2)).replaceBlock(
|
||||
verify(allocations).replaceBlock(
|
||||
org.mockito.ArgumentMatchers.eq(BUSINESS_TENANT),
|
||||
org.mockito.ArgumentMatchers.eq(INBOUND),
|
||||
org.mockito.ArgumentMatchers.eq(WAREHOUSE),
|
||||
@@ -129,11 +129,11 @@ class MeasurementLocationOccupationServiceTest {
|
||||
when(warehouse.confirmBlock(org.mockito.ArgumentMatchers.eq("internal-token"),
|
||||
org.mockito.ArgumentMatchers.any())).thenReturn(ApiResponse.success(block("OCCUPIED", 3)));
|
||||
|
||||
service.occupy(BUSINESS_TENANT, INBOUND, OPERATOR, MEASUREMENT, 3);
|
||||
service.ensureForConfirmedWeight(BUSINESS_TENANT, INBOUND, OPERATOR, MEASUREMENT, 3);
|
||||
|
||||
verify(warehouse).reserveBlock(org.mockito.ArgumentMatchers.eq("internal-token"),
|
||||
org.mockito.ArgumentMatchers.argThat(request -> request.requiredSlotCount() == 3));
|
||||
verify(allocations, times(2)).bindConfirmedWeights(BUSINESS_TENANT, INBOUND, twoWeights);
|
||||
verify(allocations).bindConfirmedWeights(BUSINESS_TENANT, INBOUND, twoWeights);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -143,7 +143,7 @@ class MeasurementLocationOccupationServiceTest {
|
||||
when(workflow.findOwned(BUSINESS_TENANT, INBOUND)).thenReturn(inbound());
|
||||
when(measurements.listWeights(BUSINESS_TENANT, INBOUND)).thenReturn(List.of(weight));
|
||||
|
||||
assertThatThrownBy(() -> service.occupy(BUSINESS_TENANT, INBOUND, OPERATOR, MEASUREMENT, 3))
|
||||
assertThatThrownBy(() -> service.ensureForConfirmedWeight(BUSINESS_TENANT, INBOUND, OPERATOR, MEASUREMENT, 3))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("缺少确认时间");
|
||||
|
||||
@@ -157,7 +157,7 @@ class MeasurementLocationOccupationServiceTest {
|
||||
when(workflow.findOwned(BUSINESS_TENANT, INBOUND)).thenReturn(inbound());
|
||||
when(measurements.listWeights(BUSINESS_TENANT, INBOUND)).thenReturn(List.of(weight));
|
||||
|
||||
assertThatThrownBy(() -> service.occupy(BUSINESS_TENANT, INBOUND, OPERATOR, MEASUREMENT, 3))
|
||||
assertThatThrownBy(() -> service.ensureForConfirmedWeight(BUSINESS_TENANT, INBOUND, OPERATOR, MEASUREMENT, 3))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("缺少有效净重");
|
||||
|
||||
@@ -171,7 +171,7 @@ class MeasurementLocationOccupationServiceTest {
|
||||
when(workflow.findOwned(BUSINESS_TENANT, INBOUND)).thenReturn(inbound());
|
||||
when(measurements.listWeights(BUSINESS_TENANT, INBOUND)).thenReturn(List.of(weight));
|
||||
|
||||
assertThatThrownBy(() -> service.occupy(BUSINESS_TENANT, INBOUND, OPERATOR, MEASUREMENT, 3))
|
||||
assertThatThrownBy(() -> service.ensureForConfirmedWeight(BUSINESS_TENANT, INBOUND, OPERATOR, MEASUREMENT, 3))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("缺少有效托盘");
|
||||
|
||||
@@ -191,15 +191,64 @@ class MeasurementLocationOccupationServiceTest {
|
||||
when(warehouse.releaseBlock(org.mockito.ArgumentMatchers.eq("internal-token"),
|
||||
org.mockito.ArgumentMatchers.any())).thenReturn(ApiResponse.success(block("RELEASED", 3)));
|
||||
|
||||
assertThatThrownBy(() -> service.occupy(BUSINESS_TENANT, INBOUND, OPERATOR, MEASUREMENT, 3))
|
||||
assertThatThrownBy(() -> service.ensureForConfirmedWeight(BUSINESS_TENANT, INBOUND, OPERATOR, MEASUREMENT, 3))
|
||||
.isSameAs(failure);
|
||||
|
||||
ArgumentCaptor<WarehousePalletLocationClient.BlockReleaseRequest> release =
|
||||
ArgumentCaptor.forClass(WarehousePalletLocationClient.BlockReleaseRequest.class);
|
||||
verify(warehouse).releaseBlock(org.mockito.ArgumentMatchers.eq("internal-token"), release.capture());
|
||||
assertThat(release.getValue().businessId()).isEqualTo(INBOUND);
|
||||
verify(allocations, never()).replaceBlock(org.mockito.ArgumentMatchers.anyLong(),
|
||||
org.mockito.ArgumentMatchers.anyLong(), org.mockito.ArgumentMatchers.anyLong(),
|
||||
org.mockito.ArgumentMatchers.anyInt(), org.mockito.ArgumentMatchers.any(),
|
||||
org.mockito.ArgumentMatchers.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void expandsOccupiedBlockWithoutChangingExistingSlotsWhenConfirmedCountGrows() {
|
||||
InboundOrderEntity inbound = inbound();
|
||||
List<InboundWeightResultEntity> weights = confirmedWeights();
|
||||
List<com.baiyue.firstleg.entity.InboundPalletAllocationEntity> current =
|
||||
java.util.stream.IntStream.rangeClosed(1, 2).mapToObj(MeasurementLocationOccupationServiceTest::slot)
|
||||
.toList();
|
||||
when(workflow.findOwned(BUSINESS_TENANT, INBOUND)).thenReturn(inbound);
|
||||
when(measurements.listWeights(BUSINESS_TENANT, INBOUND)).thenReturn(weights);
|
||||
when(allocations.list(BUSINESS_TENANT, INBOUND)).thenReturn(current);
|
||||
when(warehouse.expandBlock(org.mockito.ArgumentMatchers.eq("internal-token"),
|
||||
org.mockito.ArgumentMatchers.any())).thenReturn(ApiResponse.success(block("OCCUPIED", 3)));
|
||||
|
||||
service.ensureForConfirmedWeight(BUSINESS_TENANT, INBOUND, OPERATOR, 803L, 1);
|
||||
|
||||
verify(warehouse).expandBlock(org.mockito.ArgumentMatchers.eq("internal-token"),
|
||||
org.mockito.ArgumentMatchers.argThat(request -> request.blockId() == 500L
|
||||
&& request.businessId() == INBOUND && request.requiredSlotCount() == 3
|
||||
&& request.idempotencyKey().equals("measure-block-expand-88-803-1")));
|
||||
verify(allocations).replaceBlock(BUSINESS_TENANT, INBOUND, WAREHOUSE,
|
||||
20, new BigDecimal("1.0000"), block("RESERVED", 3));
|
||||
20, new BigDecimal("1.0000"), block("OCCUPIED", 3));
|
||||
verify(allocations).bindConfirmedWeights(BUSINESS_TENANT, INBOUND, weights);
|
||||
}
|
||||
|
||||
@Test
|
||||
void submitValidationReusesExistingBoundBlockWithoutWarehouseMutation() {
|
||||
List<InboundWeightResultEntity> weights = confirmedWeights().subList(0, 2);
|
||||
var first = slot(1);
|
||||
var second = slot(2);
|
||||
first.setWeightResultId(weights.get(0).getId());
|
||||
second.setWeightResultId(weights.get(1).getId());
|
||||
first.setSlotStatus("BOUND");
|
||||
second.setSlotStatus("BOUND");
|
||||
when(workflow.findOwned(BUSINESS_TENANT, INBOUND)).thenReturn(inbound());
|
||||
when(measurements.listWeights(BUSINESS_TENANT, INBOUND)).thenReturn(weights);
|
||||
when(allocations.list(BUSINESS_TENANT, INBOUND)).thenReturn(List.of(first, second));
|
||||
|
||||
service.requireOccupied(BUSINESS_TENANT, INBOUND);
|
||||
|
||||
verify(warehouse, never()).reserveBlock(org.mockito.ArgumentMatchers.anyString(),
|
||||
org.mockito.ArgumentMatchers.any());
|
||||
verify(warehouse, never()).confirmBlock(org.mockito.ArgumentMatchers.anyString(),
|
||||
org.mockito.ArgumentMatchers.any());
|
||||
verify(warehouse, never()).expandBlock(org.mockito.ArgumentMatchers.anyString(),
|
||||
org.mockito.ArgumentMatchers.any());
|
||||
}
|
||||
|
||||
private static InboundProperties properties() {
|
||||
@@ -235,6 +284,20 @@ class MeasurementLocationOccupationServiceTest {
|
||||
.toList());
|
||||
}
|
||||
|
||||
private static com.baiyue.firstleg.entity.InboundPalletAllocationEntity slot(int slotNo) {
|
||||
var value = new com.baiyue.firstleg.entity.InboundPalletAllocationEntity();
|
||||
value.setBlockId(500L);
|
||||
value.setSlotNo(slotNo);
|
||||
value.setWarehouseId(WAREHOUSE);
|
||||
value.setReservationId(600L + slotNo);
|
||||
value.setLocationId(700L + slotNo);
|
||||
value.setLocationCode("A-00" + slotNo);
|
||||
value.setSharedOverflow(false);
|
||||
value.setAllocationStatus("CONFIRMED");
|
||||
value.setSlotStatus("RESERVED_EMPTY");
|
||||
return value;
|
||||
}
|
||||
|
||||
private static List<InboundWeightResultEntity> confirmedWeights() {
|
||||
return java.util.stream.LongStream.rangeClosed(1, 3).mapToObj(index -> {
|
||||
InboundWeightResultEntity weight = new InboundWeightResultEntity();
|
||||
|
||||
+2
-2
@@ -35,7 +35,7 @@ class MeasurementSubmissionServiceTest {
|
||||
|
||||
assertThat(service.submit(10001L, 88L, 9L, request)).isSameAs(submitted);
|
||||
|
||||
verify(locations).occupy(10001L, 88L, 9L, 30L, 3);
|
||||
verify(locations).requireOccupied(10001L, 88L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -45,6 +45,6 @@ class MeasurementSubmissionServiceTest {
|
||||
30L, 3, "MEASURE_PENDING", "PENDING", "STANDARD", null, null, null);
|
||||
when(measurements.submit(10001L, 88L, 9L, request)).thenReturn(submitted);
|
||||
assertThat(service.submit(10001L, 88L, 9L, request)).isSameAs(submitted);
|
||||
verify(locations).occupy(10001L, 88L, 9L, 30L, 3);
|
||||
verify(locations).requireOccupied(10001L, 88L);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user