feat: complete overseas pda warehouse operations

This commit is contained in:
2026-08-25 11:20:36 +08:00
parent 45a3b9adfd
commit 042d0b738a
69 changed files with 4679 additions and 4 deletions
@@ -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);
}
}
}
@@ -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);
}
}
}
@@ -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);
}
}
@@ -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");
}
}