feat(fleet): bind quote versions and reconcile actual return quantities

This commit is contained in:
2026-10-03 13:32:55 +08:00
parent cb6c54ffe5
commit a6fd0ba3c5
60 changed files with 1363 additions and 75 deletions
@@ -5,6 +5,7 @@ import com.baiyue.lastleg.delivery.entity.DeliveryOrder;
import com.baiyue.lastleg.delivery.mapper.DeliveryOrderMapper;
import com.baiyue.lastleg.delivery.mapper.DeliveryOperationMapper;
import com.baiyue.lastleg.delivery.mapper.OutboundOrderMapper;
import com.baiyue.lastleg.delivery.model.DeliveryCargoQuantities;
import com.baiyue.lastleg.dispatch.entity.DispatchOrder;
import com.baiyue.lastleg.dispatch.mapper.DispatchOrderMapper;
import com.baiyue.lastleg.dispatch.mapper.DispatchItemMapper;
@@ -159,17 +160,26 @@ public class DispatchFleetAdapter implements FleetDispatchGateway {
var outbound = lock ? outbounds.lockByDispatch(dispatch.getCustomerId(), dispatch.getId()) : outbounds.findByDispatch(dispatch.getCustomerId(), dispatch.getId());
var rows = outbound == null ? java.util.List.<DeliveryOrder>of() : lock
? deliveries.lockByOutbound(dispatch.getCustomerId(), outbound.getId()) : deliveries.findByOutbound(dispatch.getCustomerId(), outbound.getId());
var outcomes = rows.stream().map(row -> new FleetCompletionModels.Outcome(dispatchNo, row.getDeliveryNo(), dispatch.getCustomerId(),
if (outbound != null && (rows.stream().mapToLong(row -> Objects.requireNonNullElse(row.getPackageCount(), 0)).sum()
!= Objects.requireNonNullElse(dispatch.getShippedPackageCount(), 0) || !Objects.equals(dispatch.getShippedPackageCount(), outbound.getPackageCount())))
return new FleetCompletionModels.DispatchCompletion(dispatchNo, dispatch.getOverseasWarehouseId(), java.util.List.of(), "派送单数量未覆盖本次全部实发货物");
var outcomes = rows.stream().map(row -> {
var quantities = quantities(row, deliveryOperations.returnReceipts(dispatch.getCustomerId(), row.getId()));
return new FleetCompletionModels.Outcome(dispatchNo, row.getDeliveryNo(), dispatch.getCustomerId(),
"PRIVATE".equals(row.getDestinationType()) ? row.getDeliveryAddress() : first(row.getDestinationWarehouseCode(), row.getDestinationWarehouseName()),
originallySigned(row) ? "DELIVERED" : row.getStatus(), Objects.requireNonNullElse(row.getPackageCount(), 0),
originallySigned(row) ? instant(row.getSignedAt()) : "RETURNED".equals(row.getStatus())
? instant(deliveryOperations.returnedAt(dispatch.getCustomerId(), row.getId())) : null)).toList();
? instant(deliveryOperations.returnedAt(dispatch.getCustomerId(), row.getId())) : null,
quantities.signed(), quantities.returned(), quantities.remaining());
}).toList();
String reason = null;
if (!"DEPARTED".equals(dispatch.getStatus()) || outbound == null || !"OUTBOUNDED".equals(outbound.getStatus())) reason = "排车出库尚未完成";
else if (outcomes.isEmpty()) reason = "缺少对应派送单";
else if (outcomes.stream().mapToLong(FleetCompletionModels.Outcome::packages).sum() != Objects.requireNonNullElse(dispatch.getShippedPackageCount(), 0)
|| !Objects.equals(dispatch.getShippedPackageCount(), outbound.getPackageCount())) reason = "派送单数量未覆盖本次全部实发货物";
else if (outcomes.stream().anyMatch(row -> row.packages() <= 0 || !java.util.Set.of("DELIVERED", "RETURNED").contains(row.status()) || row.terminalAt() == null))
else if (outcomes.stream().anyMatch(row -> row.packages() <= 0 || row.remainingPackages() != 0
|| row.signedPackages() + row.returnedPackages() != row.packages()
|| !java.util.Set.of("DELIVERED", "RETURNED").contains(row.status()) || row.terminalAt() == null))
reason = "所有派送单须已签收,或完成真实退回入仓";
return new FleetCompletionModels.DispatchCompletion(dispatchNo, dispatch.getOverseasWarehouseId(), outcomes, reason);
}
@@ -194,17 +204,25 @@ public class DispatchFleetAdapter implements FleetDispatchGateway {
|| !Objects.equals(row.getOutboundOrderId(), outbound.getId())
|| !Objects.equals(row.getOverseasWarehouseId(), dispatch.getOverseasWarehouseId())
|| row.getPackageCount() == null || row.getPackageCount() <= 0 || row.getVersion() == null
|| row.getStatus() == null || !java.util.Set.of("WAIT_DELIVERY", "DELIVERED", "RETURN_PENDING", "RETURNED").contains(row.getStatus()))
|| row.getStatus() == null || !java.util.Set.of("WAIT_DELIVERY", "DELIVERING", "REJECTED", "DELIVERED", "RETURN_PENDING", "RETURNED").contains(row.getStatus()))
throw conflict("派送货物归属或执行状态与原始发运不一致");
Instant terminal = originallySigned(row) ? instant(row.getSignedAt()) : "RETURNED".equals(row.getStatus())
? instant(deliveryOperations.returnedAt(dispatch.getCustomerId(), row.getId())) : null;
if (("DELIVERED".equals(row.getStatus()) || "RETURNED".equals(row.getStatus())) && terminal == null)
throw conflict("派送终态缺少真实签收或退回完成证据");
var receipts = deliveryOperations.returnReceipts(dispatch.getCustomerId(), row.getId());
var quantities = quantities(row, receipts);
if (terminal != null && quantities.remaining() != 0) throw conflict("派送终态数量未覆盖原实发货物");
String key = destination(row.getDestinationType(), row.getDestinationWarehouseCode(), null,
row.getDeliveryAddress(), row.getPostalCode());
if (key == null) throw conflict("派送单缺少真实卸货目的地");
var cargo = new com.baiyue.lastleg.fleet.exception.FleetHandoverPolicy.Delivery(dispatch.getCustomerId(),
dispatchNo, row.getDeliveryNo(), key, row.getPackageCount(), terminal);
dispatchNo, row.getDeliveryNo(), key, row.getPackageCount(), terminal,
originallySigned(row) ? java.util.List.of() : receipts.stream().map(receipt -> {
if (receipt.dispatchItemId() == null || receipt.palletId() == null) throw conflict("实际退回缺少原发运实托明细归属");
return new com.baiyue.lastleg.fleet.exception.FleetHandoverPolicy.ReturnedCargo(
receipt.dispatchItemId().toString(), receipt.palletId().toString(), Math.toIntExact(receipt.packages()), instant(receipt.receivedAt()));
}).toList());
facts.add(new com.baiyue.lastleg.fleet.exception.FleetTransitEvidence.Delivery(cargo, row.getStatus(), row.getVersion(),
new com.baiyue.lastleg.fleet.exception.FleetTransitEvidence.Transport(row.getVehiclePlate(), row.getDriverName(), row.getDriverPhone())));
packages = Math.addExact(packages, row.getPackageCount());
@@ -236,6 +254,10 @@ public class DispatchFleetAdapter implements FleetDispatchGateway {
}
private static Instant instant(LocalDateTime value) { return value == null ? null : value.atZone(ZoneId.systemDefault()).toInstant(); }
private static DeliveryCargoQuantities quantities(DeliveryOrder row, java.util.List<com.baiyue.lastleg.delivery.model.DeliveryReturnReceiptFact> receipts) {
try { return DeliveryCargoQuantities.from(row, receipts); }
catch (IllegalArgumentException error) { throw conflict(error.getMessage()); }
}
private static boolean originallySigned(DeliveryOrder order) {
return order.getSignedAt() != null && java.util.Set.of("DELIVERED", "RETURN_PENDING", "RETURNED").contains(order.getStatus());
}