fix(lastleg): repair dispatch cancellation and return execution contracts

This commit is contained in:
2026-10-18 16:34:14 +08:00
parent 66a0beb653
commit aa33514ec6
64 changed files with 2061 additions and 213 deletions
@@ -18,6 +18,7 @@ import java.util.*;
@Component
public class FleetIncomingAdapter implements FleetBookingIncomingGateway {
@jakarta.annotation.Resource private com.baiyue.lastleg.transfer.repository.FleetIncomingActualSourceRepository actualSources;
@jakarta.annotation.Resource private com.baiyue.lastleg.fleet.business.FleetPoolOrderBridge fleetPool;
private final WarehouseTransferService service;
private final WarehouseTransferOrderMapper orders;
@@ -49,25 +50,36 @@ public class FleetIncomingAdapter implements FleetBookingIncomingGateway {
var approved=service.review(input.customerId(),created.transferNo(),operator,new WarehouseTransferRequests.Review("APPROVE","外接承运已确认,按实际来货收货"));
fleetPool.bindIncoming(input.bookingNo(),input.customerId(),approved.transferNo(),approved.lastlegOrderId(),
approved.operatingWarehouseId(),key,operator);
return evidence(approved);
return evidence(input.customerId(),approved,false);
}
@Override @Transactional(propagation=Propagation.MANDATORY)
public Evidence inspect(String customerId,String incomingNo,boolean lock){
var row=lock?orders.lockNo(customerId,incomingNo):orders.findNo(customerId,incomingNo);
if(row==null)throw new ResponseStatusException(HttpStatus.NOT_FOUND,"当前客户无此外接来货单");
return evidence(service.detail(customerId,incomingNo));
return evidence(customerId,service.detail(customerId,incomingNo),lock);
}
@Override @Transactional(propagation=Propagation.MANDATORY)
public Evidence confirm(String customerId,String incomingNo,String operator,String key){
var row=orders.lockNo(customerId,incomingNo);
if(row==null)throw new ResponseStatusException(HttpStatus.NOT_FOUND,"外接来货单不存在");
if("READY_FOR_DISPATCH".equals(row.getStatus()))return evidence(service.detail(customerId,incomingNo));
return evidence(service.confirmMeasurement(customerId,incomingNo,operator,new WarehouseTransferRequests.CustomerConfirm(key,"运营代委托方确认外接实测货量")));
if("READY_FOR_DISPATCH".equals(row.getStatus()))return evidence(customerId,service.detail(customerId,incomingNo),true);
return evidence(customerId,service.confirmMeasurement(customerId,incomingNo,operator,new WarehouseTransferRequests.CustomerConfirm(key,"运营代委托方确认外接实测货量")),true);
}
private Evidence evidence(WarehouseTransferResponses.Detail value){
/** 实收完成后按真实组成重建托盘;变化后的测量值必须重新确认,不能比例推算。 */
private Evidence evidence(String customerId,WarehouseTransferResponses.Detail value,boolean lock){
var pallets=value.pallets().stream().filter(row->"COMPLETED".equals(row.status())).map(row->new Pallet(
row.warehousePalletId()==null?null:row.warehousePalletId().toString(),row.palletNo(),row.cartonCount(),row.grossWeightKg(),row.volumeCbm()))
.sorted(Comparator.comparing(row->Objects.requireNonNullElse(row.warehousePalletId(),""))).toList();
if(java.util.Set.of("WAIT_CUSTOMER_CONFIRMATION","READY_FOR_DISPATCH").contains(value.status())) {
var measured=pallets;
pallets=actualSources.pallets(customerId,value.inboundCode(),value.operatingWarehouseId(),lock).stream().map(actual->{
if(actual.palletId()==null||actual.palletId()<1||actual.palletNo()==null||actual.palletNo().isBlank())throw conflict("外接实收货物尚未绑定真实实物托盘");
var prior=measured.stream().filter(p->Objects.equals(p.warehousePalletId(),String.valueOf(actual.palletId()))&&p.packages()==actual.packages()).findFirst().orElse(null);
return new Pallet(String.valueOf(actual.palletId()),actual.palletNo(),Math.toIntExact(actual.packages()),prior==null?null:prior.weightKg(),prior==null?null:prior.volumeCbm());
}).toList();
if(pallets.stream().mapToLong(Pallet::packages).sum()!=measured.stream().mapToLong(Pallet::packages).sum())
throw conflict("实际库存箱数与仓储转运原实收量不一致,请先核对来源,不允许部分确认外接实收");
}
String destination=FleetDispatchGateway.destinationKey(value.destinationType(),value.destinationWarehouseCode(),value.fbaWarehouseCode(),value.privateDestinationAddress(),value.privateDestinationPostalCode());
int packages=pallets.stream().mapToInt(Pallet::packages).sum();
try{
@@ -0,0 +1,20 @@
package com.baiyue.lastleg.transfer.mapper;
import org.apache.ibatis.annotations.*;
import java.util.List;
/** 外接实收复核读取同库实际库存组成,不复制旧收货托盘。 */
@Mapper
public interface FleetIncomingActualSourceMapper {
record Pallet(Long palletId,String palletNo,Long packages) { }
/** 按仓位主键顺序锁定完整来源,确认期间拆合托必须等待同一库存行。 */
@Select("SELECT p.id FROM ll_inventory_position p JOIN ll_inventory_balance b ON b.id=p.balance_id AND b.customer_id=p.customer_id " +
"WHERE p.customer_id=#{customerId} AND b.inbound_code=#{inboundCode} AND p.overseas_warehouse_id=#{warehouseId} ORDER BY p.id FOR UPDATE")
List<Long> lockSources(@Param("customerId") String customerId,@Param("inboundCode") String inboundCode,@Param("warehouseId") String warehouseId);
/** 按真实入仓码及起运仓归并本票可用和已拣箱数,排除其他货主及其他来源。 */
@Select("SELECT p.pallet_id,p.pallet_no,SUM(p.available_packages+COALESCE(p.picked_packages,0)) packages " +
"FROM ll_inventory_position p JOIN ll_inventory_balance b ON b.id=p.balance_id AND b.customer_id=p.customer_id " +
"WHERE p.customer_id=#{customerId} AND b.inbound_code=#{inboundCode} AND p.overseas_warehouse_id=#{warehouseId} " +
"GROUP BY p.pallet_id,p.pallet_no HAVING SUM(p.available_packages+COALESCE(p.picked_packages,0))>0 ORDER BY p.pallet_id")
List<Pallet> pallets(@Param("customerId") String customerId,@Param("inboundCode") String inboundCode,@Param("warehouseId") String warehouseId);
}
@@ -0,0 +1,18 @@
package com.baiyue.lastleg.transfer.repository;
import com.baiyue.lastleg.transfer.mapper.FleetIncomingActualSourceMapper;
import org.springframework.stereotype.Repository;
import java.util.List;
/** 库内拆合托后的真实外接货源读取入口。 */
@Repository
public class FleetIncomingActualSourceRepository {
private final FleetIncomingActualSourceMapper mapper;
/** 装配同库组成查询。 */
public FleetIncomingActualSourceRepository(FleetIncomingActualSourceMapper mapper){this.mapper=mapper;}
/** 当前托盘由本票库存确定,旧收货托盘仅保留为历史测量证据。 */
public List<FleetIncomingActualSourceMapper.Pallet> pallets(String customer,String inbound,String warehouse,boolean lock){
if(lock)mapper.lockSources(customer,inbound,warehouse);
return mapper.pallets(customer,inbound,warehouse);
}
}