This commit is contained in:
2026-08-12 09:25:53 +08:00
parent b2af5739c8
commit 1cb4fba276
29 changed files with 1311 additions and 109 deletions
@@ -13,6 +13,7 @@ public final class UnifiedOrderResponse {
public record Row(
Long orderId, Long tenantId, String orderNo, String mainStatus, String nextAction,
OrderRelationSummary relation,
OrderStatus internalOrderStatus,
Long customerId, String customerName, String channelName,
Long receivingWarehouseId, String receivingWarehouseName,
@@ -26,6 +27,9 @@ public final class UnifiedOrderResponse {
LocalDateTime createdAt, LocalDateTime updatedAt
) {}
public record OrderRelationSummary(String role, Long rootOrderId, Long parentOrderId,
Integer splitSequence, Integer childCount) {}
public record Detail(Row summary, OrderDetailResponse order,
List<TimelineEvent> timeline) {}
@@ -226,6 +226,16 @@ public class OrderRepository {
.in(OrderEntity::getId, orderIds));
}
public List<OrderEntity> findChildrenByParentOrders(List<OrderEntity> parentOrders) {
if (parentOrders.isEmpty()) return List.of();
List<Long> tenantIds = parentOrders.stream().map(OrderEntity::getTenantId).distinct().toList();
List<Long> parentOrderIds = parentOrders.stream().map(OrderEntity::getId).distinct().toList();
return orderMapper.selectList(new LambdaQueryWrapper<OrderEntity>()
.select(OrderEntity::getId, OrderEntity::getTenantId, OrderEntity::getParentOrderId)
.in(OrderEntity::getTenantId, tenantIds)
.in(OrderEntity::getParentOrderId, parentOrderIds));
}
public List<OrderInternalNoteEntity> findNotesByOrderIds(Long tenantId, List<Long> orderIds) {
if (orderIds.isEmpty()) return List.of();
return noteMapper.selectList(new LambdaQueryWrapper<OrderInternalNoteEntity>()
@@ -57,8 +57,9 @@ public class UnifiedOrderQueryService {
? Map.of()
: summaries(tenantId, source.records().stream().map(OrderEntity::getId).toList());
Map<Long, String> latestNotes = latestNotes(tenantId, source.records().stream().map(OrderEntity::getId).toList());
Map<Long, Integer> childCounts = childCounts(source.records());
List<UnifiedOrderResponse.Row> rows = source.records().stream()
.map(order -> row(order, inboundByOrder.get(order.getId()), latestNotes.get(order.getId()))).toList();
.map(order -> row(order, inboundByOrder.get(order.getId()), latestNotes.get(order.getId()), childCounts)).toList();
return new PageResult<>(rows, source.page(), source.size(), source.total(), source.pages());
}
@@ -71,7 +72,8 @@ public class UnifiedOrderQueryService {
FirstlegUnifiedOrderClient.Detail inbound = order.getStatus() == OrderStatus.SUBMITTED
? null : firstlegDetail(tenantId, orderId);
String latestNote = orderDetail.internalNotes().isEmpty() ? null : orderDetail.internalNotes().get(0).getContent();
UnifiedOrderResponse.Row summary = row(order, inbound == null ? null : inbound.summary(), latestNote);
UnifiedOrderResponse.Row summary = row(order, inbound == null ? null : inbound.summary(), latestNote,
childCounts(List.of(order)));
List<UnifiedOrderResponse.TimelineEvent> timeline = new ArrayList<>();
orderDetail.statusHistory().forEach(item -> timeline.add(new UnifiedOrderResponse.TimelineEvent(
"ORDER", "STATUS", orderMainStatus(item.getToStatus()), item.getAction(), item.getReason(),
@@ -105,8 +107,10 @@ public class UnifiedOrderQueryService {
Map<Long, OrderEntity> orderById = orders.findByIds(tenantId, orderIds).stream()
.collect(Collectors.toMap(OrderEntity::getId, Function.identity(), (left, right) -> left));
Map<Long, String> latestNotes = latestNotes(tenantId, orderIds);
Map<Long, Integer> childCounts = childCounts(List.copyOf(orderById.values()));
List<UnifiedOrderResponse.Row> records = source.records().stream()
.map(summary -> row(summary, orderById.get(summary.inbound().orderId()), latestNotes.get(summary.inbound().orderId())))
.map(summary -> row(summary, orderById.get(summary.inbound().orderId()),
latestNotes.get(summary.inbound().orderId()), childCounts))
.toList();
return new PageResult<>(records, source.page(), source.size(), source.total(), source.pages());
}
@@ -131,11 +135,33 @@ public class UnifiedOrderQueryService {
OrderInternalNoteEntity::getOrderId, OrderInternalNoteEntity::getContent, (latest, ignored) -> latest));
}
private UnifiedOrderResponse.Row row(OrderEntity order, FirstlegUnifiedOrderClient.Summary summary, String latestInternalNote) {
private Map<Long, Integer> childCounts(List<OrderEntity> pageOrders) {
if (pageOrders.isEmpty()) return Map.of();
return orders.findChildrenByParentOrders(pageOrders).stream()
.filter(child -> child.getParentOrderId() != null)
.collect(Collectors.toMap(OrderEntity::getParentOrderId, ignored -> 1, Integer::sum));
}
private UnifiedOrderResponse.OrderRelationSummary relation(OrderEntity order,
Map<Long, Integer> childCounts) {
if (order == null) return null;
if (order.getParentOrderId() != null) {
Long rootOrderId = order.getRootOrderId() == null ? order.getParentOrderId() : order.getRootOrderId();
return new UnifiedOrderResponse.OrderRelationSummary(
"CHILD", rootOrderId, order.getParentOrderId(), order.getSplitSequence(), 0);
}
int childCount = childCounts.getOrDefault(order.getId(), 0);
return childCount == 0 ? null : new UnifiedOrderResponse.OrderRelationSummary(
"PARENT", order.getId(), null, null, childCount);
}
private UnifiedOrderResponse.Row row(OrderEntity order, FirstlegUnifiedOrderClient.Summary summary,
String latestInternalNote, Map<Long, Integer> childCounts) {
if (summary == null) {
String mainStatus = orderMainStatus(order.getStatus());
return new UnifiedOrderResponse.Row(order.getId(), order.getTenantId(), order.getOrderNo(), mainStatus,
order.getStatus() == OrderStatus.SUBMITTED ? "APPROVE_ORDER" : null,
relation(order, childCounts),
order.getStatus(), order.getCustomerId(), order.getCustomerName(),
order.getChannelName(), order.getReceivingWarehouseId(), order.getReceivingWarehouseName(),
order.getDestinationWarehouseId(), order.getDestinationWarehouseName(), null, null,
@@ -146,7 +172,8 @@ public class UnifiedOrderQueryService {
}
FirstlegUnifiedOrderClient.Inbound inbound = summary.inbound();
return new UnifiedOrderResponse.Row(order.getId(), order.getTenantId(), order.getOrderNo(),
summary.mainStatus(), summary.nextAction(), order.getStatus(), order.getCustomerId(), order.getCustomerName(),
summary.mainStatus(), summary.nextAction(), relation(order, childCounts), order.getStatus(),
order.getCustomerId(), order.getCustomerName(),
order.getChannelName(), order.getReceivingWarehouseId(), order.getReceivingWarehouseName(),
order.getDestinationWarehouseId(), order.getDestinationWarehouseName(), inbound.id(), inbound.version(), inbound.inboundCode(),
order.getExpectedShipDate(), inbound.totalPackageCount(), "STANDARD", inbound.locationCodes(),
@@ -157,10 +184,11 @@ public class UnifiedOrderQueryService {
}
private UnifiedOrderResponse.Row row(FirstlegUnifiedOrderClient.Summary summary, OrderEntity order,
String latestInternalNote) {
String latestInternalNote, Map<Long, Integer> childCounts) {
var inbound = summary.inbound();
return new UnifiedOrderResponse.Row(inbound.orderId(), inbound.tenantId(), inbound.orderNo(),
summary.mainStatus(), summary.nextAction(), parseOrderStatus(inbound.orderStatusSnapshot()),
summary.mainStatus(), summary.nextAction(), relation(order, childCounts),
parseOrderStatus(inbound.orderStatusSnapshot()),
inbound.customerId(), inbound.customerName(), inbound.channelName(), inbound.warehouseId(),
inbound.warehouseName(), inbound.deliveryWarehouseId(), inbound.deliveryWarehouseName(),
inbound.id(), inbound.version(), inbound.inboundCode(), null, inbound.totalPackageCount(),
@@ -16,7 +16,8 @@ paths:
才是 C04 loading/complete 的 expectedVersion,loadingVersion 当前等于 pickingVersion(兼容别名)。
tenantId 省略时跨全部客户;mainStatus 省略时按订单创建时间分页,指定 PENDING_REVIEW 时由 Order 查询,
指定其他主状态时由 Firstleg 精确分页。旧 /orders/first-leg 接口保持兼容。
列表行同时返回 customerRemark(客户下单备注)和 latestInternalNote(最新一条平台内部备注)。
列表行同时返回 customerRemark(客户下单备注)、latestInternalNote(最新一条平台内部备注)和
relation(仅有关联时返回;PARENT 含 childCount,CHILD 含 rootOrderId、parentOrderId 与 splitSequence)。
parameters:
- {name: tenantId, in: query, required: false, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
- $ref: '#/components/parameters/UnifiedKeyword'
@@ -41,6 +41,7 @@ class UnifiedOrderQueryServiceTest {
void stubListEnrichmentQueries() {
when(orders.findNotesByOrderIds(any(), any())).thenReturn(List.of());
when(orders.findByIds(any(), any())).thenReturn(List.of());
when(orders.findChildrenByParentOrders(any())).thenReturn(List.of());
}
@Test
@@ -67,6 +68,7 @@ class UnifiedOrderQueryServiceTest {
assertThat(row.inboundId()).isNull();
assertThat(row.customerRemark()).isEqualTo("客户要求提货前联系");
assertThat(row.latestInternalNote()).isEqualTo("客服已确认提货时间");
assertThat(row.relation()).isNull();
});
verify(firstleg, never()).summaries(any(), any());
}
@@ -87,6 +89,35 @@ class UnifiedOrderQueryServiceTest {
verify(firstleg, never()).summaries(any(), any());
}
@Test
void addsRelationSummaryOnlyForOrdersWithParentChildAssociations() {
UnifiedOrderQuery query = query("PENDING_REVIEW");
OrderEntity parent = order(OrderStatus.SUBMITTED);
OrderEntity child = order(OrderStatus.SUBMITTED);
child.setId(78L);
child.setOrderNo("FL-77-1");
child.setOrderRole("CHILD");
child.setParentOrderId(77L);
child.setRootOrderId(77L);
child.setSplitSequence(1);
when(orders.unifiedPage(eq(10001L), eq(query), eq(List.of(OrderStatus.SUBMITTED))))
.thenReturn(new PageResult<>(List.of(parent, child), 1, 20, 2, 1));
when(orders.findChildrenByParentOrders(List.of(parent, child))).thenReturn(List.of(child));
var result = service.page(10001L, query);
assertThat(result.records()).satisfiesExactly(
row -> assertThat(row.relation()).satisfies(relation -> {
assertThat(relation.role()).isEqualTo("PARENT");
assertThat(relation.childCount()).isEqualTo(1);
}),
row -> assertThat(row.relation()).satisfies(relation -> {
assertThat(relation.role()).isEqualTo("CHILD");
assertThat(relation.rootOrderId()).isEqualTo(77L);
assertThat(relation.splitSequence()).isEqualTo(1);
}));
}
@Test
void allStatusInboundCodeFilterIncludesPendingOrdersFromOrderQuery() {
UnifiedOrderQuery query = new UnifiedOrderQuery(