贴标申请,仓储转运
This commit is contained in:
+14
-42
@@ -486,7 +486,6 @@ public class DeliveryTransferService {
|
||||
public List<DeliveryResponses.ReturnOutboundCandidate> returnOutboundCandidates(String customerId, String warehouseId) {
|
||||
if (!StringUtils.hasText(warehouseId)) return List.of();
|
||||
return returnCartons.findAvailable(required(customerId, "Customer is required"), warehouseId.trim()).stream()
|
||||
.filter(carton -> !"PARCEL".equals(carton.getSourceUnitType()))
|
||||
.map(c -> new DeliveryResponses.ReturnOutboundCandidate(c.getSourceReturnNo(), c.getOverseasWarehouseId(),
|
||||
c.getCartonMark(), c.getPalletCode(), c.getFbaCartonMark(), c.getDestinationWarehouseCode(), c.getDestinationWarehouseName(),
|
||||
value(c.getTotalQuantity()), c.getStatus())).toList();
|
||||
@@ -499,23 +498,16 @@ public class DeliveryTransferService {
|
||||
OutboundOrder existing = outboundOrders.findByIdempotency(customerId, request.idempotencyKey().trim());
|
||||
if (existing != null) return replayReturnOutbound(customerId, existing, requestHash);
|
||||
String warehouseId = request.overseasWarehouseId().trim();
|
||||
boolean itemMode = request.items() != null;
|
||||
boolean skuMode = itemMode || StringUtils.hasText(request.skuCode());
|
||||
Map<String, DeliveryRequests.ReturnSkuOutboundItem> requestedItems = new LinkedHashMap<>();
|
||||
if (itemMode) {
|
||||
if (request.items().isEmpty() || StringUtils.hasText(request.skuCode()) || request.quantity() != null || "PALLET".equals(request.unitType()))
|
||||
throw conflict("散件明细出库不可同时提交统一SKU、数量或整托方式");
|
||||
for (var line : request.items()) {
|
||||
if (line == null || !StringUtils.hasText(line.cartonMark()) || !StringUtils.hasText(line.skuCode()) || line.quantity() == null || line.quantity() <= 0
|
||||
|| requestedItems.putIfAbsent(line.cartonMark().trim(), line) != null) throw conflict("散件出库明细不能为空、数量必须大于0且来源不可重复");
|
||||
}
|
||||
}
|
||||
if (request.quantity() != null && (!StringUtils.hasText(request.skuCode()) || request.quantity() <= 0
|
||||
|| "PALLET".equals(request.unitType()))) throw conflict("按件数出库必须选择单SKU,不能同时整托出库");
|
||||
// A return outbound is always a physical package/carton operation. SKU and
|
||||
// quantity are selection fields for the packing workflow, never a direct
|
||||
// outbound command. Partial package/carton quantities must be materialized
|
||||
// into a new labelled carton before they can be picked.
|
||||
boolean itemMode = request.items() != null && !request.items().isEmpty();
|
||||
boolean skuMode = itemMode || StringUtils.hasText(request.skuCode()) || request.quantity() != null;
|
||||
if (skuMode)
|
||||
throw conflict("退货出库最小单位为包裹/箱;部分数量请先合箱并打印新箱标签后再出库");
|
||||
List<String> marks = request.cartonMarks().stream().map(String::trim).filter(StringUtils::hasText).distinct().toList();
|
||||
if (marks.size() != request.cartonMarks().size()) throw conflict("箱唛不能为空且不可重复");
|
||||
if (itemMode && (marks.size() != requestedItems.size() || !requestedItems.keySet().containsAll(marks)))
|
||||
throw conflict("散件出库明细与所选来源不一致");
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
List<ReturnInventoryCarton> locked = new ArrayList<>();
|
||||
for (String mark : marks.stream().sorted().toList()) {
|
||||
@@ -529,37 +521,18 @@ public class DeliveryTransferService {
|
||||
existing = outboundOrders.findByIdempotency(customerId, request.idempotencyKey().trim());
|
||||
if (existing != null) return replayReturnOutbound(customerId, existing, requestHash);
|
||||
for (ReturnInventoryCarton carton : locked) {
|
||||
if (skuMode && !"PARCEL".equals(carton.getSourceUnitType()))
|
||||
throw conflict("单SKU出库只允许散件库存;箱内库存请按箱或托盘出库");
|
||||
if ("PARCEL".equals(carton.getSourceUnitType()) && !skuMode)
|
||||
throw conflict("包裹散件只能按 SKU 出库,不能按箱号或托盘出库");
|
||||
if ("PACKING_RESERVED".equals(carton.getStatus()))
|
||||
throw conflict("该包裹/箱正在合箱,请先完成或取消合箱");
|
||||
com.baiyue.lastleg.returnreceipt.service.ReturnCartonCountGuard.requireConfirmed(carton);
|
||||
if (!"AVAILABLE".equals(carton.getStatus())) throw conflict("退货箱已被其他作业占用:" + carton.getCartonMark());
|
||||
var contents = returnCartonItems.lockCarton(carton.getId());
|
||||
if (contents.isEmpty() || contents.stream().anyMatch(line -> value(line.getPackingReservedQuantity()) > 0))
|
||||
throw conflict("退货箱缺少明细或已被合箱计划锁定:" + carton.getCartonMark());
|
||||
String selectedSku = itemMode ? requestedItems.get(carton.getCartonMark()).skuCode().trim() : trim(request.skuCode());
|
||||
if (skuMode && (contents.size() != 1
|
||||
|| !selectedSku.equals(contents.get(0).getSkuCode())))
|
||||
throw conflict("所选散件来源必须仅包含指定SKU");
|
||||
if (itemMode && requestedItems.get(carton.getCartonMark()).quantity() > value(contents.get(0).getQuantity()))
|
||||
throw conflict("出库件数超过所选散件来源可用数量:" + carton.getCartonMark());
|
||||
if ("PALLET".equals(request.unitType()) && !StringUtils.hasText(carton.getPalletCode()))
|
||||
throw conflict("整托出库必须存在托盘绑定");
|
||||
}
|
||||
Map<String, Long> skuQuantities = new LinkedHashMap<>();
|
||||
if (itemMode) requestedItems.forEach((mark, line) -> skuQuantities.put(mark, line.quantity()));
|
||||
else if (StringUtils.hasText(request.skuCode())) {
|
||||
long available = locked.stream().mapToLong(c -> value(c.getTotalQuantity())).sum();
|
||||
long remaining = request.quantity() == null ? available : request.quantity();
|
||||
if (remaining > available) throw conflict("出库件数超过所选来源库存数量");
|
||||
for (ReturnInventoryCarton carton : locked) {
|
||||
long selected = Math.min(remaining, value(carton.getTotalQuantity()));
|
||||
if (selected > 0) skuQuantities.put(carton.getCartonMark(), selected);
|
||||
remaining -= selected;
|
||||
}
|
||||
locked = locked.stream().filter(c -> skuQuantities.containsKey(c.getCartonMark())).toList();
|
||||
}
|
||||
// No quantity allocation is performed here. The selected physical units
|
||||
// are shipped whole; partials arrive only as newly-created merge cartons.
|
||||
if ("PALLET".equals(request.unitType())) {
|
||||
String pallet = locked.get(0).getPalletCode();
|
||||
if (locked.stream().anyMatch(c -> !Objects.equals(pallet, c.getPalletCode()))) throw conflict("整托出库只能选择同一托盘");
|
||||
@@ -580,9 +553,8 @@ public class DeliveryTransferService {
|
||||
OutboundPalletItem item = new OutboundPalletItem(); item.setId(IdWorker.getId()); item.setCustomerId(customerId);
|
||||
item.setOutboundOrderId(outbound.getId()); item.setPackageCount(1); item.setPickedPackageCount(0); item.setStatus("WAIT_PICK");
|
||||
item.setCartonMarks(carton.getCartonMark()); item.setInboundCode(carton.getSourceReturnNo());
|
||||
// Packed stock is still picked by its physical carton mark; loose parcels use SKU scanning.
|
||||
item.setSkuCode("PARCEL".equals(carton.getSourceUnitType()) ? itemMode ? requestedItems.get(carton.getCartonMark()).skuCode().trim() : trim(request.skuCode()) : null);
|
||||
item.setReturnSkuQuantity(skuQuantities.get(carton.getCartonMark()));
|
||||
// Packages and cartons are picked by their physical barcode.
|
||||
item.setSkuCode(null); item.setReturnSkuQuantity(null);
|
||||
item.setPalletNo("PALLET".equals(request.unitType()) ? carton.getPalletCode() : null);
|
||||
item.setCreatedAt(now); outboundPallets.insert(item);
|
||||
carton.setStatus("OUTBOUND_PENDING"); carton.setUpdatedAt(now); returnCartons.updateById(carton);
|
||||
|
||||
Reference in New Issue
Block a user