生产更新:同步已提交的海外仓分析及业务修复(2026-10-16) #27

Merged
admin merged 138 commits from test into main 2026-10-16 18:02:59 +08:00
3 changed files with 42 additions and 0 deletions
Showing only changes of commit fee0268391 - Show all commits
@@ -84,6 +84,8 @@ public class AppointmentWorkRequirementProvider implements AppointmentWorkFactGa
customers.forEach((customer,refs)->{
var replacements=new LinkedHashSet<>(requirement.replacesRequirementKeys());
requirement.replacesRequirementKeys().stream().filter(k->k.startsWith("FBA_APPOINTMENT:")).map(k->customerRequirement(k,customer)).forEach(replacements::add);
// 客户事项承接同一实物批次的整体需求;批次身份未变化也须有明确拆分去向。
replacements.add(requirement.batchKey());
scoped.add(new Requirement(customerRequirement(requirement.batchKey(),customer),requirement.batchKey(),requirement.batchNo(),requirement.destinationKind(),
requirement.originName(),requirement.destinationName(),requirement.timezone(),requirement.references(),List.copyOf(replacements),List.copyOf(refs)));
});
@@ -12,6 +12,45 @@ import static com.baiyue.lastleg.appointmentwork.AppointmentWorkModels.*;
import static com.baiyue.lastleg.appointmentwork.AppointmentWorkFactGateway.*;
class AppointmentWorkSyncServiceTest {
/** 同一实物批次拆成客户事项时,整体事项须有完整去向,运输仍保持唯一。 */
@Test void batchWideDeliveryIsReplacedByItsExplicitCustomerRequirements() {
var f=new WorkTestFixture();String batch="FBA_APPOINTMENT:1";
var refs=List.of(new SourceReference("A","ORDER:1","FBA:ONT8"),new SourceReference("B","ORDER:2","FBA:ONT8"));
// 先保存整体需求的真实历史事项,再按相同批次和来源拆出客户事项。
var old=f.state("1",Type.DELIVERY,Flow.TRANSFER,batch);
old.setWorkItemKey(AppointmentWorkPolicy.key("BATCH:"+batch,Type.DELIVERY,null));
old.setBatchKey(batch);old.setBatchKeys(List.of(batch));old.setSources(refs);f.add(old);
var sources=mock(com.baiyue.lastleg.appointmentplanning.AppointmentSourceService.class);
var cargo=mock(com.baiyue.lastleg.appointmentplanning.AppointmentCargoVerificationService.class);
var appointments=mock(com.baiyue.lastleg.inboundplan.service.InboundFbaAppointmentService.class);
var mapping=mock(com.baiyue.lastleg.inboundplan.work.AppointmentWorkSourceMapper.class);
var provider=new com.baiyue.lastleg.inboundplan.work.AppointmentWorkRequirementProvider(sources,cargo,appointments,mapping);
when(mapping.containerSources("1")).thenReturn(List.of(new com.baiyue.lastleg.inboundplan.work.AppointmentWorkSourceMapper.Source(1L,"A","1","1","FBA","ONT8",1),new com.baiyue.lastleg.inboundplan.work.AppointmentWorkSourceMapper.Source(2L,"B","2","1","FBA","ONT8",1)));
when(sources.canonicalOrders(anyList())).thenReturn(refs.stream().map(r->new com.baiyue.lastleg.appointmentplanning.AppointmentCoverageService.SourceRef(r.customerId(),r.sourceKey(),r.destinationKey())).toList());
when(sources.resolveBatch(anyList())).thenReturn(refs.stream().map(r->new com.baiyue.lastleg.appointmentplanning.AppointmentSourceService.Source(r.sourceKey(),r.customerId(),Long.valueOf(r.sourceKey().substring(6)),null,null,"CNT",null,null,r.destinationKey(),"ONT8",1,"v1",true,List.of())).toList());
var allocation=mock(com.baiyue.lastleg.appointmentplanning.AppointmentCargoVerificationService.Context.class);
when(allocation.availablePackages()).thenReturn(0L);when(cargo.inspect(any(com.baiyue.lastleg.appointmentplanning.AppointmentSourceService.Source.class))).thenReturn(allocation);
var snapshot=mock(com.baiyue.lastleg.inboundplan.dto.FbaAppointmentResponse.class);
when(snapshot.batchKey()).thenReturn(batch);when(snapshot.appointmentNo()).thenReturn("AP-1");when(snapshot.fbaWarehouseCode()).thenReturn("ONT8");
var items=refs.stream().map(r->{var item=mock(com.baiyue.lastleg.inboundplan.dto.FbaAppointmentResponse.Item.class);when(item.customerId()).thenReturn(r.customerId());when(item.sourceKey()).thenReturn(r.sourceKey());when(item.destinationKey()).thenReturn(r.destinationKey());return item;}).toList();
when(snapshot.items()).thenReturn(items);
when(appointments.relatedBatchSnapshots(anyList())).thenReturn(List.of(snapshot));
var root=new Requirement("INBOUND:1",null,null,"FBA",null,"ONT8",null,List.of(),List.of(),refs);
var source=new ContainerSource("1","CNT",null,LocalDateTime.parse("2026-10-01T10:00:00"),null,"v1",Flow.TRANSFER,null,List.of(root));
doAnswer(c->provider.requirements(c.getArgument(0),c.getArgument(1))).when(f.gateway).requirements(any(),eq(Flow.TRANSFER));
when(f.gateway.discover(null,10)).thenReturn(new SourcePage(List.of(source),null,true,"scope"));
f.guard.setRequirementVersion(1);
f.sync.synchronize(new SyncRequest(null,10));
var deliveries=f.store.all().stream().filter(s->s.getType()==Type.DELIVERY && !s.isRetired()).toList();
assertEquals(2,deliveries.size());
var retired=f.store.get(old.getWorkItemKey());assertTrue(retired.isRetired());
assertEquals(new HashSet<>(deliveries.stream().map(AppointmentWorkState::getWorkItemKey).toList()),new HashSet<>(retired.getReplacementKeys()));
assertEquals(1,f.store.all().stream().filter(s->s.getType()==Type.TRANSPORT && !s.isRetired()).count());
assertDoesNotThrow(f.store::requireInitialized);
f.sync.synchronize(new SyncRequest(null,10));
assertEquals(2,f.store.all().stream().filter(s->s.getType()==Type.DELIVERY && !s.isRetired()).count());
}
@Test void onePhysicalBatchHasIndependentCustomerDeliveriesAndOneVehicleWithAllCustomerEdges() {
var f=new WorkTestFixture();String batch="FBA_APPOINTMENT:1";
var requirements=List.of("A","B").stream().map(customer->new Requirement(AppointmentWorkPolicy.customerRequirement(batch,customer),batch,"AP-1","FBA","海外仓","ONT8",null,List.of(),List.of(),List.of(new SourceReference(customer,"ORDER:"+customer,"FBA:ONT8")))).toList();
@@ -75,6 +75,7 @@ class AppointmentWorkRequirementProviderTest {
var result=provider.requirements(container,Flow.TRANSFER);
assertEquals(List.of("FBA_APPOINTMENT:1|CUSTOMER:C"),result.stream().map(Requirement::requirementKey).toList());
assertEquals(List.of(new SourceReference("C","ORDER:11","FBA:ONT8")),result.get(0).sources());
assertTrue(result.get(0).replacesRequirementKeys().contains("FBA_APPOINTMENT:1"));
}
when(allocation.availablePackages()).thenReturn(20L);
assertEquals(List.of("INBOUND:1","FBA_APPOINTMENT:1|CUSTOMER:C"),provider.requirements(container,Flow.TRANSFER).stream().map(Requirement::requirementKey).toList());