feat(appointments): add four-type calendar sources and align release contracts
This commit is contained in:
+44
@@ -0,0 +1,44 @@
|
||||
package com.baiyue.firstleg.terminaldrayage.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.core.MybatisSqlSessionFactoryBuilder;
|
||||
import org.apache.ibatis.mapping.Environment;
|
||||
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
|
||||
import org.h2.jdbcx.JdbcDataSource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import java.time.LocalDateTime;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class ContainerCalendarSourceMapperTest {
|
||||
/** 使用实际 Mapper 核对跨月默认日期候选、缺日期与真实出仓边界。 */
|
||||
@Test
|
||||
void selectsShiftedArrivalUndatedAndOlderReturnCandidatesWithoutInactiveOrUndepartedContainers() throws Exception {
|
||||
var ds = new JdbcDataSource();
|
||||
ds.setURL("jdbc:h2:mem:four-type-calendar;MODE=MySQL;DB_CLOSE_DELAY=-1");
|
||||
try (var connection = ds.getConnection(); var sql = connection.createStatement()) {
|
||||
sql.execute("CREATE TABLE fl_container_booking(id BIGINT,cabinet_no VARCHAR,container_no VARCHAR," +
|
||||
"vessel_name VARCHAR,voyage_no VARCHAR,destination_port_name VARCHAR,destination_warehouse_code VARCHAR," +
|
||||
"actual_port_arrival_at TIMESTAMP,estimated_port_arrival_at TIMESTAMP,unloading_completed_at TIMESTAMP,record_status VARCHAR)");
|
||||
sql.execute("CREATE TABLE fl_terminal_drayage(container_booking_id BIGINT,actual_empty_return_at TIMESTAMP)");
|
||||
sql.execute("CREATE TABLE fl_carrier_schedule_node_fact(id BIGINT,container_booking_id BIGINT,standard_node VARCHAR,actual_at TIMESTAMP,observed_at TIMESTAMP)");
|
||||
sql.execute("CREATE TABLE fl_direct_delivery_unloading(container_booking_id BIGINT,actual_at TIMESTAMP)");
|
||||
sql.execute("CREATE TABLE fl_outbound_dispatch(container_booking_id BIGINT,confirmed_at TIMESTAMP,reversed_at TIMESTAMP,status VARCHAR)");
|
||||
sql.execute("INSERT INTO fl_container_booking(id,cabinet_no,estimated_port_arrival_at,record_status) VALUES " +
|
||||
"(1,'D1','2026-10-30 12:00:00','ACTIVE'),(2,'D2','2026-11-10 12:00:00','ACTIVE')," +
|
||||
"(3,'D3',NULL,'ACTIVE'),(4,'D4','2026-01-01 12:00:00','ACTIVE')," +
|
||||
"(5,'D5','2026-11-10 12:00:00','CANCELLED'),(6,'D6','2026-11-10 12:00:00','ACTIVE')");
|
||||
sql.execute("INSERT INTO fl_outbound_dispatch SELECT id,'2026-01-01 00:00:00',NULL,'WAITING_FOR_LOADING' FROM fl_container_booking WHERE id<>6");
|
||||
sql.execute("INSERT INTO fl_terminal_drayage VALUES(4,'2026-11-05 12:00:00')");
|
||||
sql.execute("INSERT INTO fl_carrier_schedule_node_fact VALUES(1,2,'ARRIVED_DESTINATION_PORT','2026-10-25 12:00:00','2026-10-26 12:00:00')");
|
||||
}
|
||||
var config = new MybatisConfiguration();
|
||||
config.setEnvironment(new Environment("test", new JdbcTransactionFactory(), ds)); config.addMapper(ContainerCalendarSourceMapper.class);
|
||||
try (var session = new MybatisSqlSessionFactoryBuilder().build(config).openSession()) {
|
||||
var from = LocalDateTime.parse("2026-11-01T00:00:00");
|
||||
var rows = session.getMapper(ContainerCalendarSourceMapper.class).sources(from, from.minusDays(2), from.minusDays(4), from.plusDays(30));
|
||||
assertEquals(java.util.List.of(1L, 3L, 4L), rows.stream().map(row -> row.containerBookingId()).toList());
|
||||
assertEquals("2026-10-30T12:00", rows.get(0).arrivalAt().toString());
|
||||
assertNull(rows.get(1).arrivalAt());
|
||||
}
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package com.baiyue.firstleg.terminaldrayage.service;
|
||||
|
||||
import com.baiyue.firstleg.terminaldrayage.dto.ContainerCalendarSource;
|
||||
import com.baiyue.firstleg.terminaldrayage.dto.TerminalDrayageDtos.*;
|
||||
import com.baiyue.firstleg.terminaldrayage.repository.ContainerCalendarSourceRepository;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
import java.time.*;
|
||||
import java.util.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
class ContainerCalendarSourceServiceTest {
|
||||
private final ContainerCalendarSourceRepository repository = mock(ContainerCalendarSourceRepository.class);
|
||||
private final TerminalDrayageService drayages = mock(TerminalDrayageService.class);
|
||||
private final ContainerCalendarSourceService service = new ContainerCalendarSourceService(repository, drayages);
|
||||
private final LocalDate from = LocalDate.of(2026, 11, 1), to = from.plusDays(30);
|
||||
/** 缺还柜读取权限时保留范围说明,不读取受保护事实。 */
|
||||
@Test void keepsMissingReturnPermissionExplicitWithoutReadingProtectedFacts() {
|
||||
when(repository.sources(from, to)).thenReturn(List.of(
|
||||
new ContainerCalendarSource(1L,"D1",null,null,null,null,null,null,"ESTIMATED"),
|
||||
new ContainerCalendarSource(2L,"D2",null,null,null,null,null,from.minusMonths(2).atStartOfDay(),"ACTUAL")));
|
||||
var result = service.calendar(from, to, Set.of("platform:appointment:list"));
|
||||
assertEquals(1, result.containers().size()); assertFalse(result.emptyReturnsReadable());
|
||||
verifyNoInteractions(drayages);
|
||||
}
|
||||
/** 实际还柜日期优先于截止日期,范围外完成事实不回落到原提醒日。 */
|
||||
@Test void usesOriginalDeadlineButActualReturnSupersedesTheDeadlineDate() {
|
||||
when(repository.sources(from, to)).thenReturn(List.of(new ContainerCalendarSource(1L,"D1",null,null,null,null,null,null,"ESTIMATED")));
|
||||
var row = mock(View.class);
|
||||
when(row.actualEmptyReturnAt()).thenReturn(to.atStartOfDay());
|
||||
when(row.emptyReturnWarning()).thenReturn(new Warning("RESOLVED", from.atStartOfDay(),0,0));
|
||||
when(drayages.views(List.of(1L))).thenReturn(List.of(row));
|
||||
when(drayages.authorize(anyList(), anySet())).thenAnswer(call -> call.getArgument(0));
|
||||
assertTrue(service.calendar(from,to,Set.of("platform:firstleg:terminal-drayage:list")).emptyReturns().isEmpty());
|
||||
when(row.actualEmptyReturnAt()).thenReturn(null);
|
||||
assertEquals(List.of(row), service.calendar(from,to,Set.of("platform:firstleg:terminal-drayage:list")).emptyReturns());
|
||||
}
|
||||
/** 非法日期范围在访问持久化层前拒绝。 */
|
||||
@Test void rejectsInvalidRangesBeforeDatabaseAccess() {
|
||||
assertThrows(ResponseStatusException.class, () -> service.calendar(from,from,Set.of()));
|
||||
assertThrows(ResponseStatusException.class, () -> service.calendar(from,from.plusDays(43),Set.of()));
|
||||
verifyNoInteractions(repository);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user