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
@@ -28,7 +28,7 @@ public class FleetHandoverPdaQuery {
FleetDispatchGateway dispatches, FleetHandoverRepository handovers, ObjectMapper json) {
this.bindings = bindings; this.trips = trips; this.history = history; this.dispatches = dispatches; this.handovers = handovers; this.json = json;
}
public record Delivery(String deliveryNo, String status, int packages, Instant terminalAt, FleetTransitEvidence.Transport transport) { }
public record Delivery(String deliveryNo, String status, int packages, int remainingPackages, Instant terminalAt, FleetTransitEvidence.Transport transport) { }
public record Handover(String handoverNo, Instant handedOverAt, String location,
FleetTransitEvidence.Transport previous, FleetTransitEvidence.Transport replacement) { }
public record Status(String dispatchNo, String tripNo, String tripStatus, String tripRecordVersion,
@@ -53,6 +53,7 @@ public class FleetHandoverPdaQuery {
if (departed == null || !trip.getTripNo().equals(departed.tripNo()) || !Long.toString(warehouseId).equals(departed.warehouseId())) throw notFound();
var originalTransport = new FleetTransitEvidence.Transport(departed.vehiclePlate(), departed.driverName(), departed.driverPhone());
var rows = evidence.deliveries().stream().map(row -> new Delivery(row.cargo().deliveryNo(), row.status(), row.cargo().packages(),
row.cargo().terminalAt() != null ? 0 : row.cargo().packages() - row.cargo().returnedCargo().stream().mapToInt(FleetHandoverPolicy.ReturnedCargo::packages).sum(),
row.cargo().terminalAt(), row.transport())).toList();
var latest = handovers.latestForCargo(trip.getId(), json.writeValueAsString(Map.of("customerId", binding.getCustomerId(), "dispatchNo", dispatchNo)));
Handover handover = null;
@@ -12,7 +12,8 @@ public final class FleetHandoverPolicy {
String destinationKey, int packages) { }
/** terminalAt is a signed or RETURN_COMPLETED fact, not a mutable updatedAt. */
public record Delivery(String customerId, String dispatchNo, String deliveryNo,
String destinationKey, int packages, Instant terminalAt) { }
String destinationKey, int packages, Instant terminalAt, List<ReturnedCargo> returnedCargo) { }
public record ReturnedCargo(String itemId, String palletId, int packages, Instant receivedAt) { }
public record Pallet(String palletId, Load measuredLoad) { }
public record Inspection(List<Cargo> remainingCargo, List<Delivery> completedDeliveries,
Set<String> remainingPalletIds, Load occupied) { }
@@ -48,6 +49,7 @@ public final class FleetHandoverPolicy {
throw invalid("同一实托包含不同卸货目的地,请先核对原始装载");
}
Map<Destination, Delivery> byDestination = new HashMap<>();
Map<Item, Integer> returned = new HashMap<>();
Set<DeliveryKey> deliveryKeys = new HashSet<>();
List<Delivery> completed = new ArrayList<>();
for (Delivery delivery : deliveries) {
@@ -60,6 +62,16 @@ public final class FleetHandoverPolicy {
throw invalid("派送事实重复或目的地拆分尚未核平");
if (!Objects.equals(shipped.get(key), delivery.packages()))
throw invalid("派送客户、排车、目的地或箱数与原始发运不一致");
if (delivery.returnedCargo() == null) throw invalid("缺少实际退回数量依据");
for (ReturnedCargo receipt : delivery.returnedCargo()) {
Item item = new Item(delivery.customerId(), delivery.dispatchNo(), receipt.itemId());
Cargo source = departureCargo.stream().filter(cargo -> item.equals(new Item(cargo.customerId(), cargo.dispatchNo(), cargo.itemId())))
.findFirst().orElseThrow(() -> invalid("退回来源不属于原始发运明细"));
if (!destination(source).equals(key) || !Objects.equals(source.palletId(), receipt.palletId()) || receipt.packages() <= 0
|| receipt.receivedAt() == null || receipt.receivedAt().isBefore(departedAt) || receipt.receivedAt().isAfter(handedOverAt))
throw invalid("退回来源、数量或实际时间与交接不一致");
if (returned.merge(item, receipt.packages(), Math::addExact) > source.packages()) throw invalid("累计退回超过原发运明细数量");
}
if (delivery.terminalAt() != null) {
if (delivery.terminalAt().isBefore(departedAt) || delivery.terminalAt().isAfter(handedOverAt))
throw invalid("交接时间与已发生的签收或退回事实冲突,请刷新核对");
@@ -76,7 +88,10 @@ public final class FleetHandoverPolicy {
}
if (!measurements.keySet().equals(palletDestinations.keySet())) throw invalid("实测托盘与原始货物不一致");
List<Cargo> remaining = departureCargo.stream()
.filter(cargo -> byDestination.get(destination(cargo)).terminalAt() == null).toList();
.filter(cargo -> byDestination.get(destination(cargo)).terminalAt() == null)
.map(cargo -> new Cargo(cargo.customerId(), cargo.dispatchNo(), cargo.itemId(), cargo.palletId(), cargo.destinationKey(),
cargo.packages() - returned.getOrDefault(new Item(cargo.customerId(), cargo.dispatchNo(), cargo.itemId()), 0)))
.filter(cargo -> cargo.packages() > 0).toList();
if (remaining.isEmpty()) throw invalid("全部货物已完成,请按结案和资源结束流程处理");
Set<String> palletIds = new TreeSet<>();
remaining.forEach(cargo -> palletIds.add(cargo.palletId()));