fix(lastleg): scope inventory holds by cycle and align pick lock ordering
This commit is contained in:
+2
@@ -117,6 +117,8 @@ public class DispatchPdaWorkflowService {
|
||||
}
|
||||
long picked = picks.totalItem(customerId, task.getId(), itemId);
|
||||
if (picked + request.packages() > item.getPackageCount()) throw conflict("Picked packages exceed dispatch pallet packages");
|
||||
pickAttribution.preparePicking(customerId, request.positionId(),
|
||||
"RELABEL_ORDER".equals(item.getSourceBusinessType()) ? item.getOrderId() : null);
|
||||
String keyBase = "DISPATCH:" + order.getDispatchNo() + ":" + itemId + ":" + request.idempotencyKey();
|
||||
InventoryCommandRequests.Move move = new InventoryCommandRequests.Move(request.positionId(),
|
||||
request.packages(), order.getDispatchNo(), order.getDispatchNo(), null,
|
||||
|
||||
+19
@@ -25,6 +25,25 @@ public class DispatchPickOrderAttributionService {
|
||||
@Resource private LastlegOrderMapper orders;
|
||||
@Resource private LastlegOrderStatusHistoryMapper history;
|
||||
|
||||
/** Acquire source order locks before any inventory lock, matching order hold/release. */
|
||||
@org.springframework.transaction.annotation.Transactional(propagation = org.springframework.transaction.annotation.Propagation.MANDATORY)
|
||||
public void preparePicking(String customerId, long positionId, Long fallbackOrderId) {
|
||||
var sourceIds = new java.util.TreeSet<Long>();
|
||||
attributions.availableReceipts(customerId, positionId).forEach(receipt -> sourceIds.add(receipt.getOrderId()));
|
||||
if (fallbackOrderId != null && fallbackOrderId > 0) sourceIds.add(fallbackOrderId);
|
||||
if (sourceIds.isEmpty()) throw conflict("PDA pick has no attributable source order");
|
||||
for (Long orderId : sourceIds) {
|
||||
LastlegOrder order = orders.lockById(customerId, orderId);
|
||||
if (order == null || Boolean.TRUE.equals(order.getDetained())) {
|
||||
throw conflict("PDA pick source order is missing or held");
|
||||
}
|
||||
}
|
||||
if (attributions.availableReceipts(customerId, positionId).stream()
|
||||
.anyMatch(receipt -> !sourceIds.contains(receipt.getOrderId()))) {
|
||||
throw conflict("PDA pick source orders changed; reload the task");
|
||||
}
|
||||
}
|
||||
|
||||
/** Call after the inventory position has been locked/moved and the PDA pick row has been inserted. */
|
||||
public List<OrderPackages> attribute(String customerId, DispatchPdaPick pick) {
|
||||
return attribute(customerId, pick, null);
|
||||
|
||||
+10
-8
@@ -144,23 +144,25 @@ public class InventoryCommandService {
|
||||
|
||||
@Transactional
|
||||
public void holdInboundOrderInventory(String customerId, String operatorId, long inboundOrderId,
|
||||
String businessOrderNo, String remark) {
|
||||
String businessOrderNo, String remark, String holdCycle) {
|
||||
for (InventoryPosition position : commands.lockInboundAvailablePositions(customerId, inboundOrderId)) {
|
||||
long packages = value(position.getAvailablePackages());
|
||||
if (packages == 0) continue;
|
||||
hold(customerId, operatorId, new InventoryCommandRequests.Move(position.getId(), packages,
|
||||
businessOrderNo, null, null, "ORDER_HOLD:" + inboundOrderId + ":" + position.getId(), remark));
|
||||
businessOrderNo, null, holdCycle, holdCycle + ":" + position.getId(), remark));
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void releaseInboundOrderHold(String customerId, String operatorId, long inboundOrderId,
|
||||
String businessOrderNo, String remark) {
|
||||
for (InventoryPosition position : commands.lockInboundHeldPositions(customerId, inboundOrderId)) {
|
||||
long packages = value(position.getHoldPackages());
|
||||
if (packages == 0) continue;
|
||||
release(customerId, operatorId, new InventoryCommandRequests.Move(position.getId(), packages,
|
||||
businessOrderNo, null, null, "ORDER_HOLD_RELEASE:" + inboundOrderId + ":" + position.getId(), remark));
|
||||
String businessOrderNo, String remark, String holdCycle) {
|
||||
// HOLD is shared by several business flows. Only release facts owned by this order hold cycle.
|
||||
for (InventoryLedger held : ledgers.orderHoldCycle(customerId, inboundOrderId, holdCycle)) {
|
||||
if (held.getPositionId() == null || value(held.getDeltaPackages()) <= 0) {
|
||||
throw conflict("Order hold inventory fact is invalid");
|
||||
}
|
||||
release(customerId, operatorId, new InventoryCommandRequests.Move(held.getPositionId(), held.getDeltaPackages(),
|
||||
businessOrderNo, null, holdCycle, holdCycle + ":RELEASE:" + held.getPositionId(), remark));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
@@ -12,6 +12,11 @@ import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface InventoryLedgerMapper extends BaseMapper<InventoryLedger> {
|
||||
@Select("SELECT * FROM ll_inventory_ledger WHERE customer_id=#{customerId} AND inbound_order_id=#{inboundOrderId} " +
|
||||
"AND business_type='HOLD' AND source_event_id=#{holdCycle} ORDER BY position_id,id")
|
||||
List<InventoryLedger> orderHoldCycle(@Param("customerId") String customerId,
|
||||
@Param("inboundOrderId") long inboundOrderId, @Param("holdCycle") String holdCycle);
|
||||
|
||||
@Select("SELECT * FROM ll_inventory_ledger WHERE customer_id=#{customerId} AND idempotency_key=#{idempotencyKey} LIMIT 1")
|
||||
InventoryLedger findIdempotency(@Param("customerId") String customerId,
|
||||
@Param("idempotencyKey") String idempotencyKey);
|
||||
|
||||
+11
-2
@@ -78,6 +78,15 @@ public class LastlegOrderCommandService {
|
||||
|| text(order.getOutboundOrderNo()) != null)) {
|
||||
throw conflict("尾程订单已进入出库流程,不能新增暂扣");
|
||||
}
|
||||
String holdCycle = "ORDER_HOLD:" + orderId + ":" + (version + 1);
|
||||
if (!held) {
|
||||
LastlegOrderHoldHistory original = holdHistory.selectOne(new LambdaQueryWrapper<LastlegOrderHoldHistory>()
|
||||
.eq(LastlegOrderHoldHistory::getCustomerId, order.getCustomerId())
|
||||
.eq(LastlegOrderHoldHistory::getOrderId, orderId).eq(LastlegOrderHoldHistory::getHeld, true)
|
||||
.orderByDesc(LastlegOrderHoldHistory::getOrderVersion).last("LIMIT 1"));
|
||||
if (original == null) throw conflict("暂扣来源记录缺失,不能释放其他业务冻结库存");
|
||||
holdCycle = "ORDER_HOLD:" + orderId + ":" + original.getOrderVersion();
|
||||
}
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
boolean chargeable = held && request.chargeable();
|
||||
String reason = request.reason().trim();
|
||||
@@ -86,10 +95,10 @@ public class LastlegOrderCommandService {
|
||||
}
|
||||
if (held) {
|
||||
inventory.holdInboundOrderInventory(order.getCustomerId(), operatorId, orderId,
|
||||
order.getOrderNo(), reason);
|
||||
order.getOrderNo(), reason, holdCycle);
|
||||
} else {
|
||||
inventory.releaseInboundOrderHold(order.getCustomerId(), operatorId, orderId,
|
||||
order.getOrderNo(), reason);
|
||||
order.getOrderNo(), reason, holdCycle);
|
||||
}
|
||||
int nextVersion = version + 1;
|
||||
LastlegOrderCommandDtos.HoldResponse response = new LastlegOrderCommandDtos.HoldResponse(
|
||||
|
||||
Reference in New Issue
Block a user