feat(lastleg): query commercial sources independently of fulfillment types
This commit is contained in:
@@ -37,7 +37,10 @@ public final class LastlegOrderRequests {
|
||||
Boolean detained,
|
||||
LocalDateTime createdFrom,
|
||||
LocalDateTime createdTo,
|
||||
SailingStage sailingStage
|
||||
SailingStage sailingStage,
|
||||
@Size(max = 64) String businessType,
|
||||
@Size(max = 128) String businessOrderNo,
|
||||
@Size(max = 128) String externalBookingNo
|
||||
) {
|
||||
public Query {
|
||||
if (page == null || page == 0) page = 1L;
|
||||
|
||||
@@ -74,6 +74,9 @@ public record LastlegOrderSummary(
|
||||
LocalDateTime createdAt,
|
||||
LocalDateTime updatedAt,
|
||||
@Schema(description = "同一主单关联的全部换标子单及各自状态,按创建时间排序")
|
||||
List<RelabelSubOrder> relabelApplications
|
||||
List<RelabelSubOrder> relabelApplications,
|
||||
BusinessSource businessSource
|
||||
) {
|
||||
@Schema(description = "从正式单据关联读取的商业来源;与履约类型 orderType 独立")
|
||||
public record BusinessSource(String businessType, String label, String businessOrderNo, String externalBookingNo) { }
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import java.util.Arrays;
|
||||
final class LastlegExportColumns {
|
||||
private LastlegExportColumns() { }
|
||||
private static final Map<String, String> DEFINITIONS = Map.ofEntries(
|
||||
Map.entry("LAST_MILE_ORDER_LIST", "orderNo:尾程单号,orderTypeName:订单类型,inboundCode:入仓码,sourceOrderNo:头程单号,customerName:货主,overseasWarehouseName:海外仓,transferWarehouseName:转仓,transferWarehouseCode:转仓编码,fbaWarehouseCode:FBA仓库,containerNo:集装箱号,containerNode:货柜节点,abnormal:是否异常,detained:是否暂扣,palletCount:托数:INTEGER,packageCount:客户预报箱数:INTEGER,receivedPackageCount:实收箱数:INTEGER,packageDifference:差异箱数:INTEGER,volumeCbm:体积(m³):DECIMAL,productQuantity:数量:INTEGER,volumeWeightKg:体积重量:DECIMAL,estimatedArrivalAt:预计到港时间:DATETIME,actualArrivalAt:实际到港时间:DATETIME,estimatedWarehouseAt:预计到仓:DATETIME,inboundOrderNo:入库单号,relabelOrderNo:换标单号,relabelStatus:换标状态,palletMergeFlag:合托标记,currentStatus:主流程状态,subOrderStatus:子单状态,privateRemark:备注(仅自己),customerRemark:备注(客户),warehouseRemark:备注(仓库)"),
|
||||
Map.entry("LAST_MILE_ORDER_LIST", "orderNo:尾程单号,orderTypeName:履约类型,businessSource.label:业务来源,businessSource.businessOrderNo:关联商业单号,businessSource.externalBookingNo:外接预订号,inboundCode:入仓码,sourceOrderNo:来源订单号,customerName:货主,overseasWarehouseName:海外仓,transferWarehouseName:转仓,transferWarehouseCode:转仓编码,fbaWarehouseCode:FBA仓库,containerNo:集装箱号,containerNode:货柜节点,abnormal:是否异常,detained:是否暂扣,palletCount:托数:INTEGER,packageCount:客户预报箱数:INTEGER,receivedPackageCount:实收箱数:INTEGER,packageDifference:差异箱数:INTEGER,volumeCbm:体积(m³):DECIMAL,productQuantity:数量:INTEGER,volumeWeightKg:体积重量:DECIMAL,estimatedArrivalAt:预计到港时间:DATETIME,actualArrivalAt:实际到港时间:DATETIME,estimatedWarehouseAt:预计到仓:DATETIME,inboundOrderNo:入库单号,relabelOrderNo:换标单号,relabelStatus:换标状态,palletMergeFlag:合托标记,currentStatus:主流程状态,subOrderStatus:子单状态,privateRemark:备注(仅自己),customerRemark:备注(客户),warehouseRemark:备注(仓库)"),
|
||||
Map.entry("OVERSEAS_INVENTORY_BALANCE", "customerName:客户,overseasWarehouseCode:海外仓编码,overseasWarehouseName:海外仓,destinationWarehouseCode:目的仓编码,destinationWarehouseName:目的仓,inboundOrderNo:入库单号,inboundCode:入仓码,skuCode:SKU,productNameZh:中文品名,productNameEn:英文品名,totalPackages:总箱数:INTEGER,availablePackages:可用箱数:INTEGER,holdPackages:暂扣箱数:INTEGER,allocatedPackages:已分配箱数:INTEGER,pickedPackages:已拣箱数:INTEGER,inTransitPackages:在途箱数:INTEGER,receivingPackages:收货中箱数:INTEGER,procurementPackages:采购箱数:INTEGER"),
|
||||
Map.entry("OVERSEAS_INVENTORY_LEDGER", "occurredAt:发生时间:DATETIME,customerName:客户,destinationWarehouseCode:目的仓编码,inboundOrderNo:入库单号,inboundCode:入仓码,businessType:业务类型,businessOrderNo:业务单号,outboundOrderNo:出库单号,palletNo:托盘号,batchNo:批次号,statusFrom:原状态,statusTo:新状态,beforePackages:变动前箱数:INTEGER,deltaPackages:变动箱数:INTEGER,afterPackages:变动后箱数:INTEGER,remark:备注"),
|
||||
Map.entry("OVERSEAS_QUOTE_LIST", "name:报价名称,warehouseCode:合作仓编码,warehouseName:合作仓名称,providerName:报价方,currency:币种,effectiveStart:生效开始:DATE,effectiveEnd:生效结束:DATE,versionCode:版本号,status:状态,updatedAt:更新时间:DATETIME"),
|
||||
|
||||
@@ -16,6 +16,25 @@ import java.math.BigDecimal;
|
||||
|
||||
@Mapper
|
||||
public interface LastlegOrderMapper extends BaseMapper<LastlegOrder> {
|
||||
// Correlate both order identity and customer; never infer a business source from remarks.
|
||||
String POOL_SOURCE_WHERE = " FROM ll_fleet_pool_order_link l WHERE l.lastleg_order_id=ll_order.id " +
|
||||
"AND l.customer_id=ll_order.customer_id AND ll_order.order_type='WAREHOUSE_TRANSFER'";
|
||||
String BUSINESS_TYPE_SQL = "COALESCE((SELECT l.business_type" + POOL_SOURCE_WHERE + "),ll_order.order_type)";
|
||||
String BUSINESS_ORDER_NO_SQL = "COALESCE((SELECT l.business_order_no" + POOL_SOURCE_WHERE +
|
||||
"),CASE WHEN ll_order.order_type='LOCAL_LOOSEBOARD' THEN ll_order.source_order_no END)";
|
||||
String EXTERNAL_BOOKING_NO_SQL = "(SELECT b.booking_no FROM ll_fleet_external_booking b " +
|
||||
"WHERE b.customer_id=ll_order.customer_id AND b.incoming_no=ll_order.source_order_no " +
|
||||
"AND ll_order.order_type='WAREHOUSE_TRANSFER')";
|
||||
|
||||
@Select("<script>SELECT ll_order.id AS orderId," + BUSINESS_TYPE_SQL + " AS businessType," +
|
||||
BUSINESS_ORDER_NO_SQL + " AS businessOrderNo," + EXTERNAL_BOOKING_NO_SQL + " AS externalBookingNo," +
|
||||
"EXISTS(SELECT 1" + POOL_SOURCE_WHERE + ") AS poolSource FROM ll_order WHERE ll_order.id IN " +
|
||||
"<foreach collection='orderIds' item='id' open='(' separator=',' close=')'>#{id}</foreach></script>")
|
||||
List<BusinessSourceRow> findBusinessSources(@Param("orderIds") List<Long> orderIds);
|
||||
|
||||
record BusinessSourceRow(Long orderId, String businessType, String businessOrderNo,
|
||||
String externalBookingNo, boolean poolSource) { }
|
||||
|
||||
// Manual node snapshots replace container nodes permanently for the order.
|
||||
String SAILING_STAGE_FILTER_SQL = "(ll_order.order_type='FIRST_LEG' AND " +
|
||||
"(CASE WHEN COALESCE(ll_order.manual_node_override,FALSE)=TRUE THEN " +
|
||||
|
||||
@@ -100,11 +100,12 @@ public class LastlegOrderService {
|
||||
Map<Long, String> dispatchStatuses = dispatchStatuses(page.getRecords());
|
||||
Map<Long, LastlegOrderMapper.OrderWeightRow> weights = orderWeights(page.getRecords());
|
||||
Map<Long, List<com.baiyue.lastleg.dto.RelabelSubOrder>> relabels = relabelSubOrders(page.getRecords());
|
||||
Map<Long, LastlegOrderSummary.BusinessSource> businessSources = businessSources(page.getRecords());
|
||||
return new PageResult<>(page.getRecords().stream().map(order -> summary(order, mergeRelations.get(order.getId()),
|
||||
StringUtils.hasText(order.getSourceContainerBookingId())
|
||||
? containerNodes.get(order.getSourceContainerBookingId()) : null,
|
||||
dispatchStatuses.getOrDefault(order.getId(), "UNSCHEDULED"), weights.get(order.getId()),
|
||||
relabels.getOrDefault(order.getId(), List.of()))).toList(),
|
||||
relabels.getOrDefault(order.getId(), List.of()), businessSources.get(order.getId()))).toList(),
|
||||
page.getCurrent(), page.getSize(), page.getTotal(), page.getPages());
|
||||
}
|
||||
|
||||
@@ -360,7 +361,8 @@ public class LastlegOrderService {
|
||||
.sorted(java.util.Comparator.comparingInt(value -> MANUAL_NODE_CODES.indexOf(value.nodeCode()))).toList();
|
||||
return new LastlegOrderDetailResponse(summary(order, palletMergeRelations(List.of(order)).get(order.getId()),
|
||||
containerNode(order), dispatchStatuses(List.of(order)).getOrDefault(order.getId(), "UNSCHEDULED"),
|
||||
orderWeights(List.of(order)).get(order.getId()), relabelSubOrders(List.of(order)).getOrDefault(order.getId(), List.of())), manualNodes, cargo, history.stream()
|
||||
orderWeights(List.of(order)).get(order.getId()), relabelSubOrders(List.of(order)).getOrDefault(order.getId(), List.of()),
|
||||
businessSources(List.of(order)).get(order.getId())), manualNodes, cargo, history.stream()
|
||||
.map(value -> new LastlegOrderDetailResponse.History(value.getId(), value.getFromStatus(),
|
||||
value.getToStatus(), value.getRemark(), value.getCreatedAt())).toList(), fees, operations.stream()
|
||||
.map(value -> new LastlegOrderDetailResponse.Operation(value.getId(), value.getOperationNode(),
|
||||
@@ -549,6 +551,9 @@ public class LastlegOrderService {
|
||||
.or().apply("EXISTS (SELECT 1 FROM ll_relabel_application r WHERE r.lastleg_order_id=ll_order.id " +
|
||||
"AND r.customer_id=ll_order.customer_id AND r.relabel_no LIKE CONCAT('%',{0},'%'))", relabelNo))
|
||||
.eq(StringUtils.hasText(query.orderType()), LastlegOrder::getOrderType, query.orderType())
|
||||
.apply(StringUtils.hasText(query.businessType()), LastlegOrderMapper.BUSINESS_TYPE_SQL + "={0}", query.businessType())
|
||||
.apply(StringUtils.hasText(query.businessOrderNo()), LastlegOrderMapper.BUSINESS_ORDER_NO_SQL + " LIKE CONCAT('%',{0},'%')", query.businessOrderNo())
|
||||
.apply(StringUtils.hasText(query.externalBookingNo()), LastlegOrderMapper.EXTERNAL_BOOKING_NO_SQL + " LIKE CONCAT('%',{0},'%')", query.externalBookingNo())
|
||||
.apply(query.sailingStage() != null, LastlegOrderMapper.SAILING_STAGE_FILTER_SQL,
|
||||
query.sailingStage() == LastlegOrderRequests.SailingStage.AFTER_DEPARTURE ? 1 : 0)
|
||||
.like(StringUtils.hasText(query.inboundCode()), LastlegOrder::getInboundCode, query.inboundCode())
|
||||
@@ -623,10 +628,23 @@ public class LastlegOrderService {
|
||||
return rows == null ? Map.of() : rows.stream().collect(Collectors.groupingBy(com.baiyue.lastleg.dto.RelabelSubOrder::lastlegOrderId));
|
||||
}
|
||||
|
||||
private Map<Long, LastlegOrderSummary.BusinessSource> businessSources(List<LastlegOrder> orders) {
|
||||
if (orders.isEmpty()) return Map.of();
|
||||
return orderMapper.findBusinessSources(orders.stream().map(LastlegOrder::getId).toList()).stream()
|
||||
.collect(Collectors.toMap(LastlegOrderMapper.BusinessSourceRow::orderId, row -> {
|
||||
String label = "US_TRUCK".equals(row.businessType()) ? "本土卡车" : LastlegOrderType.labelOf(row.businessType());
|
||||
if (row.poolSource()) label += " · 拼车需求池";
|
||||
else if ("LOCAL_LOOSEBOARD".equals(row.businessType())) label += " · 独立下单";
|
||||
else if (StringUtils.hasText(row.externalBookingNo())) label += " · 外接来货";
|
||||
return new LastlegOrderSummary.BusinessSource(row.businessType(), label, row.businessOrderNo(), row.externalBookingNo());
|
||||
}));
|
||||
}
|
||||
|
||||
private LastlegOrderSummary summary(LastlegOrder o, List<LastlegOrderMapper.PalletMergeRelationRow> relations,
|
||||
String containerNode, String dispatchStatus,
|
||||
LastlegOrderMapper.OrderWeightRow weight,
|
||||
List<com.baiyue.lastleg.dto.RelabelSubOrder> relabelApplications) {
|
||||
List<com.baiyue.lastleg.dto.RelabelSubOrder> relabelApplications,
|
||||
LastlegOrderSummary.BusinessSource businessSource) {
|
||||
List<LastlegOrderMapper.PalletMergeRelationRow> related = relations == null ? List.of() : relations;
|
||||
return new LastlegOrderSummary(o.getId(), displayOrderNo(o), o.getOrderType(),
|
||||
LastlegOrderType.labelOf(o.getOrderType()), o.getDestinationType(), o.getSourceInboundId(),
|
||||
@@ -644,7 +662,7 @@ public class LastlegOrderService {
|
||||
o.getPalletMergeFlag(), related.stream().map(LastlegOrderMapper.PalletMergeRelationRow::mergeGroupNo).distinct().findFirst().orElse(null),
|
||||
related.stream().map(LastlegOrderMapper.PalletMergeRelationRow::relatedInboundNo).filter(Objects::nonNull).distinct().toList(),
|
||||
o.getCurrentStatus(), o.getSubOrderStatus(), o.getPrivateRemark(),
|
||||
o.getCustomerRemark(), o.getWarehouseRemark(), o.getCreatedAt(), o.getUpdatedAt(), relabelApplications);
|
||||
o.getCustomerRemark(), o.getWarehouseRemark(), o.getCreatedAt(), o.getUpdatedAt(), relabelApplications, businessSource);
|
||||
}
|
||||
|
||||
private static Integer packageDifference(LastlegOrder order) {
|
||||
|
||||
Reference in New Issue
Block a user