fix: persist PDA movement state semantics

This commit is contained in:
2026-08-25 18:11:29 +08:00
parent 335ad8269f
commit ca69434698
4 changed files with 42 additions and 11 deletions
@@ -264,7 +264,8 @@ public class PdaPalletOperationService {
ledger.setBusinessType(operation.getOperationType()); ledger.setBusinessOrderId(operation.getId()); ledger.setBusinessType(operation.getOperationType()); ledger.setBusinessOrderId(operation.getId());
ledger.setBusinessOrderNo(String.valueOf(operation.getId())); ledger.setInboundCode(balance.getInboundCode()); ledger.setBusinessOrderNo(String.valueOf(operation.getId())); ledger.setInboundCode(balance.getInboundCode());
ledger.setInboundOrderNo(balance.getInboundOrderNo()); ledger.setIdempotencyKey(idempotencyKey); ledger.setInboundOrderNo(balance.getInboundOrderNo()); ledger.setIdempotencyKey(idempotencyKey);
ledger.setStatusFrom("PALLET:" + sourcePallet); ledger.setStatusTo("PALLET:" + targetPallet); ledger.setStatusFrom("OCCUPIED");
ledger.setStatusTo("UNBOUND".equals(targetPallet) ? "UNBOUND" : "OCCUPIED");
ledger.setBeforePackages(before); ledger.setDeltaPackages(delta); ledger.setAfterPackages(after); ledger.setBeforePackages(before); ledger.setDeltaPackages(delta); ledger.setAfterPackages(after);
ledger.setOperatorId(identity.operatorKey()); ledger.setRemark(trim(reason)); ledger.setOperatorId(identity.operatorKey()); ledger.setRemark(trim(reason));
ledger.setOccurredAt(LocalDateTime.now()); ledger.setCreatedAt(ledger.getOccurredAt()); ledgers.insert(ledger); ledger.setOccurredAt(LocalDateTime.now()); ledger.setCreatedAt(ledger.getOccurredAt()); ledgers.insert(ledger);
@@ -117,8 +117,7 @@ public class PdaInventoryMoveService {
position.setWarehouseLocationId(target.locationId()); position.setWarehouseLocationCode(target.locationCode()); position.setWarehouseLocationId(target.locationId()); position.setWarehouseLocationCode(target.locationCode());
position.setVersion(value(position.getVersion()) + 1); position.setUpdatedAt(completedAt); position.setVersion(value(position.getVersion()) + 1); position.setUpdatedAt(completedAt);
positions.updateById(position); positions.updateById(position);
ledger(operation, position, balance, operatorKey, key, request.reason(), source.locationCode(), ledger(operation, position, balance, operatorKey, key, request.reason(), completedAt);
target.locationCode(), completedAt);
} }
operation.setPalletVersionAfter(relocated.afterVersion()); operation.setStatus("COMPLETED"); operation.setPalletVersionAfter(relocated.afterVersion()); operation.setStatus("COMPLETED");
operation.setCompletedAt(completedAt); operation.setCompletedAt(completedAt);
@@ -150,18 +149,18 @@ public class PdaInventoryMoveService {
} }
private void ledger(PdaInventoryMove operation, InventoryPosition position, InventoryBalance balance, private void ledger(PdaInventoryMove operation, InventoryPosition position, InventoryBalance balance,
String operator, String key, String reason, String source, String target, String operator, String key, String reason, LocalDateTime now) {
LocalDateTime now) {
InventoryLedger row = new InventoryLedger(); row.setId(IdWorker.getId()); row.setCustomerId(position.getCustomerId()); InventoryLedger row = new InventoryLedger(); row.setId(IdWorker.getId()); row.setCustomerId(position.getCustomerId());
row.setBalanceId(balance.getId()); row.setPositionId(position.getId()); row.setBalanceId(balance.getId()); row.setPositionId(position.getId());
row.setOverseasWarehouseId(String.valueOf(operation.getOverseasWarehouseId())); row.setOverseasWarehouseId(String.valueOf(operation.getOverseasWarehouseId()));
row.setDestinationWarehouseCode(balance.getDestinationWarehouseCode()); row.setInboundOrderId(balance.getInboundOrderId()); row.setDestinationWarehouseCode(balance.getDestinationWarehouseCode()); row.setInboundOrderId(balance.getInboundOrderId());
row.setPalletId(position.getPalletId()); row.setPalletNo(position.getPalletNo()); row.setBatchNo(position.getBatchNo()); row.setPalletId(position.getPalletId()); row.setPalletNo(position.getPalletNo()); row.setBatchNo(position.getBatchNo());
row.setSkuCode(balance.getSkuCode()); row.setWarehouseLocationCode(target); row.setBusinessType("INTERNAL_MOVE"); row.setSkuCode(balance.getSkuCode()); row.setWarehouseLocationCode(position.getWarehouseLocationCode());
row.setBusinessType("INTERNAL_MOVE");
row.setBusinessOrderId(operation.getId()); row.setBusinessOrderNo(operation.getMoveNo()); row.setBusinessOrderId(operation.getId()); row.setBusinessOrderNo(operation.getMoveNo());
row.setInboundCode(balance.getInboundCode()); row.setInboundOrderNo(balance.getInboundOrderNo()); row.setInboundCode(balance.getInboundCode()); row.setInboundOrderNo(balance.getInboundOrderNo());
row.setIdempotencyKey("MOVE:" + operation.getId() + ":" + position.getId()); row.setIdempotencyKey("MOVE:" + operation.getId() + ":" + position.getId());
row.setStatusFrom(source); row.setStatusTo(target); long packages = physical(position); row.setStatusFrom("OCCUPIED"); row.setStatusTo("OCCUPIED"); long packages = physical(position);
row.setBeforePackages(packages); row.setDeltaPackages(0L); row.setAfterPackages(packages); row.setBeforePackages(packages); row.setDeltaPackages(0L); row.setAfterPackages(packages);
row.setOperatorId(operator); row.setRemark(reason.trim()); row.setOccurredAt(now); row.setCreatedAt(now); row.setOperatorId(operator); row.setRemark(reason.trim()); row.setOccurredAt(now); row.setCreatedAt(now);
ledgers.insert(row); ledgers.insert(row);
@@ -3,6 +3,9 @@ package com.baiyue.lastleg.palletops.service;
import com.baiyue.lastleg.inventory.mapper.InventoryBalanceMapper; import com.baiyue.lastleg.inventory.mapper.InventoryBalanceMapper;
import com.baiyue.lastleg.inventory.mapper.InventoryLedgerMapper; import com.baiyue.lastleg.inventory.mapper.InventoryLedgerMapper;
import com.baiyue.lastleg.inventory.mapper.InventoryPositionMapper; 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.palletops.dto.PalletOperationRequests; import com.baiyue.lastleg.palletops.dto.PalletOperationRequests;
import com.baiyue.lastleg.palletops.entity.PdaPalletOperation; import com.baiyue.lastleg.palletops.entity.PdaPalletOperation;
import com.baiyue.lastleg.palletops.gateway.PalletPhysicalGateway; import com.baiyue.lastleg.palletops.gateway.PalletPhysicalGateway;
@@ -15,6 +18,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.ArgumentCaptor;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionDefinition;
@@ -94,6 +98,27 @@ class PdaPalletOperationServiceTest {
verifyNoInteractions(ledgers, history); verifyNoInteractions(ledgers, history);
} }
@Test void inventoryLedgerStoresStateSemanticsInsteadOfLongPalletCodes() {
InventoryBalance balance = new InventoryBalance(); balance.setId(200L); balance.setCustomerId("100");
balance.setOverseasWarehouseId("88"); balance.setInboundOrderId(300L); balance.setInboundCode("IN-1");
InventoryPosition position = new InventoryPosition(); position.setId(100L); position.setPalletId(20L);
position.setPalletNo("PDAOPS-ADMIN-API-20260825095854036-SWAP-TARGET");
position.setBatchNo("DEFAULT"); position.setWarehouseLocationCode("PDAOPS-LONG-LOCATION-CODE");
PdaPalletOperation operation = new PdaPalletOperation(); operation.setId(400L);
operation.setOperationType("PALLET_SWAP");
ReflectionTestUtils.invokeMethod(service, "ledger", identity(), operation, balance, position,
"idem:out", 5L, -5L, 0L,
"PDAOPS-ADMIN-API-20260825095854036-SOURCE",
"PDAOPS-ADMIN-API-20260825095854036-TARGET", "swap");
ArgumentCaptor<InventoryLedger> ledger = ArgumentCaptor.forClass(InventoryLedger.class);
verify(ledgers).insert(ledger.capture());
assertEquals("OCCUPIED", ledger.getValue().getStatusFrom());
assertEquals("OCCUPIED", ledger.getValue().getStatusTo());
assertEquals("PDAOPS-LONG-LOCATION-CODE", ledger.getValue().getWarehouseLocationCode());
}
private static PdaWarehouseIdentity identity() { return new PdaWarehouseIdentity("7", "picker", 88L); } private static PdaWarehouseIdentity identity() { return new PdaWarehouseIdentity("7", "picker", 88L); }
private static PalletPhysicalGateway.PalletReference pallet(long id, String code, long warehouse, int version) { private static PalletPhysicalGateway.PalletReference pallet(long id, String code, long warehouse, int version) {
return new PalletPhysicalGateway.PalletReference(id, code, warehouse, 1000L + id, return new PalletPhysicalGateway.PalletReference(id, code, warehouse, 1000L + id,
@@ -60,9 +60,10 @@ class PdaInventoryMoveServiceTest {
@Test @Test
void atomicallyRelocatesPhysicalPalletAndEveryWarehouseScopedPosition() { void atomicallyRelocatesPhysicalPalletAndEveryWarehouseScopedPosition() {
String targetLocationCode = "PDAOPS-ADMIN-API-20260825095854036-MOVE";
when(warehouse.resolvePallet("PAL-1")).thenReturn(pallet(900L, 20L, 2)); when(warehouse.resolvePallet("PAL-1")).thenReturn(pallet(900L, 20L, 2));
when(warehouse.resolveLocation(900L, 20L)).thenReturn(location(20L, "A-01")); when(warehouse.resolveLocation(900L, 20L)).thenReturn(location(20L, "A-01"));
when(warehouse.resolveLocation(900L, "B-01")).thenReturn(location(21L, "B-01")); when(warehouse.resolveLocation(900L, targetLocationCode)).thenReturn(location(21L, targetLocationCode));
InventoryPosition position = new InventoryPosition(); position.setId(11L); position.setCustomerId("100"); InventoryPosition position = new InventoryPosition(); position.setId(11L); position.setCustomerId("100");
position.setBalanceId(10L); position.setOverseasWarehouseId("900"); position.setPalletId(30L); position.setBalanceId(10L); position.setOverseasWarehouseId("900"); position.setPalletId(30L);
position.setPalletNo("PAL-1"); position.setWarehouseLocationId(20L); position.setWarehouseLocationCode("A-01"); position.setPalletNo("PAL-1"); position.setWarehouseLocationId(20L); position.setWarehouseLocationCode("A-01");
@@ -76,7 +77,7 @@ class PdaInventoryMoveServiceTest {
eq(21L), anyString(), eq("库位优化"))).thenReturn(transition(false)); eq(21L), anyString(), eq("库位优化"))).thenReturn(transition(false));
var response = service.move(900L, "operator-1", var response = service.move(900L, "operator-1",
new PdaInventoryMoveRequest("PAL-1", "B-01", 2, "move-1", "库位优化")); new PdaInventoryMoveRequest("PAL-1", targetLocationCode, 2, "move-1", "库位优化"));
verify(physical).apply(eq(900L), argThat(value -> value.palletId() == 30L verify(physical).apply(eq(900L), argThat(value -> value.palletId() == 30L
&& value.locationId() == 20L && value.version() == 2), eq("OCCUPIED"), && value.locationId() == 20L && value.version() == 2), eq("OCCUPIED"),
@@ -84,11 +85,16 @@ class PdaInventoryMoveServiceTest {
ArgumentCaptor<InventoryPosition> updated = ArgumentCaptor.forClass(InventoryPosition.class); ArgumentCaptor<InventoryPosition> updated = ArgumentCaptor.forClass(InventoryPosition.class);
verify(positions).updateById(updated.capture()); verify(positions).updateById(updated.capture());
assertThat(updated.getValue().getWarehouseLocationId()).isEqualTo(21L); assertThat(updated.getValue().getWarehouseLocationId()).isEqualTo(21L);
assertThat(updated.getValue().getWarehouseLocationCode()).isEqualTo("B-01"); assertThat(updated.getValue().getWarehouseLocationCode()).isEqualTo(targetLocationCode);
assertThat(response.packageCount()).isEqualTo(6L); assertThat(response.packageCount()).isEqualTo(6L);
assertThat(response.palletVersion()).isEqualTo(3); assertThat(response.palletVersion()).isEqualTo(3);
assertThat(response.status()).isEqualTo("COMPLETED"); assertThat(response.status()).isEqualTo("COMPLETED");
verify(ledgers).insert(any(InventoryLedger.class)); verify(moves).updateById(any(PdaInventoryMove.class)); ArgumentCaptor<InventoryLedger> ledger = ArgumentCaptor.forClass(InventoryLedger.class);
verify(ledgers).insert(ledger.capture());
assertThat(ledger.getValue().getStatusFrom()).isEqualTo("OCCUPIED");
assertThat(ledger.getValue().getStatusTo()).isEqualTo("OCCUPIED");
assertThat(ledger.getValue().getWarehouseLocationCode()).isEqualTo(targetLocationCode);
verify(moves).updateById(any(PdaInventoryMove.class));
} }
@Test @Test