发布:将已验 TEST 功能更新至既有生产灰度(2026-10-23) #30
@@ -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) {
|
||||
|
||||
+56
@@ -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())
|
||||
|
||||
Reference in New Issue
Block a user