贴标申请,仓储转运

This commit is contained in:
2026-09-17 17:37:29 +08:00
parent 3b8b6f5aa2
commit 29f05b7b09
106 changed files with 5019 additions and 471 deletions
@@ -70,6 +70,27 @@ public class DeliveryTransferController {
return ApiResponse.success(service.createReturnOutbound(requiredCustomer(user, customerId), operator(user), request));
}
@GetMapping("/outbound-orders/warehouse-transfer/candidates")
@PreAuthorize("hasAuthority('" + OverseasPermissions.OUTBOUND_CREATE + "')")
@Operation(summary = "List warehouse-transfer pallets that can create an outbound order")
public ApiResponse<java.util.List<DeliveryResponses.WarehouseTransferOutboundCandidate>> warehouseTransferCandidates(
@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam(required = false) String customerId,
@RequestParam(required = false) String warehouseId) {
requirePlatform(user);
return ApiResponse.success(service.warehouseTransferOutboundCandidates(customerId, warehouseId));
}
@PostMapping("/outbound-orders/warehouse-transfer")
@PreAuthorize("hasAuthority('" + OverseasPermissions.OUTBOUND_CREATE + "')")
@Operation(summary = "Create a warehouse-transfer outbound, delivery order and PDA task")
public ApiResponse<DeliveryResponses.OutboundDetail> createWarehouseTransferOutbound(
@AuthenticationPrincipal AuthenticatedUser user, @RequestParam String customerId,
@Valid @RequestBody DeliveryRequests.WarehouseTransferOutbound request) {
return ApiResponse.success(service.createWarehouseTransferOutbound(
requiredCustomer(user, customerId), operator(user), request));
}
@GetMapping("/outbound-orders")
@PreAuthorize("hasAuthority('" + OverseasPermissions.OUTBOUND_LIST + "')")
public ApiResponse<PageResult<DeliveryResponses.OutboundRow>> outboundPage(
@@ -75,6 +75,20 @@ public final class DeliveryRequests {
@NotNull @Size(min=1, max=200) List<@NotBlank @Size(max=128) String> cartonMarks,
@Size(max=1000) String remark,
@NotBlank @Size(max=255) String idempotencyKey) {}
public record WarehouseTransferOutbound(
@NotBlank @Size(max=64) String transferNo,
@NotNull @Size(min=1, max=100) List<@NotNull Long> palletIds,
@NotBlank @jakarta.validation.constraints.Pattern(regexp = "FBA|PRIVATE") String destinationType,
@Size(max=128) String destinationWarehouseId,
@Size(max=128) String destinationWarehouseCode,
@Size(max=255) String destinationWarehouseName,
@Size(max=1000) String deliveryAddress,
@Size(max=128) String contactName,
@Size(max=64) String contactPhone,
@Size(max=32) String deliveryMethod,
LocalDateTime plannedDeliveryAt,
@Size(max=1000) String remark,
@NotBlank @Size(max=255) String idempotencyKey) {}
public record OneClickItem(@NotNull Long inventoryPositionId, @NotNull @Min(1) Long packages,
@NotBlank @Size(max=128) String sku,
@Size(max=200) List<@NotBlank @Size(max=128) String> cartonMarks) {
@@ -31,7 +31,8 @@ public final class DeliveryResponses {
String remark, LocalDateTime createdAt, long version, List<String> allowedActions,
String bolNo, DeliveryDocumentDtos.Summary documentSummary,
String destinationWarehouseId, String destinationWarehouseCode,
String destinationWarehouseName) {}
String destinationWarehouseName, String destinationType,
String deliveryAddress, String contactName, String contactPhone, String postalCode) {}
public record DeliverySku(String inboundCode, String customerName, String destinationWarehouseCode,
String receivingAddress, String shipmentNo, String fbaTrackingNo,
@@ -59,6 +60,16 @@ public final class DeliveryResponses {
String cartonMark, String fbaCartonMark,
String destinationWarehouseCode, String destinationWarehouseName,
long totalQuantity, String status) {}
public record WarehouseTransferOutboundCandidate(String customerId, String customerName,
String transferNo, String overseasWarehouseId,
String transferStatus, boolean needRelabel,
String relabelNo, String destinationType,
String destinationWarehouseCode,
String privateDestinationAddress,
String privateDestinationPostalCode,
Long palletId, Long warehousePalletId, String palletNo,
int packageCount, BigDecimal grossWeightKg,
BigDecimal volumeCbm) {}
public record PdaTask(Long id, String taskNo, String status, String outboundNo, LocalDateTime pushedAt, LocalDateTime completedAt) {}
public record PdaTaskResolution(String workflow, String taskNo, String outboundNo, String dispatchNo) {}
public record PdaPickPosition(Long inventoryPositionId, String palletNo, String cartonMark,
@@ -26,6 +26,11 @@ public class DeliveryOrder {
private String destinationWarehouseId;
private String destinationWarehouseCode;
private String destinationWarehouseName;
private String destinationType;
private String deliveryAddress;
private String postalCode;
private String contactName;
private String contactPhone;
private String deliveryGroupKey;
private String status;
private String waybillNo;
@@ -39,6 +39,7 @@ public class DeliveryCompletedOutboxService {
Objects.requireNonNull(completedAt, "completedAt");
Map<String, LastlegOrder> bySourceInbound = new LinkedHashMap<>();
for (LastlegOrder order : orders == null ? List.<LastlegOrder>of() : orders) {
if ("RELABEL_APPLICATION".equals(order.getOrderType())) continue;
if (!StringUtils.hasText(order.getSourceInboundId())) continue;
requireText(order.getSourceOrderId(), "sourceOrderId");
requireText(order.getInboundCode(), "inboundCode");
@@ -60,7 +60,9 @@ import com.baiyue.lastleg.relabel.mapper.RelabelItemMapper;
import com.baiyue.lastleg.relabel.service.RelabelApplicationService;
import com.baiyue.lastleg.relabel.dto.RelabelRequests;
import com.baiyue.lastleg.transfer.entity.WarehouseTransferPallet;
import com.baiyue.lastleg.transfer.entity.WarehouseTransferOrder;
import com.baiyue.lastleg.transfer.mapper.WarehouseTransferPalletMapper;
import com.baiyue.lastleg.transfer.mapper.WarehouseTransferOrderMapper;
import jakarta.annotation.Resource;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.HttpStatus;
@@ -97,6 +99,7 @@ public class DeliveryTransferService {
@Resource private InventoryBalanceMapper inventoryBalances;
@Resource private InventoryCommandService inventoryCommands;
@Resource private WarehouseTransferPalletMapper warehouseTransferPallets;
@Resource private WarehouseTransferOrderMapper warehouseTransfers;
@Resource private DropshipOrderProfileMapper dropshipProfiles;
@Resource private DropshipCartonMapper dropshipCartons;
@Resource private DropshipCartonSkuMapper dropshipCartonSkus;
@@ -217,6 +220,11 @@ public class DeliveryTransferService {
firstText(destination.getDestinationWarehouseCode(), destination.getFbaWarehouseCode()),
destination.getDestinationWarehouseId()));
delivery.setDestinationWarehouseName(trim(destination.getDestinationWarehouseName()));
delivery.setDestinationType(destination.getDestinationType());
delivery.setDeliveryAddress(destination.getDeliveryAddress());
delivery.setPostalCode(destination.getPostalCode());
delivery.setContactName(destination.getContactName());
delivery.setContactPhone(destination.getContactPhone());
delivery.setDeliveryGroupKey(entry.getKey());
LastlegOrder firstOrder = firstTailOrder(customerId, groupItems);
delivery.setCustomerName(firstOrder == null ? null : firstOrder.getCustomerName()); delivery.setStatus("WAIT_DELIVERY"); delivery.setVersion(0L);
@@ -395,6 +403,155 @@ public class DeliveryTransferService {
return taskResult(outbound, task);
}
@Transactional(readOnly = true)
public List<DeliveryResponses.WarehouseTransferOutboundCandidate> warehouseTransferOutboundCandidates(
String customerId, String warehouseId) {
List<WarehouseTransferOrder> transfers = warehouseTransfers.selectList(
new LambdaQueryWrapper<WarehouseTransferOrder>()
.eq(StringUtils.hasText(customerId), WarehouseTransferOrder::getCustomerId, trim(customerId))
.eq(StringUtils.hasText(warehouseId), WarehouseTransferOrder::getOperatingWarehouseId, trim(warehouseId))
.eq(WarehouseTransferOrder::getStatus, "WAIT_FBA_DESTINATION")
.orderByDesc(WarehouseTransferOrder::getUpdatedAt));
List<DeliveryResponses.WarehouseTransferOutboundCandidate> result = new ArrayList<>();
for (WarehouseTransferOrder transfer : transfers) {
LastlegOrder order = tailOrders.selectById(transfer.getLastlegOrderId());
List<WarehouseTransferPallet> completedPallets = warehouseTransferPallets.listOrder(
transfer.getCustomerId(), transfer.getId()).stream()
.filter(pallet -> "COMPLETED".equals(pallet.getStatus()) && pallet.getWarehousePalletId() != null)
.toList();
long available = 0;
for (WarehouseTransferPallet pallet : completedPallets) {
long palletAvailable = inventoryPositions.listPallet(transfer.getCustomerId(), pallet.getWarehousePalletId())
.stream().filter(position -> transfer.getOperatingWarehouseId().equals(position.getOverseasWarehouseId()))
.mapToLong(position -> value(position.getAvailablePackages())).sum();
available += palletAvailable;
}
if (available <= 0 || completedPallets.isEmpty()) continue;
result.add(new DeliveryResponses.WarehouseTransferOutboundCandidate(
transfer.getCustomerId(), order == null ? null : order.getCustomerName(), transfer.getTransferNo(),
transfer.getOperatingWarehouseId(), transfer.getStatus(), Boolean.TRUE.equals(transfer.getNeedRelabel()),
transfer.getRelabelNo(), transfer.getDestinationType(), transfer.getDestinationWarehouseCode(),
transfer.getPrivateDestinationAddress(), transfer.getPrivateDestinationPostalCode(),
null, null, null, Math.toIntExact(available),
completedPallets.stream().map(WarehouseTransferPallet::getGrossWeightKg).filter(Objects::nonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add),
completedPallets.stream().map(WarehouseTransferPallet::getVolumeCbm).filter(Objects::nonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add)));
}
return result;
}
@Transactional
public DeliveryResponses.OutboundDetail createWarehouseTransferOutbound(String customerId, String operatorId,
DeliveryRequests.WarehouseTransferOutbound request) {
String key = request.idempotencyKey().trim();
String requestHash = hash(request);
OutboundOrder replay = outboundOrders.findByIdempotency(customerId, key);
if (replay != null) {
if (!"WAREHOUSE_TRANSFER_OUTBOUND".equals(replay.getOutboundType())
|| !requestHash.equals(replay.getRequestHash())) {
throw conflict("相同幂等键不能用于不同的仓储转运出库请求");
}
return outboundDetail(replay.getOutboundNo());
}
WarehouseTransferOrder transfer = warehouseTransfers.lockNo(customerId, request.transferNo().trim());
if (transfer == null) throw notFound("仓储转运单不存在");
if (!Set.of("READY_FOR_DISPATCH", "WAIT_FBA_DESTINATION").contains(transfer.getStatus())) {
throw conflict("仓储转运单尚未完成换标或已经生成出库单");
}
String destinationType = request.destinationType().trim().toUpperCase(Locale.ROOT);
if ("FBA".equals(destinationType) && !StringUtils.hasText(request.destinationWarehouseCode())) {
throw conflict("FBA出库必须选择目的仓");
}
if ("PRIVATE".equals(destinationType) && (!StringUtils.hasText(request.deliveryAddress())
|| !StringUtils.hasText(request.contactName()) || !StringUtils.hasText(request.contactPhone()))) {
throw conflict("私人仓出库必须填写地址、联系人和联系电话");
}
List<Long> palletIds = request.palletIds().stream().distinct().toList();
if (palletIds.size() != request.palletIds().size()) throw conflict("出库托盘不可重复");
List<WarehouseTransferPallet> selectedPallets = new ArrayList<>();
for (Long palletId : palletIds) {
WarehouseTransferPallet pallet = warehouseTransferPallets.lockOne(customerId, transfer.getId(), palletId);
if (pallet == null || !"COMPLETED".equals(pallet.getStatus()) || pallet.getWarehousePalletId() == null) {
throw conflict("所选托盘不属于该仓储转运单或尚未完成入库:" + palletId);
}
selectedPallets.add(pallet);
}
LocalDateTime now = LocalDateTime.now();
OutboundOrder outbound = new OutboundOrder();
outbound.setId(IdWorker.getId()); outbound.setCustomerId(customerId);
outbound.setOutboundNo(orderNoGenerator.next("OUTBOUND", transfer.getOperatingWarehouseId(), outbound.getId()));
outbound.setOverseasWarehouseId(transfer.getOperatingWarehouseId());
outbound.setOutboundType("WAREHOUSE_TRANSFER_OUTBOUND"); outbound.setStatus("WAIT_PICK");
outbound.setRecipientInfo("PRIVATE".equals(destinationType)
? request.contactName().trim() + " / " + request.contactPhone().trim() + " / " + request.deliveryAddress().trim()
: request.destinationWarehouseCode().trim());
outbound.setPalletCount(selectedPallets.size()); outbound.setIdempotencyKey(key);
outbound.setRequestHash(requestHash); outbound.setCreatedBy(operatorId); outbound.setCreatedAt(now); outbound.setUpdatedAt(now);
outboundOrders.insert(outbound);
int packageCount = 0;
for (WarehouseTransferPallet pallet : selectedPallets) {
List<InventoryPosition> positions = inventoryPositions.listPallet(customerId, pallet.getWarehousePalletId())
.stream().filter(position -> transfer.getOperatingWarehouseId().equals(position.getOverseasWarehouseId()))
.filter(position -> value(position.getAvailablePackages()) > 0).toList();
if (positions.isEmpty()) throw conflict("所选托盘没有可出库库存:" + pallet.getPalletNo());
for (InventoryPosition position : positions) {
InventoryBalance balance = inventoryBalances.selectById(position.getBalanceId());
if (balance == null || !customerId.equals(balance.getCustomerId())) throw conflict("托盘库存余额不存在");
int packages = Math.toIntExact(value(position.getAvailablePackages()));
OutboundPalletItem item = new OutboundPalletItem();
item.setId(IdWorker.getId()); item.setCustomerId(customerId); item.setOutboundOrderId(outbound.getId());
item.setWarehousePalletId(pallet.getWarehousePalletId()); item.setPalletNo(pallet.getPalletNo());
item.setDestinationWarehouseId(trim(request.destinationWarehouseId()));
item.setDestinationWarehouseCode(trim(request.destinationWarehouseCode()));
item.setDestinationWarehouseName(trim(request.destinationWarehouseName()));
item.setPackageCount(packages); item.setPickedPackageCount(0); item.setStatus("WAIT_PICK");
item.setSkuCode(balance.getSkuCode()); item.setInboundCode(balance.getInboundCode());
item.setOrderRefs(transfer.getTransferNo()); item.setCreatedAt(now); outboundPallets.insert(item);
packageCount = Math.addExact(packageCount, packages);
}
}
outbound.setPackageCount(packageCount); outboundOrders.updateById(outbound);
LastlegOrder tailOrder = tailOrders.selectById(transfer.getLastlegOrderId());
DeliveryOrder delivery = new DeliveryOrder();
delivery.setId(IdWorker.getId()); delivery.setCustomerId(customerId);
delivery.setDeliveryNo(orderNoGenerator.next("DELIVERY", transfer.getOperatingWarehouseId(), delivery.getId()));
delivery.setOutboundOrderId(outbound.getId()); delivery.setOutboundNo(outbound.getOutboundNo());
delivery.setOverseasWarehouseId(transfer.getOperatingWarehouseId());
delivery.setCustomerName(tailOrder == null ? null : tailOrder.getCustomerName());
delivery.setDestinationType(destinationType); delivery.setDestinationWarehouseId(trim(request.destinationWarehouseId()));
delivery.setDestinationWarehouseCode(trim(request.destinationWarehouseCode()));
delivery.setDestinationWarehouseName(trim(request.destinationWarehouseName()));
delivery.setDeliveryAddress(trim(request.deliveryAddress())); delivery.setContactName(trim(request.contactName()));
delivery.setContactPhone(trim(request.contactPhone())); delivery.setDeliveryGroupKey(destinationType + ":" +
firstText(request.destinationWarehouseCode(), request.deliveryAddress()));
delivery.setStatus("WAIT_DELIVERY"); delivery.setDeliveryMethod(firstText(request.deliveryMethod(), "TRUCK"));
delivery.setPackageCount(packageCount); delivery.setPalletCount(selectedPallets.size());
delivery.setTotalWeightKg(selectedPallets.stream().map(WarehouseTransferPallet::getGrossWeightKg)
.filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add));
delivery.setTotalVolumeCbm(selectedPallets.stream().map(WarehouseTransferPallet::getVolumeCbm)
.filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add));
delivery.setPlannedDeliveryAt(request.plannedDeliveryAt()); delivery.setRemark(trim(request.remark()));
delivery.setIdempotencyKey(childKey(key, "DELIVERY")); delivery.setCreatedBy(operatorId); delivery.setVersion(0L);
delivery.setCreatedAt(now); delivery.setUpdatedAt(now); deliveryOrders.insert(delivery);
recordOperation(customerId, delivery.getId(), operatorId, "CREATE_DELIVERY", "仓储转运出库自动生成派送单", now);
OutboundPdaTask task = new OutboundPdaTask(); task.setId(IdWorker.getId()); task.setCustomerId(customerId);
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); pdaTasks.insert(task);
transfer.setStatus("WAIT_OUTBOUND");
if ("FBA".equals(destinationType)) transfer.setFbaWarehouseCode(request.destinationWarehouseCode().trim());
transfer.setUpdatedAt(now); warehouseTransfers.updateById(transfer);
if (tailOrder != null && customerId.equals(tailOrder.getCustomerId())) {
tailOrder.setCurrentStatus("WAIT_OUTBOUND"); tailOrder.setOutboundOrderNo(outbound.getOutboundNo());
tailOrder.setDeliveryOrderNo(delivery.getDeliveryNo()); tailOrder.setFbaWarehouseCode(trim(request.destinationWarehouseCode()));
tailOrder.setSubOrderStatus("WAREHOUSE_TRANSFER:WAIT_OUTBOUND"); tailOrder.setUpdatedAt(now); tailOrders.updateById(tailOrder);
}
return outboundDetail(outbound.getOutboundNo());
}
private String validateOneClickItems(String customerId, List<DeliveryRequests.OneClickItem> items) {
String warehouseId = null;
Map<String, Long> requestedPackages = new LinkedHashMap<>();
@@ -541,6 +698,40 @@ public class DeliveryTransferService {
task.setStatus("IN_PROGRESS"); task.setUpdatedAt(LocalDateTime.now()); pdaTasks.updateById(task);
return taskResult(outbound, task);
}
if ("WAREHOUSE_TRANSFER_OUTBOUND".equals(outbound.getOutboundType())) {
if (request.inventoryPositionId() == null) throw conflict("仓储转运出库必须确认实际库存位置");
InventoryPosition selectedPosition = inventoryPositions.selectById(request.inventoryPositionId());
if (selectedPosition == null || !customerId.equals(selectedPosition.getCustomerId())
|| !warehouseId.equals(selectedPosition.getOverseasWarehouseId())
|| !Objects.equals(selectedPosition.getPalletId(), item.getWarehousePalletId())) {
throw conflict("确认的库存位置不属于该仓储转运出库托盘");
}
InventoryBalance balance = inventoryBalances.selectById(selectedPosition.getBalanceId());
if (!matchesOutboundItem(customerId, warehouseId, item, balance)) {
throw conflict("确认的库存SKU或入仓码与出库明细不一致");
}
if (request.packages() > value(item.getPackageCount()) - value(item.getPickedPackageCount())) {
throw conflict("出库数量超过仓储转运出库明细余量");
}
inventoryCommands.allocate(customerId, operatorId, new InventoryCommandRequests.Move(
selectedPosition.getId(), request.packages(), item.getOrderRefs(), outbound.getOutboundNo(), null,
request.idempotencyKey() + ":ALLOCATE", "仓储转运出库分配"));
inventoryCommands.pick(customerId, operatorId, new InventoryCommandRequests.Move(
selectedPosition.getId(), request.packages(), item.getOrderRefs(), outbound.getOutboundNo(), null,
request.idempotencyKey() + ":PICK", "仓储转运出库拣货"));
inventoryCommands.ship(customerId, operatorId, new InventoryCommandRequests.Move(
selectedPosition.getId(), request.packages(), item.getOrderRefs(), outbound.getOutboundNo(), null,
request.idempotencyKey() + ":SHIP", "仓储转运出库"));
OutboundPdaPick pick = new OutboundPdaPick(); pick.setId(IdWorker.getId()); pick.setCustomerId(customerId);
pick.setTaskId(task.getId()); pick.setOutboundPalletItemId(item.getId());
pick.setInventoryPositionId(selectedPosition.getId()); pick.setPackages(request.packages());
pick.setIdempotencyKey(request.idempotencyKey().trim()); pick.setPickedAt(LocalDateTime.now()); pdaPicks.insert(pick);
item.setPickedPackageCount(value(item.getPickedPackageCount()) + request.packages().intValue());
item.setStatus(item.getPickedPackageCount() >= item.getPackageCount() ? "PICKED" : "PARTIAL_PICKED");
outboundPallets.updateById(item); updateOutboundPickingStatus(customerId, outbound);
task.setStatus("IN_PROGRESS"); task.setUpdatedAt(LocalDateTime.now()); pdaTasks.updateById(task);
return taskResult(outbound, task);
}
InventoryPosition selectedPosition = inventoryPositions.selectById(request.inventoryPositionId());
if (selectedPosition == null || !customerId.equals(selectedPosition.getCustomerId()) || !Objects.equals(selectedPosition.getPalletId(), item.getWarehousePalletId())) throw conflict("Scanned inventory position does not belong to the outbound pallet");
if (!warehouseId.equals(selectedPosition.getOverseasWarehouseId())) {
@@ -612,8 +803,8 @@ public class DeliveryTransferService {
if (outbound == null || !boundWarehouseId.equals(outbound.getOverseasWarehouseId())) {
throw notFound("Outbound order not found in the bound warehouse");
}
if (!"ONE_CLICK_DROPSHIP".equals(outbound.getOutboundType())) {
throw conflict("整托扫码仅适用于一件代发出库");
if (!Set.of("ONE_CLICK_DROPSHIP", "RELABEL_OUTBOUND", "WAREHOUSE_TRANSFER_OUTBOUND").contains(outbound.getOutboundType())) {
throw conflict("整托扫码仅适用于一件代发或换标出库");
}
if (!Set.of("PENDING", "IN_PROGRESS").contains(task.getStatus())) {
throw conflict("PDA task is not available for picking");
@@ -695,6 +886,23 @@ public class DeliveryTransferService {
new RelabelRequests.ChangeStatus("COMPLETED", "换标出库PDA作业完成"));
}
}
if ("WAREHOUSE_TRANSFER_OUTBOUND".equals(outbound.getOutboundType())) {
List<String> transferNos = items.stream().map(OutboundPalletItem::getOrderRefs)
.filter(StringUtils::hasText).distinct().toList();
for (String transferNo : transferNos) {
WarehouseTransferOrder transfer = warehouseTransfers.lockNo(customerId, transferNo);
if (transfer != null && "WAIT_OUTBOUND".equals(transfer.getStatus())) {
transfer.setStatus("OUTBOUNDED"); transfer.setOutboundAt(now); transfer.setUpdatedAt(now);
warehouseTransfers.updateById(transfer);
LastlegOrder order = tailOrders.selectById(transfer.getLastlegOrderId());
if (order != null && customerId.equals(order.getCustomerId())) {
order.setCurrentStatus("IN_TRANSIT");
order.setSubOrderStatus("WAREHOUSE_TRANSFER:OUTBOUNDED"); order.setUpdatedAt(now);
tailOrders.updateById(order);
}
}
}
}
return taskResult(outbound, task);
}
@@ -724,6 +932,23 @@ public class DeliveryTransferService {
if (task != null) { task.setStatus("CANCELLED"); task.setUpdatedAt(now); pdaTasks.updateById(task); }
outbound.setStatus("CANCELLED"); outbound.setUpdatedAt(now); outboundOrders.updateById(outbound);
for (OutboundPalletItem item : outboundItems) { item.setStatus("CANCELLED"); outboundPallets.updateById(item); }
if ("WAREHOUSE_TRANSFER_OUTBOUND".equals(outbound.getOutboundType())) {
for (String transferNo : outboundItems.stream().map(OutboundPalletItem::getOrderRefs)
.filter(StringUtils::hasText).distinct().toList()) {
WarehouseTransferOrder transfer = warehouseTransfers.lockNo(customerId, transferNo);
if (transfer == null || !"WAIT_OUTBOUND".equals(transfer.getStatus())) continue;
String restored = StringUtils.hasText(transfer.getFbaWarehouseCode())
? "READY_FOR_DISPATCH" : "WAIT_FBA_DESTINATION";
transfer.setStatus(restored); transfer.setUpdatedAt(now); warehouseTransfers.updateById(transfer);
LastlegOrder order = tailOrders.selectById(transfer.getLastlegOrderId());
if (order != null && customerId.equals(order.getCustomerId())) {
order.setCurrentStatus(restored); order.setSubOrderStatus("WAREHOUSE_TRANSFER:" + restored);
order.setOutboundOrderNo(null); order.setDeliveryOrderNo(null); order.setUpdatedAt(now);
tailOrders.updateById(order);
}
}
return;
}
if (!existingDeliveries.isEmpty()) {
for (LastlegOrder order : tailOrders.selectList(new LambdaQueryWrapper<LastlegOrder>().eq(LastlegOrder::getCustomerId, customerId).eq(LastlegOrder::getOutboundOrderNo, outboundNo))) {
String from = order.getCurrentStatus(); order.setCurrentStatus("RECEIVED"); order.setSubOrderStatus(null); order.setOutboundOrderNo(null); order.setDeliveryOrderNo(null); order.setUpdatedAt(now); tailOrders.updateById(order);
@@ -958,17 +1183,20 @@ public class DeliveryTransferService {
candidate.getInboundCode(), candidate.getSkuCode(), candidate.getProductName(),
value(candidate.getAvailablePackages()));
}
private DeliveryResponses.DeliveryRow deliveryRow(DeliveryOrder d) { return new DeliveryResponses.DeliveryRow(d.getId(), d.getCustomerId(), d.getCustomerName(), d.getDeliveryNo(), d.getOutboundNo(), d.getDispatchNo(), d.getOverseasWarehouseId(), d.getOverseasWarehouseName(), d.getStatus(), d.getWaybillNo(), d.getDeliveryMethod(), d.getCarrierName(), d.getPickupMethod(), d.getLoadingType(), d.getVehicleType(), d.getVehicleSize(), d.getVehiclePlate(), d.getDriverName(), d.getDriverPhone(), d.getAppointmentAccount(), d.getSellerName(), value(d.getPalletCount()), value(d.getPackageCount()), d.getTotalWeightKg(), d.getTotalVolumeCbm(), d.getPlannedDeliveryAt(), d.getDispatchedAt(), d.getExpectedArrivalAt(), d.getSignedAt(), d.getRemark(), d.getCreatedAt(), value(d.getVersion()), DeliveryStatusPolicy.allowedActions(d.getStatus()), d.getBolNo(), com.baiyue.lastleg.delivery.model.DeliveryDocumentSnapshot.summary(d.getDocumentsJson()), d.getDestinationWarehouseId(), d.getDestinationWarehouseCode(), d.getDestinationWarehouseName()); }
private DeliveryResponses.DeliveryRow deliveryRow(DeliveryOrder d) { return new DeliveryResponses.DeliveryRow(d.getId(), d.getCustomerId(), d.getCustomerName(), d.getDeliveryNo(), d.getOutboundNo(), d.getDispatchNo(), d.getOverseasWarehouseId(), d.getOverseasWarehouseName(), d.getStatus(), d.getWaybillNo(), d.getDeliveryMethod(), d.getCarrierName(), d.getPickupMethod(), d.getLoadingType(), d.getVehicleType(), d.getVehicleSize(), d.getVehiclePlate(), d.getDriverName(), d.getDriverPhone(), d.getAppointmentAccount(), d.getSellerName(), value(d.getPalletCount()), value(d.getPackageCount()), d.getTotalWeightKg(), d.getTotalVolumeCbm(), d.getPlannedDeliveryAt(), d.getDispatchedAt(), d.getExpectedArrivalAt(), d.getSignedAt(), d.getRemark(), d.getCreatedAt(), value(d.getVersion()), DeliveryStatusPolicy.allowedActions(d.getStatus()), d.getBolNo(), com.baiyue.lastleg.delivery.model.DeliveryDocumentSnapshot.summary(d.getDocumentsJson()), d.getDestinationWarehouseId(), d.getDestinationWarehouseCode(), d.getDestinationWarehouseName(), d.getDestinationType(), d.getDeliveryAddress(), d.getContactName(), d.getContactPhone(), d.getPostalCode()); }
private DeliveryResponses.DeliverySku sku(LastlegOrderDetail d, LastlegOrder order) { return new DeliveryResponses.DeliverySku(order == null ? null : order.getInboundCode(), order == null ? null : order.getCustomerName(), d.getDestinationWarehouseCode(), null, d.getShipmentNo(), d.getFbaTrackingNo(), d.getSkuCode(), firstText(d.getProductNameZh(), d.getProductNameEn()), value(d.getPackageCount()), d.getGrossWeightKg(), d.getVolumeCbm()); }
private DeliveryResponses.DeliveryFeeRow feeRow(OverseasFeeSource f) { return new DeliveryResponses.DeliveryFeeRow(f.getId(), f.getDirection(), f.getFeeItemCode(), f.getOriginalCurrency(), f.getOriginalAmount(), f.getReviewReason()); }
private DeliveryResponses.DeliveryOperationRow operationRow(DeliveryOperation o) { return new DeliveryResponses.DeliveryOperationRow(o.getId(), o.getCreatedAt(), o.getOperationNode(), firstText(o.getOperatorName(), o.getOperatorId()), o.getResult()); }
private void recordOperation(String customerId, Long deliveryId, String operatorId, String node, String result, LocalDateTime now) { DeliveryOperation operation = new DeliveryOperation(); operation.setCustomerId(customerId); operation.setDeliveryOrderId(deliveryId); operation.setOperationNode(node); operation.setOperatorId(operatorId); operation.setOperatorName(operatorId); operation.setResult(result); operation.setCreatedAt(now); deliveryOperations.insert(operation); }
private LastlegOrder firstTailOrder(String customerId, List<DispatchItem> items) { return items.stream().map(DispatchItem::getOrderId).filter(Objects::nonNull).map(tailOrders::selectById).filter(Objects::nonNull).filter(order -> customerId.equals(order.getCustomerId())).findFirst().orElse(null); }
private String deliveryGroupKey(DispatchItem item) {
if ("PRIVATE".equals(item.getDestinationType()) && StringUtils.hasText(item.getDeliveryAddress())) {
return "PRIVATE:" + item.getDeliveryAddress().trim() + ":" + Objects.toString(item.getPostalCode(), "").trim();
}
String code = firstText(item.getDestinationWarehouseCode(), item.getFbaWarehouseCode());
if (StringUtils.hasText(code)) return "CODE:" + code.toUpperCase(Locale.ROOT);
if (StringUtils.hasText(item.getDestinationWarehouseId())) return "ID:" + item.getDestinationWarehouseId().trim();
throw conflict("Dispatch pallet is missing its destination FBA warehouse");
throw conflict("Dispatch pallet is missing its destination");
}
private String resolveLxCodes(String customerId, List<DispatchItem> items) {
List<String> inboundCodes = items.stream().map(DispatchItem::getInboundCode)
@@ -1029,6 +1257,14 @@ public class DeliveryTransferService {
return measuredWeight.add(orderWeight);
}
private static String trim(String value) { return StringUtils.hasText(value) ? value.trim() : null; }
private String hash(Object value) {
try {
return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256")
.digest(objectMapper.writeValueAsString(value).getBytes(StandardCharsets.UTF_8)));
} catch (JsonProcessingException | NoSuchAlgorithmException error) {
throw new IllegalStateException("无法生成请求摘要", error);
}
}
private static Long parseLong(String value) {
try { return StringUtils.hasText(value) ? Long.parseLong(value.trim()) : null; }
catch (NumberFormatException ignored) { return null; }