fix(lastleg): reconfirm received bookings after releasing all own cargo

This commit is contained in:
2026-10-18 19:53:35 +08:00
parent 2c330bf9df
commit 45fbd6eff8
7 changed files with 139 additions and 13 deletions
@@ -7,4 +7,6 @@ public class FleetBookingCargoRepository {
public FleetBookingCargoRepository(FleetBookingMapper bookings){this.bookings=bookings;}
/** 按客户与真实入仓码定位承运,历史取消记录交由业务层分别处理。 */
public FleetBookingEntity forInboundSource(String customerId,String inboundCode){return bookings.forInboundSource(customerId,inboundCode);}
/** 在车次事务内读取全部承运预留,统一计算区段容量。 */
public java.util.List<FleetBookingEntity> listForTrip(long tripId){return bookings.list(tripId);}
}
@@ -28,12 +28,27 @@ public class FleetBookingReservations {
row.getOfferId()==null?null:offers.selectById(row.getOfferId()).getOfferNo(),row.getExpectedPalletCount());
}
public FleetQuoteCandidateModels.SourceSnapshot quoteSource(FleetBookingEntity row){return row.getQuoteSourceJson()==null?null:read(row.getQuoteSourceJson(),FleetQuoteCandidateModels.SourceSnapshot.class);}
/** 使用当前自有货计划与全部未绑定预留计算区段,不重复计入待替换承运。 */
public List<TripCapacityPolicy.Segment> segments(FleetTripEntity trip, FleetExecutionModels.PlannedLoad own,
Long excludedId, FleetBookingEntity addition) {
return segments(trip,own.pallets(),excludedId,addition);
}
/** 已实收承运解除唯一排车后,自有货为零仍可按本票新实测和其他预留重新核容。 */
public void requireReceiptCapacity(FleetTripEntity trip,FleetExecutionModels.Planning own,FleetBookingEntity row) {
boolean releasedReceipt=own.cargo().isEmpty() && "RECEIVED".equals(row.getStatus()) && receipt(row)!=null;
if(!releasedReceipt && (!own.planValid() || own.blockReason()!=null))
throw conflict("请先重新核对自有货容量计划,再确认外接实收");
// 空自有货只用于已有实收的重新确认;不创建计划、不允许首次实收跳过自有货计划。
List<FleetExecutionModels.Measurement> pallets=releasedReceipt?List.of():own.capacityPlan().pallets();
segments(trip,pallets,row.getId(),row);
}
/** 所有承诺使用同一容量计算,空自有货也必须保留其他承运占位及车辆上限。 */
private List<TripCapacityPolicy.Segment> segments(FleetTripEntity trip,List<FleetExecutionModels.Measurement> own,
Long excludedId,FleetBookingEntity addition) {
var stops=stops(trip); var loads=new ArrayList<TripCapacityPolicy.Booking>();
for (var pallet:own.pallets()) loads.add(new TripCapacityPolicy.Booking("PALLET:"+pallet.warehousePalletId(),0,pallet.deliveryIndex(),
for (var pallet:own) loads.add(new TripCapacityPolicy.Booking("PALLET:"+pallet.warehousePalletId(),0,pallet.deliveryIndex(),
new TripCapacityPolicy.Load(pallet.palletSpaces(),pallet.weightKg(),pallet.volumeCbm())));
for(var row:bookings.list(trip.getId())) if(unbound(row) && !Objects.equals(row.getId(),excludedId)) add(loads,stops,row);
for(var row:new FleetBookingCargoRepository(bookings).listForTrip(trip.getId())) if(unbound(row) && !Objects.equals(row.getId(),excludedId)) add(loads,stops,row);
if(addition!=null) add(loads,stops,addition);
try { return TripCapacityPolicy.calculate(stops.size(),new TripCapacityPolicy.Load(trip.getMaxPalletSpaces(),trip.getMaxLoadKg(),trip.getMaxVolumeCbm()),loads); }
catch(IllegalArgumentException|ArithmeticException error){throw conflict(error.getMessage());}
@@ -176,8 +176,7 @@ public class FleetBookingService {
int totalSpaces=spaces.values().stream().reduce(0,Math::addExact);var old=reservations.cargo(row);
apply(trip,row,new FleetBookingModels.Cargo(old.destinationKey(),old.cargoName(),count,totalSpaces,weight,volume,old.priceBasis(),request.unitPrice(),old.currency(),old.contactName(),old.contactPhone(),old.remark()));
var plan=execution.capacityPlan(tripNo);
if(!plan.planValid()||plan.blockReason()!=null)throw conflict("请先重新核对自有货容量计划,再确认外接实收");
reservations.segments(trip,plan.capacityPlan(),row.getId(),row);
reservations.requireReceiptCapacity(trip,plan,row);
var confirmed=incoming.confirm(row.getCustomerId(),row.getIncomingNo(),operator,"FLEET:"+bookingNo+":RECEIPT");
if(!"READY_FOR_DISPATCH".equals(confirmed.status())||!Objects.equals(confirmed.receiptHash(),evidence.receiptHash()))throw conflict("来货实收确认未完成或内容发生变化,请重新核对");
var measuredReceipt=new FleetBookingIncomingGateway.Evidence(confirmed.incomingNo(),confirmed.inboundCode(),confirmed.warehouseId(),confirmed.destinationKey(),confirmed.status(),confirmed.receiptHash(),java.util.List.copyOf(measured),confirmed.receivedPackages());