fix(lastleg): preserve return relabel delivery metadata and measurements

This commit is contained in:
2026-10-15 21:40:37 +08:00
parent 607aaf74dc
commit b8739eebf0
10 changed files with 224 additions and 28 deletions
@@ -1478,10 +1478,17 @@ public class DeliveryTransferService {
&& !Objects.equals(delivery.getDestinationWarehouseCode(), item.getDestinationWarehouseCode())) continue;
ReturnInventoryCarton carton = returnCartons.findCustomerMark(customerId, item.getCartonMarks());
if (carton == null) throw conflict("退货派送单来源箱记录缺失");
for (var line : returnCartonItems.listCarton(carton.getId()))
boolean relabelCarton = "RELABEL_OUTBOUND".equals(sourceOutbound.getOutboundType());
boolean firstSku = true;
// Match receipt cargo semantics: stable first SKU carries the physical box once, never per piece.
for (var line : returnCartonItems.listCarton(carton.getId())) {
cargo.add(new DeliveryResponses.DeliverySku(item.getInboundCode(), delivery.getCustomerName(),
carton.getDestinationWarehouseCode(), delivery.getDeliveryAddress(), null, null,
line.getSkuCode(), line.getProductNameZh(), 1, null, null, line.getQuantity()));
line.getSkuCode(), line.getProductNameZh(), !relabelCarton || firstSku ? 1 : 0,
relabelCarton && firstSku ? item.getGrossWeightKg() : null,
relabelCarton && firstSku ? item.getVolumeCbm() : null, line.getQuantity()));
firstSku = false;
}
}
return new DeliveryResponses.DeliveryDetail(deliveryRow(delivery), cargo,
feeSources.listForBusiness(customerId, "DELIVERY_ORDER", delivery.getId()).stream().map(this::feeRow).toList(),
@@ -105,6 +105,8 @@ public class RelabelApplicationService implements WalletResultHandler {
@Resource private OutboundPalletItemMapper outboundItems;
@Resource private OutboundPdaTaskMapper outboundTasks;
@Resource private com.baiyue.lastleg.delivery.mapper.DeliveryOrderMapper deliveryOrders;
@Resource private com.baiyue.lastleg.fleet.customer.FleetCustomerDirectory customerDirectory;
@Resource private com.baiyue.lastleg.fleet.driver.FleetWarehouseDirectory warehouseDirectory;
@Resource private OverseasFeeSourceService feeSources;
@Resource private WarehouseTransferOrderMapper warehouseTransfers;
@Resource private com.baiyue.lastleg.transfer.service.WarehouseTransferRelabelCompletionService transferRelabelCompletion;
@@ -1089,6 +1091,7 @@ public class RelabelApplicationService implements WalletResultHandler {
return;
}
List<RelabelItem> relabelItems = itemList(application.getId());
Map<Long, ReturnInventoryCarton> confirmedReturnCartons = new LinkedHashMap<>();
for (RelabelItem source : relabelItems) {
if (source.getReturnCartonId() == null) continue;
ReturnInventoryCarton carton = returnCartons.lockId(source.getReturnCartonId());
@@ -1099,6 +1102,22 @@ public class RelabelApplicationService implements WalletResultHandler {
|| intValue(source.getNotifiedPackageCount()) != 1)
throw conflict("退货整箱换标出库必须覆盖该箱全部 SKU 数量:" + source.getCartonMark());
com.baiyue.lastleg.returnreceipt.service.ReturnCartonCountGuard.requireConfirmed(carton);
confirmedReturnCartons.put(carton.getId(), carton);
}
boolean returnOnly = relabelItems.stream().allMatch(item -> item.getReturnCartonId() != null);
String customerName = null;
String warehouseName = null;
if (returnOnly) {
// Resolve once per new outbound, before any insert; every destination shares these snapshots.
var customer = customerDirectory.find(application.getCustomerId());
var warehouse = warehouseDirectory.required(Long.parseLong(application.getOverseasWarehouseId()), "OVERSEAS");
if (customer == null || !application.getCustomerId().equals(customer.id()) || !StringUtils.hasText(customer.name())
|| warehouse == null || !application.getOverseasWarehouseId().equals(warehouse.id())
|| !"OVERSEAS".equals(warehouse.region()) || !StringUtils.hasText(warehouse.name())) {
throw conflict("客户或作业仓库名称未完整返回,请核实主档后重试");
}
customerName = customer.name().trim();
warehouseName = warehouse.name().trim();
}
OutboundOrder outbound = new OutboundOrder();
outbound.setId(IdWorker.getId()); outbound.setCustomerId(application.getCustomerId());
@@ -1107,6 +1126,10 @@ public class RelabelApplicationService implements WalletResultHandler {
outbound.setStatus("WAIT_PICK"); outbound.setPackageCount(relabelItems.stream().mapToInt(item -> item.getReturnCartonId() == null ? intValue(item.getNotifiedPackageCount()) : 1).sum());
outbound.setPalletCount((int) relabelItems.stream().map(this::outboundSourceKey).distinct().count());
outbound.setRecipientInfo(application.getDeliveryMethod()); outbound.setIdempotencyKey(key);
if (returnOnly) {
var measurement = returnMeasurement(relabelItems, confirmedReturnCartons);
outbound.setGrossWeightKg(measurement.weightKg()); outbound.setVolumeCbm(measurement.volumeCbm());
}
outbound.setCreatedBy(operatorId); outbound.setCreatedAt(now); outbound.setUpdatedAt(now); outboundOrders.insert(outbound);
Map<Long, List<RelabelItem>> byPosition = relabelItems.stream().collect(Collectors.groupingBy(
this::outboundSourceKey, LinkedHashMap::new, Collectors.toList()));
@@ -1132,12 +1155,16 @@ public class RelabelApplicationService implements WalletResultHandler {
.filter(StringUtils::hasText).distinct().collect(Collectors.joining(",")));
item.setInboundCode(group.stream().map(RelabelItem::getSourceInboundCode).filter(StringUtils::hasText).distinct().collect(Collectors.joining(",")));
item.setOrderRefs(writeOutboundRefs(application.getRelabelNo(), position == null ? null : entry.getKey(), group));
if (position == null) {
var measurement = returnMeasurement(group, confirmedReturnCartons);
item.setGrossWeightKg(measurement.weightKg()); item.setVolumeCbm(measurement.volumeCbm());
}
item.setCreatedAt(now); outboundItems.insert(item);
}
OutboundPdaTask task = new OutboundPdaTask(); task.setId(IdWorker.getId()); task.setCustomerId(application.getCustomerId());
task.setOutboundOrderId(outbound.getId()); task.setTaskNo("PDA" + now.format(DateTimeFormatter.BASIC_ISO_DATE) + task.getId());
task.setStatus("PENDING"); task.setPushedAt(now); task.setCreatedAt(now); task.setUpdatedAt(now); outboundTasks.insert(task);
if (relabelItems.stream().allMatch(item -> item.getReturnCartonId() != null)) {
if (returnOnly) {
Map<String, List<RelabelItem>> destinations = relabelItems.stream().collect(Collectors.groupingBy(
item -> Objects.toString(item.getDestinationFbaWarehouseCode(), ""), LinkedHashMap::new, Collectors.toList()));
int index = 0;
@@ -1147,12 +1174,15 @@ public class RelabelApplicationService implements WalletResultHandler {
delivery.setDeliveryNo(orderNoGenerator.next("DELIVERY", application.getOverseasWarehouseId(), delivery.getId()));
delivery.setOutboundOrderId(outbound.getId()); delivery.setOutboundNo(outbound.getOutboundNo());
delivery.setOverseasWarehouseId(application.getOverseasWarehouseId());
delivery.setCustomerName(customerName); delivery.setOverseasWarehouseName(warehouseName);
delivery.setDeliveryGroupKey("RETURN_RELABEL:" + destination.getKey());
delivery.setDestinationWarehouseCode(destination.getKey());
delivery.setDestinationType(StringUtils.hasText(destination.getKey()) ? "FBA" : null);
delivery.setContactName(application.getContactName()); delivery.setContactPhone(application.getContactPhone());
delivery.setDeliveryMethod(application.getDeliveryMethod()); delivery.setStatus("WAIT_DELIVERY");
delivery.setPackageCount(destination.getValue().size()); delivery.setPalletCount(0);
var measurement = returnMeasurement(destination.getValue(), confirmedReturnCartons);
delivery.setTotalWeightKg(measurement.weightKg()); delivery.setTotalVolumeCbm(measurement.volumeCbm());
delivery.setIdempotencyKey(key + ":DELIVERY:" + index++); delivery.setVersion(0L);
delivery.setCreatedBy(operatorId); delivery.setCreatedAt(now); delivery.setUpdatedAt(now);
deliveryOrders.insert(delivery);
@@ -1161,6 +1191,24 @@ public class RelabelApplicationService implements WalletResultHandler {
application.setOutboundOrderNo(outbound.getOutboundNo());
}
private record ReturnMeasurement(BigDecimal weightKg, BigDecimal volumeCbm) { }
private ReturnMeasurement returnMeasurement(List<RelabelItem> sources, Map<Long, ReturnInventoryCarton> cartons) {
BigDecimal weight = BigDecimal.ZERO;
BigDecimal volume = BigDecimal.ZERO;
// The selected physical carton contributes once, regardless of its SKU count or piece quantities.
for (Long cartonId : sources.stream().map(RelabelItem::getReturnCartonId).distinct().toList()) {
ReturnInventoryCarton carton = cartons.get(cartonId);
if (carton == null) return new ReturnMeasurement(null, null);
weight = weight == null || !positive(carton.getWeight()) ? null : weight.add(carton.getWeight());
volume = volume == null || !positive(carton.getLength()) || !positive(carton.getWidth()) || !positive(carton.getHeight())
? null : volume.add(carton.getLength().multiply(carton.getWidth()).multiply(carton.getHeight()).movePointLeft(6));
}
return new ReturnMeasurement(weight, volume);
}
private static boolean positive(BigDecimal value) { return value != null && value.signum() > 0; }
private String writeOutboundRefs(String relabelNo, Long positionId, List<RelabelItem> values) {
try {
Map<String, Object> refs = new LinkedHashMap<>();