feat: freeze and propagate first-leg pallet labels
This commit is contained in:
+10
-3
@@ -142,13 +142,20 @@ public class HttpWarehouseResourceGateway implements WarehouseResourceGateway {
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT,
|
||||
"Reserved pallet customer binding is invalid");
|
||||
}
|
||||
WarehousePalletData pallet = lookupPallet(palletCode).data();
|
||||
if (pallet.id() != palletId || pallet.warehouseId() != warehouseId || pallet.locationId() != locationId
|
||||
WarehousePalletReference pallet;
|
||||
try {
|
||||
pallet = reference(lookupPallet(palletCode).data());
|
||||
} catch (ResponseStatusException exception) {
|
||||
if (exception.getStatusCode().value() != 404) throw exception;
|
||||
// 已生成计划可能保留早期展示码;确认过的资源绑定仍是唯一可信物理身份。
|
||||
pallet = resolvePallet(palletId);
|
||||
}
|
||||
if (pallet.palletId() != palletId || pallet.warehouseId() != warehouseId || pallet.locationId() != locationId
|
||||
|| !"OCCUPIED".equals(pallet.status())) {
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT,
|
||||
"Scanned pallet does not match the reserved inbound plan resource");
|
||||
}
|
||||
return reference(pallet);
|
||||
return pallet;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
-1
@@ -55,7 +55,8 @@ public interface WarehouseResourceGateway {
|
||||
java.math.BigDecimal maxLoadKg, String status) { }
|
||||
record PalletBatchEnsure(long warehouseId, String batchKey, List<PalletBatchItemRequest> items) { }
|
||||
record PalletBatchItemRequest(long customerId, String businessKey, String inboundPlanNo,
|
||||
int sequenceNo, String palletType, String fbaWarehouseCode) { }
|
||||
int sequenceNo, String palletType, String fbaWarehouseCode,
|
||||
String palletCode) { }
|
||||
record PalletBatchCommand(long warehouseId, String batchKey, String commandKey) { }
|
||||
record PalletBatchCompensation(long warehouseId, String batchKey, String commandKey, String reason) { }
|
||||
record PalletBatchResponse(long batchId, String batchKey, String status, int attemptNo,
|
||||
|
||||
+8
-3
@@ -117,7 +117,7 @@ public class ContainerInboundPlanConversionCoordinator {
|
||||
preparation.warehouseId(), command.getBatchKey(), preparation.items().stream().map(item ->
|
||||
new WarehouseResourceGateway.PalletBatchItemRequest(Long.parseLong(item.customerId()),
|
||||
item.businessKey(), item.planNo(), item.globalSequenceNo(), "STANDARD",
|
||||
item.fbaWarehouseCode())).toList());
|
||||
item.fbaWarehouseCode(), item.palletCode())).toList());
|
||||
WarehouseResourceGateway.PalletBatchResponse reservation;
|
||||
try {
|
||||
reservation = warehouse.ensurePalletBatch(ensure);
|
||||
@@ -207,10 +207,15 @@ public class ContainerInboundPlanConversionCoordinator {
|
||||
.flatMap(group -> group.orders().stream()).filter(item -> "READY".equals(item.readiness())).toList()) {
|
||||
long planId = IdWorker.getId();
|
||||
String planNo = numbers.next("INBOUND_PLAN", order.destinationWarehouseCode(), planId);
|
||||
List<String> sourcePalletNos = plans.sourcePalletNos(
|
||||
order.customerId(), Long.parseLong(order.orderId()), order.orderType());
|
||||
if (sourcePalletNos.size() != order.suggestedPalletCount()) {
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "头程托盘码数量与柜级预览不一致");
|
||||
}
|
||||
for (int localSequence = 1; localSequence <= order.suggestedPalletCount(); localSequence++) {
|
||||
items.add(new PreparedItem(order.customerId(), Long.parseLong(order.orderId()), planId, planNo,
|
||||
localSequence, ++globalSequence, "CIP:" + generationId + ":" + globalSequence,
|
||||
trimToNull(order.fbaWarehouseCode())));
|
||||
trimToNull(order.fbaWarehouseCode()), sourcePalletNos.get(localSequence - 1)));
|
||||
}
|
||||
}
|
||||
Preparation preparation = new Preparation(warehouseId, List.copyOf(items));
|
||||
@@ -373,5 +378,5 @@ public class ContainerInboundPlanConversionCoordinator {
|
||||
public record Preparation(long warehouseId, List<PreparedItem> items) { }
|
||||
public record PreparedItem(String customerId, long orderId, long planId, String planNo,
|
||||
int localSequenceNo, int globalSequenceNo, String businessKey,
|
||||
String fbaWarehouseCode) { }
|
||||
String fbaWarehouseCode, String palletCode) { }
|
||||
}
|
||||
|
||||
+20
-1
@@ -27,6 +27,7 @@ import com.baiyue.lastleg.dropship.service.DropshipOrderService;
|
||||
import com.baiyue.common.event.ContainerNumbers;
|
||||
import com.baiyue.lastleg.entity.LastlegOrderCommandRecord;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderCommandRecordMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegSourcePalletMapper;
|
||||
import com.baiyue.lastleg.inboundplan.pda.gateway.WarehouseResourceGateway;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -65,8 +66,16 @@ public class InboundPlanService {
|
||||
@Resource private DropshipOrderService dropshipOrders;
|
||||
@Resource private WarehouseResourceGateway warehouseGateway;
|
||||
@Resource private LastlegOrderCommandRecordMapper commandMapper;
|
||||
@Resource private LastlegSourcePalletMapper sourcePalletMapper;
|
||||
@Resource private ObjectMapper objectMapper;
|
||||
|
||||
public List<String> sourcePalletNos(String customerId, long orderId, String orderType) {
|
||||
return "ONE_CLICK_DROPSHIP".equals(orderType)
|
||||
? dropshipOrders.palletSnapshots(customerId, orderId).stream()
|
||||
.map(DropshipPalletSnapshot::sourcePalletNo).toList()
|
||||
: sourcePalletMapper.palletNos(customerId, orderId);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public InboundPlanResponse convertManual(ManualInboundPlanRequest request, String idempotencyKey,
|
||||
String operatorId) {
|
||||
@@ -393,6 +402,10 @@ public class InboundPlanService {
|
||||
PalletAllocator.Result allocation = wholePallets == null && manual == null ? PalletAllocator.allocate(orders.stream()
|
||||
.map(order -> new PalletAllocator.LineInput(order.orderId(), warehouseKey(order), volume(order),
|
||||
order.packageCount(), order.recommendedPalletCount())).toList()) : null;
|
||||
if (allocation != null && sourcePalletNos != null && !sourcePalletNos.isEmpty()
|
||||
&& sourcePalletNos.size() != allocation.pallets().size()) {
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "头程托盘码数量与尾程计划托盘数不一致");
|
||||
}
|
||||
if (wholePallets != null) validateWholePallets(orders, wholePallets);
|
||||
plan.setPalletCount(manual != null ? manual.palletCount()
|
||||
: wholePallets == null ? allocation.pallets().size() : wholePallets.size());
|
||||
@@ -479,6 +492,7 @@ public class InboundPlanService {
|
||||
List<Integer> pendingIndexes = new ArrayList<>();
|
||||
List<List<SourceOrderSnapshot>> snapshots = new ArrayList<>();
|
||||
List<List<WholePalletInput>> wholePallets = new ArrayList<>();
|
||||
List<List<String>> standardPalletNos = new ArrayList<>();
|
||||
for (int index = 0; index < refs.size(); index++) {
|
||||
InboundPlanOrderMapper.ContainerOrderRef ref = refs.get(index);
|
||||
InboundPlan existing = existingPlans.get(index);
|
||||
@@ -517,6 +531,11 @@ public class InboundPlanService {
|
||||
validateWholePallets(orders, sourcePallets);
|
||||
}
|
||||
wholePallets.add(sourcePallets);
|
||||
List<String> standardNos = "ONE_CLICK_DROPSHIP".equals(ref.orderType()) ? List.of()
|
||||
: sourcePalletMapper.palletNos(ref.customerId(), ref.orderId());
|
||||
if (sourcePallets == null && standardNos.isEmpty()) throw new ResponseStatusException(
|
||||
HttpStatus.CONFLICT, "散货订单缺少头程托盘码快照");
|
||||
standardPalletNos.add(standardNos);
|
||||
}
|
||||
requireSingleWarehouseId(warehouseIds);
|
||||
|
||||
@@ -531,7 +550,7 @@ public class InboundPlanService {
|
||||
Map<Integer, WarehouseResourceGateway.PalletBatchItem> reserved = prepared == null ? Map.of()
|
||||
: prepared.pallets().stream().collect(Collectors.toMap(PreparedPallet::sequenceNo,
|
||||
PreparedPallet::reservation));
|
||||
created.put(refIndex, createPlan(ref.customerId(), ref.orderId(), snapshots.get(pendingIndex), List.of(),
|
||||
created.put(refIndex, createPlan(ref.customerId(), ref.orderId(), snapshots.get(pendingIndex), standardPalletNos.get(pendingIndex),
|
||||
wholePallets.get(pendingIndex), null, null, prepared, reserved));
|
||||
}
|
||||
List<InboundPlanResponse> result = new ArrayList<>(refs.size());
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package com.baiyue.lastleg.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Insert;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface LastlegSourcePalletMapper {
|
||||
@Insert("""
|
||||
INSERT INTO ll_order_source_pallet
|
||||
(id,customer_id,order_id,source_pallet_no,sequence_no,source_event_id,created_at)
|
||||
VALUES (#{id},#{customerId},#{orderId},#{palletNo},#{sequenceNo},#{eventId},CURRENT_TIMESTAMP(3))
|
||||
""")
|
||||
int insert(@Param("id") long id, @Param("customerId") String customerId,
|
||||
@Param("orderId") long orderId, @Param("palletNo") String palletNo,
|
||||
@Param("sequenceNo") int sequenceNo, @Param("eventId") String eventId);
|
||||
|
||||
@Select("""
|
||||
SELECT source_pallet_no FROM ll_order_source_pallet
|
||||
WHERE customer_id=#{customerId} AND order_id=#{orderId}
|
||||
ORDER BY sequence_no,id
|
||||
""")
|
||||
List<String> palletNos(@Param("customerId") String customerId, @Param("orderId") long orderId);
|
||||
}
|
||||
+16
@@ -44,6 +44,7 @@ public class LoadingCompletedConsumer {
|
||||
@Resource private DropshipOrderService dropshipOrders;
|
||||
@Resource private InventoryCommandService inventory;
|
||||
@Resource private ContainerShippingGateMapper shippingGates;
|
||||
@Resource private LastlegSourcePalletMapper sourcePallets;
|
||||
|
||||
@Transactional(isolation = Isolation.READ_COMMITTED)
|
||||
public int consume(EventEnvelope envelope) {
|
||||
@@ -273,6 +274,21 @@ public class LoadingCompletedConsumer {
|
||||
}
|
||||
createInboundOrderSnapshot(event, payload, source, order, orderHash);
|
||||
|
||||
if ("FIRST_LEG".equals(orderType) && event.eventVersion() >= 2) {
|
||||
List<FirstLegLoadingPlanCompletedV1.PalletSnapshot> pallets = source.pallets() == null ? List.of() : source.pallets();
|
||||
Set<String> palletNos = new HashSet<>();
|
||||
for (int palletIndex = 0; palletIndex < pallets.size(); palletIndex++) {
|
||||
String palletNo = pallets.get(palletIndex).palletNo();
|
||||
if (palletNo == null || palletNo.isBlank() || palletNo.length() > 128 || !palletNos.add(palletNo)) {
|
||||
throw new IllegalArgumentException("散货来源托盘码为空、重复或过长");
|
||||
}
|
||||
sourcePallets.insert(IdWorker.getId(), customerId, order.getId(), palletNo.trim(), palletIndex + 1, event.eventId());
|
||||
}
|
||||
if (!pallets.isEmpty() && order.getPalletCount() > 0 && pallets.size() != order.getPalletCount()) {
|
||||
throw new IllegalArgumentException("散货来源托盘码数量与托盘数不一致");
|
||||
}
|
||||
}
|
||||
|
||||
for(var item:source.items()) {
|
||||
LastlegOrderDetail detail=new LastlegOrderDetail();
|
||||
detail.setCustomerId(customerId);
|
||||
|
||||
Reference in New Issue
Block a user