This commit is contained in:
2026-08-25 11:54:19 +08:00
parent 322de03312
commit d9c8cc478e
9 changed files with 36 additions and 28 deletions
@@ -18,7 +18,7 @@ import org.springframework.scheduling.annotation.EnableScheduling;
"com.baiyue.firstleg.outbound.dispatch.mapper", "com.baiyue.firstleg.outbound.dispatch.mapper",
"com.baiyue.firstleg.portschedule.mapper", "com.baiyue.firstleg.portschedule.mapper",
"com.baiyue.firstleg.customs.mapper", "com.baiyue.firstleg.customs.mapper",
"com.baiyue.firstleg.customerstatistics.mapper" "com.baiyue.firstleg.customerstatistics.adapter.out.persistence"
}) })
@EnableFeignClients(basePackages = "com.baiyue.firstleg") @EnableFeignClients(basePackages = "com.baiyue.firstleg")
@EnableScheduling @EnableScheduling
@@ -1,9 +1,9 @@
package com.baiyue.firstleg.customerstatistics.controller; package com.baiyue.firstleg.customerstatistics.adapter.in.web;
import com.baiyue.common.core.api.ApiResponse; import com.baiyue.common.core.api.ApiResponse;
import com.baiyue.common.security.AuthenticatedUser; import com.baiyue.common.security.AuthenticatedUser;
import com.baiyue.firstleg.customerstatistics.dto.CustomerTransitStatisticsDtos.TransitSummary; import com.baiyue.firstleg.customerstatistics.application.CustomerTransitStatisticsService;
import com.baiyue.firstleg.customerstatistics.service.CustomerTransitStatisticsService; import com.baiyue.firstleg.customerstatistics.application.model.CustomerTransitStatisticsDtos.TransitSummary;
import com.baiyue.firstleg.service.FirstlegScopeGuard; import com.baiyue.firstleg.service.FirstlegScopeGuard;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.validation.constraints.Positive; import jakarta.validation.constraints.Positive;
@@ -1,14 +1,13 @@
package com.baiyue.firstleg.customerstatistics.mapper; package com.baiyue.firstleg.customerstatistics.adapter.out.persistence;
import com.baiyue.firstleg.customerstatistics.application.port.out.CustomerTransitStatisticsReader;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Select;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Mapper @Mapper
public interface CustomerTransitStatisticsMapper { public interface CustomerTransitStatisticsMapper extends CustomerTransitStatisticsReader {
@Override
@Select(""" @Select("""
SELECT #{customerId} AS customerId, SELECT #{customerId} AS customerId,
COUNT(*) AS containerCount, COUNT(*) AS containerCount,
@@ -49,8 +48,5 @@ public interface CustomerTransitStatisticsMapper {
WHERE x.customer_id = #{customerId} WHERE x.customer_id = #{customerId}
) scoped ) scoped
""") """)
SummaryRow summarize(@Param("customerId") long customerId); SummaryProjection summarize(@Param("customerId") long customerId);
record SummaryRow(Long customerId, Long containerCount, BigDecimal totalTeu,
Long unknownContainerCount, LocalDateTime asOf) {}
} }
@@ -1,9 +1,9 @@
package com.baiyue.firstleg.customerstatistics.service; package com.baiyue.firstleg.customerstatistics.application;
import com.baiyue.common.core.api.ResultCode; import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException; import com.baiyue.common.core.exception.BizException;
import com.baiyue.firstleg.customerstatistics.dto.CustomerTransitStatisticsDtos.TransitSummary; import com.baiyue.firstleg.customerstatistics.application.model.CustomerTransitStatisticsDtos.TransitSummary;
import com.baiyue.firstleg.customerstatistics.mapper.CustomerTransitStatisticsMapper; import com.baiyue.firstleg.customerstatistics.application.port.out.CustomerTransitStatisticsReader;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -12,11 +12,11 @@ import java.math.BigDecimal;
@Service @Service
public class CustomerTransitStatisticsService { public class CustomerTransitStatisticsService {
private static final String DEFINITION = "actual_sailing_at IS NOT NULL 且 actual_warehouse_arrival_at IS NULL;排除取消/失败订舱;客户+containerBookingId去重;拼柜按active LoadingPlanItem.accountId归属,整柜按OceanBooking.customerId归属。TEU:20*=1,40*=2,45*=2.25,未知箱型不计totalTeu。"; private static final String DEFINITION = "actual_sailing_at IS NOT NULL 且 actual_warehouse_arrival_at IS NULL;排除取消/失败订舱;客户+containerBookingId去重;拼柜按active LoadingPlanItem.accountId归属,整柜按OceanBooking.customerId归属。TEU:20*=1,40*=2,45*=2.25,未知箱型不计totalTeu。";
@Resource private CustomerTransitStatisticsMapper mapper; @Resource private CustomerTransitStatisticsReader reader;
public TransitSummary summarize(long customerId) { public TransitSummary summarize(long customerId) {
if (customerId <= 0) throw new BizException(ResultCode.BAD_REQUEST, "customerId必须为正数"); if (customerId <= 0) throw new BizException(ResultCode.BAD_REQUEST, "customerId必须为正数");
var row = mapper.summarize(customerId); var row = reader.summarize(customerId);
return new TransitSummary(customerId, return new TransitSummary(customerId,
value(row == null ? null : row.containerCount()), value(row == null ? null : row.containerCount()),
row == null || row.totalTeu() == null ? BigDecimal.ZERO : row.totalTeu(), row == null || row.totalTeu() == null ? BigDecimal.ZERO : row.totalTeu(),
@@ -1,4 +1,4 @@
package com.baiyue.firstleg.customerstatistics.dto; package com.baiyue.firstleg.customerstatistics.application.model;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@@ -0,0 +1,11 @@
package com.baiyue.firstleg.customerstatistics.application.port.out;
import java.math.BigDecimal;
import java.time.LocalDateTime;
public interface CustomerTransitStatisticsReader {
SummaryProjection summarize(long customerId);
record SummaryProjection(Long customerId, Long containerCount, BigDecimal totalTeu,
Long unknownContainerCount, LocalDateTime asOf) {}
}
@@ -20,7 +20,8 @@ class BaiyueFirstlegApplicationContractTest {
.contains("com.baiyue.firstleg.outbound.dispatch.mapper") .contains("com.baiyue.firstleg.outbound.dispatch.mapper")
.contains("com.baiyue.firstleg.portschedule.mapper") .contains("com.baiyue.firstleg.portschedule.mapper")
.contains("com.baiyue.firstleg.customs.mapper") .contains("com.baiyue.firstleg.customs.mapper")
.contains("com.baiyue.firstleg.customerstatistics.mapper"); .contains("com.baiyue.firstleg.customerstatistics.adapter.out.persistence")
.doesNotContain("com.baiyue.firstleg.customerstatistics.mapper");
} }
@Test @Test
@@ -1,8 +1,8 @@
package com.baiyue.firstleg.customerstatistics; package com.baiyue.firstleg.customerstatistics;
import com.baiyue.firstleg.customerstatistics.controller.PlatformCustomerTransitStatisticsController; import com.baiyue.firstleg.customerstatistics.adapter.in.web.PlatformCustomerTransitStatisticsController;
import com.baiyue.firstleg.customerstatistics.mapper.CustomerTransitStatisticsMapper; import com.baiyue.firstleg.customerstatistics.adapter.out.persistence.CustomerTransitStatisticsMapper;
import com.baiyue.firstleg.customerstatistics.service.CustomerTransitStatisticsService; import com.baiyue.firstleg.customerstatistics.application.CustomerTransitStatisticsService;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
@@ -1,8 +1,8 @@
package com.baiyue.firstleg.customerstatistics; package com.baiyue.firstleg.customerstatistics;
import com.baiyue.common.core.exception.BizException; import com.baiyue.common.core.exception.BizException;
import com.baiyue.firstleg.customerstatistics.mapper.CustomerTransitStatisticsMapper; import com.baiyue.firstleg.customerstatistics.application.CustomerTransitStatisticsService;
import com.baiyue.firstleg.customerstatistics.service.CustomerTransitStatisticsService; import com.baiyue.firstleg.customerstatistics.application.port.out.CustomerTransitStatisticsReader;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
@@ -19,12 +19,12 @@ import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class CustomerTransitStatisticsServiceTest { class CustomerTransitStatisticsServiceTest {
@Mock CustomerTransitStatisticsMapper mapper; @Mock CustomerTransitStatisticsReader reader;
@InjectMocks CustomerTransitStatisticsService service; @InjectMocks CustomerTransitStatisticsService service;
@Test void preservesDecimalTeuAndUnknownCount() { @Test void preservesDecimalTeuAndUnknownCount() {
var asOf = LocalDateTime.of(2026, 8, 13, 12, 0); var asOf = LocalDateTime.of(2026, 8, 13, 12, 0);
when(mapper.summarize(88L)).thenReturn(new CustomerTransitStatisticsMapper.SummaryRow( when(reader.summarize(88L)).thenReturn(new CustomerTransitStatisticsReader.SummaryProjection(
88L, 4L, new BigDecimal("6.25"), 1L, asOf)); 88L, 4L, new BigDecimal("6.25"), 1L, asOf));
var result = service.summarize(88L); var result = service.summarize(88L);
@@ -35,7 +35,7 @@ class CustomerTransitStatisticsServiceTest {
assertThat(result.unknownContainerCount()).isEqualTo(1); assertThat(result.unknownContainerCount()).isEqualTo(1);
assertThat(result.asOf()).isEqualTo(asOf); assertThat(result.asOf()).isEqualTo(asOf);
assertThat(result.definition()).contains("20*=1", "40*=2", "45*=2.25", "未知箱型"); assertThat(result.definition()).contains("20*=1", "40*=2", "45*=2.25", "未知箱型");
verify(mapper).summarize(88L); verify(reader).summarize(88L);
} }
@Test void rejectsInvalidCustomerBeforeQuery() { @Test void rejectsInvalidCustomerBeforeQuery() {