This commit is contained in:
2026-10-20 11:33:20 +08:00
parent 16ac817524
commit 6fa5d281ec
61 changed files with 31358 additions and 8 deletions
@@ -0,0 +1,40 @@
package com.baiyue.order.dto;
import jakarta.validation.constraints.Size;
import jakarta.validation.constraints.Pattern;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDate;
import java.time.LocalDateTime;
/** 页签统计独立接收筛选条件,避免未传分页时基本类型构造绑定失败。 */
public record UnifiedOrderStatusCountQuery(
@Size(max = 64) String keyword,
@Size(max = 64) String orderNo,
Long customerId,
Long receivingWarehouseId,
Long destinationWarehouseId,
@Size(max = 64) String inboundCode,
@Size(max = 32) String internalOrderStatus,
LocalDate expectedArrivalDate,
@Size(max = 32) String feeReviewStatus,
@Size(max = 128) String channelName,
@Size(max = 64) String containerNo,
@Size(max = 64) String cabinetNo,
LocalDate sailingDate,
Boolean loaded,
Boolean fullContainer,
@Size(max = 32) String measurementReviewStatus,
@Size(max = 64) String fbaCode,
LocalDateTime createdFrom,
LocalDateTime createdTo,
@Schema(allowableValues = {"TAX_REFUND", "BUY_DECLARATION"})
@Pattern(regexp = "TAX_REFUND|BUY_DECLARATION") String taxMode
) {
/** 复用列表查询条件,统计不受当前页签和分页影响。 */
public UnifiedOrderQuery toOrderQuery() {
return new UnifiedOrderQuery(keyword, orderNo, null, customerId, receivingWarehouseId,
destinationWarehouseId, inboundCode, internalOrderStatus, expectedArrivalDate,
feeReviewStatus, channelName, containerNo, cabinetNo, sailingDate, loaded, fullContainer,
measurementReviewStatus, fbaCode, createdFrom, createdTo, 1, 200, taxMode);
}
}
@@ -0,0 +1,91 @@
package com.baiyue.order.controller;
import com.baiyue.common.security.AuthenticatedUser;
import com.baiyue.order.dto.UnifiedOrderQuery;
import com.baiyue.order.service.UnifiedOrderQueryService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.core.MethodParameter;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.Set;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
class UnifiedOrderStatusCountBindingTest {
private UnifiedOrderQueryService service;
private MockMvc mvc;
/** 使用真实 MVC 构造绑定,复现浏览器不传分页的调用。 */
@BeforeEach
void setup() {
service = mock(UnifiedOrderQueryService.class);
when(service.statusCounts(any(), any())).thenReturn(
new UnifiedOrderQueryService.StatusCounts(Map.of("WAIT_WEIGHT_CONFIRM", 125L, "SIGNED", 0L)));
var controller = new PlatformUnifiedOrderController();
ReflectionTestUtils.setField(controller, "service", service);
mvc = MockMvcBuilders.standaloneSetup(controller).setCustomArgumentResolvers(new HandlerMethodArgumentResolver() {
/** 仅为测试注入已认证的平台身份。 */
public boolean supportsParameter(MethodParameter p) { return p.getParameterType() == AuthenticatedUser.class; }
/** 保持平台作用域守卫正常执行。 */
public Object resolveArgument(MethodParameter p, ModelAndViewContainer c, NativeWebRequest r, WebDataBinderFactory f) {
return new AuthenticatedUser(1L, 0L, null, 1L, "operator", "PLATFORM", "WEB", Set.of("platform:order:list"));
}
}).build();
}
/** 不传分页时正常返回,并保留零数量。 */
@Test
void acceptsRequestWithoutPagination() throws Exception {
mvc.perform(get("/api/platform/v1/orders/status-counts"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.counts.WAIT_WEIGHT_CONFIRM").value(125))
.andExpect(jsonPath("$.data.counts.SIGNED").value(0));
var query = org.mockito.ArgumentCaptor.forClass(UnifiedOrderQuery.class);
verify(service).statusCounts(isNull(), query.capture());
assertThat(query.getValue().mainStatus()).isNull();
}
/** 筛选值完整保留,外部附带的页签和分页不会缩小统计范围。 */
@Test
void preservesFiltersAndIgnoresPaginationAndStatus() throws Exception {
mvc.perform(get("/api/platform/v1/orders/status-counts")
.param("tenantId", "11").param("customerId", "11").param("loaded", "false")
.param("fullContainer", "false").param("taxMode", "TAX_REFUND")
.param("fbaCode", "FBA123").param("measurementReviewStatus", "APPROVED")
.param("createdFrom", "2026-10-01T00:00:00")
.param("mainStatus", "LOADED").param("page", "null").param("size", "null"))
.andExpect(status().isOk());
var captured = org.mockito.ArgumentCaptor.forClass(UnifiedOrderQuery.class);
verify(service).statusCounts(eq(11L), captured.capture());
var query = captured.getValue();
assertThat(query.customerId()).isEqualTo(11L);
assertThat(query.loaded()).isFalse();
assertThat(query.fullContainer()).isFalse();
assertThat(query.taxMode()).isEqualTo("TAX_REFUND");
assertThat(query.fbaCode()).isEqualTo("FBA123");
assertThat(query.measurementReviewStatus()).isEqualTo("APPROVED");
assertThat(query.createdFrom()).isEqualTo(LocalDateTime.of(2026, 10, 1, 0, 0));
assertThat(query.mainStatus()).isNull();
assertThat(query.page()).isEqualTo(1);
assertThat(query.size()).isEqualTo(200);
}
/** 独立统计对象仍执行原有筛选值校验。 */
@Test
void rejectsInvalidFilter() throws Exception {
mvc.perform(get("/api/platform/v1/orders/status-counts").param("taxMode", "INVALID"))
.andExpect(status().isBadRequest());
verifyNoInteractions(service);
}
}