优化
This commit is contained in:
@@ -22,7 +22,7 @@ public interface FirstlegUnifiedOrderClient {
|
||||
|
||||
record WorkbenchFilterRequest(long tenantId, String mainStatus) {}
|
||||
record WorkbenchFilters(List<Long> matchingStatusOrderIds, List<Long> feeOrderIds,
|
||||
List<Long> customsOrderIds) {}
|
||||
List<Long> customsOrderIds, List<com.baiyue.order.dto.MerchantOrderProgress> progress) {}
|
||||
|
||||
@PostMapping("/query")
|
||||
ApiResponse<PageResult<Summary>> page(@RequestHeader("X-Internal-Service-Token") String token,
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.baiyue.order.client;
|
||||
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.order.dto.MerchantOrderProgress;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import java.util.List;
|
||||
|
||||
@FeignClient(name="lastleg-service", contextId="orderLastlegProgressClient",
|
||||
url="${baiyue.clients.lastleg.url:}", path="/internal/lastleg/v1/customer-order-progress")
|
||||
public interface LastlegOrderProgressClient {
|
||||
@PostMapping
|
||||
ApiResponse<Response> read(@RequestHeader("X-Internal-Service-Token") String token, @RequestBody Request request);
|
||||
record Request(long customerId) {}
|
||||
record Response(List<MerchantOrderProgress> progress) {}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.baiyue.order.dto;
|
||||
|
||||
/** Customer progress is a read projection, not another writable order state machine. */
|
||||
public record MerchantOrderProgress(long orderId, String stage, boolean partial) {
|
||||
public static final java.util.List<String> STAGES = java.util.List.of("none", "loaded", "declared", "cleared", "scheduled", "delivering", "delivered");
|
||||
public int rank() {
|
||||
int rank = STAGES.indexOf(stage);
|
||||
if (rank < 0 || orderId <= 0) throw new IllegalArgumentException("Invalid order progress");
|
||||
return rank;
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ public record MerchantOrderWorkbenchQuery(
|
||||
@Size(max=128) String trackingNumber,
|
||||
@Pattern(regexp="DRAFT|PENDING_REVIEW|WAIT_MEASURE|WAIT_PALLETIZE|WAIT_SPLIT|LOADING|LOADED|DELIVERED|COMPLETED|CANCELLED") String mainStatus,
|
||||
LocalDateTime createdFrom, LocalDateTime createdTo,
|
||||
@Pattern(regexp="all|fee|customs|draft") String queue,
|
||||
@Pattern(regexp="all|fee|customs|draft|loaded|declared|cleared|scheduled|delivering|delivered") String queue,
|
||||
@Min(1) long page, @Min(1) @Max(200) long size) {
|
||||
public MerchantOrderWorkbenchQuery {
|
||||
if (page==0) page=1;
|
||||
|
||||
@@ -4,6 +4,14 @@ import com.baiyue.common.core.api.PageResult;
|
||||
import java.util.List;
|
||||
|
||||
public record MerchantOrderWorkbenchResponse(PageResult<UnifiedOrderResponse.Row> page,
|
||||
Counts queues, List<String> channelNames) {
|
||||
public record Counts(long all, long fee, long customs, long draft) {}
|
||||
Counts queues, List<String> channelNames, List<MerchantOrderProgress> progress) {
|
||||
public record Counts(long all, long fee, long customs, long draft, long loaded, long declared,
|
||||
long cleared, long scheduled, long delivering, long delivered) {
|
||||
public long forQueue(String queue) {
|
||||
return switch(queue) { case "fee" -> fee; case "customs" -> customs; case "draft" -> draft;
|
||||
case "loaded" -> loaded; case "declared" -> declared; case "cleared" -> cleared;
|
||||
case "scheduled" -> scheduled; case "delivering" -> delivering; case "delivered" -> delivered;
|
||||
case "all" -> all; default -> throw new IllegalArgumentException("Invalid queue"); };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+16
-3
@@ -21,14 +21,18 @@ public class MerchantOrderWorkbenchRepository {
|
||||
|
||||
@Transactional(readOnly=true)
|
||||
public Result read(long customerId, MerchantOrderWorkbenchQuery q, List<Long> statusIds,
|
||||
List<Long> feeIds, List<Long> customsIds) {
|
||||
List<Long> feeIds, List<Long> customsIds, java.util.Map<String,List<Long>> progressIds) {
|
||||
var base=base(customerId,q,statusIds);
|
||||
long all=mapper.selectCount(base.clone());
|
||||
long fee=mapper.selectCount(ids(base.clone(),feeIds));
|
||||
long customs=mapper.selectCount(ids(base.clone(),customsIds));
|
||||
long draft=mapper.selectCount(base.clone().eq("status","DRAFT"));
|
||||
var counts=new Counts(all,fee,customs,draft);
|
||||
long total=switch(q.queue()) {case "fee"->fee;case "customs"->customs;case "draft"->draft;default->all;};
|
||||
var counts=new Counts(all,fee,customs,draft,
|
||||
progressCount(base,progressIds,"loaded"), progressCount(base,progressIds,"declared"),
|
||||
progressCount(base,progressIds,"cleared"), progressCount(base,progressIds,"scheduled"),
|
||||
progressCount(base,progressIds,"delivering"), progressCount(base,progressIds,"delivered"));
|
||||
long total=counts.forQueue(q.queue());
|
||||
if (progressIds.containsKey(q.queue())) ids(base,progressIds.get(q.queue()));
|
||||
if ("fee".equals(q.queue())) ids(base,feeIds);
|
||||
if ("customs".equals(q.queue())) ids(base,customsIds);
|
||||
if ("draft".equals(q.queue())) base.eq("status","DRAFT");
|
||||
@@ -43,6 +47,15 @@ public class MerchantOrderWorkbenchRepository {
|
||||
total/q.size()+(total%q.size()==0?0:1)),counts,channels);
|
||||
}
|
||||
|
||||
public List<OrderEntity> progressScope(long customerId) {
|
||||
return mapper.selectList(new QueryWrapper<OrderEntity>().select("id","root_order_id","status")
|
||||
.eq("tenant_id",customerId).ne("status","CANCELLED"));
|
||||
}
|
||||
|
||||
private long progressCount(QueryWrapper<OrderEntity> base,java.util.Map<String,List<Long>> progressIds,String stage) {
|
||||
return mapper.selectCount(ids(base.clone(),progressIds.get(stage)));
|
||||
}
|
||||
|
||||
private QueryWrapper<OrderEntity> base(long customerId, MerchantOrderWorkbenchQuery q, List<Long> statusIds) {
|
||||
var w=new QueryWrapper<OrderEntity>().eq("tenant_id",customerId);
|
||||
String direct=switch(q.mainStatus()==null?"":q.mainStatus()) {
|
||||
|
||||
@@ -4,40 +4,109 @@ import com.baiyue.common.core.api.PageResult;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.order.client.FirstlegUnifiedOrderClient;
|
||||
import com.baiyue.order.client.LastlegOrderProgressClient;
|
||||
import com.baiyue.order.dto.MerchantOrderProgress;
|
||||
import com.baiyue.order.dto.MerchantOrderWorkbenchQuery;
|
||||
import com.baiyue.order.dto.MerchantOrderWorkbenchResponse;
|
||||
import com.baiyue.order.entity.OrderEntity;
|
||||
import com.baiyue.order.entity.OrderStatus;
|
||||
import com.baiyue.order.repository.MerchantOrderWorkbenchRepository;
|
||||
import jakarta.annotation.Resource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.util.Set;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
public class MerchantOrderWorkbenchService {
|
||||
@Resource private MerchantOrderWorkbenchRepository repository;
|
||||
@Resource private FirstlegUnifiedOrderClient firstleg;
|
||||
@Resource private UnifiedOrderQueryService unified;
|
||||
@Resource private LastlegOrderProgressClient lastleg;
|
||||
@Value("${baiyue.security.internal-service-token}") private String internalToken;
|
||||
private static final Set<String> DIRECT=Set.of("DRAFT","PENDING_REVIEW","COMPLETED","CANCELLED");
|
||||
|
||||
@Transactional(readOnly=true)
|
||||
public MerchantOrderWorkbenchResponse page(long customerId,MerchantOrderWorkbenchQuery query) {
|
||||
if(query.createdFrom()!=null&&query.createdTo()!=null&&query.createdFrom().isAfter(query.createdTo()))
|
||||
throw new BizException(ResultCode.BAD_REQUEST,"创建时间起止范围不合法");
|
||||
String status=query.mainStatus()==null||DIRECT.contains(query.mainStatus())?null:query.mainStatus();
|
||||
FirstlegUnifiedOrderClient.WorkbenchFilters filters;
|
||||
List<MerchantOrderProgress> tailProgress;
|
||||
try {
|
||||
var response=firstleg.workbenchFilters(internalToken,
|
||||
new FirstlegUnifiedOrderClient.WorkbenchFilterRequest(customerId,status));
|
||||
if(response==null||!response.successful()||response.data()==null) throw new IllegalStateException();
|
||||
filters=response.data();
|
||||
if(filters.feeOrderIds()==null||filters.customsOrderIds()==null
|
||||
if(filters.feeOrderIds()==null||filters.customsOrderIds()==null||filters.progress()==null
|
||||
||status!=null&&filters.matchingStatusOrderIds()==null) throw new IllegalStateException();
|
||||
var tail=lastleg.read(internalToken,new LastlegOrderProgressClient.Request(customerId));
|
||||
if(tail==null||!tail.successful()||tail.data()==null||tail.data().progress()==null) throw new IllegalStateException();
|
||||
tailProgress=tail.data().progress();
|
||||
validateFacts(filters.progress(),1,3);
|
||||
validateFacts(tailProgress,4,5);
|
||||
} catch(RuntimeException failure) {
|
||||
throw new BizException(ResultCode.SERVICE_UNAVAILABLE,"订单待办查询暂时不可用,请重试");
|
||||
}
|
||||
var result=repository.read(customerId,query,filters.matchingStatusOrderIds(),filters.feeOrderIds(),filters.customsOrderIds());
|
||||
var progress=project(repository.progressScope(customerId),filters.progress(),tailProgress);
|
||||
var progressIds=new LinkedHashMap<String,List<Long>>();
|
||||
for(String stage:MerchantOrderProgress.STAGES.subList(1,7))
|
||||
progressIds.put(stage,progress.values().stream().filter(p->stage.equals(p.stage())).map(MerchantOrderProgress::orderId).toList());
|
||||
var result=repository.read(customerId,query,filters.matchingStatusOrderIds(),filters.feeOrderIds(),filters.customsOrderIds(),progressIds);
|
||||
var source=result.page();
|
||||
return new MerchantOrderWorkbenchResponse(new PageResult<>(unified.merchantWorkbenchRows(customerId,source.records()),
|
||||
source.page(),source.size(),source.total(),source.pages()),result.queues(),result.channelNames());
|
||||
source.page(),source.size(),source.total(),source.pages()),result.queues(),result.channelNames(),
|
||||
source.records().stream().map(o->progress.getOrDefault(o.getId(),new MerchantOrderProgress(o.getId(),"none",false))).toList());
|
||||
}
|
||||
|
||||
private static void validateFacts(List<MerchantOrderProgress> facts, int min, int max) {
|
||||
var seen=new java.util.HashSet<Long>();
|
||||
for(var fact:facts) if(fact==null||fact.rank()<min||fact.rank()>max||!seen.add(fact.orderId()))
|
||||
throw new IllegalStateException("Invalid progress response");
|
||||
}
|
||||
|
||||
static Map<Long,MerchantOrderProgress> project(
|
||||
List<OrderEntity> scope,
|
||||
List<MerchantOrderProgress> firstleg,
|
||||
List<MerchantOrderProgress> lastleg) {
|
||||
var scopeById=scope.stream().collect(Collectors.toMap(OrderEntity::getId,o->o));
|
||||
var facts=new HashMap<Long,MerchantOrderProgress>();
|
||||
Stream.concat(firstleg.stream(),lastleg.stream()).forEach(p->
|
||||
facts.merge(p.orderId(),p,(a,b)->a.rank()>b.rank()?a:b));
|
||||
var result=new LinkedHashMap<Long,MerchantOrderProgress>();
|
||||
for(var order:scope) {
|
||||
var status=order.getStatus();
|
||||
var fact=status==OrderStatus.COMPLETED
|
||||
? new MerchantOrderProgress(order.getId(),"delivered",false)
|
||||
: status==OrderStatus.DRAFT||status==OrderStatus.SUBMITTED
|
||||
? new MerchantOrderProgress(order.getId(),"none",false)
|
||||
: facts.getOrDefault(order.getId(),new MerchantOrderProgress(order.getId(),"none",false));
|
||||
result.put(order.getId(),fact);
|
||||
}
|
||||
var children=scope.stream().filter(o->o.getRootOrderId()!=null && !o.getRootOrderId().equals(o.getId()))
|
||||
.collect(Collectors.groupingBy(OrderEntity::getRootOrderId));
|
||||
children.forEach((root,orders)->{
|
||||
var own=result.get(root);
|
||||
if(own==null||"delivered".equals(own.stage())) return;
|
||||
var rootOrder=scopeById.get(root);
|
||||
if(rootOrder.getStatus()==OrderStatus.DRAFT||rootOrder.getStatus()==OrderStatus.SUBMITTED) return;
|
||||
var fragments=new ArrayList<MerchantOrderProgress>();
|
||||
fragments.add(own); orders.forEach(o->fragments.add(result.get(o.getId())));
|
||||
// Completion remains exclusively owned by Order's trusted root completion gate.
|
||||
int max=fragments.stream().mapToInt(MerchantOrderProgress::rank).max().orElse(0);
|
||||
if(max==6) max=5;
|
||||
final int rank=max;
|
||||
boolean partial=fragments.stream().anyMatch(p->p.rank()<rank||p.partial());
|
||||
result.put(root,new MerchantOrderProgress(root,
|
||||
MerchantOrderProgress.STAGES.get(rank),partial));
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ paths:
|
||||
summary: 客户订单工作台分页和筛选后待办数量
|
||||
description: >-
|
||||
仅按登录客户查询,权限merchant:order:list。草稿优先,再按创建时间和订单ID降序稳定分页。
|
||||
入仓码/SKU采用或匹配;所有条件先筛选再计数。四类数量共用当前筛选,不从当前页行数计算。
|
||||
入仓码/SKU采用或匹配;所有条件先筛选再计数。十类数量共用当前筛选,不从当前页行数计算。
|
||||
默认包含草稿和有效正式单;履约子状态以Firstleg为准,待审核和已完成以Order为准。
|
||||
security: [{bearerAuth: []}]
|
||||
parameters:
|
||||
@@ -23,7 +23,7 @@ paths:
|
||||
- {name: mainStatus, in: query, schema: {type: string, enum: [DRAFT, PENDING_REVIEW, WAIT_MEASURE, WAIT_PALLETIZE, WAIT_SPLIT, LOADING, LOADED, DELIVERED, COMPLETED, CANCELLED]}}
|
||||
- {name: createdFrom, in: query, schema: {type: string, format: date-time}}
|
||||
- {name: createdTo, in: query, schema: {type: string, format: date-time}}
|
||||
- {name: queue, in: query, schema: {type: string, enum: [all, fee, customs, draft], default: all}}
|
||||
- {name: queue, in: query, schema: {type: string, enum: [all, fee, customs, draft, loaded, declared, cleared, scheduled, delivering, delivered], default: all}}
|
||||
- {name: page, in: query, schema: {type: integer, minimum: 1, default: 1}}
|
||||
- {name: size, in: query, schema: {type: integer, minimum: 1, maximum: 200, default: 10}}
|
||||
responses:
|
||||
@@ -37,18 +37,35 @@ paths:
|
||||
code: {type: string}
|
||||
data:
|
||||
type: object
|
||||
required: [page, queues, channelNames]
|
||||
required: [page, queues, channelNames, progress]
|
||||
properties:
|
||||
page: {$ref: '#/components/schemas/UnifiedOrderPage'}
|
||||
queues:
|
||||
type: object
|
||||
required: [all, fee, customs, draft]
|
||||
required: [all, fee, customs, draft, loaded, declared, cleared, scheduled, delivering, delivered]
|
||||
properties:
|
||||
all: {type: integer, format: int64, minimum: 0}
|
||||
fee: {type: integer, format: int64, minimum: 0}
|
||||
customs: {type: integer, format: int64, minimum: 0}
|
||||
draft: {type: integer, format: int64, minimum: 0}
|
||||
loaded: {type: integer, format: int64, minimum: 0}
|
||||
declared: {type: integer, format: int64, minimum: 0}
|
||||
cleared: {type: integer, format: int64, minimum: 0}
|
||||
scheduled: {type: integer, format: int64, minimum: 0}
|
||||
delivering: {type: integer, format: int64, minimum: 0}
|
||||
delivered: {type: integer, format: int64, minimum: 0}
|
||||
channelNames: {type: array, items: {type: string}, description: 当前客户完整渠道筛选项,不限于当前页}
|
||||
progress:
|
||||
type: array
|
||||
description: 与当前页订单一一对应的客户履约进度;分类互斥,待办可重叠。分片未齐时partial为true,完成只认Order可信回写。
|
||||
items:
|
||||
type: object
|
||||
required: [orderId, stage, partial]
|
||||
properties:
|
||||
orderId: {type: string, pattern: '^[1-9][0-9]*$'}
|
||||
stage: {type: string, enum: [none, loaded, declared, cleared, scheduled, delivering, delivered]}
|
||||
partial: {type: boolean}
|
||||
|
||||
/internal/order/v1/fulfillment-completions:
|
||||
post:
|
||||
security: []
|
||||
|
||||
+30
-12
@@ -68,11 +68,16 @@ class MerchantOrderWorkbenchRepositoryTest {
|
||||
private MerchantOrderWorkbenchQuery query(String keyword,String queue,long page) {
|
||||
return new MerchantOrderWorkbenchQuery(keyword,null,null,null,null,null,null,null,queue,page,10);
|
||||
}
|
||||
private java.util.Map<String,List<Long>> progressIds() {
|
||||
var map=new java.util.LinkedHashMap<String,List<Long>>();
|
||||
for(String key:List.of("loaded","declared","cleared","scheduled","delivering","delivered")) map.put(key,List.of());
|
||||
return map;
|
||||
}
|
||||
@Test void readsBeyondTheOldTwoHundredLimitWithStableDraftPrefixAndCorrectTotals() {
|
||||
var first=repository.read(11,query(null,"all",1),null,List.of(210L),List.of(211L));
|
||||
var first=repository.read(11,query(null,"all",1),null,List.of(210L),List.of(211L), progressIds());
|
||||
assertThat(first.page().total()).isEqualTo(212);
|
||||
assertThat(first.page().records().subList(0,3)).extracting(OrderEntity::getId).containsExactly(3L,2L,1L);
|
||||
var last=repository.read(11,query(null,"all",22),null,List.of(210L),List.of(211L));
|
||||
var last=repository.read(11,query(null,"all",22),null,List.of(210L),List.of(211L), progressIds());
|
||||
assertThat(last.page().records()).extracting(OrderEntity::getId).containsExactly(5L,4L);
|
||||
assertThat(last.page().pages()).isEqualTo(22);
|
||||
assertThat(last.queues().all()).isEqualTo(212);
|
||||
@@ -80,31 +85,44 @@ class MerchantOrderWorkbenchRepositoryTest {
|
||||
assertThat(last.queues().fee()).isEqualTo(1);
|
||||
assertThat(last.queues().customs()).isEqualTo(1);
|
||||
assertThat(last.channelNames()).containsExactly("AIR","SEA");
|
||||
assertThat(repository.read(11,query(null,"all",23),null,List.of(),List.of()).page().records()).isEmpty();
|
||||
assertThat(repository.read(11,query(null,"all",23),null,List.of(),List.of(), progressIds()).page().records()).isEmpty();
|
||||
}
|
||||
@Test void filtersBeforeCountingAndPagingAndNeverUsesForeignSkuOrQueueIds() {
|
||||
var sku=repository.read(11,query("SPECIAL","all",1),null,List.of(210L,214L),List.of(215L));
|
||||
var sku=repository.read(11,query("SPECIAL","all",1),null,List.of(210L,214L),List.of(215L), progressIds());
|
||||
assertThat(sku.page().total()).isEqualTo(1);
|
||||
assertThat(sku.page().records()).extracting(OrderEntity::getId).containsExactly(210L);
|
||||
assertThat(sku.queues().fee()).isEqualTo(1);assertThat(sku.queues().customs()).isZero();
|
||||
var fee=repository.read(11,query(null,"fee",1),null,List.of(210L,214L),List.of());
|
||||
var fee=repository.read(11,query(null,"fee",1),null,List.of(210L,214L),List.of(), progressIds());
|
||||
assertThat(fee.page().records()).extracting(OrderEntity::getId).containsExactly(210L);
|
||||
assertThat(repository.read(11,query(null,"fee",1),null,List.of(),List.of()).page().records()).isEmpty();
|
||||
var drafts=repository.read(11,query(null,"draft",1),null,List.of(),List.of());
|
||||
assertThat(repository.read(11,query(null,"fee",1),null,List.of(),List.of(), progressIds()).page().records()).isEmpty();
|
||||
var drafts=repository.read(11,query(null,"draft",1),null,List.of(),List.of(), progressIds());
|
||||
assertThat(drafts.page().total()).isEqualTo(3);
|
||||
}
|
||||
@Test void combinesWarehouseChannelDestinationTrackingDatesAndStatusBeforePaging() {
|
||||
var from=java.time.LocalDateTime.of(2026,10,2,0,0);
|
||||
var to=from.plusDays(1).minusNanos(1);
|
||||
var q=new MerchantOrderWorkbenchQuery("IN-210",10L,"SEA","FBA","TRACK-210","LOADED",from,to,"all",1,10);
|
||||
assertThat(repository.read(11,q,List.of(210L,214L),List.of(),List.of()).page().records())
|
||||
assertThat(repository.read(11,q,List.of(210L,214L),List.of(),List.of(), progressIds()).page().records())
|
||||
.extracting(OrderEntity::getId).containsExactly(210L);
|
||||
assertThat(repository.read(11,q,List.of(),List.of(),List.of()).page().total()).isZero();
|
||||
assertThat(repository.read(11,q,List.of(),List.of(),List.of(), progressIds()).page().total()).isZero();
|
||||
jdbc.update("UPDATE ord_order SET status='SUBMITTED' WHERE id=210");
|
||||
assertThat(repository.read(11,q,List.of(210L),List.of(),List.of()).page().total()).isZero();
|
||||
assertThat(repository.read(11,q,List.of(210L),List.of(),List.of(), progressIds()).page().total()).isZero();
|
||||
var pending=new MerchantOrderWorkbenchQuery(null,null,null,null,null,"PENDING_REVIEW",null,null,"all",1,10);
|
||||
assertThat(repository.read(11,pending,null,List.of(),List.of()).page().records()).extracting(OrderEntity::getId).containsExactly(210L);
|
||||
assertThat(repository.read(11,pending,null,List.of(),List.of(), progressIds()).page().records()).extracting(OrderEntity::getId).containsExactly(210L);
|
||||
var outside=new MerchantOrderWorkbenchQuery(null,null,null,null,null,null,to.plusDays(1),null,"all",1,10);
|
||||
assertThat(repository.read(11,outside,null,List.of(),List.of()).page().total()).isZero();
|
||||
assertThat(repository.read(11,outside,null,List.of(),List.of(), progressIds()).page().total()).isZero();
|
||||
}
|
||||
|
||||
@Test void progressQueuesShareFiltersAndCountBeforePagination() {
|
||||
var ids=progressIds();ids.put("cleared",List.of(210L,211L,214L));ids.put("delivering",List.of(212L));
|
||||
var result=repository.read(11,query(null,"cleared",1),null,List.of(210L),List.of(),ids);
|
||||
assertThat(result.page().total()).isEqualTo(2);
|
||||
assertThat(result.queues().cleared()).isEqualTo(2);
|
||||
assertThat(result.queues().delivering()).isEqualTo(1);
|
||||
assertThat(result.page().records()).extracting(OrderEntity::getId).containsExactly(211L,210L);
|
||||
var filtered=repository.read(11,query("SPECIAL","cleared",1),null,List.of(210L),List.of(),ids);
|
||||
assertThat(filtered.page().total()).isEqualTo(1);
|
||||
assertThat(filtered.queues().fee()).isEqualTo(1);
|
||||
assertThat(repository.progressScope(11)).hasSize(212);
|
||||
}
|
||||
}
|
||||
|
||||
+35
-6
@@ -19,11 +19,14 @@ import static org.mockito.ArgumentMatchers.*;
|
||||
class MerchantOrderWorkbenchServiceTest {
|
||||
private final FirstlegUnifiedOrderClient firstleg = mock(FirstlegUnifiedOrderClient.class);
|
||||
private final MerchantOrderWorkbenchRepository repository = mock(MerchantOrderWorkbenchRepository.class);
|
||||
private final com.baiyue.order.client.LastlegOrderProgressClient lastleg = mock(com.baiyue.order.client.LastlegOrderProgressClient.class);
|
||||
private final UnifiedOrderQueryService unified = mock(UnifiedOrderQueryService.class);
|
||||
private final MerchantOrderWorkbenchService service = new MerchantOrderWorkbenchService();
|
||||
|
||||
@BeforeEach void setup() {
|
||||
ReflectionTestUtils.setField(service, "firstleg", firstleg);
|
||||
ReflectionTestUtils.setField(service, "lastleg", lastleg);
|
||||
when(lastleg.read(anyString(),any())).thenReturn(ApiResponse.success(new com.baiyue.order.client.LastlegOrderProgressClient.Response(List.of())));
|
||||
ReflectionTestUtils.setField(service, "repository", repository);
|
||||
ReflectionTestUtils.setField(service, "unified", unified);
|
||||
ReflectionTestUtils.setField(service, "internalToken", "test-service-token");
|
||||
@@ -33,10 +36,10 @@ class MerchantOrderWorkbenchServiceTest {
|
||||
}
|
||||
@Test void combinesScopedFiltersWithDatabasePageWithoutReplacingTotals() {
|
||||
var q = query("LOADED");
|
||||
var filters = new FirstlegUnifiedOrderClient.WorkbenchFilters(List.of(1L),List.of(2L),List.of(3L));
|
||||
var filters = new FirstlegUnifiedOrderClient.WorkbenchFilters(List.of(1L),List.of(2L),List.of(3L), List.of());
|
||||
when(firstleg.workbenchFilters(anyString(),any())).thenReturn(ApiResponse.success(filters));
|
||||
when(repository.read(11,q,filters.matchingStatusOrderIds(),filters.feeOrderIds(),filters.customsOrderIds()))
|
||||
.thenReturn(new MerchantOrderWorkbenchRepository.Result(new PageResult<>(List.of(),22,10,210,21),new Counts(210,2,1,0),List.of("SEA")));
|
||||
when(repository.read(eq(11L),eq(q),eq(filters.matchingStatusOrderIds()),eq(filters.feeOrderIds()),eq(filters.customsOrderIds()),anyMap()))
|
||||
.thenReturn(new MerchantOrderWorkbenchRepository.Result(new PageResult<>(List.of(),22,10,210,21),new Counts(210,2,1,0,0,0,0,0,0,0),List.of("SEA")));
|
||||
when(unified.merchantWorkbenchRows(11,List.of())).thenReturn(List.of());
|
||||
var result = service.page(11,q);
|
||||
assertThat(result.page().total()).isEqualTo(210);
|
||||
@@ -47,9 +50,10 @@ class MerchantOrderWorkbenchServiceTest {
|
||||
}
|
||||
@Test void unavailableOrIncompleteFulfillmentFiltersCannotProduceMisleadingEmptyQueues() {
|
||||
for(var filters : List.of(
|
||||
new FirstlegUnifiedOrderClient.WorkbenchFilters(null,List.of(),List.of()),
|
||||
new FirstlegUnifiedOrderClient.WorkbenchFilters(List.of(),null,List.of()),
|
||||
new FirstlegUnifiedOrderClient.WorkbenchFilters(List.of(),List.of(),null))) {
|
||||
new FirstlegUnifiedOrderClient.WorkbenchFilters(null,List.of(),List.of(), List.of()),
|
||||
new FirstlegUnifiedOrderClient.WorkbenchFilters(List.of(),null,List.of(), List.of()),
|
||||
new FirstlegUnifiedOrderClient.WorkbenchFilters(List.of(),List.of(),null, List.of()),
|
||||
new FirstlegUnifiedOrderClient.WorkbenchFilters(List.of(),List.of(),List.of(),null))) {
|
||||
when(firstleg.workbenchFilters(anyString(),any())).thenReturn(ApiResponse.success(filters));
|
||||
assertThatThrownBy(()->service.page(11,query("LOADED"))).isInstanceOf(BizException.class).hasMessageContaining("暂时不可用");
|
||||
}
|
||||
@@ -63,4 +67,29 @@ class MerchantOrderWorkbenchServiceTest {
|
||||
assertThatThrownBy(()->service.page(11,q)).isInstanceOf(BizException.class).hasMessageContaining("时间");
|
||||
verifyNoInteractions(firstleg,repository,unified);
|
||||
}
|
||||
|
||||
@Test void tailFailureDoesNotReturnPartialQueueCounts() {
|
||||
when(firstleg.workbenchFilters(anyString(),any())).thenReturn(ApiResponse.success(
|
||||
new FirstlegUnifiedOrderClient.WorkbenchFilters(null,List.of(),List.of(),List.of())));
|
||||
when(lastleg.read(anyString(),any())).thenThrow(new IllegalStateException());
|
||||
assertThatThrownBy(()->service.page(11,query(null))).isInstanceOf(BizException.class);
|
||||
verifyNoInteractions(repository,unified);
|
||||
}
|
||||
private com.baiyue.order.entity.OrderEntity order(long id,Long root,com.baiyue.order.entity.OrderStatus status) {
|
||||
var o=new com.baiyue.order.entity.OrderEntity();o.setId(id);o.setRootOrderId(root);o.setStatus(status);return o;
|
||||
}
|
||||
@Test void currentStageIsExclusiveAndRootRequiresEveryFragmentWithTrustedCompletion() {
|
||||
var status=com.baiyue.order.entity.OrderStatus.APPROVED;
|
||||
var scope=List.of(order(1L,1L,status),order(2L,1L,status),order(3L,null,com.baiyue.order.entity.OrderStatus.COMPLETED),
|
||||
order(4L,null,com.baiyue.order.entity.OrderStatus.SUBMITTED));
|
||||
var first=List.of(new com.baiyue.order.dto.MerchantOrderProgress(1L,"cleared",false),
|
||||
new com.baiyue.order.dto.MerchantOrderProgress(2L,"loaded",false));
|
||||
var tail=List.of(new com.baiyue.order.dto.MerchantOrderProgress(2L,"delivering",false),
|
||||
new com.baiyue.order.dto.MerchantOrderProgress(4L,"scheduled",false),
|
||||
new com.baiyue.order.dto.MerchantOrderProgress(999L,"delivering",false));
|
||||
var projected=MerchantOrderWorkbenchService.project(scope,first,tail);
|
||||
assertThat(projected.get(1L).stage()).isEqualTo("delivering");assertThat(projected.get(1L).partial()).isTrue();
|
||||
assertThat(projected.get(2L).partial()).isFalse();assertThat(projected.get(3L).stage()).isEqualTo("delivered");
|
||||
assertThat(projected.get(4L).stage()).isEqualTo("none");assertThat(projected).doesNotContainKey(999L);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user