fix(inventory): enforce warehouse transfer reservation ownership

This commit is contained in:
2026-10-05 07:24:53 +08:00
parent 548a133de6
commit 5b4d9624a9
14 changed files with 293 additions and 62 deletions
@@ -58,6 +58,7 @@ import java.util.stream.Collectors;
@Service
public class PdaPalletOperationService {
@Resource private PdaPalletOperationMapper mapper;
@Resource private com.baiyue.lastleg.inventory.command.mapper.InventoryCommandMapper inventoryCommands;
@Resource private InventoryPositionMapper positions;
@Resource private InventoryBalanceMapper balances;
@Resource private InventoryLedgerMapper ledgers;
@@ -465,14 +466,33 @@ public class PdaPalletOperationService {
List<PalletPhysicalGateway.Transition> transitions = new ArrayList<>();
try {
for (PalletChange change : plan.changes()) {
transitions.add(physical.apply(identity.overseasWarehouseId(), change.pallet(), change.targetStatus(),
change.targetLocationId(), transitionKey(identity.operatorKey(), normalizedKey,
change.pallet().palletId()), reason));
}
saveTransitions(operation.getId(), transitions);
PalletOperationResponses.Operation result = transaction.execute(status ->
mutate(identity, operation, plan, normalizedKey, requestHash, reason));
PalletOperationResponses.Operation result = transaction.execute(status -> {
if (plan.relabelMutation() != null) lockRelabelMutation(plan.relabelMutation(), identity.overseasWarehouseId());
Long relabelOwner = plan.relabelMutation() == null ? null : inventoryCommands.relabelTransfer(
plan.relabelMutation().application().getCustomerId(), plan.relabelMutation().application().getId());
// Allocation also locks the position first: preserve mutual exclusion until commit.
for (PlannedMove move : plan.moves()) {
PalletInventoryRow source = mapper.lockPosition(String.valueOf(identity.overseasWarehouseId()), move.positionId());
if (source == null || !Objects.equals(source.getPalletId(), move.sourcePalletId())
|| value(source.getVersion()) != move.expectedVersion()) throw conflict("Inventory source changed");
requirePalletReservation(source.getCustomerId(), move.positionId(),
move.bucket() == InventoryBucket.HOLD ? relabelOwner : null);
}
if (plan.relabelMutation() != null) {
for (Long anchorId : plan.relabelMutation().anchors().keySet()) {
PalletInventoryRow anchor = mapper.lockPosition(String.valueOf(identity.overseasWarehouseId()), anchorId);
if (anchor == null) throw conflict("Relabel inventory anchor disappeared");
requirePalletReservation(anchor.getCustomerId(), anchorId, relabelOwner);
}
}
for (PalletChange change : plan.changes()) {
transitions.add(physical.apply(identity.overseasWarehouseId(), change.pallet(), change.targetStatus(),
change.targetLocationId(), transitionKey(identity.operatorKey(), normalizedKey,
change.pallet().palletId()), reason));
}
saveTransitions(operation.getId(), transitions);
return mutate(identity, operation, plan, normalizedKey, requestHash, reason);
});
if (result == null) throw new IllegalStateException("托盘作业事务没有返回结果");
return result;
} catch (RuntimeException failure) {
@@ -482,6 +502,12 @@ public class PdaPalletOperationService {
}
}
private void requirePalletReservation(String customerId, Long positionId, Long owner) {
var reservation = inventoryCommands.transferReservation(customerId, positionId);
if (reservation != null && (owner == null || !owner.equals(reservation.transferOrderId())))
throw conflict("Inventory is reserved by a warehouse transfer");
}
private PalletOperationResponses.Operation mutate(PdaWarehouseIdentity identity, PdaPalletOperation operation,
Plan plan, String key, String requestHash, String reason) {
if (plan.relabelMutation() != null) lockRelabelMutation(plan.relabelMutation(), identity.overseasWarehouseId());