feat(orders): expose customer-owned loaded transport facts
This commit is contained in:
+15
@@ -59,4 +59,19 @@ class UnifiedInboundOpenApiContractTest {
|
||||
private static List<String> stringList(Object value) {
|
||||
return (List<String>) value;
|
||||
}
|
||||
@Test
|
||||
void transportFragmentsDocumentCustomerSafeFactsWithoutInternalIdentifiers() throws Exception {
|
||||
Map<String,Object> document=stringMap(new Yaml().load(new ClassPathResource("openapi/firstleg-v1.yaml")
|
||||
.getContentAsString(StandardCharsets.UTF_8)));
|
||||
var schemas=stringMap(stringMap(document.get("components")).get("schemas"));
|
||||
var detail=stringMap(schemas.get("UnifiedInboundDetail"));
|
||||
assertThat(stringList(detail.get("required"))).contains("transportFragments");
|
||||
var fragments=stringMap(stringMap(detail.get("properties")).get("transportFragments"));
|
||||
assertThat(stringMap(fragments.get("items")).get("$ref")).isEqualTo("#/components/schemas/CustomerTransportFragment");
|
||||
assertThat(stringMap(stringMap(schemas.get("CustomerTransportFragment")).get("properties")))
|
||||
.containsOnlyKeys("inboundCode","cabinetNo","containerNo","nodes");
|
||||
assertThat(stringMap(stringMap(schemas.get("CustomerTransportNode")).get("properties")))
|
||||
.containsOnlyKeys("standardNode","nodeSequence","providerCode","estimatedAt","actualAt","locationCode","locationName");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
package com.baiyue.firstleg.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
|
||||
import com.baiyue.firstleg.mapper.UnifiedInboundTransportMapper;
|
||||
import org.h2.jdbcx.JdbcDataSource;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mybatis.spring.SqlSessionTemplate;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class CustomerTransportFragmentPersistenceTest {
|
||||
private JdbcTemplate jdbc;
|
||||
private UnifiedInboundReadRepository repository;
|
||||
|
||||
@BeforeEach
|
||||
void setup() throws Exception {
|
||||
var ds = new JdbcDataSource();
|
||||
ds.setURL("jdbc:h2:mem:customer-transport-" + UUID.randomUUID() + ";MODE=MySQL;DB_CLOSE_DELAY=-1");
|
||||
jdbc = new JdbcTemplate(ds);
|
||||
jdbc.execute("CREATE TABLE fl_inbound_order(id BIGINT,tenant_id BIGINT,order_id BIGINT,inbound_code VARCHAR,deleted INT)");
|
||||
jdbc.execute("CREATE TABLE fl_loading_plan_item(id BIGINT,loading_plan_id BIGINT,inbound_id BIGINT,account_id BIGINT,deleted INT)");
|
||||
jdbc.execute("CREATE TABLE fl_loading_plan(id BIGINT,container_booking_id BIGINT,plan_status VARCHAR)");
|
||||
jdbc.execute("CREATE TABLE fl_loading_pick_task(id BIGINT,loading_plan_id BIGINT,task_status VARCHAR)");
|
||||
jdbc.execute("CREATE TABLE fl_loading_pick_item(pick_task_id BIGINT,loading_plan_item_id BIGINT,item_status VARCHAR,picked_package_count INT)");
|
||||
jdbc.execute("CREATE TABLE fl_container_booking(id BIGINT,cabinet_no VARCHAR,container_no VARCHAR)");
|
||||
jdbc.execute("CREATE TABLE fl_carrier_schedule_node_fact(id BIGINT,container_booking_id BIGINT,standard_node VARCHAR,node_sequence INT,provider_code VARCHAR,estimated_at TIMESTAMP,actual_at TIMESTAMP,location_code VARCHAR,location_name VARCHAR)");
|
||||
jdbc.update("INSERT INTO fl_inbound_order VALUES(1,100,77,'NY-1',0),(2,100,77,'NY-2',0),(3,200,88,'OTHER',0),(4,100,99,'OWN-OTHER-ORDER',0)");
|
||||
jdbc.update("INSERT INTO fl_loading_plan VALUES(10,50,'COMPLETED'),(11,51,'COMPLETED'),(12,52,'LOADING')");
|
||||
jdbc.update("INSERT INTO fl_container_booking VALUES(50,'D1','TSTU0000001'),(51,'D2','TSTU0000002'),(52,'D3','TSTU0000003')");
|
||||
jdbc.update("INSERT INTO fl_loading_pick_task VALUES(20,10,'COMPLETED'),(21,11,'COMPLETED'),(22,12,'LOADING')");
|
||||
jdbc.update("INSERT INTO fl_loading_plan_item VALUES(101,10,1,100,0),(102,10,1,100,0),(103,10,2,100,0),(104,10,3,200,0),(105,10,4,100,0),(106,11,2,100,0),(107,12,2,100,0)");
|
||||
jdbc.update("INSERT INTO fl_loading_pick_item VALUES(20,101,'PARTIAL',1),(20,102,'PICKED',1),(20,103,'SKIPPED',0),(20,104,'PICKED',1),(20,105,'PICKED',1),(21,106,'PICKED',1),(22,107,'PARTIAL',1)");
|
||||
jdbc.update("INSERT INTO fl_carrier_schedule_node_fact VALUES(1,50,'AT_ORIGIN_PORT',20,'MANUAL',NULL,'2026-10-05 09:00:00','CNSZX','深圳'),(2,50,'DEPARTED_ORIGIN_PORT',40,'MATSON','2026-10-07 08:00:00',NULL,NULL,NULL),(3,51,'AT_ORIGIN_PORT',20,'MATSON',NULL,'2026-10-06 10:00:00',NULL,NULL)");
|
||||
var config = new MybatisConfiguration();
|
||||
config.setMapUnderscoreToCamelCase(true); config.addMapper(UnifiedInboundTransportMapper.class);
|
||||
var factory = new MybatisSqlSessionFactoryBean(); factory.setDataSource(ds); factory.setConfiguration(config);
|
||||
var mapper = new SqlSessionTemplate(factory.getObject()).getMapper(UnifiedInboundTransportMapper.class);
|
||||
repository = new UnifiedInboundReadRepository(); ReflectionTestUtils.setField(repository, "transports", mapper);
|
||||
}
|
||||
|
||||
@Test
|
||||
void separatesSplitFragmentsAndKeepsActualPortEntryDistinctFromEstimatedSailing() {
|
||||
var fragments = repository.findCustomerTransportFragments(100,77);
|
||||
assertThat(fragments).hasSize(2);
|
||||
assertThat(fragments.get(0).inboundCode()).isEqualTo("NY-1");
|
||||
assertThat(fragments.get(0).nodes()).hasSize(2); // two SKU memberships do not duplicate facts
|
||||
assertThat(fragments.get(0).nodes().get(0).standardNode()).isEqualTo("AT_ORIGIN_PORT");
|
||||
assertThat(fragments.get(0).nodes().get(0).actualAt()).isEqualTo(LocalDateTime.of(2026,10,5,9,0));
|
||||
assertThat(fragments.get(0).nodes().get(1).actualAt()).isNull();
|
||||
assertThat(fragments.get(0).nodes().get(1).estimatedAt()).isNotNull();
|
||||
assertThat(fragments.get(1).cabinetNo()).isEqualTo("D2");
|
||||
assertThat(fragments.get(1).nodes()).singleElement().satisfies(n ->
|
||||
assertThat(n.actualAt()).isEqualTo(LocalDateTime.of(2026,10,6,10,0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sameCustomerOtherOrderAndOtherCustomerCannotReadThisOrdersFragments() {
|
||||
assertThat(repository.findCustomerTransportFragments(200,77)).isEmpty();
|
||||
assertThat(repository.findCustomerTransportFragments(100,88)).isEmpty();
|
||||
assertThat(repository.findCustomerTransportFragments(100,99)).singleElement()
|
||||
.satisfies(fragment -> assertThat(fragment.inboundCode()).isEqualTo("OWN-OTHER-ORDER"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void wholeSkippedAndDeletedMembershipsCannotGrantTransportAccess() {
|
||||
jdbc.update("UPDATE fl_loading_pick_item SET item_status='SKIPPED',picked_package_count=0 WHERE loading_plan_item_id IN(101,102,106)");
|
||||
assertThat(repository.findCustomerTransportFragments(100,77)).isEmpty();
|
||||
jdbc.update("UPDATE fl_loading_pick_item SET item_status='PICKED',picked_package_count=1 WHERE loading_plan_item_id=101");
|
||||
jdbc.update("UPDATE fl_loading_plan_item SET deleted=1 WHERE id=101");
|
||||
assertThat(repository.findCustomerTransportFragments(100,77)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void oneInboundInMultipleCompletedCabinetsRetainsEachFactAndEmptyNodeLists() {
|
||||
jdbc.update("UPDATE fl_loading_plan_item SET inbound_id=1 WHERE id=106");
|
||||
jdbc.update("DELETE FROM fl_carrier_schedule_node_fact WHERE container_booking_id=51");
|
||||
var fragments=repository.findCustomerTransportFragments(100,77);
|
||||
assertThat(fragments).extracting(f -> f.inboundCode()).containsExactly("NY-1","NY-1");
|
||||
assertThat(fragments.get(1).nodes()).isEmpty();
|
||||
}
|
||||
}
|
||||
+22
@@ -138,4 +138,26 @@ class UnifiedInboundQueryServiceTest {
|
||||
assertThatThrownBy(() -> new UnifiedInboundQueryService().page(query))
|
||||
.isInstanceOf(BizException.class);
|
||||
}
|
||||
@Test
|
||||
void remainingWaitSplitFragmentDoesNotHideOlderLoadedFragmentsOfSameOrder() {
|
||||
var inbounds = mock(InboundOrderRepository.class);
|
||||
var operations = mock(UnifiedInboundReadRepository.class);
|
||||
var pricing = mock(FbaPricingRepository.class);
|
||||
var service = new UnifiedInboundQueryService();
|
||||
ReflectionTestUtils.setField(service,"inbounds",inbounds);
|
||||
ReflectionTestUtils.setField(service,"operations",operations);
|
||||
ReflectionTestUtils.setField(service,"pricing",pricing);
|
||||
var remaining = new InboundOrderEntity();
|
||||
remaining.setId(88L); remaining.setOrderId(99L); remaining.setTenantId(10001L);
|
||||
remaining.setProcessStatus("WAIT_SPLIT");
|
||||
var loaded = new InboundOrderEntity(); loaded.setProcessStatus("LOADED");
|
||||
when(inbounds.findByOrderId(10001L,99L)).thenReturn(remaining);
|
||||
when(inbounds.findByOrderIds(10001L,List.of(99L))).thenReturn(List.of(remaining,loaded));
|
||||
when(inbounds.detail(10001L,remaining)).thenReturn(new InboundOrderDetail(null,null,null,null,null,List.of()));
|
||||
var fragment = new com.baiyue.firstleg.dto.CustomerTransportFragment("NY-1","D1","TSTU0000001",List.of());
|
||||
when(operations.findCustomerTransportFragments(10001L,99L)).thenReturn(List.of(fragment));
|
||||
assertThat(service.detail(10001L,99L).transportFragments()).containsExactly(fragment);
|
||||
org.mockito.Mockito.verify(operations).findCustomerTransportFragments(10001L,99L);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user