尾程订单列表查询修改

This commit is contained in:
2026-10-21 21:49:48 +08:00
parent 5356a8c898
commit bbf8f2f792
5 changed files with 161 additions and 4 deletions
@@ -0,0 +1,24 @@
package com.baiyue.lastleg.client;
import com.baiyue.common.core.api.ApiResponse;
import com.baiyue.common.core.api.PageResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@FeignClient(name="order-service", contextId="lastlegOrderPreDepartureClient", path="/internal/order/v1/predeparture-orders")
public interface OrderPreDepartureClient {
@GetMapping
ApiResponse<PageResult<Row>> page(@RequestHeader("X-Internal-Service-Token") String token,
@RequestParam(required=false) Long customerId, @RequestParam(required=false) String orderNo,
@RequestParam(required=false) String inboundCode, @RequestParam int page, @RequestParam int size);
record Row(Long orderId, Long customerId, String orderNo, String inboundCode, String mainStatus,
String customerName, String lxCode, String receivingWarehouseName, String destinationWarehouseName,
String destinationCode, Integer packageCount, BigDecimal volumeCbm, BigDecimal grossWeightKg,
BigDecimal netWeightKg, LocalDateTime expectedArrivalAt, String containerNo, String cabinetNo,
LocalDateTime estimatedSailingAt, LocalDateTime actualSailingAt, LocalDateTime createdAt, LocalDateTime updatedAt) {}
}
@@ -19,6 +19,7 @@ import com.baiyue.lastleg.entity.LastlegOrderOperationLog;
import com.baiyue.lastleg.entity.LastlegOrderManualNode;
import com.baiyue.lastleg.entity.ReceivingWarehouseOperation;
import com.baiyue.lastleg.entity.ContainerArrivalTimeOperation;
import com.baiyue.lastleg.client.OrderPreDepartureClient;
import com.baiyue.lastleg.inboundplan.pda.gateway.WarehouseResourceGateway;
import com.baiyue.lastleg.inboundplan.entity.InboundPlanCarton;
import com.baiyue.lastleg.inboundplan.mapper.InboundPlanCartonMapper;
@@ -42,6 +43,7 @@ import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.server.ResponseStatusException;
import java.nio.charset.StandardCharsets;
@@ -87,12 +89,23 @@ public class LastlegOrderService {
@Resource private ContainerShippingGateMapper containerShippingGates;
@Resource private ReturnOrderReferenceResolver returnOrderReferences;
@Resource private com.baiyue.lastleg.transfer.service.TransferQuantityService transferQuantities;
@Resource private OrderPreDepartureClient orderPreDepartureClient;
@Value("${baiyue.security.internal-service-token:}") private String internalServiceToken;
public PageResult<LastlegOrderSummary> page(String customerId, LastlegOrderRequests.Query query) {
return page(customerId, query, null);
}
public PageResult<LastlegOrderSummary> page(String customerId, LastlegOrderRequests.Query query, String relabelNo) {
if (query.sailingStage() == LastlegOrderRequests.SailingStage.BEFORE_DEPARTURE) {
Long customer = parseLong(customerId);
var response = orderPreDepartureClient.page(internalServiceToken, customer, query.orderNo(), query.inboundCode(),
query.page().intValue(), query.size().intValue());
if (response == null || response.data() == null) throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE, "订单服务暂时不可用");
List<LastlegOrderSummary> rows = response.data().records().stream().map(this::preDepartureSummary).toList();
return new PageResult<>(rows, response.data().page(), response.data().size(), response.data().total(), response.data().pages());
}
if (query.sailingStage() == null && relabelNo == null) return aggregateAllPage(customerId, query);
Page<LastlegOrder> page = orderMapper.selectPage(
new Page<>(query.page(), query.size()), filter(customerId, query, relabelNo).orderByDesc(LastlegOrder::getCreatedAt).orderByDesc(LastlegOrder::getId));
Map<Long, List<LastlegOrderMapper.PalletMergeRelationRow>> mergeRelations = palletMergeRelations(page.getRecords());
@@ -112,6 +125,53 @@ public class LastlegOrderService {
page.getCurrent(), page.getSize(), page.getTotal(), page.getPages());
}
/** 全部页签合并订单服务的未开船头程数据与本服务已有尾程单,尾程记录优先覆盖同一来源订单。 */
private PageResult<LastlegOrderSummary> aggregateAllPage(String customerId, LastlegOrderRequests.Query query) {
var source = orderPreDepartureClient.page(internalServiceToken, parseLong(customerId), query.orderNo(), query.inboundCode(), 1, 2000);
if (source == null || source.data() == null) throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE, "订单服务暂时不可用");
List<LastlegOrder> existing = orderMapper.selectList(filter(customerId, query, null)
.orderByDesc(LastlegOrder::getCreatedAt).orderByDesc(LastlegOrder::getId));
Map<String, String> existingNodes = containerNodes(existing);
Map<Long, LastlegOrderMapper.OrderWeightRow> existingWeights = orderWeights(existing);
Map<String, LastlegOrderSummary> merged = new LinkedHashMap<>();
source.data().records().forEach(row -> {
var summary = preDepartureSummary(row);
merged.put(orderKey(summary.customerId(), summary.sourceOrderNo()), summary);
});
for (LastlegOrder order : existing) {
String key = orderKey(order.getCustomerId(), StringUtils.hasText(order.getSourceOrderNo()) ? order.getSourceOrderNo() : order.getOrderNo());
merged.put(key, summary(order, List.of(), StringUtils.hasText(order.getSourceContainerBookingId())
? existingNodes.get(order.getSourceContainerBookingId()) : null, "UNSCHEDULED",
existingWeights.get(order.getId()), List.of(), null, null));
}
List<LastlegOrderSummary> rows = merged.values().stream().sorted(java.util.Comparator.comparing(LastlegOrderSummary::createdAt,
java.util.Comparator.nullsLast(java.util.Comparator.reverseOrder()))).toList();
int from = Math.min((query.page().intValue() - 1) * query.size().intValue(), rows.size());
int to = Math.min(from + query.size().intValue(), rows.size());
long total = rows.size();
return new PageResult<>(rows.subList(from, to), query.page(), query.size(), total,
total == 0 ? 0 : (total + query.size() - 1) / query.size());
}
private static String orderKey(String customerId, String orderNo) {
return String.valueOf(customerId) + ":" + String.valueOf(orderNo);
}
/** 将订单服务权威头程摘要投影为只读尾程列表行;尚未生成尾程单的字段保持为空。 */
private LastlegOrderSummary preDepartureSummary(OrderPreDepartureClient.Row row) {
LastlegOrder order = new LastlegOrder();
// 头程聚合行尚未生成尾程单,尾程订单号必须保持为空;来源头程订单号单独保留。
order.setId(row.orderId()); order.setOrderNo(null); order.setOrderType("FIRST_LEG");
order.setInboundCode(row.inboundCode()); order.setLxCode(row.lxCode()); order.setSourceOrderNo(row.orderNo());
order.setCustomerId(row.customerId() == null ? null : String.valueOf(row.customerId())); order.setCustomerName(row.customerName());
order.setSourceWarehouseName(row.receivingWarehouseName()); order.setOverseasWarehouseName(row.receivingWarehouseName());
order.setFbaWarehouseCode(row.destinationCode());
order.setContainerNo(row.containerNo()); order.setPackageCount(row.packageCount()); order.setVolumeCbm(row.volumeCbm());
order.setEstimatedArrivalAt(row.expectedArrivalAt()); order.setCurrentStatus(row.mainStatus());
order.setCreatedAt(row.createdAt()); order.setUpdatedAt(row.updatedAt()); order.setVersion(0);
return summary(order, List.of(), null, "UNSCHEDULED", null, List.of(), null, null);
}
@Transactional(readOnly = true)
public PageResult<ContainerPlanProgressSummary> containerProgressPage(
LastlegOrderRequests.ContainerProgressQuery query) {
@@ -0,0 +1,56 @@
package com.baiyue.order.controller;
import com.baiyue.common.core.api.ApiResponse;
import com.baiyue.common.core.api.PageResult;
import com.baiyue.order.dto.PreDepartureOrderDtos;
import com.baiyue.order.dto.UnifiedOrderQuery;
import com.baiyue.order.dto.UnifiedOrderResponse;
import com.baiyue.order.service.UnifiedOrderQueryService;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;
/** Service-only aggregate query. It intentionally has no platform-user permission. */
@RestController
@RequestMapping("/internal/order/v1/predeparture-orders")
public class InternalPreDepartureOrderController {
@Resource private UnifiedOrderQueryService service;
@Value("${baiyue.security.internal-service-token}") private String expectedToken;
@GetMapping
public ApiResponse<PageResult<PreDepartureOrderDtos.Row>> page(
@RequestHeader(value="X-Internal-Service-Token", required=false) String token,
@RequestParam(required=false) Long customerId, @RequestParam(required=false) String orderNo,
@RequestParam(required=false) String inboundCode, @RequestParam(defaultValue="1") int page,
@RequestParam(defaultValue="200") int size) {
if (expectedToken == null || expectedToken.isBlank() || token == null || !java.security.MessageDigest.isEqual(expectedToken.getBytes(StandardCharsets.UTF_8), token.getBytes(StandardCharsets.UTF_8))) throw new org.springframework.web.server.ResponseStatusException(org.springframework.http.HttpStatus.FORBIDDEN);
if (page < 1 || size < 1 || size > 2000) throw new org.springframework.web.server.ResponseStatusException(org.springframework.http.HttpStatus.BAD_REQUEST);
Map<Long, UnifiedOrderResponse.Row> all = new LinkedHashMap<>();
// 由统一订单服务一次返回全部履约状态,再按实际开船事实筛选。
for (long sourcePage = 1; ; sourcePage++) {
UnifiedOrderQuery q = new UnifiedOrderQuery(null, orderNo, "PRE_DEPARTURE", customerId, null, null,
inboundCode, null, null, null, null, null, null, null, null, null, null, null,
null, null, sourcePage, 200, null);
var result = service.platformPage(customerId, q);
result.records().forEach(row -> { if (row.transport() == null || row.transport().actualSailingAt() == null) all.putIfAbsent(row.orderId(), row); });
if (sourcePage >= result.pages()) break;
}
List<PreDepartureOrderDtos.Row> rows = all.values().stream().sorted(Comparator.comparing(UnifiedOrderResponse.Row::createdAt, Comparator.nullsLast(Comparator.reverseOrder())))
.map(this::row).toList();
int from = Math.min((page - 1) * size, rows.size());
int to = Math.min(from + size, rows.size());
long total = rows.size(), pages = total == 0 ? 0 : (total + size - 1) / size;
return ApiResponse.success(new PageResult<>(rows.subList(from, to), page, size, total, pages));
}
private PreDepartureOrderDtos.Row row(UnifiedOrderResponse.Row r) {
var t = r.transport();
return new PreDepartureOrderDtos.Row(r.orderId(), r.customerId(), r.orderNo(), r.inboundCode(), r.mainStatus(),
r.customerName(), r.lxCode(), r.receivingWarehouseName(), r.destinationWarehouseName(), r.destinationCode(),
r.totalPackageCount(), r.actualVolumeCbm() == null ? r.expectedVolumeCbm() : r.actualVolumeCbm(),
r.actualGrossWeightKg(), r.totalNetWeightKg(), r.expectedArrivalAt(), t == null ? null : t.containerNo(),
t == null ? null : t.cabinetNo(), t == null ? null : t.estimatedSailingAt(), t == null ? null : t.actualSailingAt(), r.createdAt(), r.updatedAt());
}
}
@@ -0,0 +1,17 @@
package com.baiyue.order.dto;
import java.time.LocalDateTime;
/** Internal read model used by lastleg to show orders before an actual sailing fact exists. */
public final class PreDepartureOrderDtos {
private PreDepartureOrderDtos() {}
public record Query(Long customerId, String orderNo, String inboundCode, int page, int size) {}
public record Row(Long orderId, Long customerId, String orderNo, String inboundCode,
String mainStatus, String customerName, String lxCode,
String receivingWarehouseName, String destinationWarehouseName,
String destinationCode, Integer packageCount, java.math.BigDecimal volumeCbm,
java.math.BigDecimal grossWeightKg, java.math.BigDecimal netWeightKg,
LocalDateTime expectedArrivalAt, String containerNo, String cabinetNo,
LocalDateTime estimatedSailingAt, LocalDateTime actualSailingAt,
LocalDateTime createdAt, LocalDateTime updatedAt) {}
}
@@ -148,7 +148,7 @@ public class UnifiedOrderQueryService {
/** 状态投影和当前页摘要共用原筛选,候选订单用于服务内批量收敛范围。 */
private FirstlegUnifiedOrderClient.Query statusRequest(Long tenantId, UnifiedOrderQuery query, List<Long> candidates) {
String status = Set.of("PENDING_REVIEW", "COMPLETED").contains(
String status = Set.of("PENDING_REVIEW", "COMPLETED", "PRE_DEPARTURE").contains(
query.mainStatus() == null ? "" : query.mainStatus()) ? null : query.mainStatus();
return new FirstlegUnifiedOrderClient.Query(tenantId, query.keyword(), query.orderNo(), status,
query.customerId(), query.receivingWarehouseId(), query.destinationWarehouseId(), query.inboundCode(),
@@ -261,7 +261,7 @@ public class UnifiedOrderQueryService {
private PageResult<UnifiedOrderResponse.Row> pageFromFirstleg(Long tenantId, UnifiedOrderQuery query,
boolean includeCoLoadGroups) {
List<FirstlegUnifiedOrderClient.StatusRow> matching = matchingStatusRows(tenantId, query).stream()
.filter(row -> !StringUtils.hasText(query.mainStatus()) || query.mainStatus().equals(row.mainStatus()))
.filter(row -> !StringUtils.hasText(query.mainStatus()) || "PRE_DEPARTURE".equals(query.mainStatus()) || query.mainStatus().equals(row.mainStatus()))
.collect(Collectors.collectingAndThen(Collectors.toMap(FirstlegUnifiedOrderClient.StatusRow::orderId,
Function.identity(), (left, right) -> left, java.util.LinkedHashMap::new),
values -> List.copyOf(values.values())));
@@ -682,7 +682,7 @@ public class UnifiedOrderQueryService {
if (query.taxMode() != null && !Set.of("TAX_REFUND", "BUY_DECLARATION").contains(query.taxMode())) {
throw new BizException(ResultCode.BAD_REQUEST, "taxMode仅支持TAX_REFUND或BUY_DECLARATION");
}
if (StringUtils.hasText(query.mainStatus())
if (StringUtils.hasText(query.mainStatus()) && !"PRE_DEPARTURE".equals(query.mainStatus())
&& !MAIN_STATUSES.contains(query.mainStatus())
&& !WORKFLOW_STATUSES.contains(query.mainStatus())
) {
@@ -708,7 +708,7 @@ public class UnifiedOrderQueryService {
}
private boolean requiresFirstlegQuery(UnifiedOrderQuery query) {
return (StringUtils.hasText(query.mainStatus())
return ("PRE_DEPARTURE".equals(query.mainStatus()) || StringUtils.hasText(query.mainStatus())
&& !Set.of("PENDING_REVIEW", "COMPLETED").contains(query.mainStatus()))
|| query.expectedArrivalDate() != null
|| StringUtils.hasText(query.feeReviewStatus()) || StringUtils.hasText(query.containerNo())