Merge remote-tracking branch 'origin/test' into dev

# Conflicts:
#	order/AGENTS.md
This commit is contained in:
2026-09-04 12:29:57 +08:00
87 changed files with 3134 additions and 720 deletions
@@ -23,6 +23,7 @@ public final class PendingReviewOrderDtos {
@Size(max = 40) String inboundCode,
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate createdStart,
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate createdEnd,
List<@Min(1) Long> excludedOrderIds,
@Min(1) long page,
@Min(1) @Max(200) long size
) {
@@ -217,6 +217,7 @@ public class OrderRepository {
Long customerId, String inboundCode,
LocalDateTime createdFrom,
LocalDateTime createdBefore,
List<Long> excludedOrderIds,
long page, long size) {
LambdaQueryWrapper<OrderEntity> wrapper = new LambdaQueryWrapper<OrderEntity>()
.eq(OrderEntity::getDeleted, false)
@@ -227,6 +228,8 @@ public class OrderRepository {
.eq(StringUtils.hasText(inboundCode), OrderEntity::getInboundCode, inboundCode)
.ge(createdFrom != null, OrderEntity::getCreatedAt, createdFrom)
.lt(createdBefore != null, OrderEntity::getCreatedAt, createdBefore)
.notIn(excludedOrderIds != null && !excludedOrderIds.isEmpty(), OrderEntity::getId,
excludedOrderIds)
.orderByDesc(OrderEntity::getCreatedAt)
.orderByDesc(OrderEntity::getId);
return PageResult.from(orderMapper.selectPage(new Page<>(page, size), wrapper));
@@ -34,7 +34,8 @@ public class PendingReviewOrderQueryService {
LocalDateTime createdBefore = query.createdEnd() == null ? null
: query.createdEnd().plusDays(1).atStartOfDay(BUSINESS_ZONE).toLocalDateTime();
var source = orders.pendingReviewPage(channelCodes, query.warehouseId(), query.customerId(),
normalize(query.inboundCode()), createdFrom, createdBefore, query.page(), query.size());
normalize(query.inboundCode()), createdFrom, createdBefore, query.excludedOrderIds(),
query.page(), query.size());
List<Long> orderIds = source.records().stream().map(order -> order.getId()).toList();
Map<Long, Integer> packageCounts = orders.findCargoByOrderIds(null, orderIds).stream()
.collect(Collectors.groupingBy(OrderCargoEntity::getOrderId,
@@ -14,6 +14,7 @@ paths:
仅接受内部服务令牌。只返回 ord_order 中未删除且 status=SUBMITTED 的头程订单,
mainStatus 固定为 PENDING_REVIEW。createdStart/createdEnd 按 Asia/Shanghai 自然日闭区间过滤
ord_order.created_at,稳定按 created_at DESC、id DESC 分页;不读取或修复 Firstleg 投影。
excludedOrderIds 由 Firstleg 根据当前有效装柜计划提供,用于在数据库分页前排除已经占柜的订单。
parameters:
- name: X-Internal-Service-Token
in: header
@@ -25,6 +26,7 @@ paths:
- {name: inboundCode, in: query, required: false, schema: {type: string, maxLength: 40}}
- {name: createdStart, in: query, required: false, schema: {type: string, format: date}}
- {name: createdEnd, in: query, required: false, schema: {type: string, format: date}}
- {name: excludedOrderIds, in: query, required: false, style: form, explode: true, schema: {type: array, items: {type: string, pattern: '^[1-9][0-9]*$'}}}
- {name: page, in: query, required: true, schema: {type: integer, format: int64, minimum: 1}}
- {name: size, in: query, required: true, schema: {type: integer, format: int64, minimum: 1, maximum: 200}}
responses:
@@ -25,7 +25,8 @@ class InternalPendingReviewOrderControllerTest {
@Test
void acceptsOnlyTheConfiguredInternalServiceToken() {
var query = new PendingReviewOrderDtos.Query(List.of("SEA-US"), null, null, null, null, null, 1, 20);
var query = new PendingReviewOrderDtos.Query(List.of("SEA-US"), null, null, null, null, null,
List.of(101L), 1, 20);
var page = new PageResult<PendingReviewOrderDtos.Row>(List.of(), 1, 20, 0, 0);
when(service.page(query)).thenReturn(page);
@@ -16,6 +16,7 @@ class PendingReviewOrderOpenApiContractTest {
.contains("channelCodes")
.contains("createdStart")
.contains("createdEnd")
.contains("excludedOrderIds")
.contains("PendingReviewOrderRow:")
.contains("mainStatus: {type: string, enum: [PENDING_REVIEW]}")
.contains("orderId: {type: string, pattern: '^[1-9][0-9]*$'}")
@@ -38,7 +38,8 @@ class PendingReviewOrderRepositoryTest {
void buildsSubmittedOnlyStablePageWithEverySupportedFilter() throws Exception {
when(orderMapper.selectPage(any(Page.class), any(Wrapper.class))).thenReturn(new Page<>());
repository.pendingReviewPage(List.of("SEA-US", "AIR-US"), 11L, 22L, "RK-1",
LocalDateTime.of(2026, 9, 1, 0, 0), LocalDateTime.of(2026, 9, 4, 0, 0), 3, 40);
LocalDateTime.of(2026, 9, 1, 0, 0), LocalDateTime.of(2026, 9, 4, 0, 0),
List.of(101L, 102L), 3, 40);
ArgumentCaptor<Page<OrderEntity>> page = ArgumentCaptor.forClass(Page.class);
ArgumentCaptor<Wrapper<OrderEntity>> wrapper = ArgumentCaptor.forClass(Wrapper.class);
@@ -53,6 +54,7 @@ class PendingReviewOrderRepositoryTest {
.contains(".eq(OrderEntity::getStatus, OrderStatus.SUBMITTED)")
.contains(".in(channelCodes != null && !channelCodes.isEmpty(), OrderEntity::getChannelCode, channelCodes)")
.contains(".lt(createdBefore != null, OrderEntity::getCreatedAt, createdBefore)")
.contains(".notIn(excludedOrderIds != null && !excludedOrderIds.isEmpty(), OrderEntity::getId,")
.contains(".orderByDesc(OrderEntity::getCreatedAt)")
.contains(".orderByDesc(OrderEntity::getId)");
}
@@ -36,16 +36,16 @@ class PendingReviewOrderQueryServiceTest {
@Test
void convertsShanghaiNaturalDatesToClosedDayRangeAndNormalizesFilters() {
when(repository.pendingReviewPage(any(), any(), any(), any(), any(), any(), anyLong(), anyLong()))
when(repository.pendingReviewPage(any(), any(), any(), any(), any(), any(), any(), anyLong(), anyLong()))
.thenReturn(new PageResult<>(List.of(), 2, 20, 0, 0));
service.page(new PendingReviewOrderDtos.Query(List.of(" SEA-US ", "SEA-US"), 11L, 22L,
" RK-1 ", LocalDate.of(2026, 9, 1), LocalDate.of(2026, 9, 3), 2, 20));
" RK-1 ", LocalDate.of(2026, 9, 1), LocalDate.of(2026, 9, 3), List.of(101L), 2, 20));
@SuppressWarnings("unchecked") ArgumentCaptor<List<String>> channels = ArgumentCaptor.forClass(List.class);
verify(repository).pendingReviewPage(channels.capture(), eq(11L), eq(22L), eq("RK-1"),
eq(LocalDateTime.of(2026, 9, 1, 0, 0)),
eq(LocalDateTime.of(2026, 9, 4, 0, 0)), eq(2L), eq(20L));
eq(LocalDateTime.of(2026, 9, 4, 0, 0)), eq(List.of(101L)), eq(2L), eq(20L));
assertThat(channels.getValue()).containsExactly("SEA-US");
}
@@ -58,11 +58,12 @@ class PendingReviewOrderQueryServiceTest {
order.setCreatedAt(LocalDateTime.of(2026, 9, 3, 10, 30));
OrderCargoEntity first = cargo(101L, 2);
OrderCargoEntity second = cargo(101L, 3);
when(repository.pendingReviewPage(any(), any(), any(), any(), any(), any(), anyLong(), anyLong()))
when(repository.pendingReviewPage(any(), any(), any(), any(), any(), any(), any(), anyLong(), anyLong()))
.thenReturn(new PageResult<>(List.of(order), 1, 20, 1, 1));
when(repository.findCargoByOrderIds(null, List.of(101L))).thenReturn(List.of(first, second));
var result = service.page(new PendingReviewOrderDtos.Query(null, null, null, " ", null, null, 1, 20));
var result = service.page(new PendingReviewOrderDtos.Query(null, null, null, " ", null, null,
List.of(), 1, 20));
assertThat(result.records()).singleElement().satisfies(row -> {
assertThat(row.orderId()).isEqualTo(101L);
@@ -75,9 +76,9 @@ class PendingReviewOrderQueryServiceTest {
@Test
void rejectsReversedDatesAndBlankChannelValues() {
assertThatThrownBy(() -> service.page(new PendingReviewOrderDtos.Query(null, null, null, null,
LocalDate.of(2026, 9, 4), LocalDate.of(2026, 9, 3), 1, 20))).isInstanceOf(BizException.class);
LocalDate.of(2026, 9, 4), LocalDate.of(2026, 9, 3), List.of(), 1, 20))).isInstanceOf(BizException.class);
assertThatThrownBy(() -> service.page(new PendingReviewOrderDtos.Query(List.of(" "), null, null,
null, null, null, 1, 20))).isInstanceOf(BizException.class);
null, null, null, List.of(), 1, 20))).isInstanceOf(BizException.class);
}
private static OrderCargoEntity cargo(long orderId, int packageCount) {