优化
This commit is contained in:
@@ -23,6 +23,8 @@ public final class PendingReviewOrderDtos {
|
||||
@Size(max = 40) String inboundCode,
|
||||
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate createdStart,
|
||||
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate createdEnd,
|
||||
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate expectedArrivalStart,
|
||||
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate expectedArrivalEnd,
|
||||
List<@Min(1) Long> excludedOrderIds,
|
||||
@Min(1) long page,
|
||||
@Min(1) @Max(200) long size
|
||||
@@ -31,6 +33,12 @@ public final class PendingReviewOrderDtos {
|
||||
public boolean isCreatedRangeValid() {
|
||||
return createdStart == null || createdEnd == null || !createdStart.isAfter(createdEnd);
|
||||
}
|
||||
|
||||
@AssertTrue(message = "预计到货日期起始日不能晚于结束日")
|
||||
public boolean isExpectedArrivalRangeValid() {
|
||||
return expectedArrivalStart == null || expectedArrivalEnd == null
|
||||
|| !expectedArrivalStart.isAfter(expectedArrivalEnd);
|
||||
}
|
||||
}
|
||||
|
||||
public record Row(
|
||||
@@ -44,6 +52,7 @@ public final class PendingReviewOrderDtos {
|
||||
String channelName,
|
||||
int totalPackageCount,
|
||||
LocalDateTime createdAt,
|
||||
LocalDateTime expectedArrivalAt,
|
||||
String mainStatus
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -217,6 +217,8 @@ public class OrderRepository {
|
||||
Long customerId, String inboundCode,
|
||||
LocalDateTime createdFrom,
|
||||
LocalDateTime createdBefore,
|
||||
LocalDateTime expectedArrivalFrom,
|
||||
LocalDateTime expectedArrivalBefore,
|
||||
List<Long> excludedOrderIds,
|
||||
long page, long size) {
|
||||
LambdaQueryWrapper<OrderEntity> wrapper = new LambdaQueryWrapper<OrderEntity>()
|
||||
@@ -228,6 +230,8 @@ public class OrderRepository {
|
||||
.eq(StringUtils.hasText(inboundCode), OrderEntity::getInboundCode, inboundCode)
|
||||
.ge(createdFrom != null, OrderEntity::getCreatedAt, createdFrom)
|
||||
.lt(createdBefore != null, OrderEntity::getCreatedAt, createdBefore)
|
||||
.ge(expectedArrivalFrom != null, OrderEntity::getExpectedArrivalAt, expectedArrivalFrom)
|
||||
.lt(expectedArrivalBefore != null, OrderEntity::getExpectedArrivalAt, expectedArrivalBefore)
|
||||
.notIn(excludedOrderIds != null && !excludedOrderIds.isEmpty(), OrderEntity::getId,
|
||||
excludedOrderIds)
|
||||
.orderByDesc(OrderEntity::getCreatedAt)
|
||||
|
||||
@@ -25,7 +25,7 @@ public class PendingReviewOrderQueryService {
|
||||
|
||||
public PageResult<PendingReviewOrderDtos.Row> page(PendingReviewOrderDtos.Query query) {
|
||||
if (query == null || query.page() < 1 || query.size() < 1 || query.size() > 200
|
||||
|| !query.isCreatedRangeValid()) {
|
||||
|| !query.isCreatedRangeValid() || !query.isExpectedArrivalRangeValid()) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "待审核订单查询参数不正确");
|
||||
}
|
||||
List<String> channelCodes = normalizeChannelCodes(query.channelCodes());
|
||||
@@ -33,9 +33,13 @@ public class PendingReviewOrderQueryService {
|
||||
: query.createdStart().atStartOfDay(BUSINESS_ZONE).toLocalDateTime();
|
||||
LocalDateTime createdBefore = query.createdEnd() == null ? null
|
||||
: query.createdEnd().plusDays(1).atStartOfDay(BUSINESS_ZONE).toLocalDateTime();
|
||||
LocalDateTime expectedArrivalFrom = query.expectedArrivalStart() == null ? null
|
||||
: query.expectedArrivalStart().atStartOfDay(BUSINESS_ZONE).toLocalDateTime();
|
||||
LocalDateTime expectedArrivalBefore = query.expectedArrivalEnd() == null ? null
|
||||
: query.expectedArrivalEnd().plusDays(1).atStartOfDay(BUSINESS_ZONE).toLocalDateTime();
|
||||
var source = orders.pendingReviewPage(channelCodes, query.warehouseId(), query.customerId(),
|
||||
normalize(query.inboundCode()), createdFrom, createdBefore, query.excludedOrderIds(),
|
||||
query.page(), query.size());
|
||||
normalize(query.inboundCode()), createdFrom, createdBefore, expectedArrivalFrom,
|
||||
expectedArrivalBefore, 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,
|
||||
@@ -44,7 +48,7 @@ public class PendingReviewOrderQueryService {
|
||||
order.getId(), order.getOrderNo(), order.getInboundCode(), order.getCustomerId(),
|
||||
order.getCustomerName(), order.getReceivingWarehouseId(), order.getChannelCode(),
|
||||
order.getChannelName(), packageCounts.getOrDefault(order.getId(), 0), order.getCreatedAt(),
|
||||
"PENDING_REVIEW")).toList();
|
||||
order.getExpectedArrivalAt(), "PENDING_REVIEW")).toList();
|
||||
return new PageResult<>(rows, source.page(), source.size(), source.total(), source.pages());
|
||||
}
|
||||
|
||||
|
||||
@@ -12,8 +12,9 @@ paths:
|
||||
summary: Firstleg 分页读取 Order 权威待审核订单
|
||||
description: >-
|
||||
仅接受内部服务令牌。只返回 ord_order 中未删除且 status=SUBMITTED 的头程订单,
|
||||
mainStatus 固定为 PENDING_REVIEW。createdStart/createdEnd 按 Asia/Shanghai 自然日闭区间过滤
|
||||
ord_order.created_at,稳定按 created_at DESC、id DESC 分页;不读取或修复 Firstleg 投影。
|
||||
mainStatus 固定为 PENDING_REVIEW。createdStart/createdEnd 和 expectedArrivalStart/expectedArrivalEnd
|
||||
均按 Asia/Shanghai 自然日闭区间分别过滤 ord_order.created_at 与 expected_arrival_at;
|
||||
稳定按 created_at DESC、id DESC 分页;不读取或修复 Firstleg 投影。
|
||||
excludedOrderIds 由 Firstleg 根据当前有效装柜计划提供,用于在数据库分页前排除已经占柜的订单。
|
||||
parameters:
|
||||
- name: X-Internal-Service-Token
|
||||
@@ -26,6 +27,8 @@ 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: expectedArrivalStart, in: query, required: false, schema: {type: string, format: date}}
|
||||
- {name: expectedArrivalEnd, 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}}
|
||||
@@ -1047,7 +1050,7 @@ components:
|
||||
pages: {type: integer, format: int64, minimum: 0}
|
||||
PendingReviewOrderRow:
|
||||
type: object
|
||||
required: [orderId, orderNo, inboundCode, customerId, customerName, warehouseId, channelCode, channelName, totalPackageCount, createdAt, mainStatus]
|
||||
required: [orderId, orderNo, inboundCode, customerId, customerName, warehouseId, channelCode, channelName, totalPackageCount, createdAt, expectedArrivalAt, mainStatus]
|
||||
properties:
|
||||
orderId: {type: string, pattern: '^[1-9][0-9]*$'}
|
||||
orderNo: {type: string}
|
||||
@@ -1059,6 +1062,7 @@ components:
|
||||
channelName: {type: string}
|
||||
totalPackageCount: {type: integer, minimum: 0}
|
||||
createdAt: {type: string, format: date-time}
|
||||
expectedArrivalAt: {type: string, format: date-time, nullable: true}
|
||||
mainStatus: {type: string, enum: [PENDING_REVIEW]}
|
||||
FirstLegBatchDraftResponse:
|
||||
type: object
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ class InternalPendingReviewOrderControllerTest {
|
||||
@Test
|
||||
void acceptsOnlyTheConfiguredInternalServiceToken() {
|
||||
var query = new PendingReviewOrderDtos.Query(List.of("SEA-US"), null, null, null, null, null,
|
||||
List.of(101L), 1, 20);
|
||||
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);
|
||||
|
||||
|
||||
+3
@@ -16,8 +16,11 @@ class PendingReviewOrderOpenApiContractTest {
|
||||
.contains("channelCodes")
|
||||
.contains("createdStart")
|
||||
.contains("createdEnd")
|
||||
.contains("expectedArrivalStart")
|
||||
.contains("expectedArrivalEnd")
|
||||
.contains("excludedOrderIds")
|
||||
.contains("PendingReviewOrderRow:")
|
||||
.contains("expectedArrivalAt: {type: string, format: date-time, nullable: true}")
|
||||
.contains("mainStatus: {type: string, enum: [PENDING_REVIEW]}")
|
||||
.contains("orderId: {type: string, pattern: '^[1-9][0-9]*$'}")
|
||||
.contains("warehouseId: {type: string, pattern: '^[1-9][0-9]*$'}");
|
||||
|
||||
@@ -39,6 +39,7 @@ class PendingReviewOrderRepositoryTest {
|
||||
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),
|
||||
LocalDateTime.of(2026, 9, 5, 0, 0), LocalDateTime.of(2026, 9, 8, 0, 0),
|
||||
List.of(101L, 102L), 3, 40);
|
||||
|
||||
ArgumentCaptor<Page<OrderEntity>> page = ArgumentCaptor.forClass(Page.class);
|
||||
@@ -54,6 +55,8 @@ 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(".ge(expectedArrivalFrom != null, OrderEntity::getExpectedArrivalAt, expectedArrivalFrom)")
|
||||
.contains(".lt(expectedArrivalBefore != null, OrderEntity::getExpectedArrivalAt, expectedArrivalBefore)")
|
||||
.contains(".notIn(excludedOrderIds != null && !excludedOrderIds.isEmpty(), OrderEntity::getId,")
|
||||
.contains(".orderByDesc(OrderEntity::getCreatedAt)")
|
||||
.contains(".orderByDesc(OrderEntity::getId)");
|
||||
|
||||
+16
-7
@@ -36,16 +36,19 @@ class PendingReviewOrderQueryServiceTest {
|
||||
|
||||
@Test
|
||||
void convertsShanghaiNaturalDatesToClosedDayRangeAndNormalizesFilters() {
|
||||
when(repository.pendingReviewPage(any(), any(), any(), any(), any(), any(), any(), anyLong(), anyLong()))
|
||||
when(repository.pendingReviewPage(any(), any(), 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), List.of(101L), 2, 20));
|
||||
" RK-1 ", LocalDate.of(2026, 9, 1), LocalDate.of(2026, 9, 3),
|
||||
LocalDate.of(2026, 9, 5), LocalDate.of(2026, 9, 7), 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(List.of(101L)), eq(2L), eq(20L));
|
||||
eq(LocalDateTime.of(2026, 9, 4, 0, 0)),
|
||||
eq(LocalDateTime.of(2026, 9, 5, 0, 0)),
|
||||
eq(LocalDateTime.of(2026, 9, 8, 0, 0)), eq(List.of(101L)), eq(2L), eq(20L));
|
||||
assertThat(channels.getValue()).containsExactly("SEA-US");
|
||||
}
|
||||
|
||||
@@ -56,19 +59,21 @@ class PendingReviewOrderQueryServiceTest {
|
||||
order.setCustomerId(22L); order.setCustomerName("测试客户"); order.setReceivingWarehouseId(33L);
|
||||
order.setChannelCode("SEA-US"); order.setChannelName("美线");
|
||||
order.setCreatedAt(LocalDateTime.of(2026, 9, 3, 10, 30));
|
||||
order.setExpectedArrivalAt(LocalDateTime.of(2026, 9, 8, 15, 0));
|
||||
OrderCargoEntity first = cargo(101L, 2);
|
||||
OrderCargoEntity second = cargo(101L, 3);
|
||||
when(repository.pendingReviewPage(any(), any(), any(), any(), any(), any(), any(), anyLong(), anyLong()))
|
||||
when(repository.pendingReviewPage(any(), any(), 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,
|
||||
List.of(), 1, 20));
|
||||
null, null, List.of(), 1, 20));
|
||||
|
||||
assertThat(result.records()).singleElement().satisfies(row -> {
|
||||
assertThat(row.orderId()).isEqualTo(101L);
|
||||
assertThat(row.warehouseId()).isEqualTo(33L);
|
||||
assertThat(row.totalPackageCount()).isEqualTo(5);
|
||||
assertThat(row.expectedArrivalAt()).isEqualTo(LocalDateTime.of(2026, 9, 8, 15, 0));
|
||||
assertThat(row.mainStatus()).isEqualTo("PENDING_REVIEW");
|
||||
});
|
||||
}
|
||||
@@ -76,9 +81,13 @@ 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), List.of(), 1, 20))).isInstanceOf(BizException.class);
|
||||
LocalDate.of(2026, 9, 4), LocalDate.of(2026, 9, 3), null, null,
|
||||
List.of(), 1, 20))).isInstanceOf(BizException.class);
|
||||
assertThatThrownBy(() -> service.page(new PendingReviewOrderDtos.Query(null, null, null, null,
|
||||
null, null, LocalDate.of(2026, 9, 9), LocalDate.of(2026, 9, 8),
|
||||
List.of(), 1, 20))).isInstanceOf(BizException.class);
|
||||
assertThatThrownBy(() -> service.page(new PendingReviewOrderDtos.Query(List.of(" "), null, null,
|
||||
null, null, null, List.of(), 1, 20))).isInstanceOf(BizException.class);
|
||||
null, null, null, null, null, List.of(), 1, 20))).isInstanceOf(BizException.class);
|
||||
}
|
||||
|
||||
private static OrderCargoEntity cargo(long orderId, int packageCount) {
|
||||
|
||||
Reference in New Issue
Block a user