提交
This commit is contained in:
@@ -27,7 +27,9 @@ public class WorkflowTabService {
|
||||
if(facts.stream().noneMatch(f->f.tenantId()==fee.tenantId() && f.orderId()==fee.orderId())) throw unavailable("费用确认事实不属于本批入仓来源");
|
||||
result.putIfAbsent(fee.orderId()+":"+fee.mainStatus(),fee);
|
||||
}
|
||||
var sources=facts.stream().filter(WorkflowFacts.Fact::departed).map(f->new WorkflowFacts.Source(f.tenantId(),f.inboundId())).distinct().toList();
|
||||
// 出库单由尾程审核建单触发,不能因国内开船节点尚未补录而跳过事实查询。
|
||||
var sources=facts.stream().map(f->new WorkflowFacts.Source(f.tenantId(),f.inboundId())).distinct().toList();
|
||||
var requiredSources=facts.stream().filter(WorkflowFacts.Fact::departed).map(f->new WorkflowFacts.Source(f.tenantId(),f.inboundId())).distinct().toList();
|
||||
Map<String,WorkflowFacts.Overseas> overseas=new HashMap<>();
|
||||
if(!sources.isEmpty()) {
|
||||
try {
|
||||
@@ -40,8 +42,10 @@ public class WorkflowTabService {
|
||||
if(fact==null || !allowed.contains(new WorkflowFacts.Source(fact.tenantId(),fact.inboundId()))) throw unavailable("尾程事实包含空记录或本批以外的客户/入仓来源");
|
||||
overseas.merge(fact.tenantId()+":"+fact.inboundId(),fact,(a,b)->new WorkflowFacts.Overseas(a.tenantId(),a.inboundId(),a.outboundExists()||b.outboundExists(),a.pendingOutbound()||b.pendingOutbound(),a.delivering()||b.delivering(),a.unloaded()&&b.unloaded()));
|
||||
}
|
||||
if(overseas.size()!=sources.size()) throw unavailable("尾程事实缺少来源 expected="+sources.size()+" actual="+overseas.size()
|
||||
+" missing="+sources.stream().filter(s->!overseas.containsKey(s.tenantId()+":"+s.inboundId())).limit(20).toList());
|
||||
// 未开船来源可能尚未产生尾程订单;已开船来源仍须完整返回,避免依赖缺项被误当作零。
|
||||
var missing=requiredSources.stream().filter(s->!overseas.containsKey(s.tenantId()+":"+s.inboundId())).toList();
|
||||
if(!missing.isEmpty()) throw unavailable("尾程事实缺少来源 expected="+requiredSources.size()+" actual="+(requiredSources.size()-missing.size())
|
||||
+" missing="+missing.stream().limit(20).toList());
|
||||
} catch(BizException e) { throw e; } catch(Exception e) {
|
||||
throw (BizException) new BizException(ResultCode.SERVICE_UNAVAILABLE,"订单业务页签事实暂不可用,请稍后重试").initCause(e);
|
||||
}
|
||||
@@ -61,6 +65,11 @@ public class WorkflowTabService {
|
||||
Set<String> keys=new LinkedHashSet<>();
|
||||
if("MEASURE_PENDING".equals(process) && "PENDING".equals(f.reviewStatus())) keys.add("MEASUREMENT_REVIEW");
|
||||
boolean signed="DELIVERED".equals(process);
|
||||
// 有效出库与派送事实独立于船期、清关资料;签收后不再保留待出库或派送任务。
|
||||
if(!signed && o!=null) {
|
||||
if(o.pendingOutbound()) keys.add("OUTBOUND_DOCUMENT");
|
||||
if(o.delivering()) keys.add("DELIVERING");
|
||||
}
|
||||
// 人工补录或船司查询的实际开船节点才推进阶段,报关完成和提前上传资料不能代替开船。
|
||||
boolean clearanceStage=f.departed();
|
||||
if(!clearanceStage && !signed) {
|
||||
@@ -75,8 +84,6 @@ public class WorkflowTabService {
|
||||
if(!o.outboundExists() && !o.unloaded()) keys.add("WAIT_UNLOAD");
|
||||
// 取消的出库不构成有效覆盖,读取事实后自动回显,不能保存永久隐藏标志。
|
||||
if(!o.outboundExists()) keys.add("WAIT_DISPATCH");
|
||||
if(o.pendingOutbound()) keys.add("OUTBOUND_DOCUMENT");
|
||||
if(o.delivering()) keys.add("DELIVERING");
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
|
||||
@@ -1679,7 +1679,7 @@ components:
|
||||
type: string
|
||||
enum: [PENDING_REVIEW, WAIT_MEASURE, WAIT_PALLETIZE, WAIT_SPLIT, LOADING, LOADED, DELIVERED, WAIT_WEIGHT_CONFIRM, WEIGHT_CONFIRMED, WAIT_CUSTOMS_DOCS, EXPORT_CUSTOMS, SAILING_CLEARANCE, WAIT_UNLOAD, WAIT_DISPATCH, OUTBOUND_DOCUMENT, DELIVERING, SIGNED, MEASUREMENT_REVIEW]
|
||||
nullable: true
|
||||
description: WAIT_SPLIT 为唯一待分柜筛选值;WAIT_CUSTOMS_DOCS 为实际装柜后缺少本票客户报关资料;EXPORT_CUSTOMS 按本票资料已上传且实际装柜完成汇总,买单无需客户退税资料,不依赖点击开始报关,报关完成或实际开船后移出;SAILING_CLEARANCE 在整柜报关完成后进入,也接受实际离港事实;清关资料保存成功后才进入 WAIT_UNLOAD/WAIT_DISPATCH。WAIT_UNLOAD 持续到实际卸柜或生成有效出库单;生成出库单后进入 OUTBOUND_DOCUMENT,实际出库后进入 DELIVERING,派送完成进入 SIGNED。
|
||||
description: WAIT_SPLIT 为唯一待分柜筛选值;WAIT_CUSTOMS_DOCS 为实际装柜后缺少本票客户报关资料;EXPORT_CUSTOMS 按本票资料已上传且实际装柜完成汇总,买单无需客户退税资料,不依赖点击开始报关,报关完成或实际开船后移出;SAILING_CLEARANCE 在整柜报关完成后进入,也接受实际离港事实;清关资料保存成功后才进入 WAIT_UNLOAD/WAIT_DISPATCH。WAIT_UNLOAD 持续到实际卸柜或生成有效出库单;尾程排车理货审核通过并生成有效出库单后进入 OUTBOUND_DOCUMENT,不以前置开船节点或清关资料为条件;取消或实际出库后移出,实际出库并发车后进入 DELIVERING,派送完成进入 SIGNED。
|
||||
customerId: {type: integer, format: int64, nullable: true}
|
||||
receivingWarehouseId: {type: integer, format: int64, nullable: true}
|
||||
destinationWarehouseId: {type: integer, format: int64, nullable: true}
|
||||
|
||||
@@ -36,7 +36,7 @@ class WorkflowBoundaryTest {
|
||||
assertThatThrownBy(()->service.rows(List.of(inbound))).isInstanceOf(BizException.class).hasCause(failure);
|
||||
}
|
||||
|
||||
/** 报关完成或提前上传资料仍留在出口报关,实际开船后才查询精确海外来源。 */
|
||||
/** 未开船也查询尾程建单事实,未产生尾程来源时仍保留国内阶段。 */
|
||||
@Test void completedCustomsAndClearanceUploadWaitForActualDeparture() {
|
||||
var repository=mock(WorkflowFactRepository.class);
|
||||
var downstream=mock(LastlegWorkflowClient.class);
|
||||
@@ -45,12 +45,15 @@ class WorkflowBoundaryTest {
|
||||
inbound.setOrderId(101L);inbound.setProcessStatus("LOADED");
|
||||
when(repository.facts(List.of(1L))).thenReturn(List.of(
|
||||
new WorkflowFacts.Fact(11,1,101,"APPROVED",false,false,false,true,true)));
|
||||
when(downstream.read(any(),any())).thenReturn(ApiResponse.success(List.of()));
|
||||
assertThat(service.rows(List.of(inbound))).extracting(r->r.mainStatus()).containsExactly("EXPORT_CUSTOMS");
|
||||
verifyNoInteractions(downstream);
|
||||
verify(downstream).read(any(),any());
|
||||
clearInvocations(downstream);
|
||||
when(repository.facts(List.of(1L))).thenReturn(List.of(
|
||||
new WorkflowFacts.Fact(11,1,101,"APPROVED",false,true,false,true,true)));
|
||||
assertThat(service.rows(List.of(inbound))).extracting(r->r.mainStatus()).containsExactly("EXPORT_CUSTOMS");
|
||||
verifyNoInteractions(downstream);
|
||||
verify(downstream).read(any(),any());
|
||||
clearInvocations(downstream);
|
||||
when(repository.facts(List.of(1L))).thenReturn(List.of(
|
||||
new WorkflowFacts.Fact(11,1,101,"APPROVED",true,false,false,true,true)));
|
||||
when(downstream.read(any(),any())).thenReturn(ApiResponse.success(List.of(
|
||||
@@ -64,4 +67,18 @@ class WorkflowBoundaryTest {
|
||||
verify(downstream).read(any(),eq(new WorkflowFacts.Request(List.of(new WorkflowFacts.Source(11,1)))));
|
||||
}
|
||||
|
||||
/** 理货审核建单后,未补录开船与清关资料也必须返回出库待办。 */
|
||||
@Test void outboundBeforeDepartureIsReadAndClassified() {
|
||||
var repository=mock(WorkflowFactRepository.class);
|
||||
var downstream=mock(LastlegWorkflowClient.class);
|
||||
var service=new WorkflowTabService(repository,downstream);
|
||||
var inbound=new InboundOrderEntity();inbound.setId(1L);inbound.setTenantId(11L);
|
||||
inbound.setOrderId(101L);inbound.setProcessStatus("LOADED");
|
||||
when(repository.facts(List.of(1L))).thenReturn(List.of(
|
||||
new WorkflowFacts.Fact(11,1,101,"APPROVED",false,false,false,false,false)));
|
||||
when(downstream.read(any(),any())).thenReturn(ApiResponse.success(List.of(
|
||||
new WorkflowFacts.Overseas(11,1,true,true,false,true))));
|
||||
assertThat(service.rows(List.of(inbound))).extracting(r->r.mainStatus()).containsExactly("OUTBOUND_DOCUMENT");
|
||||
verify(downstream).read(any(),eq(new WorkflowFacts.Request(List.of(new WorkflowFacts.Source(11,1)))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,12 +30,12 @@ class WorkflowBusinessTest {
|
||||
assertThat(WorkflowTabService.classify(uploaded,"DELIVERED",unloaded))
|
||||
.contains("SIGNED").doesNotContain("EXPORT_CUSTOMS","WAIT_CUSTOMS_DOCS");
|
||||
}
|
||||
/** 清关前不能被海外建单事实提前推进,清关后出库与派送依次替换待办。 */
|
||||
/** 出库与派送按尾程事实展示,不受清关资料限制。 */
|
||||
@Test void downstreamFactsCannotBypassClearanceAndOutboundReplacesQueues() {
|
||||
var pending=new WorkflowFacts.Overseas(11,1,true,true,false,false);
|
||||
var shipped=new WorkflowFacts.Overseas(11,1,true,false,true,false);
|
||||
assertThat(WorkflowTabService.classify(fact(1,false),"LOADED",pending)).containsExactly("SAILING_CLEARANCE");
|
||||
assertThat(WorkflowTabService.classify(fact(1,false),"LOADED",shipped)).containsExactly("SAILING_CLEARANCE");
|
||||
assertThat(WorkflowTabService.classify(fact(1,false),"LOADED",pending)).containsExactly("OUTBOUND_DOCUMENT","SAILING_CLEARANCE");
|
||||
assertThat(WorkflowTabService.classify(fact(1,false),"LOADED",shipped)).containsExactly("DELIVERING","SAILING_CLEARANCE");
|
||||
assertThat(WorkflowTabService.classify(fact(1,true),"LOADED",pending)).containsExactly("OUTBOUND_DOCUMENT");
|
||||
assertThat(WorkflowTabService.classify(fact(1,true),"LOADED",shipped)).containsExactly("DELIVERING");
|
||||
assertThat(WorkflowTabService.classify(fact(1,true),"DELIVERED",shipped)).containsExactly("SIGNED");
|
||||
|
||||
Reference in New Issue
Block a user