feat: complete tail order controls and terminal drayage

This commit is contained in:
2026-09-07 23:58:18 +08:00
parent f018ca85a9
commit e32fe65fa8
53 changed files with 2566 additions and 87 deletions
@@ -66,6 +66,10 @@ public interface DispatchCandidateMapper {
"<if test='fbaWarehouseCode != null and fbaWarehouseCode != \"\"'> AND l.fba_warehouse_code=#{fbaWarehouseCode}</if> " +
"<if test='sourceStage != null and sourceStage != \"\"'> AND " +
"CASE WHEN p.warehouse_pallet_id IS NULL THEN 'PLAN' ELSE 'RECEIVED' END=#{sourceStage}</if> " +
"AND NOT EXISTS (SELECT 1 FROM ll_inbound_plan_pallet_allocation ha " +
"JOIN ll_inbound_plan_line hl ON hl.id=ha.line_id " +
"JOIN ll_order ho ON ho.id=hl.order_id AND ho.customer_id=ip.customer_id " +
"WHERE ha.pallet_id=p.id AND ho.detained=1) " +
"AND (p.warehouse_pallet_id IS NULL OR EXISTS (SELECT 1 FROM ll_inventory_position pos " +
"WHERE pos.customer_id=ip.customer_id AND pos.pallet_id=p.warehouse_pallet_id AND pos.available_packages &gt; 0)) " +
"AND NOT EXISTS (SELECT 1 FROM ll_dispatch_pallet_lock d " +
@@ -124,6 +128,10 @@ public interface DispatchCandidateMapper {
"LEFT JOIN ll_order o ON o.id=l.order_id " +
"WHERE ip.customer_id=#{customerId} AND p.id IN " +
"<foreach collection='planPalletIds' item='id' open='(' separator=',' close=')'>#{id}</foreach> " +
"AND NOT EXISTS (SELECT 1 FROM ll_inbound_plan_pallet_allocation ha " +
"JOIN ll_inbound_plan_line hl ON hl.id=ha.line_id " +
"JOIN ll_order ho ON ho.id=hl.order_id AND ho.customer_id=ip.customer_id " +
"WHERE ha.pallet_id=p.id AND ho.detained=1) " +
"AND (p.warehouse_pallet_id IS NULL OR EXISTS (SELECT 1 FROM ll_inventory_position pos " +
"WHERE pos.customer_id=#{customerId} AND pos.pallet_id=p.warehouse_pallet_id AND pos.available_packages &gt; 0)) " +
"AND NOT EXISTS (SELECT 1 FROM ll_warehouse_transfer_inventory_allocation ta " +
@@ -22,6 +22,8 @@ import com.baiyue.lastleg.dispatch.dto.DispatchDraftResponse;
import com.baiyue.lastleg.dispatch.model.DispatchPalletCandidate;
import com.baiyue.lastleg.dispatch.pda.entity.DispatchPdaTask;
import com.baiyue.lastleg.dispatch.pda.mapper.DispatchPdaTaskMapper;
import com.baiyue.lastleg.entity.LastlegOrder;
import com.baiyue.lastleg.mapper.LastlegOrderMapper;
import jakarta.annotation.Resource;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.HttpStatus;
@@ -53,6 +55,7 @@ public class DispatchService {
@Resource private DispatchCandidateMapper candidates;
@Resource private DispatchPdaTaskMapper pdaTasks;
@Resource private LastlegOrderNoGenerator orderNoGenerator;
@Resource private LastlegOrderMapper tailOrders;
public String resolveCustomerScopeForPlans(List<Long> planIds) {
return requireSingleCustomerScope(candidates.findCustomerScopesByPlanIds(normalizeIds(planIds, "Plan IDs")),
@@ -221,6 +224,7 @@ public class DispatchService {
if (selected.size() != palletIds.size()) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "One or more selected pallets are not in this tenant");
}
assertNoDetainedOrders(customerId, selected);
boolean missingWarehouse = selected.stream().map(DispatchPalletCandidate::overseasWarehouseId)
.anyMatch(value -> value == null || value.isBlank());
if (missingWarehouse) {
@@ -351,6 +355,23 @@ public class DispatchService {
return response(dispatch, items.findByDispatchOrder(customerId, dispatch.getId()));
}
private void assertNoDetainedOrders(String customerId, List<DispatchPalletCandidate> selected) {
List<Long> orderIds = selected.stream().map(DispatchPalletCandidate::orderId)
.filter(Objects::nonNull).distinct().sorted().toList();
if (orderIds.isEmpty()) return;
List<LastlegOrder> scoped = tailOrders.selectList(new LambdaQueryWrapper<LastlegOrder>()
.eq(LastlegOrder::getCustomerId, customerId)
.in(LastlegOrder::getId, orderIds)
.orderByAsc(LastlegOrder::getId)
.last("FOR UPDATE"));
List<String> detainedNos = scoped.stream().filter(order -> Boolean.TRUE.equals(order.getDetained()))
.map(LastlegOrder::getOrderNo).filter(StringUtils::hasText).distinct().toList();
if (!detainedNos.isEmpty()) {
throw new ResponseStatusException(HttpStatus.CONFLICT,
"暂扣订单不可创建排车或出库计划:" + String.join("、", detainedNos));
}
}
public PageResult<DispatchResponse> page(String customerId, DispatchRequests.Query query) {
if (!StringUtils.hasText(customerId)) {
return new PageResult<>(List.of(), query.page(), query.size(), 0, 0);