feat(appointments): unify pre-inbound planning and confirmed FBA bookings
This commit is contained in:
+15
@@ -14,4 +14,19 @@ public class FleetAppointmentSourceAdapter implements AppointmentExternalSourceG
|
||||
return sources.resolve(refs.stream().map(r->new FleetPoolSourceResolver.SourceRef(r.customerId(),r.bookingNo())).toList())
|
||||
.stream().map(s->new Source(s.customerId(),s.bookingNo(),s.tripNo(),s.bookingStatus(),s.warehouseId(),s.destinationKey(),s.lastlegOrderId(),s.packageCount(),s.recordVersion())).toList();
|
||||
}
|
||||
@Override
|
||||
public List<Source> resolveOrders(List<OrderRef> refs) {
|
||||
if (refs == null || refs.isEmpty()) return List.of();
|
||||
if (refs.size() > 100 || refs.stream().anyMatch(java.util.Objects::isNull))
|
||||
throw new IllegalArgumentException("订单来源每次最多100条,且不能包含空项");
|
||||
return sources.resolveOrders(refs.stream().map(r -> new FleetPoolSourceResolver.OrderRef(r.customerId(), r.orderId())).toList())
|
||||
.stream().map(s -> new Source(s.customerId(), s.bookingNo(), s.tripNo(), s.bookingStatus(), s.warehouseId(),
|
||||
s.destinationKey(), s.lastlegOrderId(), s.packageCount(), s.recordVersion())).toList();
|
||||
}
|
||||
@Override
|
||||
public void lockForAppointment(List<SourceRef> refs) {
|
||||
if (refs == null || refs.size() > 100 || refs.stream().anyMatch(java.util.Objects::isNull))
|
||||
throw new IllegalArgumentException("预约来源须完整,每次最多100条");
|
||||
sources.lockForAppointment(refs.stream().map(r -> new FleetPoolSourceResolver.SourceRef(r.customerId(), r.bookingNo())).toList());
|
||||
}
|
||||
}
|
||||
|
||||
+5
-1
@@ -33,11 +33,14 @@ public class FleetPoolOrderBridge {
|
||||
private final LastlegOrderNoGenerator numbers;
|
||||
private final ObjectMapper json;
|
||||
private final DeliverySourceEvidenceMapper deliveryEvidence;
|
||||
private final com.baiyue.lastleg.appointmentplanning.AppointmentSourceLinker appointmentSources;
|
||||
public FleetPoolOrderBridge(FleetPoolOrderMapper links,LocalLooseboardOrderMapper loose,
|
||||
LocalLooseboardHistoryMapper looseHistory,UsTruckOrderMapper trucks,
|
||||
UsTruckOrderStatusHistoryMapper truckHistory,LastlegOrderNoGenerator numbers,ObjectMapper json,DeliverySourceEvidenceMapper deliveryEvidence) {
|
||||
UsTruckOrderStatusHistoryMapper truckHistory,LastlegOrderNoGenerator numbers,ObjectMapper json,DeliverySourceEvidenceMapper deliveryEvidence,
|
||||
com.baiyue.lastleg.appointmentplanning.AppointmentSourceLinker appointmentSources) {
|
||||
this.links=links;this.loose=loose;this.looseHistory=looseHistory;this.trucks=trucks;
|
||||
this.truckHistory=truckHistory;this.numbers=numbers;this.json=json;this.deliveryEvidence=deliveryEvidence;
|
||||
this.appointmentSources=appointmentSources;
|
||||
}
|
||||
@Transactional(readOnly=true)
|
||||
public Map<Long,String> fulfillmentStages(List<Long> bookingIds) {
|
||||
@@ -100,6 +103,7 @@ public class FleetPoolOrderBridge {
|
||||
if(order.getLastlegOrderId()!=null&&!order.getLastlegOrderId().equals(lastlegOrderId))throw conflict("散板商业单已存在另一尾程来源");
|
||||
order.setLastlegOrderId(lastlegOrderId);order.setVersion(order.getVersion()+1);order.setUpdatedAt(row.getUpdatedAt());loose.updateById(order);
|
||||
}
|
||||
appointmentSources.materialized(customerId, bookingNo, lastlegOrderId);
|
||||
}
|
||||
@Transactional(propagation=Propagation.MANDATORY)
|
||||
public void receiving(String customerId,String incomingNo,String operator) {
|
||||
|
||||
+12
@@ -7,6 +7,8 @@ import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface FleetPoolOrderMapper extends BaseMapper<FleetPoolOrderLink> {
|
||||
@Select("SELECT customer_id,booking_no FROM ll_fleet_external_booking WHERE booking_no=#{bookingNo} AND offer_id IS NOT NULL FOR UPDATE")
|
||||
FleetPoolSourceResolver.SourceRef lockAppointmentSource(String bookingNo);
|
||||
@Select({"<script>SELECT b.customer_id,b.booking_no,b.trip_no,b.status AS booking_status,",
|
||||
"t.warehouse_id,b.destination_key,b.destination_label,b.incoming_no,",
|
||||
"l.lastleg_order_id,l.business_order_no,l.fulfillment_stage,b.record_version,",
|
||||
@@ -16,6 +18,16 @@ public interface FleetPoolOrderMapper extends BaseMapper<FleetPoolOrderLink> {
|
||||
"WHERE b.offer_id IS NOT NULL AND <foreach collection='sources' item='s' open='(' separator=' OR ' close=')'>",
|
||||
"(b.customer_id=#{s.customerId} AND b.booking_no=#{s.bookingNo})</foreach></script>"})
|
||||
List<FleetPoolSourceResolver.SourceRow> resolveSources(@Param("sources") List<FleetPoolSourceResolver.SourceRef> sources);
|
||||
@Select({"<script>SELECT b.customer_id,b.booking_no,b.trip_no,b.status AS booking_status,",
|
||||
"t.warehouse_id,b.destination_key,b.destination_label,b.incoming_no,",
|
||||
"l.lastleg_order_id,l.business_order_no,l.fulfillment_stage,b.record_version,",
|
||||
"b.cargo_json,b.expected_pallet_count,b.estimated_arrival_at",
|
||||
"FROM ll_fleet_pool_order_link l JOIN ll_fleet_external_booking b ON b.id=l.booking_id AND b.customer_id=l.customer_id",
|
||||
"JOIN ll_fleet_trip t ON t.id=b.trip_id",
|
||||
"JOIN ll_order o ON o.id=l.lastleg_order_id AND o.customer_id=l.customer_id",
|
||||
"WHERE b.offer_id IS NOT NULL AND <foreach collection='orders' item='o' open='(' separator=' OR ' close=')'>",
|
||||
"(l.customer_id=#{o.customerId} AND l.lastleg_order_id=#{o.orderId})</foreach></script>"})
|
||||
List<FleetPoolSourceResolver.SourceRow> resolveOrderSources(@Param("orders") List<FleetPoolSourceResolver.OrderRef> orders);
|
||||
@Select("SELECT * FROM ll_fleet_pool_order_link WHERE booking_id=#{id} FOR UPDATE")
|
||||
FleetPoolOrderLink lockBooking(long id);
|
||||
@Select("SELECT * FROM ll_fleet_pool_order_link WHERE booking_no=#{no} FOR UPDATE")
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ public class FleetPoolOrderQueries {
|
||||
row.getTripNo(),row.getPlannedDepartureAt()==null?null:row.getPlannedDepartureAt().toInstant(java.time.ZoneOffset.UTC),row.getOriginWarehouseName(),row.getDestinationLabel(),
|
||||
row.getDeliveryMode(),row.getDeliveryInstructions(),row.getFulfillmentStage(),row.getStageOccurredAt()==null?null:row.getStageOccurredAt().atZone(java.time.ZoneId.systemDefault()).toInstant(),
|
||||
row.getAgreedFreightAmount(),row.getCurrency(),row.getIncomingNo(),row.getDispatchNo(),row.getDeliveryNo(),
|
||||
signed,unsigned,returned,exception,"此订单由拼车承运实际履约推进,请从来货、排车和签收入口操作");
|
||||
signed,unsigned,returned,exception,"此订单已关联拼车承运,状态随实际收货、排车和签收更新,无需重复下单。");
|
||||
}
|
||||
private static ResponseStatusException conflict(String message){return new ResponseStatusException(HttpStatus.CONFLICT,message);}
|
||||
}
|
||||
|
||||
+33
-1
@@ -8,6 +8,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
@Service
|
||||
public class FleetPoolSourceResolver {
|
||||
public record SourceRef(String customerId, String bookingNo) { }
|
||||
public record OrderRef(String customerId, Long orderId) { }
|
||||
public record Source(String customerId, String bookingNo, String tripNo, String bookingStatus,
|
||||
String warehouseId, String destinationKey, String destinationLabel, String incomingNo,
|
||||
Long lastlegOrderId, String businessOrderNo, String fulfillmentStage, Long recordVersion, Integer packageCount, Integer expectedPalletCount, java.time.LocalDateTime estimatedArrivalAt) { }
|
||||
@@ -19,12 +20,43 @@ public class FleetPoolSourceResolver {
|
||||
private final FleetPoolOrderMapper links;
|
||||
public FleetPoolSourceResolver(FleetPoolOrderMapper links,com.fasterxml.jackson.databind.ObjectMapper json) { this.links = links; this.json=json; }
|
||||
|
||||
@Transactional(propagation = org.springframework.transaction.annotation.Propagation.MANDATORY)
|
||||
public void lockForAppointment(List<SourceRef> sources) {
|
||||
if (!org.springframework.transaction.support.TransactionSynchronizationManager.isActualTransactionActive())
|
||||
throw new IllegalStateException("预约来源锁必须在调用方事务内获取");
|
||||
if (sources == null || sources.size() > 100 || sources.stream().anyMatch(s -> s == null
|
||||
|| s.customerId() == null || s.customerId().isBlank() || s.bookingNo() == null || s.bookingNo().isBlank()))
|
||||
throw new IllegalArgumentException("来源须包含客户及登记业务号,每次最多100条");
|
||||
// Lock the original booking, not the optional commercial link: an inquiry may have no incoming yet.
|
||||
// One unique-key lookup at a time gives concurrent batches the same lock order.
|
||||
for (var source : sources.stream().distinct().sorted(java.util.Comparator.comparing(SourceRef::bookingNo)
|
||||
.thenComparing(SourceRef::customerId)).toList()) {
|
||||
var locked = links.lockAppointmentSource(source.bookingNo());
|
||||
if (locked == null || !source.equals(locked))
|
||||
throw new org.springframework.web.server.ResponseStatusException(org.springframework.http.HttpStatus.CONFLICT,
|
||||
"预约货源不存在或客户归属不一致,请重新核对");
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public List<Source> resolve(List<SourceRef> sources) {
|
||||
if (sources == null || sources.isEmpty()) return List.of();
|
||||
if (sources.size() > 100 || sources.stream().anyMatch(s -> s == null || s.customerId() == null || s.customerId().isBlank() || s.bookingNo() == null || s.bookingNo().isBlank()))
|
||||
throw new IllegalArgumentException("来源须包含客户及登记业务号,每次最多100条");
|
||||
return links.resolveSources(sources.stream().distinct().toList()).stream().map(row -> {
|
||||
return project(links.resolveSources(sources.stream().distinct().toList()));
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public List<Source> resolveOrders(List<OrderRef> orders) {
|
||||
if (orders == null || orders.isEmpty()) return List.of();
|
||||
if (orders.size() > 100 || orders.stream().anyMatch(o -> o == null || o.customerId() == null
|
||||
|| o.customerId().isBlank() || o.orderId() == null || o.orderId() <= 0))
|
||||
throw new IllegalArgumentException("来源须包含客户及有效订单,每次最多100条");
|
||||
return project(links.resolveOrderSources(orders.stream().distinct().toList()));
|
||||
}
|
||||
|
||||
private List<Source> project(List<SourceRow> rows) {
|
||||
return rows.stream().map(row -> {
|
||||
Integer packages;
|
||||
try {
|
||||
var value=json.readTree(row.cargoJson()).get("packageCount");
|
||||
|
||||
Reference in New Issue
Block a user