fix(appointment): exclude cancelled batches without active allocations
This commit is contained in:
+7
-2
@@ -60,14 +60,19 @@ class AppointmentWorkSyncServiceTest {
|
||||
when(f.gateway.discover(null,10)).thenReturn(new SourcePage(List.of(container),null,true,"scope"));
|
||||
assertThrows(ResponseStatusException.class,()->f.sync.synchronize(new SyncRequest(null,10)));
|
||||
}
|
||||
/** 空取消批次回到原货源时退役客户送仓及运输事项,并使版本门禁恢复可读。 */
|
||||
@Test void cancellationReopensOriginalDemandAndRestoringTheSameAppointmentDoesNotDuplicateWork() {
|
||||
var f=new WorkTestFixture();
|
||||
var root=new Requirement("INBOUND:1",null,null,"FBA",null,"ONT8",null,List.of(),List.of("FBA_APPOINTMENT:1"),List.of());
|
||||
var batch=new Requirement("FBA_APPOINTMENT:1","FBA_APPOINTMENT:1","AP-1","FBA",null,"ONT8",null,List.of(),List.of("INBOUND:1"),List.of(new SourceReference("C","ORDER:1","FBA:ONT8")));
|
||||
var refs=List.of(new SourceReference("C","ORDER:1","FBA:ONT8"));
|
||||
var root=new Requirement("INBOUND:1",null,null,"FBA",null,"ONT8",null,List.of(),List.of("FBA_APPOINTMENT:1","FBA_APPOINTMENT:1|CUSTOMER:C"),refs);
|
||||
var batch=new Requirement("FBA_APPOINTMENT:1|CUSTOMER:C","FBA_APPOINTMENT:1","AP-1","FBA",null,"ONT8",null,List.of(),List.of("INBOUND:1"),refs);
|
||||
for(var requirement:List.of(root,batch,root,batch)) {
|
||||
f.guard.setRequirementVersion(f.guard.getRequirementVersion()+1);
|
||||
assertThrows(ResponseStatusException.class,f.store::requireInitialized);
|
||||
var source=new ContainerSource("1","CNT",null,LocalDateTime.parse("2026-10-01T10:00:00"),null,"v1",Flow.TRANSFER,null,List.of(requirement));
|
||||
when(f.gateway.discover(null,10)).thenReturn(new SourcePage(List.of(source),null,true,"scope"));
|
||||
f.sync.synchronize(new SyncRequest(null,10));
|
||||
assertDoesNotThrow(f.store::requireInitialized);
|
||||
var deliveries=f.store.all().stream().filter(s->s.getType()==Type.DELIVERY && !s.isRetired()).toList();
|
||||
assertEquals(1,deliveries.size()); assertEquals(requirement.requirementKey(),deliveries.get(0).getRequirementKey());
|
||||
}
|
||||
|
||||
+41
@@ -11,6 +11,47 @@ import static com.baiyue.lastleg.appointmentwork.AppointmentWorkFactGateway.*;
|
||||
import static com.baiyue.lastleg.appointmentwork.AppointmentWorkModels.*;
|
||||
|
||||
class AppointmentWorkRequirementProviderTest {
|
||||
/** 取消历史没有活动货物时退役旧事项,仍按权威分配保留新批次和未分配余量。 */
|
||||
@Test void cancelledEmptyHistoryDoesNotBlockCurrentBatchOrRestoreAlreadyAllocatedCargo() {
|
||||
var sources=mock(AppointmentSourceService.class);var cargo=mock(AppointmentCargoVerificationService.class);
|
||||
var appointments=mock(InboundFbaAppointmentService.class);var mapping=mock(AppointmentWorkSourceMapper.class);
|
||||
var provider=new AppointmentWorkRequirementProvider(sources,cargo,appointments,mapping);
|
||||
var requirement=new Requirement("INBOUND:1",null,null,"FBA",null,"ONT8",null,List.of(),List.of(),List.of());
|
||||
var container=new ContainerSource("1","CNT",null,null,LocalDateTime.parse("2026-10-15T11:00:00"),"1",Flow.TRANSFER,null,List.of(requirement));
|
||||
when(mapping.containerSources("1")).thenReturn(List.of(new AppointmentWorkSourceMapper.Source(11L,"C","10","1","FBA","ONT8",60)));
|
||||
when(sources.canonicalOrders(anyList())).thenReturn(List.of(new AppointmentCoverageService.SourceRef("C","ORDER:11",null)));
|
||||
var source=new AppointmentSourceService.Source("ORDER:11","C",11L,"IN-11",null,"CNT",null,null,"FBA:ONT8","ONT8",60,"v1",true,List.of());
|
||||
when(sources.resolveBatch(anyList())).thenReturn(List.of(source));
|
||||
var allocation=mock(AppointmentCargoVerificationService.Context.class);
|
||||
when(cargo.inspect(source)).thenReturn(allocation);when(allocation.availablePackages()).thenReturn(0L);
|
||||
var cancelled=mock(com.baiyue.lastleg.inboundplan.dto.FbaAppointmentResponse.class);
|
||||
when(cancelled.batchKey()).thenReturn("FBA_APPOINTMENT:1");when(cancelled.appointmentStatus()).thenReturn("CANCELLED");
|
||||
when(cancelled.items()).thenReturn(List.of());when(cancelled.replacementBatchKeys()).thenReturn(List.of());
|
||||
var current=mock(com.baiyue.lastleg.inboundplan.dto.FbaAppointmentResponse.class);
|
||||
var item=mock(com.baiyue.lastleg.inboundplan.dto.FbaAppointmentResponse.Item.class);
|
||||
when(item.customerId()).thenReturn("C");when(item.sourceKey()).thenReturn("ORDER:11");when(item.destinationKey()).thenReturn("FBA:ONT8");
|
||||
when(current.batchKey()).thenReturn("FBA_APPOINTMENT:2");when(current.appointmentNo()).thenReturn("AP-2");
|
||||
when(current.fbaWarehouseCode()).thenReturn("ONT8");when(current.items()).thenReturn(List.of(item));when(current.replacementBatchKeys()).thenReturn(List.of());
|
||||
when(appointments.relatedBatchSnapshots(anyList())).thenReturn(List.of(cancelled,current));
|
||||
var actual=provider.requirements(container,Flow.TRANSFER);
|
||||
assertEquals(List.of("FBA_APPOINTMENT:2|CUSTOMER:C"),actual.stream().map(Requirement::requirementKey).toList());
|
||||
assertEquals(List.of(new SourceReference("C","ORDER:11","FBA:ONT8")),actual.get(0).sources());
|
||||
assertTrue(actual.get(0).replacesRequirementKeys().contains("FBA_APPOINTMENT:1|CUSTOMER:C"));
|
||||
when(allocation.availablePackages()).thenReturn(20L);
|
||||
actual=provider.requirements(container,Flow.TRANSFER);
|
||||
assertEquals(List.of("INBOUND:1","FBA_APPOINTMENT:2|CUSTOMER:C"),actual.stream().map(Requirement::requirementKey).toList());
|
||||
assertEquals(List.of("FBA_APPOINTMENT:1","FBA_APPOINTMENT:1|CUSTOMER:C"),actual.get(0).replacesRequirementKeys());
|
||||
when(appointments.relatedBatchSnapshots(anyList())).thenReturn(List.of(cancelled));
|
||||
when(allocation.availablePackages()).thenReturn(60L);
|
||||
actual=provider.requirements(container,Flow.TRANSFER);
|
||||
assertEquals(List.of("INBOUND:1"),actual.stream().map(Requirement::requirementKey).toList());
|
||||
assertEquals(List.of(new SourceReference("C","ORDER:11","FBA:ONT8")),actual.get(0).sources());
|
||||
assertTrue(actual.get(0).replacesRequirementKeys().contains("FBA_APPOINTMENT:1|CUSTOMER:C"));
|
||||
// 非取消的空批次仍属缺失事实,不能静默跳过。
|
||||
when(cancelled.appointmentStatus()).thenReturn("PENDING");
|
||||
assertEquals(503,assertThrows(org.springframework.web.server.ResponseStatusException.class,()->provider.requirements(container,Flow.TRANSFER)).getStatusCode().value());
|
||||
}
|
||||
|
||||
@Test void cancelledAndFailedBookingsRetainTheirAllocatedBatchAndOnlyAuthorityDefinesRemainder() {
|
||||
var sources=mock(AppointmentSourceService.class);var cargo=mock(AppointmentCargoVerificationService.class);
|
||||
var appointments=mock(InboundFbaAppointmentService.class);var mapping=mock(AppointmentWorkSourceMapper.class);
|
||||
|
||||
Reference in New Issue
Block a user