perf(lastleg): batch live appointment work facts and filter candidates
This commit is contained in:
+22
@@ -16,6 +16,17 @@ public interface AppointmentCargoVerificationMapper extends BaseMapper<Appointme
|
||||
@Select("SELECT * FROM ll_appointment_cargo_verification WHERE customer_id=#{customerId} AND source_key=#{sourceKey} ORDER BY version DESC LIMIT 1")
|
||||
AppointmentCargoVerification latest(@Param("customerId") String customerId,@Param("sourceKey") String sourceKey);
|
||||
|
||||
@Select("""
|
||||
<script>
|
||||
SELECT ranked.* FROM (
|
||||
SELECT v.*,ROW_NUMBER() OVER(PARTITION BY customer_id,source_key ORDER BY version DESC) work_row
|
||||
FROM ll_appointment_cargo_verification v WHERE
|
||||
<foreach collection='refs' item='ref' separator=' OR '>(customer_id=#{ref.customerId} AND source_key=#{ref.sourceKey})</foreach>
|
||||
) ranked WHERE work_row=1
|
||||
</script>
|
||||
""")
|
||||
java.util.List<AppointmentCargoVerification> latestBatch(@Param("refs") java.util.List<AppointmentCoverageService.SourceRef> refs);
|
||||
|
||||
@Select("SELECT * FROM ll_appointment_cargo_verification WHERE customer_id=#{customerId} AND source_key=#{sourceKey} AND command_key=#{key}")
|
||||
AppointmentCargoVerification command(@Param("customerId") String customerId,@Param("sourceKey") String sourceKey,@Param("key") String key);
|
||||
|
||||
@@ -27,4 +38,15 @@ public interface AppointmentCargoVerificationMapper extends BaseMapper<Appointme
|
||||
WHERE customer_id=#{customerId} AND source_key=#{sourceKey} AND active=1
|
||||
""")
|
||||
Allocation allocated(@Param("customerId") String customerId,@Param("sourceKey") String sourceKey);
|
||||
|
||||
record SourceAllocation(String customerId,String sourceKey,long packages,long unknownItems) { }
|
||||
@Select("""
|
||||
<script>
|
||||
SELECT customer_id,source_key,COALESCE(SUM(package_count),0) packages,COUNT(*)-COUNT(package_count) unknown_items
|
||||
FROM ll_inbound_fba_appointment_item WHERE active=1 AND (
|
||||
<foreach collection='refs' item='ref' separator=' OR '>(customer_id=#{ref.customerId} AND source_key=#{ref.sourceKey})</foreach>
|
||||
) GROUP BY customer_id,source_key
|
||||
</script>
|
||||
""")
|
||||
java.util.List<SourceAllocation> allocatedBatch(@Param("refs") java.util.List<AppointmentCoverageService.SourceRef> refs);
|
||||
}
|
||||
|
||||
+26
@@ -47,10 +47,36 @@ public class AppointmentCargoVerificationService {
|
||||
var source=sources.resolve(sourceKey,customerId,destinationKey);
|
||||
return inspect(source);
|
||||
}
|
||||
public record Inspection(AppointmentCoverageService.SourceRef reference,Context context,ResponseStatusException error) {
|
||||
public Context requireContext() { if(error!=null) throw error; return context; }
|
||||
}
|
||||
/** Shares source and warehouse reads within this call only; never caches mutable business facts across requests. */
|
||||
public List<Inspection> inspectBatch(List<AppointmentCoverageService.SourceRef> refs) {
|
||||
var result=new ArrayList<Inspection>();
|
||||
for(int start=0;start<refs.size();start+=100) {
|
||||
var batch=refs.subList(start,Math.min(start+100,refs.size()));
|
||||
var resolved=sources.inspectBatch(batch);
|
||||
var valid=resolved.stream().filter(r->r.error()==null).map(AppointmentSourceService.Resolution::reference).toList();
|
||||
var latest=new HashMap<List<String>,AppointmentCargoVerification>();
|
||||
var allocations=new HashMap<List<String>,AppointmentCargoVerificationMapper.Allocation>();
|
||||
if(!valid.isEmpty()) {
|
||||
mapper.latestBatch(valid).forEach(r->latest.put(List.of(r.getCustomerId(),r.getSourceKey()),r));
|
||||
mapper.allocatedBatch(valid).forEach(r->allocations.put(List.of(r.customerId(),r.sourceKey()),new AppointmentCargoVerificationMapper.Allocation(r.packages(),r.unknownItems())));
|
||||
}
|
||||
for(var resolution:resolved) {
|
||||
var key=List.of(resolution.reference().customerId(),resolution.reference().sourceKey());
|
||||
result.add(new Inspection(resolution.reference(),resolution.error()==null?inspect(resolution.source(),latest.get(key),allocations.getOrDefault(key,new AppointmentCargoVerificationMapper.Allocation(0,0))):null,resolution.error()));
|
||||
}
|
||||
}
|
||||
return List.copyOf(result);
|
||||
}
|
||||
/** Uses an already-authorized batched source resolution; never repeats warehouse HTTP lookups per row. */
|
||||
public Context inspect(AppointmentSourceService.Source source) {
|
||||
var row=mapper.latest(source.customerId(),source.sourceKey());
|
||||
var allocation=mapper.allocated(source.customerId(),source.sourceKey());
|
||||
return inspect(source,row,allocation);
|
||||
}
|
||||
private Context inspect(AppointmentSourceService.Source source,AppointmentCargoVerification row,AppointmentCargoVerificationMapper.Allocation allocation) {
|
||||
boolean changed=row!=null && changed(row,source);
|
||||
Integer total=row==null?source.packages():row.getPackageCount();
|
||||
var reasons=new ArrayList<>(source.blockReasons());
|
||||
|
||||
+26
-5
@@ -60,12 +60,22 @@ public class AppointmentSourceService {
|
||||
}
|
||||
/** One catalog request and batched source reads, including mixed first-leg and external bookings. */
|
||||
public List<Source> resolveBatch(List<AppointmentCoverageService.SourceRef> requested) {
|
||||
return resolveBatch(requested,true).stream().map(Resolution::requireSource).toList();
|
||||
}
|
||||
/** Read-only inspection keeps each source's prerequisite failure separate; infrastructure errors still escape. */
|
||||
public record Resolution(AppointmentCoverageService.SourceRef reference,Source source,ResponseStatusException error) {
|
||||
public Source requireSource() { if(error!=null) throw error; return source; }
|
||||
}
|
||||
public List<Resolution> inspectBatch(List<AppointmentCoverageService.SourceRef> requested) {
|
||||
return resolveBatch(requested,false);
|
||||
}
|
||||
private List<Resolution> resolveBatch(List<AppointmentCoverageService.SourceRef> requested,boolean strict) {
|
||||
if(requested==null || requested.isEmpty() || requested.size()>100) throw bad("每批请选择1至100票真实货源");
|
||||
var keys=new LinkedHashSet<String>(); var orderIds=new LinkedHashSet<Long>();
|
||||
var bookingRefs=new java.util.ArrayList<AppointmentExternalSourceGateway.SourceRef>();
|
||||
for(var ref:requested) {
|
||||
if(ref==null || blank(ref.sourceKey()) || ref.sourceKey().length()>160 || blank(ref.customerId()) || ref.customerId().length()>128) throw bad("请选择完整的真实货源");
|
||||
if(!keys.add(ref.sourceKey())) throw bad("同一来源货物不能重复选择");
|
||||
if(!keys.add(ref.sourceKey()) && strict) throw bad("同一来源货物不能重复选择");
|
||||
if(ref.sourceKey().startsWith("ORDER:")) orderIds.add(orderId(ref.sourceKey()));
|
||||
else if(ref.sourceKey().startsWith("BOOKING:") && !blank(ref.sourceKey().substring(8))) bookingRefs.add(new AppointmentExternalSourceGateway.SourceRef(ref.customerId(),ref.sourceKey().substring(8)));
|
||||
else throw bad("不支持的货源类型");
|
||||
@@ -77,17 +87,28 @@ public class AppointmentSourceService {
|
||||
if(booking.orderId()!=null) orderIds.add(booking.orderId());
|
||||
}
|
||||
var orderRows=orderIds.isEmpty()?List.<com.baiyue.lastleg.entity.LastlegOrder>of():orders.selectBatchIds(orderIds);
|
||||
var directOrders=requested.stream().filter(ref->ref.sourceKey().startsWith("ORDER:")).map(ref->new AppointmentExternalSourceGateway.OrderRef(ref.customerId(),orderId(ref.sourceKey()))).toList();
|
||||
if(!directOrders.isEmpty() && !external.resolveOrders(directOrders).isEmpty()) throw bad("该货物来自外接登记,请使用原登记来源继续预约");
|
||||
var directOrders=requested.stream().filter(ref->ref.sourceKey().startsWith("ORDER:")).map(ref->new AppointmentExternalSourceGateway.OrderRef(ref.customerId(),orderId(ref.sourceKey()))).distinct().toList();
|
||||
var externalOrders=directOrders.isEmpty()?Set.<AppointmentExternalSourceGateway.OrderRef>of():external.resolveOrders(directOrders).stream()
|
||||
.map(s->new AppointmentExternalSourceGateway.OrderRef(s.customerId(),s.orderId())).collect(Collectors.toSet());
|
||||
if(strict && !externalOrders.isEmpty()) throw bad("该货物来自外接登记,请使用原登记来源继续预约");
|
||||
var orderMap=orderRows.stream().collect(Collectors.toMap(com.baiyue.lastleg.entity.LastlegOrder::getId,Function.identity()));
|
||||
var transferRefs=orderRows.stream().filter(o->"WAREHOUSE_TRANSFER".equals(o.getOrderType())).map(o->new AppointmentCoverageMapper.OrderRef(o.getCustomerId(),o.getId())).toList();
|
||||
var destinations=transferRefs.isEmpty()?Map.<Long,AppointmentCoverageMapper.Source>of():destinationFacts.sources(transferRefs).stream().collect(Collectors.toMap(AppointmentCoverageMapper.Source::orderId,Function.identity()));
|
||||
var containerIds=orderRows.stream().filter(o->"FIRST_LEG".equals(o.getOrderType())).map(com.baiyue.lastleg.entity.LastlegOrder::getSourceContainerBookingId).filter(Objects::nonNull).distinct().toList();
|
||||
var gates=containerIds.isEmpty()?Map.<String,Boolean>of():shipping.selectBatchIds(containerIds).stream().collect(Collectors.toMap(g->g.getContainerBookingId(),g->g.getNodeSequence()!=null && g.getNodeSequence()>=40));
|
||||
var catalog=catalog();
|
||||
var result=requested.stream().map(ref->resolve(ref.sourceKey(),ref.customerId(),ref.destinationKey(),orderMap,bookingMap,gates,catalog,destinations)).toList();
|
||||
var result=requested.stream().map(ref->{
|
||||
try {
|
||||
if(ref.sourceKey().startsWith("ORDER:") && externalOrders.contains(new AppointmentExternalSourceGateway.OrderRef(ref.customerId(),orderId(ref.sourceKey()))))
|
||||
throw bad("该货物来自外接登记,请使用原登记来源继续预约");
|
||||
return new Resolution(ref,resolve(ref.sourceKey(),ref.customerId(),ref.destinationKey(),orderMap,bookingMap,gates,catalog,destinations),null);
|
||||
} catch(ResponseStatusException error) {
|
||||
if(strict || error.getStatusCode().value()>=500) throw error;
|
||||
return new Resolution(ref,null,error);
|
||||
}
|
||||
}).toList();
|
||||
var materialized=new java.util.HashSet<Long>();
|
||||
for(var source:result) if(source.orderId()!=null && !materialized.add(source.orderId())) throw bad("同一货物存在重复来源,请保留原登记来源");
|
||||
if(strict) for(var resolution:result) { var source=resolution.requireSource(); if(source.orderId()!=null && !materialized.add(source.orderId())) throw bad("同一货物存在重复来源,请保留原登记来源"); }
|
||||
return result;
|
||||
}
|
||||
private Source resolve(String sourceKey,String customerId,String destinationKey,
|
||||
|
||||
Reference in New Issue
Block a user