fix(lastleg): complete receiving by container
This commit is contained in:
+22
@@ -4,15 +4,18 @@ import com.baiyue.lastleg.inboundplan.pda.controller.ReceivingContainerControlle
|
||||
import com.baiyue.lastleg.inboundplan.pda.dto.ReceivingContainerRequests;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.RecordComponent;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class ReceivingContainerContractTest {
|
||||
@@ -27,6 +30,13 @@ class ReceivingContainerContractTest {
|
||||
.map(RecordComponent::getName).collect(Collectors.toSet());
|
||||
assertTrue(java.util.Collections.disjoint(fields, FORBIDDEN_CALLER_SCOPE), type.getSimpleName());
|
||||
}
|
||||
for (Class<?> type : Set.of(ReceivingContainerRequests.Action.class, ReceivingContainerRequests.Sign.class,
|
||||
ReceivingContainerRequests.BulkReceive.class, ReceivingContainerRequests.CartonReceive.class)) {
|
||||
RecordComponent key = Arrays.stream(type.getRecordComponents())
|
||||
.filter(component -> "idempotencyKey".equals(component.getName())).findFirst().orElseThrow();
|
||||
Size size = key.getAccessor().getAnnotation(Size.class);
|
||||
assertTrue(size != null && size.max() == 255, type.getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -54,6 +64,10 @@ class ReceivingContainerContractTest {
|
||||
assertTrue(schema.contains("unique key uk_ll_receiving_active_container(active_container_key)"));
|
||||
assertTrue(schema.contains("create table if not exists ll_receiving_container_operation"));
|
||||
assertTrue(schema.contains("wait_sign、signed、receiving、completed"));
|
||||
assertTrue(unified.contains("customer_id is null or l.customer_id=''"));
|
||||
assertTrue(unified.contains("customer_id is null or v.customer_id=''"));
|
||||
assertTrue(unified.contains("customer_id is null or a.customer_id=''"));
|
||||
assertTrue(unified.contains("signal sqlstate '45000'"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -65,6 +79,13 @@ class ReceivingContainerContractTest {
|
||||
assertTrue(yaml.contains(prefix + "/current/receipts/carton:"));
|
||||
assertTrue(yaml.contains(prefix + "/current/complete:"));
|
||||
assertFalse(yaml.contains("/api/pda/v1/last-leg/receiving-containers/{planNo}"));
|
||||
String receivingPaths = yaml.substring(yaml.indexOf(prefix), yaml.indexOf("/api/platform/v1/last-leg/orders:"));
|
||||
List<String> successResponses = receivingPaths.lines().map(String::trim)
|
||||
.filter(line -> line.startsWith("responses: { '200':")).toList();
|
||||
assertEquals(11, successResponses.size());
|
||||
assertTrue(successResponses.stream().allMatch(line ->
|
||||
line.contains("content: { application/json: { schema: { $ref: '#/components/schemas/Receiving")));
|
||||
assertTrue(receivingPaths.contains("返回 WAIT_SIGN、SIGNED 或 RECEIVING 当前柜"));
|
||||
}
|
||||
|
||||
private String resource(String name) throws IOException {
|
||||
@@ -79,4 +100,5 @@ class ReceivingContainerContractTest {
|
||||
int end = schema.indexOf(") engine=innodb", start);
|
||||
return start < 0 || end < 0 ? "" : schema.substring(start, end);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+12
-1
@@ -5,6 +5,8 @@ import com.baiyue.lastleg.inboundplan.entity.InboundPlanLine;
|
||||
import com.baiyue.lastleg.inboundplan.entity.InboundPlanPalletAllocation;
|
||||
import com.baiyue.lastleg.inboundplan.pda.entity.InboundPdaTask;
|
||||
import com.baiyue.lastleg.inboundplan.pda.repository.PdaWorkflowRepository;
|
||||
import com.baiyue.lastleg.entity.LastlegOrder;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -15,9 +17,11 @@ import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
@@ -28,6 +32,7 @@ class InboundPdaContainerCompletionTest {
|
||||
@Mock PdaWorkflowRepository repository;
|
||||
@Mock InboundStockReceiptService stockReceipts;
|
||||
@Mock InboundUnloadCompletedOutboxService outbox;
|
||||
@Mock LastlegOrderMapper orders;
|
||||
private InboundPdaWorkflowService service;
|
||||
|
||||
@BeforeEach
|
||||
@@ -36,6 +41,7 @@ class InboundPdaContainerCompletionTest {
|
||||
ReflectionTestUtils.setField(service, "repository", repository);
|
||||
ReflectionTestUtils.setField(service, "stockReceipts", stockReceipts);
|
||||
ReflectionTestUtils.setField(service, "unloadCompletedOutbox", outbox);
|
||||
ReflectionTestUtils.setField(service, "orders", orders);
|
||||
ReflectionTestUtils.setField(service, "objectMapper", new ObjectMapper().findAndRegisterModules());
|
||||
}
|
||||
|
||||
@@ -53,12 +59,16 @@ class InboundPdaContainerCompletionTest {
|
||||
when(repository.allocations(2L)).thenReturn(List.of(allocation(202L)));
|
||||
when(repository.cartons(any())).thenReturn(List.of());
|
||||
when(repository.pallets(any())).thenReturn(List.of());
|
||||
LastlegOrder source = new LastlegOrder(); source.setSourceContainerBookingId("BOOKING-1");
|
||||
when(orders.selectById(101L)).thenReturn(source);
|
||||
when(orders.selectById(102L)).thenReturn(source);
|
||||
|
||||
service.completeContainer("100", List.of(second, first));
|
||||
service.completeContainer("100", List.of(first, second));
|
||||
|
||||
verify(stockReceipts, times(2)).stockIn(eq("100"), any(), any(), any(), any(), any(), any());
|
||||
verify(outbox, times(1)).publish(eq(first), eq(firstTask), eq(List.of(10L)));
|
||||
verify(outbox, times(1)).publishContainerCompleted(eq("100"), eq("BOOKING-1"), eq("CONT-1"),
|
||||
any(Instant.class), any(Instant.class), anyLong());
|
||||
verify(repository, times(2)).update(any(InboundPlan.class));
|
||||
verify(repository, times(2)).update(any(InboundPdaTask.class));
|
||||
}
|
||||
@@ -66,6 +76,7 @@ class InboundPdaContainerCompletionTest {
|
||||
private static InboundPlan plan(long id, String planNo) {
|
||||
InboundPlan value = new InboundPlan();
|
||||
value.setId(id); value.setPlanNo(planNo); value.setCustomerId("100"); value.setContainerNo("CONT-1");
|
||||
value.setSourceOrderId(id == 1L ? 101L : 102L);
|
||||
value.setStatus("RECEIVING");
|
||||
return value;
|
||||
}
|
||||
|
||||
+32
-1
@@ -33,6 +33,7 @@ import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
@@ -96,6 +97,21 @@ class ReceivingContainerServiceBehaviorTest {
|
||||
.isInstanceOf(ResponseStatusException.class).hasMessageContaining("409");
|
||||
}
|
||||
|
||||
@Test
|
||||
void prepareEnforcesBothActiveAccountAndActiveContainerUniqueness() {
|
||||
when(plans.selectList(any())).thenReturn(List.of(plan("CONT-B")));
|
||||
when(sessions.lockActiveByOperator("100", "100:7")).thenReturn(session("CONT-A", "WAIT_SIGN"));
|
||||
assertThatThrownBy(() -> service.prepare("100", "7", "receiver", "CONT-B",
|
||||
new ReceivingContainerRequests.Action("account-lock"))).isInstanceOf(ResponseStatusException.class);
|
||||
verify(sessions, never()).insert(any());
|
||||
|
||||
org.mockito.Mockito.reset(sessions, operations);
|
||||
when(sessions.lockActiveByContainer("100", "100:CONT-B")).thenReturn(session("CONT-B", "WAIT_SIGN"));
|
||||
assertThatThrownBy(() -> service.prepare("100", "7", "receiver", "CONT-B",
|
||||
new ReceivingContainerRequests.Action("container-lock"))).isInstanceOf(ResponseStatusException.class);
|
||||
verify(sessions, never()).insert(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void destinationModesAreHardRejectedBeforeWarehouseCalls() {
|
||||
stubCurrent("OVERSEAS", "CONT-A");
|
||||
@@ -138,7 +154,7 @@ class ReceivingContainerServiceBehaviorTest {
|
||||
when(warehouse.resolvePallet("PAL-1")).thenReturn(resource(100L, "FBA-1"));
|
||||
when(pallets.selectList(any())).thenReturn(List.of(planPallet()));
|
||||
doThrow(new ResponseStatusException(org.springframework.http.HttpStatus.CONFLICT, "over receipt"))
|
||||
.when(workflow).validateBulkResolved(any(), any(), any(), any(Integer.class));
|
||||
.when(workflow).validateBulkResolved(any(), any(), any(), anyInt());
|
||||
assertThatThrownBy(() -> service.receiveBulk("100", "7", "receiver",
|
||||
new ReceivingContainerRequests.BulkReceive("IN-1", "PAL-1", 9, "over-key")))
|
||||
.isInstanceOf(ResponseStatusException.class);
|
||||
@@ -168,6 +184,21 @@ class ReceivingContainerServiceBehaviorTest {
|
||||
verify(workflow, never()).completeContainer(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void localFailureAfterRemoteReservationTriggersReleaseCompensation() {
|
||||
stubCurrent("FBA", "CONT-A");
|
||||
when(warehouse.resolvePallet("PAL-1")).thenReturn(resource(100L, "FBA-1"));
|
||||
when(pallets.selectList(any())).thenReturn(List.of(planPallet()));
|
||||
when(workflow.assignResolvedPallet(any(), any(), any(), any())).thenReturn(55L);
|
||||
ResponseStatusException failure = new ResponseStatusException(org.springframework.http.HttpStatus.CONFLICT, "race");
|
||||
doThrow(failure).when(workflow).receiveBulkResolved(any(), any(), any(), any(), anyInt(), any(), any());
|
||||
|
||||
assertThatThrownBy(() -> service.receiveBulk("100", "7", "receiver",
|
||||
new ReceivingContainerRequests.BulkReceive("IN-1", "PAL-1", 1, "compensate-key")))
|
||||
.isSameAs(failure);
|
||||
verify(workflow).compensateReservation("100", 55L, failure);
|
||||
}
|
||||
|
||||
private void stubCurrent(String destinationType, String containerNo) {
|
||||
InboundPlan plan = plan(containerNo);
|
||||
InboundPlanLine line = line(destinationType);
|
||||
|
||||
+1
@@ -71,6 +71,7 @@ class TaillegRealConsumerAcceptanceTest {
|
||||
}
|
||||
public long reserve(String customerId, long warehouseId, long palletId, long locationId,
|
||||
String businessType, String businessId, String reservationKey) { return 1L; }
|
||||
public void release(String customerId, long resourceLockId) { }
|
||||
public void scrap(String customerId, long resourceLockId) { }
|
||||
public void scrapPallet(String customerId, long palletId) { }
|
||||
public WarehousePalletReference resolvePallet(String palletCode) {
|
||||
|
||||
Reference in New Issue
Block a user