feat: complete overseas pda warehouse operations
This commit is contained in:
+27
@@ -0,0 +1,27 @@
|
||||
package com.baiyue.lastleg.palletops.controller;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class PdaPalletOperationControllerContractTest {
|
||||
@Test void exposesOnlyDedicatedPdaPalletEndpointsWithCanonicalPickerAuthority() {
|
||||
RequestMapping mapping = PdaPalletOperationController.class.getAnnotation(RequestMapping.class);
|
||||
assertArrayEquals(new String[]{"/api/pda/v1/last-leg/pallet-operations"}, mapping.value());
|
||||
PreAuthorize authorize = PdaPalletOperationController.class.getAnnotation(PreAuthorize.class);
|
||||
assertEquals("hasAuthority('lastleg:pda:operate') and hasAuthority('ROLE_WAREHOUSE_PICKER')", authorize.value());
|
||||
Set<String> paths = Arrays.stream(PdaPalletOperationController.class.getDeclaredMethods())
|
||||
.map(Method::getDeclaredAnnotations).flatMap(Arrays::stream)
|
||||
.filter(PostMapping.class::isInstance).map(PostMapping.class::cast)
|
||||
.flatMap(annotation -> Arrays.stream(annotation.value())).collect(Collectors.toSet());
|
||||
assertEquals(Set.of("/pallets/resolve", "/assemble", "/disassemble", "/swap", "/unbind"), paths);
|
||||
}
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
package com.baiyue.lastleg.palletops.service;
|
||||
|
||||
import com.baiyue.lastleg.inventory.mapper.InventoryBalanceMapper;
|
||||
import com.baiyue.lastleg.inventory.mapper.InventoryLedgerMapper;
|
||||
import com.baiyue.lastleg.inventory.mapper.InventoryPositionMapper;
|
||||
import com.baiyue.lastleg.palletops.dto.PalletOperationRequests;
|
||||
import com.baiyue.lastleg.palletops.entity.PdaPalletOperation;
|
||||
import com.baiyue.lastleg.palletops.gateway.PalletPhysicalGateway;
|
||||
import com.baiyue.lastleg.palletops.mapper.PdaPalletOperationMapper;
|
||||
import com.baiyue.lastleg.palletops.model.PalletInventoryRow;
|
||||
import com.baiyue.lastleg.pda.security.PdaWarehouseIdentity;
|
||||
import com.baiyue.lastleg.pda.warehouse.service.PdaWarehouseService;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.support.AbstractPlatformTransactionManager;
|
||||
import org.springframework.transaction.support.DefaultTransactionStatus;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PdaPalletOperationServiceTest {
|
||||
@Mock private PdaPalletOperationMapper mapper;
|
||||
@Mock private InventoryPositionMapper positions;
|
||||
@Mock private InventoryBalanceMapper balances;
|
||||
@Mock private InventoryLedgerMapper ledgers;
|
||||
@Mock private PalletPhysicalGateway physical;
|
||||
@Mock private PdaWarehouseService history;
|
||||
private PdaPalletOperationService service;
|
||||
|
||||
@BeforeEach void setUp() {
|
||||
service = new PdaPalletOperationService();
|
||||
ReflectionTestUtils.setField(service, "mapper", mapper);
|
||||
ReflectionTestUtils.setField(service, "positions", positions);
|
||||
ReflectionTestUtils.setField(service, "balances", balances);
|
||||
ReflectionTestUtils.setField(service, "ledgers", ledgers);
|
||||
ReflectionTestUtils.setField(service, "physical", physical);
|
||||
ReflectionTestUtils.setField(service, "history", history);
|
||||
ReflectionTestUtils.setField(service, "objectMapper", new ObjectMapper().findAndRegisterModules());
|
||||
service.configureTransactions(new NoOpTransactionManager());
|
||||
}
|
||||
|
||||
@Test void resolveRejectsPalletOutsideTokenFrozenWarehouse() {
|
||||
when(physical.resolve("P-1")).thenReturn(pallet(10L, "P-1", 99L, 1));
|
||||
assertThrows(ResponseStatusException.class, () -> service.resolve(identity(),
|
||||
new PalletOperationRequests.Scan("P-1")));
|
||||
verifyNoInteractions(mapper);
|
||||
}
|
||||
|
||||
@Test void localConcurrencyFailureCompensatesEveryAppliedPhysicalTransitionInReverseSafeForm() {
|
||||
var source = pallet(10L, "SOURCE", 88L, 1);
|
||||
var target = pallet(20L, "TARGET", 88L, 2);
|
||||
when(physical.resolve("SOURCE")).thenReturn(source);
|
||||
when(physical.resolve("TARGET")).thenReturn(target);
|
||||
when(physical.resolveLocation(88L, target.locationId())).thenReturn(
|
||||
new PalletPhysicalGateway.LocationReference(target.locationId(), "LOC-" + target.locationId(),
|
||||
"STORAGE", "ACTIVE"));
|
||||
PalletInventoryRow planned = row(100L, 10L, 1L, 5L);
|
||||
PalletInventoryRow stale = row(100L, 10L, 2L, 5L);
|
||||
when(mapper.content("88", 10L)).thenReturn(List.of(planned));
|
||||
when(mapper.content("88", 20L)).thenReturn(List.of());
|
||||
when(mapper.palletPackages("88", 10L)).thenReturn(5L);
|
||||
when(mapper.palletPackages("88", 20L)).thenReturn(0L);
|
||||
when(mapper.lockPosition("88", 100L)).thenReturn(stale);
|
||||
|
||||
AtomicReference<PdaPalletOperation> operation = new AtomicReference<>();
|
||||
doAnswer(invocation -> { operation.set(invocation.getArgument(0)); return 1; })
|
||||
.when(mapper).insert(any(PdaPalletOperation.class));
|
||||
when(mapper.selectById(anyLong())).thenAnswer(invocation -> operation.get());
|
||||
when(physical.apply(eq(88L), any(), anyString(), anyLong(), anyString(), any()))
|
||||
.thenAnswer(invocation -> transition(invocation.getArgument(1), invocation.getArgument(2)));
|
||||
|
||||
var request = new PalletOperationRequests.Assemble("idem-1",
|
||||
new PalletOperationRequests.PalletExpectation("TARGET", 2),
|
||||
List.of(new PalletOperationRequests.PalletExpectation("SOURCE", 1)),
|
||||
List.of(new PalletOperationRequests.MoveLine(100L, 5L, 1L)), "assemble");
|
||||
|
||||
assertThrows(ResponseStatusException.class, () -> service.assemble(identity(), request));
|
||||
|
||||
verify(physical, times(2)).compensate(eq(88L), any(PalletPhysicalGateway.Transition.class));
|
||||
assertEquals("FAILED_COMPENSATED", operation.get().getStatus());
|
||||
verify(positions, never()).updateById(any(com.baiyue.lastleg.inventory.entity.InventoryPosition.class));
|
||||
verifyNoInteractions(ledgers, history);
|
||||
}
|
||||
|
||||
private static PdaWarehouseIdentity identity() { return new PdaWarehouseIdentity("7", "picker", 88L); }
|
||||
private static PalletPhysicalGateway.PalletReference pallet(long id, String code, long warehouse, int version) {
|
||||
return new PalletPhysicalGateway.PalletReference(id, code, warehouse, 1000L + id,
|
||||
id == 20L ? "IDLE" : "OCCUPIED", null, version);
|
||||
}
|
||||
private static PalletPhysicalGateway.Transition transition(PalletPhysicalGateway.PalletReference pallet,
|
||||
String targetStatus) {
|
||||
return new PalletPhysicalGateway.Transition("op-" + pallet.palletId(), pallet.palletId(), pallet.palletCode(),
|
||||
pallet.warehouseId(), pallet.status(), targetStatus, pallet.locationId(), pallet.locationId(),
|
||||
pallet.version(), pallet.version() + 1, "APPLIED", false);
|
||||
}
|
||||
private static PalletInventoryRow row(long positionId, long palletId, long version, long packages) {
|
||||
PalletInventoryRow value = new PalletInventoryRow(); value.setCustomerId("100"); value.setCustomerName("C");
|
||||
value.setBalanceId(200L); value.setPositionId(positionId); value.setInboundCode("IN-1");
|
||||
value.setSkuCode("SKU-1"); value.setDestinationWarehouseCode("ONT8"); value.setPalletId(palletId);
|
||||
value.setPalletNo(palletId == 10L ? "SOURCE" : "TARGET"); value.setBatchNo("DEFAULT");
|
||||
value.setWarehouseLocationId(1010L); value.setWarehouseLocationCode("L-1");
|
||||
value.setAvailablePackages(packages); value.setHoldPackages(0L); value.setAllocatedPackages(0L);
|
||||
value.setPickedPackages(0L); value.setReceivingPackages(0L); value.setVersion(version); return value;
|
||||
}
|
||||
|
||||
private static final class NoOpTransactionManager extends AbstractPlatformTransactionManager {
|
||||
@Override protected Object doGetTransaction() { return new Object(); }
|
||||
@Override protected void doBegin(Object transaction, TransactionDefinition definition) { }
|
||||
@Override protected void doCommit(DefaultTransactionStatus status) { }
|
||||
@Override protected void doRollback(DefaultTransactionStatus status) { }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user