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
@@ -0,0 +1,32 @@
package com.baiyue.lastleg.delivery.model;
import com.baiyue.lastleg.delivery.entity.DeliveryOrder;
import java.util.HashSet;
import java.util.List;
/** Quantity accounting for the original journey; later post-signature returns belong to another movement. */
public record DeliveryCargoQuantities(int shipped, int signed, int returned, int remaining) {
public static DeliveryCargoQuantities from(DeliveryOrder order, List<DeliveryReturnReceiptFact> receipts) {
if (order.getPackageCount() == null || order.getPackageCount() <= 0) throw invalid("派送缺少实发数量");
var ids = new HashSet<Long>();
long received = 0;
for (var receipt : receipts) {
if (receipt.ledgerId() == null || !ids.add(receipt.ledgerId()) || receipt.packages() <= 0 || receipt.receivedAt() == null)
throw invalid("退回实际入账流水缺失或来源映射重复");
received = Math.addExact(received, receipt.packages());
}
int shipped = order.getPackageCount();
if (received > shipped) throw invalid("退回实收超过原实发数量");
if (order.getSignedAt() != null) {
Integer signed = order.getSignedPackages();
long beforeSignature = receipts.stream().filter(receipt -> !receipt.receivedAt().isAfter(order.getSignedAt())).mapToLong(DeliveryReturnReceiptFact::packages).sum();
if (signed == null || signed <= 0 || signed > shipped || beforeSignature < shipped - signed)
throw invalid("签收数量快照缺失或与实际退回不守恒");
// Do not re-open or reclassify signed cargo when another return is later received.
return new DeliveryCargoQuantities(shipped, signed, shipped - signed, 0);
}
if (order.getSignedPackages() != null) throw invalid("签收数量缺少签收时间");
return new DeliveryCargoQuantities(shipped, 0, Math.toIntExact(received), shipped - Math.toIntExact(received));
}
private static IllegalArgumentException invalid(String message) { return new IllegalArgumentException(message); }
}
@@ -0,0 +1,6 @@
package com.baiyue.lastleg.delivery.model;
import java.time.LocalDateTime;
/** Immutable actual receipt, attributed to the original dispatch item and physical pallet. */
public record DeliveryReturnReceiptFact(Long ledgerId, Long dispatchItemId, Long palletId, long packages, LocalDateTime receivedAt) { }
@@ -31,7 +31,7 @@ public final class DeliveryStatusPolicy {
case "WAIT_DELIVERY" -> List.of(
"START_DELIVERY", "REJECT_DELIVERY", "CANCEL_DELIVERY");
case "DELIVERING" -> List.of("REJECT_DELIVERY", "MARK_RETURN");
case "DELIVERED", "REJECTED" -> List.of("MARK_RETURN");
case "DELIVERED", "REJECTED", "RETURN_PENDING" -> List.of("MARK_RETURN");
default -> List.of();
};
}