贴标申请,仓储转运
This commit is contained in:
+14
-2
@@ -80,7 +80,13 @@ public final class DeliveryRequests {
|
||||
@Size(max=1000) String remark,
|
||||
@NotBlank @Size(max=255) String idempotencyKey,
|
||||
@Size(max=128) String skuCode,
|
||||
@jakarta.validation.constraints.Pattern(regexp = "BOX|PALLET") String unitType) {
|
||||
@jakarta.validation.constraints.Pattern(regexp = "BOX|PALLET") String unitType,
|
||||
@com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = WholePieceQuantityDeserializer.class)
|
||||
@jakarta.validation.constraints.Positive Long quantity) {
|
||||
public ReturnOutbound(String recipient, String overseasWarehouseId, String returnNo,
|
||||
List<String> cartonMarks, String remark, String idempotencyKey, String skuCode, String unitType) {
|
||||
this(recipient, overseasWarehouseId, returnNo, cartonMarks, remark, idempotencyKey, skuCode, unitType, null);
|
||||
}
|
||||
public ReturnOutbound(String recipient, String overseasWarehouseId, String returnNo,
|
||||
List<String> cartonMarks, String remark, String idempotencyKey) {
|
||||
this(recipient, overseasWarehouseId, returnNo, cartonMarks, remark, idempotencyKey, null, null);
|
||||
@@ -114,7 +120,13 @@ public final class DeliveryRequests {
|
||||
public record PdaPick(@NotNull Long outboundPalletItemId, Long inventoryPositionId,
|
||||
@NotNull @Min(1) Long packages, @NotBlank @Size(max=255) String idempotencyKey,
|
||||
@Size(max=500) List<@NotBlank @Size(max=255) String> cartonMarks,
|
||||
@Size(max=128) String skuCode) {
|
||||
@Size(max=128) String skuCode,
|
||||
@com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = WholePieceQuantityDeserializer.class)
|
||||
@jakarta.validation.constraints.Positive Long productQuantity) {
|
||||
public PdaPick(Long outboundPalletItemId, Long inventoryPositionId, Long packages, String idempotencyKey,
|
||||
List<String> cartonMarks, String skuCode) {
|
||||
this(outboundPalletItemId, inventoryPositionId, packages, idempotencyKey, cartonMarks, skuCode, null);
|
||||
}
|
||||
public PdaPick(Long outboundPalletItemId, Long inventoryPositionId, Long packages, String idempotencyKey) {
|
||||
this(outboundPalletItemId, inventoryPositionId, packages, idempotencyKey, List.of(), null);
|
||||
}
|
||||
|
||||
+2
-2
@@ -92,7 +92,7 @@ public final class DeliveryResponses {
|
||||
String skuCode, String inboundCode, long availablePackages,
|
||||
long pickedPackages) {}
|
||||
public record PdaTaskItem(Long outboundPalletItemId, String palletNo, String skuCode, String inboundCode,
|
||||
int packageCount, int pickedPackageCount, String status,
|
||||
List<PdaPickPosition> positions) {}
|
||||
int packageCount, int pickedPackageCount, String status,
|
||||
List<PdaPickPosition> positions, Long returnSkuQuantity) {}
|
||||
public record PdaTaskDetail(PdaTask task, List<PdaTaskItem> items) {}
|
||||
}
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package com.baiyue.lastleg.delivery.dto;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import java.io.IOException;
|
||||
|
||||
/** Piece quantities must not silently truncate a fractional JSON number. */
|
||||
public final class WholePieceQuantityDeserializer extends JsonDeserializer<Long> {
|
||||
@Override public Long deserialize(JsonParser parser, DeserializationContext context) throws IOException {
|
||||
if (parser.currentToken() != JsonToken.VALUE_NUMBER_INT)
|
||||
return (Long) context.handleUnexpectedToken(Long.class, parser);
|
||||
return parser.getLongValue();
|
||||
}
|
||||
}
|
||||
+2
@@ -28,6 +28,8 @@ public class OutboundPalletItem {
|
||||
private Integer pickedPackageCount;
|
||||
private String status;
|
||||
private String skuCode;
|
||||
/** Immutable piece quantity for a selected single-SKU return source. Null means legacy whole-carton work. */
|
||||
private Long returnSkuQuantity;
|
||||
private String cartonMarks;
|
||||
private String orderRefs;
|
||||
private String inboundCode;
|
||||
|
||||
+29
-8
@@ -499,6 +499,8 @@ public class DeliveryTransferService {
|
||||
OutboundOrder existing = outboundOrders.findByIdempotency(customerId, request.idempotencyKey().trim());
|
||||
if (existing != null) return replayReturnOutbound(customerId, existing, requestHash);
|
||||
String warehouseId = request.overseasWarehouseId().trim();
|
||||
if (request.quantity() != null && (!StringUtils.hasText(request.skuCode()) || request.quantity() <= 0
|
||||
|| "PALLET".equals(request.unitType()))) throw conflict("按件数出库必须选择单SKU,不能同时整托出库");
|
||||
List<String> marks = request.cartonMarks().stream().map(String::trim).filter(StringUtils::hasText).distinct().toList();
|
||||
if (marks.size() != request.cartonMarks().size()) throw conflict("箱唛不能为空且不可重复");
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
@@ -514,6 +516,8 @@ public class DeliveryTransferService {
|
||||
existing = outboundOrders.findByIdempotency(customerId, request.idempotencyKey().trim());
|
||||
if (existing != null) return replayReturnOutbound(customerId, existing, requestHash);
|
||||
for (ReturnInventoryCarton carton : locked) {
|
||||
if ("PARCEL".equals(carton.getSourceUnitType()) && !StringUtils.hasText(request.skuCode()))
|
||||
throw conflict("包裹散件只能按 SKU 出库,不能按箱号或托盘出库");
|
||||
com.baiyue.lastleg.returnreceipt.service.ReturnCartonCountGuard.requireConfirmed(carton);
|
||||
if (!"AVAILABLE".equals(carton.getStatus())) throw conflict("退货箱已被其他作业占用:" + carton.getCartonMark());
|
||||
var contents = returnCartonItems.lockCarton(carton.getId());
|
||||
@@ -521,12 +525,22 @@ public class DeliveryTransferService {
|
||||
throw conflict("退货箱缺少明细或已被合箱计划锁定:" + carton.getCartonMark());
|
||||
if (StringUtils.hasText(request.skuCode()) && (contents.size() != 1
|
||||
|| !request.skuCode().trim().equals(contents.get(0).getSkuCode())))
|
||||
throw conflict("单SKU出库只支持指定SKU的散件库存");
|
||||
if (StringUtils.hasText(request.skuCode()) && !"PARCEL".equals(carton.getSourceUnitType()))
|
||||
throw conflict("已装箱库存请按箱唛出库");
|
||||
throw conflict("单SKU出库只支持箱内仅有指定SKU的库存;混合箱请先拆箱");
|
||||
if ("PALLET".equals(request.unitType()) && !StringUtils.hasText(carton.getPalletCode()))
|
||||
throw conflict("整托出库必须存在托盘绑定");
|
||||
}
|
||||
Map<String, Long> skuQuantities = new LinkedHashMap<>();
|
||||
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();
|
||||
}
|
||||
if ("PALLET".equals(request.unitType())) {
|
||||
String pallet = locked.get(0).getPalletCode();
|
||||
if (locked.stream().anyMatch(c -> !Objects.equals(pallet, c.getPalletCode()))) throw conflict("整托出库只能选择同一托盘");
|
||||
@@ -539,7 +553,7 @@ public class DeliveryTransferService {
|
||||
outbound.setId(IdWorker.getId()); outbound.setCustomerId(customerId);
|
||||
outbound.setOutboundNo(orderNoGenerator.next("RETURN_OUTBOUND", warehouseId, outbound.getId()));
|
||||
outbound.setOutboundType("RETURN_OUTBOUND"); outbound.setStatus("WAIT_PICK");
|
||||
outbound.setRecipientInfo(request.recipient().trim()); outbound.setPackageCount(marks.size()); outbound.setPalletCount("PALLET".equals(request.unitType()) ? 1 : 0);
|
||||
outbound.setRecipientInfo(request.recipient().trim()); outbound.setPackageCount(locked.size()); outbound.setPalletCount("PALLET".equals(request.unitType()) ? 1 : 0);
|
||||
outbound.setOverseasWarehouseId(warehouseId); outbound.setIdempotencyKey(request.idempotencyKey().trim());
|
||||
outbound.setRequestHash(requestHash);
|
||||
outbound.setCreatedBy(operatorId); outbound.setCreatedAt(now); outbound.setUpdatedAt(now); outboundOrders.insert(outbound);
|
||||
@@ -547,7 +561,9 @@ 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());
|
||||
item.setSkuCode(trim(request.skuCode()));
|
||||
// Packed stock is still picked by its physical carton mark; loose parcels use SKU scanning.
|
||||
item.setSkuCode("PARCEL".equals(carton.getSourceUnitType()) ? trim(request.skuCode()) : null);
|
||||
item.setReturnSkuQuantity(skuQuantities.get(carton.getCartonMark()));
|
||||
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);
|
||||
@@ -557,7 +573,7 @@ public class DeliveryTransferService {
|
||||
delivery.setOutboundOrderId(outbound.getId()); delivery.setOutboundNo(outbound.getOutboundNo());
|
||||
delivery.setOverseasWarehouseId(warehouseId); delivery.setDeliveryGroupKey("RETURN:" + request.returnNo().trim());
|
||||
delivery.setStatus("WAIT_DELIVERY"); delivery.setDeliveryAddress(request.recipient().trim());
|
||||
delivery.setPackageCount(marks.size()); delivery.setPalletCount("PALLET".equals(request.unitType()) ? 1 : 0);
|
||||
delivery.setPackageCount(locked.size()); delivery.setPalletCount("PALLET".equals(request.unitType()) ? 1 : 0);
|
||||
delivery.setRemark(trim(request.remark())); delivery.setIdempotencyKey(childKey(request.idempotencyKey(), "RETURN-DELIVERY"));
|
||||
delivery.setVersion(0L); delivery.setCreatedBy(operatorId); delivery.setCreatedAt(now); delivery.setUpdatedAt(now);
|
||||
deliveryOrders.insert(delivery);
|
||||
@@ -822,7 +838,10 @@ public class DeliveryTransferService {
|
||||
requireReturnScan(outbound, item, request);
|
||||
if (value(item.getPickedPackageCount()) > 0) throw conflict("退货箱不可重复出库");
|
||||
OutboundPdaPick pick = new OutboundPdaPick(); pick.setId(IdWorker.getId()); pick.setCustomerId(customerId); pick.setTaskId(task.getId()); pick.setOutboundPalletItemId(item.getId()); pick.setInventoryPositionId(null); pick.setPackages(request.packages()); pick.setIdempotencyKey(request.idempotencyKey().trim()); pick.setPickedAt(LocalDateTime.now()); pdaPicks.insert(pick);
|
||||
returnStock.shipCarton(customerId, warehouseId, item.getInboundCode(), item.getCartonMarks(), outbound.getOutboundNo(), pick.getId());
|
||||
if (item.getReturnSkuQuantity() != null)
|
||||
returnStock.shipCartonQuantity(customerId, warehouseId, item.getInboundCode(), item.getCartonMarks(),
|
||||
outbound.getOutboundNo(), pick.getId(), item.getReturnSkuQuantity());
|
||||
else returnStock.shipCarton(customerId, warehouseId, item.getInboundCode(), item.getCartonMarks(), outbound.getOutboundNo(), pick.getId());
|
||||
item.setPickedPackageCount(request.packages().intValue()); item.setStatus("PICKED"); outboundPallets.updateById(item);
|
||||
updateOutboundPickingStatus(customerId, outbound);
|
||||
task.setStatus("IN_PROGRESS"); task.setUpdatedAt(LocalDateTime.now()); pdaTasks.updateById(task);
|
||||
@@ -1287,7 +1306,7 @@ public class DeliveryTransferService {
|
||||
value(item.getPickedPackageCount()), item.getStatus(),
|
||||
("RETURN_OUTBOUND".equals(outbound.getOutboundType())
|
||||
|| ("RELABEL_OUTBOUND".equals(outbound.getOutboundType()) && item.getWarehousePalletId() == null)) ? returnPickPositions(outbound, task, item)
|
||||
: pdaPickPositions(task.getCustomerId(), boundWarehouseId, item))).toList();
|
||||
: pdaPickPositions(task.getCustomerId(), boundWarehouseId, item), item.getReturnSkuQuantity())).toList();
|
||||
return new DeliveryResponses.PdaTaskDetail(taskResult(outbound, task), items);
|
||||
}
|
||||
|
||||
@@ -1326,6 +1345,8 @@ public class DeliveryTransferService {
|
||||
|| !outbound.getCustomerId().equals(item.getCustomerId())) throw notFound("退货出库明细不存在");
|
||||
if (request.inventoryPositionId() != null || !Long.valueOf(1).equals(request.packages()) || value(item.getPackageCount()) != 1)
|
||||
throw conflict("退货出库须逐箱或逐SKU扫描,无需库存仓位");
|
||||
if (item.getReturnSkuQuantity() != null && !item.getReturnSkuQuantity().equals(request.productQuantity()))
|
||||
throw conflict("实际出库件数必须等于本明细计划件数,请核对数量");
|
||||
if (StringUtils.hasText(item.getSkuCode())) {
|
||||
if (!Objects.equals(item.getSkuCode(), trim(request.skuCode()))) throw conflict("请扫描指定的退货SKU");
|
||||
} else if (request.cartonMarks() == null || request.cartonMarks().size() != 1
|
||||
|
||||
Reference in New Issue
Block a user