贴标申请,仓储转运

This commit is contained in:
2026-10-13 13:46:16 +08:00
parent cebcdfb66e
commit 89a05a2348
11 changed files with 75 additions and 31 deletions
@@ -34,7 +34,7 @@ public final class PalletOperationRequests {
@NotEmpty @Size(max = 20) List<@Valid TargetMove> targets,
@Size(max = 500) String reason) { }
/** actualSource为现场确认的原入仓库存行;cartonMarks确认本批目标绑定,不推断历史箱唛位置。 */
/** source为空时表示退货箱没有来源托盘,现场仅扫描箱唛后绑定目标托盘;actualSource为现场确认的原入仓库存行。 */
public record RelabelDisassemble(@NotBlank @Size(max = 128) String idempotencyKey,
@NotBlank @Size(max = 64) String relabelNo,
@Valid PalletExpectation source,
@@ -105,13 +105,16 @@ public class PdaPalletOperationService {
|| !Objects.equals(carton.getOverseasWarehouseId(), app.getOverseasWarehouseId())
|| !Objects.equals(carton.getStatus(), "RELABEL_PENDING"))
throw conflict("退货换标来源托盘不存在或已被占用");
PalletPhysicalGateway.PalletReference pallet = physical.resolve(carton.getPalletCode());
if (pallet == null || pallet.warehouseId() != identity.overseasWarehouseId())
String palletCode = text(carton.getPalletCode());
PalletPhysicalGateway.PalletReference pallet = palletCode.isBlank() || "UNBOUND".equalsIgnoreCase(palletCode)
? null : physical.resolve(palletCode);
if (palletCode.isBlank() || "UNBOUND".equalsIgnoreCase(palletCode)) palletCode = "UNBOUND";
if (pallet != null && pallet.warehouseId() != identity.overseasWarehouseId())
throw conflict("退货换标来源托盘不属于当前仓库");
long quantity = entry.getValue().stream().anyMatch(item -> "RETURN_BULK_SKU_LABEL".equals(item.getOperationType()))
? value(carton.getTotalQuantity()) : 1;
result.add(new PalletOperationResponses.RelabelSource(carton.getId(), 0L,
pallet.palletId(), pallet.palletCode(), null, carton.getSourceBatchNo(), quantity, quantity,
pallet == null ? null : pallet.palletId(), palletCode, null, carton.getSourceBatchNo(), quantity, quantity,
entry.getValue().stream().map(RelabelItem::getCartonMark).filter(Objects::nonNull).toList()));
}
return result;
@@ -225,11 +228,15 @@ public class PdaPalletOperationService {
if (!Objects.equals(application.getOverseasWarehouseId(), String.valueOf(identity.overseasWarehouseId()))) {
throw conflict("换标单不属于 Token 冻结的海外仓");
}
Map<String, PalletPhysicalGateway.PalletReference> pallets = resolvePallets(identity.overseasWarehouseId(),
request.source(), List.of(request.target()));
PalletPhysicalGateway.PalletReference source = pallets.get(code(request.source().palletCode()));
PalletPhysicalGateway.PalletReference target = pallets.get(code(request.target().palletCode()));
if (source.palletId() == target.palletId()) throw conflict("原托盘和目标托盘不能相同");
if (request.target() == null) throw badRequest("目标托盘不能为空");
PalletPhysicalGateway.PalletReference target = resolvePallets(identity.overseasWarehouseId(),
List.of(request.target())).get(code(request.target().palletCode()));
PalletPhysicalGateway.PalletReference source = request.source() == null ? null
: resolvePallets(identity.overseasWarehouseId(), List.of(request.source())).get(code(request.source().palletCode()));
if (source != null && source.palletId() == target.palletId()) throw conflict("原托盘和目标托盘不能相同");
Map<String, PalletPhysicalGateway.PalletReference> pallets = new LinkedHashMap<>();
if (source != null) pallets.put(source.palletCode(), source);
pallets.put(target.palletCode(), target);
List<String> marks = request.cartonMarks().stream().map(String::trim).filter(value -> !value.isEmpty()).toList();
if (marks.size() != request.cartonMarks().size() || new HashSet<>(marks).size() != marks.size()) {
throw badRequest("箱唛不能为空且不能重复");
@@ -245,6 +252,13 @@ public class PdaPalletOperationService {
Map<Long, List<RelabelItem>> itemsByPosition = new LinkedHashMap<>();
Map<Long, InventoryPosition> anchors = new LinkedHashMap<>();
boolean returnRelabel = selected.stream().allMatch(item -> item.getReturnCartonId() != null);
boolean unboundReturnRelabel = returnRelabel && selected.stream().allMatch(item -> {
ReturnInventoryCarton carton = returnCartons.selectById(item.getReturnCartonId());
return carton != null && (!org.springframework.util.StringUtils.hasText(carton.getPalletCode())
|| "UNBOUND".equalsIgnoreCase(carton.getPalletCode()));
});
if (unboundReturnRelabel && source != null) throw conflict("无来源托盘的退货箱不能提交来源托盘");
if (!unboundReturnRelabel && source == null) throw conflict("请先确认来源托盘");
InventoryPosition actual = returnRelabel || request.actualSource() == null
? null : positions.selectById(request.actualSource().positionId());
if (!returnRelabel && request.actualSource() != null && (actual == null
@@ -269,7 +283,7 @@ public class PdaPalletOperationService {
}
}
if (returnRelabel) {
return bindReturnRelabelPallet(identity, application, selected, source, target, request);
return bindReturnRelabelPallet(identity, application, selected, source, target, request, unboundReturnRelabel);
}
for (RelabelItem item : selected) {
if (item.getSourceInventoryPositionId() == null || item.getNotifiedPackageCount() == null
@@ -313,7 +327,7 @@ public class PdaPalletOperationService {
private PalletOperationResponses.Operation bindReturnRelabelPallet(PdaWarehouseIdentity identity,
RelabelApplication application, List<RelabelItem> selected,
PalletPhysicalGateway.PalletReference source, PalletPhysicalGateway.PalletReference target,
PalletOperationRequests.RelabelDisassemble request) {
PalletOperationRequests.RelabelDisassemble request, boolean unboundSource) {
requireRelabelDestination(identity.overseasWarehouseId(), selected, target);
Set<String> alreadyBound = boundRelabelMarks(application);
boolean rebind = request.actualSource() == null && selected.stream().allMatch(item -> alreadyBound.contains(item.getCartonMark()));
@@ -324,14 +338,15 @@ public class PdaPalletOperationService {
for (RelabelItem item : selected) {
ReturnInventoryCarton carton = returnCartons.lockId(item.getReturnCartonId());
if (carton == null || !Objects.equals(carton.getCustomerId(), application.getCustomerId())
|| !Objects.equals(carton.getPalletCode(), source.palletCode())
|| (unboundSource ? (org.springframework.util.StringUtils.hasText(carton.getPalletCode()) && !"UNBOUND".equalsIgnoreCase(carton.getPalletCode()))
: !Objects.equals(carton.getPalletCode(), source.palletCode()))
|| !Objects.equals(carton.getRelabelApplicationId(), application.getId())
|| !Objects.equals(carton.getStatus(), "RELABEL_PENDING"))
throw conflict("退货换标来源托盘或库存状态已变化,请刷新后重试");
quantity += value(item.getNotifiedPackageCount());
cartons.add(carton);
}
if (!rebind && (request.actualSource() == null || request.actualSource().positionId() != cartons.get(0).getId()
if (!rebind && !unboundSource && (request.actualSource() == null || request.actualSource().positionId() != cartons.get(0).getId()
|| request.actualSource().packages() != quantity))
throw conflict("退货换标拆出数量必须等于本批已完成贴标数量");
String key = required(request.idempotencyKey(), "幂等键不能为空");
@@ -345,7 +360,8 @@ public class PdaPalletOperationService {
RelabelItem item = selected.get(index);
ReturnInventoryCarton carton = returnCartons.lockId(item.getReturnCartonId());
if (carton == null || !Objects.equals(carton.getCustomerId(), application.getCustomerId())
|| !Objects.equals(carton.getPalletCode(), source.palletCode())
|| (unboundSource ? (org.springframework.util.StringUtils.hasText(carton.getPalletCode()) && !"UNBOUND".equalsIgnoreCase(carton.getPalletCode()))
: !Objects.equals(carton.getPalletCode(), source.palletCode()))
|| !Objects.equals(carton.getRelabelApplicationId(), application.getId())
|| !"RELABEL_PENDING".equals(carton.getStatus()))
throw conflict("退货换标来源库存已变化,请刷新后重试");
@@ -363,9 +379,10 @@ public class PdaPalletOperationService {
}
}
recordReturnRelabelBinding(identity, application, selected, target.palletCode());
List<PalletOperationResponses.PalletContent> contents = List.of(
content(identity.overseasWarehouseId(), physical.resolve(source.palletCode())),
content(identity.overseasWarehouseId(), physical.resolve(target.palletCode())));
List<PalletOperationResponses.PalletContent> contents = source == null
? List.of(content(identity.overseasWarehouseId(), physical.resolve(target.palletCode())))
: List.of(content(identity.overseasWarehouseId(), physical.resolve(source.palletCode())),
content(identity.overseasWarehouseId(), physical.resolve(target.palletCode())));
PalletOperationResponses.Operation result = new PalletOperationResponses.Operation(operation.getId(),
operation.getOperationType(), key, "COMPLETED", false, LocalDateTime.now(), contents);
operation.setStatus("COMPLETED"); operation.setResultJson(write(result));