暂存提交
This commit is contained in:
+213
@@ -0,0 +1,213 @@
|
||||
package com.baiyue.lastleg.delivery.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baiyue.common.core.api.PageResult;
|
||||
import com.baiyue.lastleg.delivery.dto.DeliveryRequests;
|
||||
import com.baiyue.lastleg.delivery.dto.DeliveryResponses;
|
||||
import com.baiyue.lastleg.delivery.entity.DeliveryOrder;
|
||||
import com.baiyue.lastleg.delivery.entity.OutboundOrder;
|
||||
import com.baiyue.lastleg.delivery.entity.OutboundPalletItem;
|
||||
import com.baiyue.lastleg.delivery.mapper.DeliveryOrderMapper;
|
||||
import com.baiyue.lastleg.delivery.mapper.OutboundOrderMapper;
|
||||
import com.baiyue.lastleg.delivery.mapper.OutboundPalletItemMapper;
|
||||
import com.baiyue.lastleg.dispatch.entity.DispatchItem;
|
||||
import com.baiyue.lastleg.dispatch.entity.DispatchOrder;
|
||||
import com.baiyue.lastleg.dispatch.mapper.DispatchItemMapper;
|
||||
import com.baiyue.lastleg.dispatch.mapper.DispatchOrderMapper;
|
||||
import com.baiyue.lastleg.entity.LastlegOrder;
|
||||
import com.baiyue.lastleg.entity.LastlegOrderStatusHistory;
|
||||
import com.baiyue.lastleg.inboundplan.entity.InboundPlanLine;
|
||||
import com.baiyue.lastleg.inboundplan.entity.InboundPlanPalletAllocation;
|
||||
import com.baiyue.lastleg.inboundplan.mapper.InboundPlanLineMapper;
|
||||
import com.baiyue.lastleg.inboundplan.mapper.InboundPlanPalletAllocationMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderStatusHistoryMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
|
||||
@Service
|
||||
public class DeliveryTransferService {
|
||||
@Resource private DispatchOrderMapper dispatchOrders;
|
||||
@Resource private DispatchItemMapper dispatchItems;
|
||||
@Resource private OutboundOrderMapper outboundOrders;
|
||||
@Resource private OutboundPalletItemMapper outboundPallets;
|
||||
@Resource private DeliveryOrderMapper deliveryOrders;
|
||||
@Resource private InboundPlanPalletAllocationMapper planAllocations;
|
||||
@Resource private InboundPlanLineMapper planLines;
|
||||
@Resource private LastlegOrderMapper tailOrders;
|
||||
@Resource private LastlegOrderStatusHistoryMapper histories;
|
||||
|
||||
@Transactional
|
||||
public DeliveryResponses.TransferResult transfer(String customerId, String dispatchNo, String operatorId,
|
||||
DeliveryRequests.Transfer request) {
|
||||
DispatchOrder found = dispatchOrders.findByDispatchNo(customerId, dispatchNo);
|
||||
if (found == null) throw notFound("Dispatch order not found");
|
||||
DispatchOrder dispatch = dispatchOrders.lockById(customerId, found.getId());
|
||||
OutboundOrder replay = outboundOrders.findByIdempotency(customerId, request.idempotencyKey().trim());
|
||||
if (replay != null) {
|
||||
if (!dispatch.getId().equals(replay.getDispatchOrderId())) throw conflict("Idempotency key belongs to another dispatch");
|
||||
return result(replay, requireDelivery(customerId, replay.getId()), true);
|
||||
}
|
||||
OutboundOrder existing = outboundOrders.lockByDispatch(customerId, dispatch.getId());
|
||||
if (existing != null) return result(existing, requireDelivery(customerId, existing.getId()), true);
|
||||
if (Set.of("CANCELLED", "DEPARTED", "PARTIAL_DEPARTED", "COMPLETED").contains(dispatch.getStatus())) {
|
||||
throw conflict("Dispatch cannot be transferred to delivery in its current status");
|
||||
}
|
||||
List<DispatchItem> items = dispatchItems.findByDispatchOrder(customerId, dispatch.getId());
|
||||
if (items.isEmpty()) throw conflict("Dispatch has no pallets");
|
||||
if (items.stream().anyMatch(item -> item.getWarehousePalletId() == null)) {
|
||||
throw conflict("All dispatch pallets must be received before transfer to delivery");
|
||||
}
|
||||
if (!StringUtils.hasText(dispatch.getOverseasWarehouseId())) throw conflict("Dispatch warehouse is required");
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
OutboundOrder outbound = new OutboundOrder();
|
||||
outbound.setId(IdWorker.getId()); outbound.setCustomerId(customerId);
|
||||
outbound.setOutboundNo("OUT" + now.format(DateTimeFormatter.BASIC_ISO_DATE) + outbound.getId());
|
||||
outbound.setDispatchOrderId(dispatch.getId()); outbound.setDispatchNo(dispatch.getDispatchNo());
|
||||
outbound.setOverseasWarehouseId(dispatch.getOverseasWarehouseId()); outbound.setStatus("WAIT_OUTBOUND");
|
||||
outbound.setPackageCount(items.stream().map(DispatchItem::getPackageCount).filter(Objects::nonNull).mapToInt(Integer::intValue).sum());
|
||||
outbound.setPalletCount(items.size()); outbound.setIdempotencyKey(request.idempotencyKey().trim());
|
||||
outbound.setCreatedBy(operatorId); outbound.setCreatedAt(now); outbound.setUpdatedAt(now);
|
||||
try {
|
||||
outboundOrders.insert(outbound);
|
||||
} catch (DuplicateKeyException e) {
|
||||
// The unique keys are the final concurrent guard. A client retry must observe
|
||||
// the document created by the winning transaction rather than receive a false failure.
|
||||
OutboundOrder concurrent = outboundOrders.findByIdempotency(customerId, request.idempotencyKey().trim());
|
||||
if (concurrent != null && dispatch.getId().equals(concurrent.getDispatchOrderId())) {
|
||||
return result(concurrent, requireDelivery(customerId, concurrent.getId()), true);
|
||||
}
|
||||
concurrent = outboundOrders.lockByDispatch(customerId, dispatch.getId());
|
||||
if (concurrent != null) {
|
||||
return result(concurrent, requireDelivery(customerId, concurrent.getId()), true);
|
||||
}
|
||||
throw conflict("Dispatch was concurrently transferred to delivery");
|
||||
}
|
||||
|
||||
for (DispatchItem source : items) {
|
||||
OutboundPalletItem item = new OutboundPalletItem();
|
||||
item.setId(IdWorker.getId()); item.setCustomerId(customerId); item.setOutboundOrderId(outbound.getId());
|
||||
item.setWarehousePalletId(source.getWarehousePalletId()); item.setPalletNo(source.getPalletNo());
|
||||
item.setDestinationWarehouseId(source.getDestinationWarehouseId());
|
||||
item.setDestinationWarehouseCode(source.getDestinationWarehouseCode());
|
||||
item.setDestinationWarehouseName(source.getDestinationWarehouseName());
|
||||
item.setPackageCount(Objects.requireNonNullElse(source.getPackageCount(), 0));
|
||||
item.setOrderRefs(source.getOrderRefs()); item.setInboundCode(source.getInboundCode()); item.setCreatedAt(now);
|
||||
outboundPallets.insert(item);
|
||||
}
|
||||
|
||||
DeliveryOrder delivery = new DeliveryOrder();
|
||||
delivery.setId(IdWorker.getId()); delivery.setCustomerId(customerId);
|
||||
delivery.setDeliveryNo("DEL" + now.format(DateTimeFormatter.BASIC_ISO_DATE) + delivery.getId());
|
||||
delivery.setOutboundOrderId(outbound.getId()); delivery.setOutboundNo(outbound.getOutboundNo());
|
||||
delivery.setDispatchOrderId(dispatch.getId()); delivery.setDispatchNo(dispatch.getDispatchNo());
|
||||
delivery.setOverseasWarehouseId(dispatch.getOverseasWarehouseId()); delivery.setStatus("WAIT_DELIVERY");
|
||||
delivery.setPackageCount(outbound.getPackageCount()); delivery.setPalletCount(outbound.getPalletCount());
|
||||
delivery.setIdempotencyKey(request.idempotencyKey().trim()); delivery.setCreatedBy(operatorId);
|
||||
delivery.setCreatedAt(now); delivery.setUpdatedAt(now); deliveryOrders.insert(delivery);
|
||||
bindTailOrders(customerId, items, outbound.getOutboundNo(), delivery.getDeliveryNo(), request.remark(), now);
|
||||
return result(outbound, delivery, false);
|
||||
}
|
||||
|
||||
public PageResult<DeliveryResponses.OutboundRow> outboundPage(String customerId, DeliveryRequests.Query query) {
|
||||
Page<OutboundOrder> page = outboundOrders.selectPage(new Page<>(query.page(), query.size()),
|
||||
new LambdaQueryWrapper<OutboundOrder>().eq(OutboundOrder::getCustomerId, customerId)
|
||||
.like(StringUtils.hasText(query.documentNo()), OutboundOrder::getOutboundNo, query.documentNo())
|
||||
.eq(StringUtils.hasText(query.warehouseId()), OutboundOrder::getOverseasWarehouseId, query.warehouseId())
|
||||
.eq(StringUtils.hasText(query.status()), OutboundOrder::getStatus, query.status())
|
||||
.orderByDesc(OutboundOrder::getCreatedAt));
|
||||
return page(page, page.getRecords().stream().map(this::outboundRow).toList());
|
||||
}
|
||||
|
||||
public DeliveryResponses.OutboundDetail outboundDetail(String customerId, String outboundNo,
|
||||
DeliveryRequests.PalletQuery query) {
|
||||
OutboundOrder outbound = outboundOrders.findByNo(customerId, outboundNo);
|
||||
if (outbound == null) throw notFound("Outbound order not found");
|
||||
DeliveryOrder delivery = requireDelivery(customerId, outbound.getId());
|
||||
Page<OutboundPalletItem> page = outboundPallets.selectPage(new Page<>(query.page(), query.size()),
|
||||
new LambdaQueryWrapper<OutboundPalletItem>().eq(OutboundPalletItem::getCustomerId, customerId)
|
||||
.eq(OutboundPalletItem::getOutboundOrderId, outbound.getId())
|
||||
.eq(StringUtils.hasText(query.destinationWarehouseCode()),
|
||||
OutboundPalletItem::getDestinationWarehouseCode, query.destinationWarehouseCode())
|
||||
.orderByAsc(OutboundPalletItem::getId));
|
||||
return new DeliveryResponses.OutboundDetail(outboundRow(outbound), deliveryRow(delivery),
|
||||
page(page, page.getRecords().stream().map(this::palletRow).toList()));
|
||||
}
|
||||
|
||||
public PageResult<DeliveryResponses.DeliveryRow> deliveryPage(String customerId, DeliveryRequests.Query query) {
|
||||
Page<DeliveryOrder> page = deliveryOrders.selectPage(new Page<>(query.page(), query.size()),
|
||||
new LambdaQueryWrapper<DeliveryOrder>().eq(DeliveryOrder::getCustomerId, customerId)
|
||||
.like(StringUtils.hasText(query.documentNo()), DeliveryOrder::getDeliveryNo, query.documentNo())
|
||||
.eq(StringUtils.hasText(query.warehouseId()), DeliveryOrder::getOverseasWarehouseId, query.warehouseId())
|
||||
.eq(StringUtils.hasText(query.status()), DeliveryOrder::getStatus, query.status())
|
||||
.orderByDesc(DeliveryOrder::getCreatedAt));
|
||||
return page(page, page.getRecords().stream().map(this::deliveryRow).toList());
|
||||
}
|
||||
|
||||
public DeliveryResponses.OutboundDetail deliveryDetail(String customerId, String deliveryNo,
|
||||
DeliveryRequests.PalletQuery query) {
|
||||
DeliveryOrder delivery = deliveryOrders.findByNo(customerId, deliveryNo);
|
||||
if (delivery == null) throw notFound("Delivery order not found");
|
||||
OutboundOrder outbound = outboundOrders.selectById(delivery.getOutboundOrderId());
|
||||
if (outbound == null || !customerId.equals(outbound.getCustomerId())) throw notFound("Outbound order not found");
|
||||
return outboundDetail(customerId, outbound.getOutboundNo(), query);
|
||||
}
|
||||
|
||||
private void bindTailOrders(String customerId, List<DispatchItem> items, String outboundNo, String deliveryNo,
|
||||
String remark, LocalDateTime now) {
|
||||
Set<Long> orderIds = new LinkedHashSet<>();
|
||||
items.stream().map(DispatchItem::getOrderId).filter(Objects::nonNull).forEach(orderIds::add);
|
||||
List<Long> palletIds = items.stream().map(DispatchItem::getPlanPalletId).filter(Objects::nonNull).distinct().toList();
|
||||
if (!palletIds.isEmpty()) {
|
||||
List<InboundPlanPalletAllocation> allocations = planAllocations.selectList(
|
||||
new LambdaQueryWrapper<InboundPlanPalletAllocation>().in(InboundPlanPalletAllocation::getPalletId, palletIds));
|
||||
List<Long> lineIds = allocations.stream().map(InboundPlanPalletAllocation::getLineId)
|
||||
.filter(Objects::nonNull).distinct().toList();
|
||||
if (!lineIds.isEmpty()) {
|
||||
planLines.selectBatchIds(lineIds).stream().map(InboundPlanLine::getOrderId)
|
||||
.filter(Objects::nonNull).forEach(orderIds::add);
|
||||
}
|
||||
}
|
||||
if (orderIds.isEmpty()) return;
|
||||
for (LastlegOrder order : tailOrders.selectList(new LambdaQueryWrapper<LastlegOrder>()
|
||||
.eq(LastlegOrder::getCustomerId, customerId).in(LastlegOrder::getId, orderIds))) {
|
||||
String from = order.getCurrentStatus(); order.setOutboundOrderNo(outboundNo);
|
||||
order.setDeliveryOrderNo(deliveryNo); order.setCurrentStatus("WAIT_OUTBOUND"); order.setUpdatedAt(now);
|
||||
order.setSubOrderStatus("OUTBOUND:WAIT_OUTBOUND;DELIVERY:WAIT_DELIVERY");
|
||||
tailOrders.updateById(order);
|
||||
LastlegOrderStatusHistory history = new LastlegOrderStatusHistory(); history.setOrderId(order.getId());
|
||||
history.setFromStatus(from); history.setToStatus("WAIT_OUTBOUND");
|
||||
history.setEventId("TRANSFER_DELIVERY:" + deliveryNo);
|
||||
history.setRemark(StringUtils.hasText(remark) ? remark.trim() : "Transferred to outbound and delivery documents");
|
||||
history.setCreatedAt(now); histories.insert(history);
|
||||
}
|
||||
}
|
||||
|
||||
private DeliveryOrder requireDelivery(String customerId, Long outboundId) {
|
||||
DeliveryOrder value = deliveryOrders.lockByOutbound(customerId, outboundId);
|
||||
if (value == null) throw new IllegalStateException("Delivery document is missing for outbound order");
|
||||
return value;
|
||||
}
|
||||
private DeliveryResponses.TransferResult result(OutboundOrder o, DeliveryOrder d, boolean replayed) {
|
||||
return new DeliveryResponses.TransferResult(o.getDispatchNo(), o.getOutboundNo(), o.getStatus(),
|
||||
d.getDeliveryNo(), d.getStatus(), o.getPalletCount(), o.getPackageCount(), replayed);
|
||||
}
|
||||
private DeliveryResponses.OutboundRow outboundRow(OutboundOrder o) { return new DeliveryResponses.OutboundRow(o.getId(), o.getOutboundNo(), o.getDispatchNo(), o.getOverseasWarehouseId(), o.getStatus(), o.getPalletCount(), o.getPackageCount(), o.getCreatedAt()); }
|
||||
private DeliveryResponses.DeliveryRow deliveryRow(DeliveryOrder d) { return new DeliveryResponses.DeliveryRow(d.getId(), d.getDeliveryNo(), d.getOutboundNo(), d.getDispatchNo(), d.getOverseasWarehouseId(), d.getStatus(), d.getPalletCount(), d.getPackageCount(), d.getCreatedAt()); }
|
||||
private DeliveryResponses.PalletRow palletRow(OutboundPalletItem p) { return new DeliveryResponses.PalletRow(p.getId(), p.getWarehousePalletId(), p.getPalletNo(), p.getInboundCode(), p.getOrderRefs(), p.getDestinationWarehouseId(), p.getDestinationWarehouseCode(), p.getDestinationWarehouseName(), p.getPackageCount()); }
|
||||
private static <T> PageResult<T> page(Page<?> page, List<T> records) { return new PageResult<>(records, page.getCurrent(), page.getSize(), page.getTotal(), page.getPages()); }
|
||||
private static ResponseStatusException conflict(String message) { return new ResponseStatusException(HttpStatus.CONFLICT, message); }
|
||||
private static ResponseStatusException notFound(String message) { return new ResponseStatusException(HttpStatus.NOT_FOUND, message); }
|
||||
}
|
||||
Reference in New Issue
Block a user