feat: complete overseas pda warehouse operations
This commit is contained in:
+41
@@ -0,0 +1,41 @@
|
||||
package com.baiyue.lastleg.pda;
|
||||
|
||||
import com.baiyue.lastleg.pda.move.controller.PdaInventoryMoveController;
|
||||
import com.baiyue.lastleg.pda.move.dto.PdaInventoryMoveRequest;
|
||||
import com.baiyue.lastleg.pda.stocktake.controller.PdaStocktakeController;
|
||||
import com.baiyue.lastleg.pda.stocktake.dto.PdaStocktakeRequests;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class PdaInventoryOperationContractTest {
|
||||
@Test
|
||||
void controllersUseOnlyCanonicalPdaIdentityAndRoutes() {
|
||||
assertThat(PdaStocktakeController.class.getAnnotation(RequestMapping.class).value())
|
||||
.containsExactly("/api/pda/v1/last-leg/stocktakes");
|
||||
assertThat(PdaInventoryMoveController.class.getAnnotation(RequestMapping.class).value())
|
||||
.containsExactly("/api/pda/v1/last-leg/inventory-moves");
|
||||
assertThat(PdaStocktakeController.class.getAnnotation(PreAuthorize.class).value())
|
||||
.isEqualTo("hasAuthority('lastleg:pda:operate') and hasAuthority('ROLE_WAREHOUSE_PICKER')");
|
||||
assertThat(PdaInventoryMoveController.class.getAnnotation(PreAuthorize.class).value())
|
||||
.isEqualTo("hasAuthority('lastleg:pda:operate') and hasAuthority('ROLE_WAREHOUSE_PICKER')");
|
||||
}
|
||||
|
||||
@Test
|
||||
void deviceRequestsCannotSupplyIdentityOrWarehouseScope() {
|
||||
Set<String> forbidden = Set.of("customerId", "warehouseId", "overseasWarehouseId", "operator", "operatorId");
|
||||
for (Class<?> type : List.of(PdaStocktakeRequests.Create.class, PdaStocktakeRequests.Count.class,
|
||||
PdaStocktakeRequests.Submit.class, PdaInventoryMoveRequest.class)) {
|
||||
Set<String> fields = Stream.of(type.getRecordComponents()).map(component -> component.getName())
|
||||
.collect(Collectors.toSet());
|
||||
assertThat(fields).doesNotContainAnyElementsOf(forbidden);
|
||||
}
|
||||
}
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
package com.baiyue.lastleg.pda;
|
||||
|
||||
import com.baiyue.lastleg.palletops.dto.PalletOperationRequests;
|
||||
import com.baiyue.lastleg.pda.move.dto.PdaInventoryMoveRequest;
|
||||
import com.baiyue.lastleg.pda.stocktake.dto.PdaStocktakeRequests;
|
||||
import com.baiyue.lastleg.relabel.pda.dto.PdaRelabelRequests;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
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.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class PdaOperationsOpenApiContractTest {
|
||||
private static final Set<String> SERVER_OWNED = Set.of(
|
||||
"customerId", "warehouseId", "overseasWarehouseId", "operator", "operatorKey", "operatorName");
|
||||
|
||||
@Test
|
||||
void pdaOperationRequestsNeverAcceptServerOwnedScope() {
|
||||
List<Class<?>> containers = List.of(PdaStocktakeRequests.class, PalletOperationRequests.class,
|
||||
PdaRelabelRequests.class);
|
||||
for (Class<?> container : containers) {
|
||||
for (Class<?> type : container.getDeclaredClasses()) {
|
||||
if (type.isRecord()) assertNoServerOwnedFields(type);
|
||||
}
|
||||
}
|
||||
assertNoServerOwnedFields(PdaInventoryMoveRequest.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void versionedOpenApiPublishesEveryPdaWarehouseOperation() throws IOException {
|
||||
String yaml = resource("openapi/overseas-pda-operations-v1.yaml");
|
||||
List<String> paths = List.of(
|
||||
"/api/pda/v1/last-leg/stocktakes:",
|
||||
"/api/pda/v1/last-leg/stocktakes/{taskNo}:",
|
||||
"/api/pda/v1/last-leg/stocktakes/{taskNo}/counts:",
|
||||
"/api/pda/v1/last-leg/stocktakes/{taskNo}/submit:",
|
||||
"/api/pda/v1/last-leg/inventory-moves:",
|
||||
"/api/pda/v1/last-leg/pallet-operations/pallets/resolve:",
|
||||
"/api/pda/v1/last-leg/pallet-operations/assemble:",
|
||||
"/api/pda/v1/last-leg/pallet-operations/disassemble:",
|
||||
"/api/pda/v1/last-leg/pallet-operations/swap:",
|
||||
"/api/pda/v1/last-leg/pallet-operations/unbind:",
|
||||
"/api/pda/v1/last-leg/relabel-tasks/candidates:",
|
||||
"/api/pda/v1/last-leg/relabel-tasks/{relabelNo}:",
|
||||
"/api/pda/v1/last-leg/relabel-tasks/{relabelNo}/claim:",
|
||||
"/api/pda/v1/last-leg/relabel-tasks/{relabelNo}/units:",
|
||||
"/api/pda/v1/last-leg/relabel-tasks/{relabelNo}/units/skip:",
|
||||
"/api/pda/v1/last-leg/relabel-tasks/{relabelNo}/exceptions:",
|
||||
"/api/pda/v1/last-leg/relabel-tasks/{relabelNo}/photos/files:",
|
||||
"/api/pda/v1/last-leg/relabel-tasks/{relabelNo}/complete:",
|
||||
"/api/pda/v1/last-leg/relabel-tasks/{relabelNo}/review:");
|
||||
for (String path : paths) assertTrue(yaml.contains(path), path);
|
||||
assertEquals(19, paths.size());
|
||||
assertTrue(yaml.contains("enum: [LOCATION, PALLET]"));
|
||||
assertTrue(yaml.contains("enum: [APPROVE, REWORK]"));
|
||||
assertTrue(yaml.contains("enum: [DAMAGED, MISSING_LABEL, LABEL_MISMATCH, UNREADABLE, OTHER]"));
|
||||
for (String field : SERVER_OWNED) assertFalse(yaml.contains(field + ":"), field);
|
||||
for (String excluded : List.of("/outbound", "/dispatch", "/delivery")) {
|
||||
assertFalse(yaml.contains(excluded), excluded);
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertNoServerOwnedFields(Class<?> type) {
|
||||
Set<String> fields = Arrays.stream(type.getRecordComponents())
|
||||
.map(RecordComponent::getName).collect(Collectors.toSet());
|
||||
assertTrue(java.util.Collections.disjoint(fields, SERVER_OWNED), type.getSimpleName());
|
||||
}
|
||||
|
||||
private String resource(String name) throws IOException {
|
||||
try (var input = getClass().getClassLoader().getResourceAsStream(name)) {
|
||||
assertTrue(input != null, name);
|
||||
return new String(input.readAllBytes(), StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
package com.baiyue.lastleg.pda.move.service;
|
||||
|
||||
import com.baiyue.lastleg.inboundplan.pda.gateway.WarehouseResourceGateway;
|
||||
import com.baiyue.lastleg.inventory.entity.InventoryBalance;
|
||||
import com.baiyue.lastleg.inventory.entity.InventoryLedger;
|
||||
import com.baiyue.lastleg.inventory.entity.InventoryPosition;
|
||||
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.gateway.PalletPhysicalGateway;
|
||||
import com.baiyue.lastleg.pda.move.dto.PdaInventoryMoveRequest;
|
||||
import com.baiyue.lastleg.pda.move.entity.PdaInventoryMove;
|
||||
import com.baiyue.lastleg.pda.move.mapper.PdaInventoryMoveMapper;
|
||||
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.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
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.argThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PdaInventoryMoveServiceTest {
|
||||
@Mock PdaInventoryMoveMapper moves;
|
||||
@Mock InventoryPositionMapper positions;
|
||||
@Mock InventoryBalanceMapper balances;
|
||||
@Mock InventoryLedgerMapper ledgers;
|
||||
@Mock WarehouseResourceGateway warehouse;
|
||||
@Mock PalletPhysicalGateway physical;
|
||||
@Mock PdaWarehouseService history;
|
||||
private PdaInventoryMoveService service;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
service = new PdaInventoryMoveService(); ReflectionTestUtils.setField(service, "moves", moves);
|
||||
ReflectionTestUtils.setField(service, "positions", positions); ReflectionTestUtils.setField(service, "balances", balances);
|
||||
ReflectionTestUtils.setField(service, "ledgers", ledgers); ReflectionTestUtils.setField(service, "warehouse", warehouse);
|
||||
ReflectionTestUtils.setField(service, "physical", physical);
|
||||
ReflectionTestUtils.setField(service, "history", history);
|
||||
ReflectionTestUtils.setField(service, "objectMapper", new ObjectMapper().findAndRegisterModules());
|
||||
}
|
||||
|
||||
@Test
|
||||
void atomicallyRelocatesPhysicalPalletAndEveryWarehouseScopedPosition() {
|
||||
when(warehouse.resolvePallet("PAL-1")).thenReturn(pallet(900L, 20L, 2));
|
||||
when(warehouse.resolveLocation(900L, 20L)).thenReturn(location(20L, "A-01"));
|
||||
when(warehouse.resolveLocation(900L, "B-01")).thenReturn(location(21L, "B-01"));
|
||||
InventoryPosition position = new InventoryPosition(); position.setId(11L); position.setCustomerId("100");
|
||||
position.setBalanceId(10L); position.setOverseasWarehouseId("900"); position.setPalletId(30L);
|
||||
position.setPalletNo("PAL-1"); position.setWarehouseLocationId(20L); position.setWarehouseLocationCode("A-01");
|
||||
position.setAvailablePackages(5L); position.setHoldPackages(1L); position.setAllocatedPackages(0L);
|
||||
position.setPickedPackages(0L); position.setReceivingPackages(0L); position.setVersion(4L);
|
||||
when(moves.lockPositions("900", 30L)).thenReturn(List.of(position));
|
||||
InventoryBalance balance = new InventoryBalance(); balance.setId(10L); balance.setCustomerId("100");
|
||||
balance.setOverseasWarehouseId("900"); balance.setInboundOrderId(50L); balance.setInboundCode("IN-1");
|
||||
when(balances.selectById(10L)).thenReturn(balance);
|
||||
when(physical.apply(eq(900L), any(PalletPhysicalGateway.PalletReference.class), eq("OCCUPIED"),
|
||||
eq(21L), anyString(), eq("库位优化"))).thenReturn(transition(false));
|
||||
|
||||
var response = service.move(900L, "operator-1",
|
||||
new PdaInventoryMoveRequest("PAL-1", "B-01", 2, "move-1", "库位优化"));
|
||||
|
||||
verify(physical).apply(eq(900L), argThat(value -> value.palletId() == 30L
|
||||
&& value.locationId() == 20L && value.version() == 2), eq("OCCUPIED"),
|
||||
eq(21L), anyString(), eq("库位优化"));
|
||||
ArgumentCaptor<InventoryPosition> updated = ArgumentCaptor.forClass(InventoryPosition.class);
|
||||
verify(positions).updateById(updated.capture());
|
||||
assertThat(updated.getValue().getWarehouseLocationId()).isEqualTo(21L);
|
||||
assertThat(updated.getValue().getWarehouseLocationCode()).isEqualTo("B-01");
|
||||
assertThat(response.packageCount()).isEqualTo(6L);
|
||||
assertThat(response.palletVersion()).isEqualTo(3);
|
||||
assertThat(response.status()).isEqualTo("COMPLETED");
|
||||
verify(ledgers).insert(any(InventoryLedger.class)); verify(moves).updateById(any(PdaInventoryMove.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsCrossWarehousePalletWithoutMovingResources() {
|
||||
when(warehouse.resolvePallet("PAL-1")).thenReturn(pallet(901L, 20L, 2));
|
||||
assertThatThrownBy(() -> service.move(900L, "operator-1",
|
||||
new PdaInventoryMoveRequest("PAL-1", "B-01", 2, "move-1", "库位优化")))
|
||||
.isInstanceOf(ResponseStatusException.class).hasMessageContaining("409");
|
||||
verify(physical, never()).apply(anyLong(), any(), anyString(), anyLong(), anyString(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void resumesWhenWarehouseCommittedButLastlegTransactionRolledBack() {
|
||||
when(warehouse.resolvePallet("PAL-1")).thenReturn(pallet(900L, 21L, 3));
|
||||
when(warehouse.resolveLocation(900L, "B-01")).thenReturn(location(21L, "B-01"));
|
||||
when(warehouse.resolveLocation(900L, 20L)).thenReturn(location(20L, "A-01"));
|
||||
InventoryPosition position = new InventoryPosition(); position.setId(11L); position.setCustomerId("100");
|
||||
position.setBalanceId(10L); position.setOverseasWarehouseId("900"); position.setPalletId(30L);
|
||||
position.setPalletNo("PAL-1"); position.setWarehouseLocationId(20L); position.setWarehouseLocationCode("A-01");
|
||||
position.setAvailablePackages(5L); position.setHoldPackages(0L); position.setAllocatedPackages(0L);
|
||||
position.setPickedPackages(0L); position.setReceivingPackages(0L); position.setVersion(4L);
|
||||
when(moves.lockPositions("900", 30L)).thenReturn(List.of(position));
|
||||
InventoryBalance balance = new InventoryBalance(); balance.setId(10L); balance.setCustomerId("100");
|
||||
balance.setOverseasWarehouseId("900"); balance.setInboundOrderId(50L); balance.setInboundCode("IN-1");
|
||||
when(balances.selectById(10L)).thenReturn(balance);
|
||||
when(physical.apply(eq(900L), any(PalletPhysicalGateway.PalletReference.class), eq("OCCUPIED"),
|
||||
eq(21L), anyString(), eq("恢复移库"))).thenReturn(transition(true));
|
||||
|
||||
var response = service.move(900L, "operator-1",
|
||||
new PdaInventoryMoveRequest("PAL-1", "B-01", 2, "move-retry", "恢复移库"));
|
||||
|
||||
verify(physical).apply(eq(900L), argThat(value -> value.locationId() == 20L && value.version() == 2),
|
||||
eq("OCCUPIED"), eq(21L), anyString(), eq("恢复移库"));
|
||||
assertThat(response.sourceLocationCode()).isEqualTo("A-01");
|
||||
assertThat(response.targetLocationCode()).isEqualTo("B-01");
|
||||
verify(positions).updateById(any(InventoryPosition.class));
|
||||
}
|
||||
|
||||
private static WarehouseResourceGateway.WarehousePalletReference pallet(long warehouseId, long locationId, int version) {
|
||||
return new WarehouseResourceGateway.WarehousePalletReference(30L, "PAL-1", warehouseId, locationId,
|
||||
"OCCUPIED", null, version);
|
||||
}
|
||||
private static WarehouseResourceGateway.WarehouseLocationReference location(long id, String code) {
|
||||
return new WarehouseResourceGateway.WarehouseLocationReference(id, code, "STORAGE", BigDecimal.TEN, "OCCUPIED");
|
||||
}
|
||||
private static PalletPhysicalGateway.Transition transition(boolean replayed) {
|
||||
return new PalletPhysicalGateway.Transition("move-transition", 30L, "PAL-1", 900L,
|
||||
"OCCUPIED", "OCCUPIED", 20L, 21L, 2, 3, "APPLIED", replayed);
|
||||
}
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
package com.baiyue.lastleg.pda.stocktake.service;
|
||||
|
||||
import com.baiyue.lastleg.inboundplan.pda.gateway.WarehouseResourceGateway;
|
||||
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.inventory.entity.InventoryBalance;
|
||||
import com.baiyue.lastleg.inventory.entity.InventoryLedger;
|
||||
import com.baiyue.lastleg.inventory.entity.InventoryPosition;
|
||||
import com.baiyue.lastleg.pda.stocktake.dto.PdaStocktakeRequests;
|
||||
import com.baiyue.lastleg.pda.stocktake.entity.PdaStocktakeItem;
|
||||
import com.baiyue.lastleg.pda.stocktake.entity.PdaStocktakeTask;
|
||||
import com.baiyue.lastleg.pda.stocktake.mapper.PdaStocktakeActionMapper;
|
||||
import com.baiyue.lastleg.pda.stocktake.mapper.PdaStocktakeInventoryMapper;
|
||||
import com.baiyue.lastleg.pda.stocktake.mapper.PdaStocktakeItemMapper;
|
||||
import com.baiyue.lastleg.pda.stocktake.mapper.PdaStocktakeTaskMapper;
|
||||
import com.baiyue.lastleg.pda.stocktake.model.StocktakeCandidate;
|
||||
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.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
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.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PdaStocktakeServiceTest {
|
||||
@Mock PdaStocktakeTaskMapper tasks;
|
||||
@Mock PdaStocktakeItemMapper items;
|
||||
@Mock PdaStocktakeActionMapper actions;
|
||||
@Mock PdaStocktakeInventoryMapper inventory;
|
||||
@Mock InventoryPositionMapper positions;
|
||||
@Mock InventoryBalanceMapper balances;
|
||||
@Mock InventoryLedgerMapper ledgers;
|
||||
@Mock WarehouseResourceGateway warehouse;
|
||||
@Mock PdaWarehouseService history;
|
||||
private PdaStocktakeService service;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
service = new PdaStocktakeService();
|
||||
ReflectionTestUtils.setField(service, "tasks", tasks); ReflectionTestUtils.setField(service, "items", items);
|
||||
ReflectionTestUtils.setField(service, "actions", actions); ReflectionTestUtils.setField(service, "inventory", inventory);
|
||||
ReflectionTestUtils.setField(service, "positions", positions); ReflectionTestUtils.setField(service, "balances", balances);
|
||||
ReflectionTestUtils.setField(service, "ledgers", ledgers); ReflectionTestUtils.setField(service, "warehouse", warehouse);
|
||||
ReflectionTestUtils.setField(service, "history", history);
|
||||
ReflectionTestUtils.setField(service, "objectMapper", new ObjectMapper().findAndRegisterModules());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createsWarehouseBoundLocationTaskAndFreezesEveryPosition() {
|
||||
when(warehouse.resolveLocation(900L, "A-01")).thenReturn(new WarehouseResourceGateway.WarehouseLocationReference(
|
||||
20L, "A-01", "STORAGE", BigDecimal.TEN, "OCCUPIED"));
|
||||
StocktakeCandidate candidate = new StocktakeCandidate(); candidate.setCustomerId("100");
|
||||
candidate.setBalanceId(10L); candidate.setPositionId(11L); candidate.setSkuCode("SKU-1");
|
||||
candidate.setPalletId(30L); candidate.setPalletCode("PAL-1"); candidate.setLocationId(20L);
|
||||
candidate.setLocationCode("A-01"); candidate.setBookPackages(8L); candidate.setPositionVersion(3L);
|
||||
when(items.candidatesByLocation("900", 20L, "A-01")).thenReturn(List.of(candidate));
|
||||
when(items.views(anyLong())).thenReturn(List.of());
|
||||
|
||||
var response = service.create(900L, "operator-1", new PdaStocktakeRequests.Create(
|
||||
"location", "A-01", "create-1"));
|
||||
|
||||
ArgumentCaptor<PdaStocktakeTask> task = ArgumentCaptor.forClass(PdaStocktakeTask.class);
|
||||
ArgumentCaptor<PdaStocktakeItem> item = ArgumentCaptor.forClass(PdaStocktakeItem.class);
|
||||
verify(tasks).insert(task.capture()); verify(items).insert(item.capture());
|
||||
assertThat(task.getValue().getOverseasWarehouseId()).isEqualTo(900L);
|
||||
assertThat(task.getValue().getScopeType()).isEqualTo("LOCATION");
|
||||
assertThat(task.getValue().getCreatedBy()).isEqualTo("operator-1");
|
||||
assertThat(item.getValue().getCustomerId()).isEqualTo("100");
|
||||
assertThat(item.getValue().getBookPackages()).isEqualTo(8L);
|
||||
assertThat(item.getValue().getPositionVersion()).isEqualTo(3L);
|
||||
assertThat(response.task().status()).isEqualTo("IN_PROGRESS");
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsPalletFromAnotherWarehouseBeforeReadingInventory() {
|
||||
when(warehouse.resolvePallet("PAL-1")).thenReturn(new WarehouseResourceGateway.WarehousePalletReference(
|
||||
30L, "PAL-1", 901L, 20L, "OCCUPIED", null, 2));
|
||||
|
||||
assertThatThrownBy(() -> service.create(900L, "operator-1",
|
||||
new PdaStocktakeRequests.Create("PALLET", "PAL-1", "create-1")))
|
||||
.isInstanceOf(ResponseStatusException.class).hasMessageContaining("409");
|
||||
verify(items, never()).candidatesByPallet(anyString(), anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
void submitsCountedDifferenceAndWritesBalancedAuditLedger() {
|
||||
PdaStocktakeTask task = new PdaStocktakeTask(); task.setId(40L); task.setTaskNo("CC-40");
|
||||
task.setOverseasWarehouseId(900L); task.setScopeType("PALLET"); task.setScopeCode("PAL-1");
|
||||
task.setStatus("IN_PROGRESS"); task.setPositionCount(1); task.setCountedCount(1); task.setDifferenceCount(1);
|
||||
task.setVersion(2L);
|
||||
PdaStocktakeTask submitted = new PdaStocktakeTask(); submitted.setId(40L); submitted.setTaskNo("CC-40");
|
||||
submitted.setOverseasWarehouseId(900L); submitted.setScopeType("PALLET"); submitted.setScopeCode("PAL-1");
|
||||
submitted.setStatus("SUBMITTED"); submitted.setPositionCount(1); submitted.setCountedCount(1);
|
||||
submitted.setDifferenceCount(1); submitted.setVersion(3L);
|
||||
when(tasks.find(900L, "CC-40")).thenReturn(task, submitted);
|
||||
when(tasks.lock(900L, "CC-40")).thenReturn(task); when(tasks.submit(40L, 2L, "operator-1", "复盘确认")).thenReturn(1);
|
||||
|
||||
PdaStocktakeItem item = new PdaStocktakeItem(); item.setId(41L); item.setTaskId(40L); item.setCustomerId("100");
|
||||
item.setBalanceId(10L); item.setPositionId(11L); item.setSkuCode("SKU-1"); item.setBookPackages(5L);
|
||||
item.setActualPackages(7L); item.setDifferencePackages(2L); item.setPositionVersion(3L); item.setVersion(1L);
|
||||
when(items.lockAll(40L)).thenReturn(List.of(item)); when(items.views(40L)).thenReturn(List.of());
|
||||
|
||||
InventoryPosition position = new InventoryPosition(); position.setId(11L); position.setCustomerId("100");
|
||||
position.setBalanceId(10L); position.setOverseasWarehouseId("900"); position.setPalletId(30L);
|
||||
position.setPalletNo("PAL-1"); position.setAvailablePackages(5L); position.setHoldPackages(0L);
|
||||
position.setAllocatedPackages(0L); position.setPickedPackages(0L); position.setReceivingPackages(0L);
|
||||
position.setVersion(3L);
|
||||
InventoryBalance balance = new InventoryBalance(); balance.setId(10L); balance.setCustomerId("100");
|
||||
balance.setOverseasWarehouseId("900"); balance.setAvailablePackages(5L); balance.setVersion(8L);
|
||||
when(inventory.lockPosition(11L, "100", "900")).thenReturn(position);
|
||||
when(inventory.lockBalance(10L, "100", "900")).thenReturn(balance);
|
||||
|
||||
var response = service.submit(900L, "operator-1", "CC-40",
|
||||
new PdaStocktakeRequests.Submit(2L, "复盘确认", "submit-1"));
|
||||
|
||||
ArgumentCaptor<InventoryPosition> changedPosition = ArgumentCaptor.forClass(InventoryPosition.class);
|
||||
ArgumentCaptor<InventoryBalance> changedBalance = ArgumentCaptor.forClass(InventoryBalance.class);
|
||||
ArgumentCaptor<InventoryLedger> ledger = ArgumentCaptor.forClass(InventoryLedger.class);
|
||||
verify(positions).updateById(changedPosition.capture()); verify(balances).updateById(changedBalance.capture());
|
||||
verify(ledgers).insert(ledger.capture());
|
||||
assertThat(changedPosition.getValue().getAvailablePackages()).isEqualTo(7L);
|
||||
assertThat(changedBalance.getValue().getAvailablePackages()).isEqualTo(7L);
|
||||
assertThat(ledger.getValue().getBeforePackages()).isEqualTo(5L);
|
||||
assertThat(ledger.getValue().getDeltaPackages()).isEqualTo(2L);
|
||||
assertThat(ledger.getValue().getAfterPackages()).isEqualTo(7L);
|
||||
assertThat(ledger.getValue().getBusinessType()).isEqualTo("STOCKTAKE_ADJUST");
|
||||
assertThat(response.task().status()).isEqualTo("SUBMITTED");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user