From 994215d98131eb61149e88a709e11d7bcf25c221 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Thu, 15 Oct 2026 17:34:45 +0800 Subject: [PATCH] fix(appointment): keep source inspection in work transaction --- .../AppointmentCargoVerificationService.java | 4 ++++ .../work/AppointmentWorkDeliveryProvider.java | 2 +- .../AppointmentWorkDeliveryProviderTest.java | 21 +++++++++++++++++-- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/appointmentplanning/AppointmentCargoVerificationService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/appointmentplanning/AppointmentCargoVerificationService.java index 1e25b5cb6..b4c5605a1 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/appointmentplanning/AppointmentCargoVerificationService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/appointmentplanning/AppointmentCargoVerificationService.java @@ -40,6 +40,10 @@ public class AppointmentCargoVerificationService { @Transactional(readOnly=true) public Context get(String sourceKey,String customerId,String destinationKey) { + return inspect(sourceKey,customerId,destinationKey); + } + /** Caller-owned transaction: a handled read prerequisite must not mark the whole work scan rollback-only. */ + public Context inspect(String sourceKey,String customerId,String destinationKey) { var source=sources.resolve(sourceKey,customerId,destinationKey); return inspect(source); } diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/work/AppointmentWorkDeliveryProvider.java b/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/work/AppointmentWorkDeliveryProvider.java index a469dc093..059c8f6e2 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/work/AppointmentWorkDeliveryProvider.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/work/AppointmentWorkDeliveryProvider.java @@ -64,7 +64,7 @@ public class AppointmentWorkDeliveryProvider implements AppointmentWorkFactGatew else { for(var source:request.sources()) { try { - var context=cargo.get(source.sourceKey(),source.customerId(),source.destinationKey()); + var context=cargo.inspect(source.sourceKey(),source.customerId(),source.destinationKey()); fingerprints.add(context.toString()); blocks.addAll(context.blockReasons()); } catch(org.springframework.web.server.ResponseStatusException error) { if(!AppointmentWorkSourceAvailability.unresolved(error)) throw error; diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/work/AppointmentWorkDeliveryProviderTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/work/AppointmentWorkDeliveryProviderTest.java index 2e7fbaee2..f1810f60b 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/work/AppointmentWorkDeliveryProviderTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/work/AppointmentWorkDeliveryProviderTest.java @@ -12,15 +12,32 @@ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; class AppointmentWorkDeliveryProviderTest { + @Test void handledPrerequisiteCommitsTheCallerTransactionThroughTheRealCargoProxy() { + var sourceService=mock(com.baiyue.lastleg.appointmentplanning.AppointmentSourceService.class); + when(sourceService.resolve("ORDER:11","C",null)).thenThrow(new org.springframework.web.server.ResponseStatusException(org.springframework.http.HttpStatus.BAD_REQUEST,"当前货源类型不支持FBA预约")); + var cargo=new AppointmentCargoVerificationService(sourceService,mock(com.baiyue.lastleg.appointmentplanning.AppointmentCargoVerificationMapper.class),new com.fasterxml.jackson.databind.ObjectMapper()); + var ds=new org.h2.jdbcx.JdbcDataSource();ds.setURL("jdbc:h2:mem:work-read-tx-"+java.util.UUID.randomUUID()+";DB_CLOSE_DELAY=-1"); + var manager=new org.springframework.jdbc.datasource.DataSourceTransactionManager(ds); + var proxy=new org.springframework.aop.framework.ProxyFactory(cargo);proxy.setProxyTargetClass(true); + proxy.addAdvice(new org.springframework.transaction.interceptor.TransactionInterceptor(manager,new org.springframework.transaction.annotation.AnnotationTransactionAttributeSource())); + var provider=new AppointmentWorkDeliveryProvider(mock(InboundFbaAppointmentService.class),(AppointmentCargoVerificationService)proxy.getProxy(),mock(AppointmentWorkCommitmentService.class),mock(AppointmentWorkDeliveryMapper.class),mock(AppointmentWorkSourceMapper.class)); + var jdbc=new org.springframework.jdbc.core.JdbcTemplate(ds);jdbc.execute("CREATE TABLE work_probe(id INT PRIMARY KEY)"); + var request=new FactRequest("W",Type.DELIVERY,Flow.TRANSFER,"1","INBOUND:1",null,null,List.of(),List.of(),List.of(new SourceReference("C","ORDER:11",null))); + new org.springframework.transaction.support.TransactionTemplate(manager).executeWithoutResult(tx->{ + var fact=provider.read(List.of(request)).get(0);assertNull(fact.action());assertEquals(List.of("SOURCE_CONFIGURATION"),fact.missingFields()); + jdbc.update("INSERT INTO work_probe VALUES (1)"); + }); + assertEquals(1,jdbc.queryForObject("SELECT COUNT(*) FROM work_probe",Integer.class)); + } @Test void knownSourcePrerequisiteIsVisibleAndCannotOfferBookingButOutagesStillFail() { var cargo=mock(AppointmentCargoVerificationService.class);var sources=mock(AppointmentWorkSourceMapper.class); var provider=new AppointmentWorkDeliveryProvider(mock(InboundFbaAppointmentService.class),cargo,mock(AppointmentWorkCommitmentService.class),mock(AppointmentWorkDeliveryMapper.class),sources); var request=new FactRequest("W",Type.DELIVERY,Flow.TRANSFER,"1","INBOUND:1",null,null,List.of(),List.of(),List.of(new SourceReference("C","ORDER:11",null))); - when(cargo.get("ORDER:11","C",null)).thenThrow(new org.springframework.web.server.ResponseStatusException(org.springframework.http.HttpStatus.BAD_REQUEST,"接仓点不在启用的美国仓目录中")); + when(cargo.inspect("ORDER:11","C",null)).thenThrow(new org.springframework.web.server.ResponseStatusException(org.springframework.http.HttpStatus.BAD_REQUEST,"接仓点不在启用的美国仓目录中")); var fact=provider.read(List.of(request)).get(0); assertEquals(ExecutionStatus.PENDING,fact.executionStatus());assertNull(fact.action());assertNull(fact.actual()); assertEquals(List.of("SOURCE_CONFIGURATION"),fact.missingFields());assertEquals(List.of("接仓点不在启用的美国仓目录中"),fact.blockReasons()); - doThrow(new org.springframework.web.server.ResponseStatusException(org.springframework.http.HttpStatus.SERVICE_UNAVAILABLE,"服务不可用")).when(cargo).get("ORDER:11","C",null); + doThrow(new org.springframework.web.server.ResponseStatusException(org.springframework.http.HttpStatus.SERVICE_UNAVAILABLE,"服务不可用")).when(cargo).inspect("ORDER:11","C",null); assertThrows(org.springframework.web.server.ResponseStatusException.class,()->provider.read(List.of(request))); } @Test void privateCompletionCoversThisDispatchPerSourceWithoutCompletingTheWholeOrder() {