diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/service/DispatchAggregateService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/service/DispatchAggregateService.java index 7ba70e6c5..a2c6ba90e 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/service/DispatchAggregateService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/service/DispatchAggregateService.java @@ -174,10 +174,10 @@ public class DispatchAggregateService implements DispatchExecutionPort,DispatchA var destination=new Destination(com.baiyue.lastleg.fleet.trip.FleetDispatchGateway.destinationKey(row.destinationType(),row.destinationWarehouseCode(), row.fbaWarehouseCode(),row.deliveryAddress(),row.postalCode()),row.destinationType(),row.destinationWarehouseId(),row.destinationWarehouseCode(), row.destinationWarehouseName(),row.deliveryAddress(),row.postalCode(),row.contactName(),row.contactPhone()); - var fact=row.planPalletId()==null?null:batchFacts.get(new com.baiyue.lastleg.appointmentwork.AppointmentWorkDispatchPort.CandidatePhysical(row.customerId(),row.planPalletId())); + var fact=type!=SourceType.INBOUND_PLAN || row.planPalletId()==null?null:batchFacts.get(new com.baiyue.lastleg.appointmentwork.AppointmentWorkDispatchPort.CandidatePhysical(row.customerId(),row.planPalletId())); var references=new ArrayList();var reasons=new LinkedHashSet(); reasons.addAll(group.blockReasons()); - if("FBA".equals(destination.type()) && fact==null)reasons.add("本票FBA货物缺少可核对的正式预约来源,请先确认送仓时间"); + if(type==SourceType.INBOUND_PLAN && "FBA".equals(destination.type()) && fact==null)reasons.add("本票FBA货物缺少可核对的正式预约来源,请先确认送仓时间"); if(fact!=null) { reasons.addAll(fact.blockReasons()); // 来源候选已验证当前草稿拥有者并过滤其他作业占用,不能把本草稿占用再次当禁用。 @@ -859,7 +859,7 @@ public class DispatchAggregateService implements DispatchExecutionPort,DispatchA for(var source:resolved) { if(keys.contains(source.sourceKey())) throw conflict("所选来源已在本车,不能重复加入"); var c=source.candidate(); - if("FBA".equals(c.destinationType()) && c.planPalletId()==null)throw conflict("本票FBA货物缺少可核对的正式预约来源,请先确认送仓时间"); + if(source.selection().sourceType()==SourceType.INBOUND_PLAN && "FBA".equals(c.destinationType()) && c.planPalletId()==null)throw conflict("本票FBA货物缺少可核对的正式预约来源,请先确认送仓时间"); sources.expire(c.customerId(),source.sourceKey(),at); if(!sources.claims(c.customerId(),source.sourceKey(),at).isEmpty())throw conflict("所选逻辑来源已被有效运输安排占用"); try {if(sources.claim(IdWorker.getId(),c.customerId(),source.sourceKey(),c.planPalletId(),c.warehousePalletId(),c.returnReceiptId(),c.warehousePalletId()==null?null:physical.get(c.warehousePalletId()),row.getId(),actor.operatorId(),at)!=1)throw conflict("来源占用未保存");} diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/service/DispatchDraftService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/service/DispatchDraftService.java index 533dc145c..c4f2113db 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/service/DispatchDraftService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/service/DispatchDraftService.java @@ -423,13 +423,14 @@ public class DispatchDraftService implements DispatchPlanningPort { .map(c->new DraftContainer(c.getId(),c.getSequenceNo(),c.getCapacity(),keys(c))).toList(),selected(row).stream().map(c->projectCandidate(c,batches)).toList(),row.getExpiresAt().toInstant(ZoneOffset.UTC));} /** 真实客户及计划托盘来自服务器来源,整页一次调用原Provider。 */ private Map readCandidateBatches(List candidates) { - var identities=candidates.stream().filter(c->c.selection().planPalletId()!=null).map(c->new AppointmentWorkDispatchPort.CandidatePhysical(c.customerId(),c.selection().planPalletId())).distinct().toList(); + var identities=candidates.stream().filter(c->c.selection().sourceType()==DispatchTypes.SourceType.INBOUND_PLAN && c.selection().planPalletId()!=null).map(c->new AppointmentWorkDispatchPort.CandidatePhysical(c.customerId(),c.selection().planPalletId())).distinct().toList(); var result=new LinkedHashMap(); for(int offset=0;offset batches) { + if(c.selection().sourceType()!=DispatchTypes.SourceType.INBOUND_PLAN)return c; if(c.selection().planPalletId()==null) { if(!"FBA".equals(c.destination().type()))return c; return new Candidate(c.selection(),c.sourceKey(),c.sourceVersion(),c.customerId(),c.customerName(),c.warehouseId(),c.warehouseCode(),c.warehouseName(),c.orderNo(),c.inboundCode(),c.planNo(),c.palletNo(),c.containerNo(),c.destination(),c.availablePackages(),c.suggestedWeightKg(),c.suggestedVolumeCbm(),c.measurementSource(),List.of(),List.of("本票FBA货物缺少可核对的正式预约来源,请先确认送仓时间"),c.selectionGroup()); diff --git a/lastleg-service/src/main/resources/openapi/overseas-business-v1.yaml b/lastleg-service/src/main/resources/openapi/overseas-business-v1.yaml index 52df15bb7..6d9d6b869 100644 --- a/lastleg-service/src/main/resources/openapi/overseas-business-v1.yaml +++ b/lastleg-service/src/main/resources/openapi/overseas-business-v1.yaml @@ -5974,7 +5974,7 @@ components: required: - idempotencyKey - warehouseId - description: 同一起运仓,允许空单或多客户、多目的仓。FBA货物须先取得覆盖本客户、来源和箱数的正式预约时间才能选入,不能等提交审核才检查。客户和来源归属由服务端解析,临时资源不生成假档案。 + description: 同一起运仓,允许空单或多客户、多目的仓。普通入仓FBA货物须先取得覆盖本客户、来源和箱数的正式预约时间才能选入,不能等提交审核才检查;换标和退货独立来源保留原流程。客户和来源归属由服务端解析,临时资源不生成假档案。 DispatchAddCargo: type: object x-java-record: DispatchCommands.AddCargo diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/dispatch/service/DispatchDraftPersistenceTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/dispatch/service/DispatchDraftPersistenceTest.java index 69745c6aa..741d4949f 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/dispatch/service/DispatchDraftPersistenceTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/dispatch/service/DispatchDraftPersistenceTest.java @@ -129,12 +129,17 @@ class DispatchDraftPersistenceTest { assertThat(db.queryForObject("SELECT COUNT(*) FROM ll_dispatch_pallet_lock",Integer.class)).isZero(); assertThat(service.drafts(owner).get(0).sources()).isEmpty(); } - /** FBA来源尚不能关联正式预约时不得借其他来源类型绕过确认门禁。 */ - @Test void fbaWithoutAuthoritativeAppointmentSourceCannotEnterDraft() { - var state=empty(1);var choice=new DispatchCommands.CargoSelection(SourceType.RELABEL,null,null,"RL-1",1001L,"version-1");choices.put(1L,choice); + /** 本轮门禁限普通入仓,独立换标和退货FBA保留原排车流程。 */ + @ParameterizedTest + @org.junit.jupiter.params.provider.EnumSource(value=SourceType.class,names={"RELABEL","RETURN_RECEIPT"}) + void independentFbaKeepsExistingDraftFlow(SourceType sourceType) { + var state=empty(1);var choice=sourceType==SourceType.RELABEL + ?new DispatchCommands.CargoSelection(sourceType,null,null,"RL-1",1001L,"version-1") + :new DispatchCommands.CargoSelection(sourceType,null,1L,null,1001L,"version-1");choices.put(1L,choice); doAnswer(call->{var resolved=whole(choice);resolved.candidate().destinationType("FBA");return List.of(resolved);}).when(sources).lockDraftSources(anyString(),anyList()); - assertThatThrownBy(()->select(state,0,1)).hasMessageContaining("缺少可核对的正式预约来源"); - assertThat(db.queryForObject("SELECT COUNT(*) FROM ll_dispatch_pallet_lock",Integer.class)).isZero(); + assertThat(select(state,0,1).sources()).hasSize(1); + assertThat(db.queryForObject("SELECT COUNT(*) FROM ll_dispatch_pallet_lock",Integer.class)).isOne(); + verify(appointmentProvider,never()).candidateBatches(anyList()); } /** 新实例只依赖数据库命令结果,关闭内存对象也能恢复创建及原布局结果。 */ @Test void restartRecoversOriginalResultsAndRejectsChangedActorVersionOrPayload(){ @@ -540,8 +545,8 @@ class DispatchDraftPersistenceTest { .returnReceiptId(selection.sourceType()==SourceType.RETURN_RECEIPT?sourceId:null) .customerId(sourceId%2==0?"102":"101").customerName("客户"+sourceId).overseasWarehouseId("92") .overseasWarehouseCode("WH").overseasWarehouseName("海外仓").packageCount(10).palletNo("P-"+pallet) - .sourceStage(selection.sourceType()==SourceType.RELABEL?"RELABEL":"RECEIVED").destinationType(selection.sourceType()==SourceType.INBOUND_PLAN?"FBA":"PRIVATE") - .destinationWarehouseCode(destination).fbaWarehouseCode(selection.sourceType()==SourceType.INBOUND_PLAN?destination:null); + .sourceStage(selection.sourceType()==SourceType.RELABEL?"RELABEL":"RECEIVED").destinationType("FBA") + .destinationWarehouseCode(destination).fbaWarehouseCode(destination); return new DispatchService.WholeSource(row,selection,DispatchService.wholeSourceKey(selection),selection.sourceVersion()); } /** 受控事实按原来源类型及服务器版本产生完整组,不把组装责任交给前端。 */