fix(orders): filter tax mode before unified order pagination
This commit is contained in:
@@ -35,7 +35,9 @@ public record UnifiedInboundQuery(
|
||||
LocalDateTime createdFrom,
|
||||
LocalDateTime createdTo,
|
||||
@Min(1) long page,
|
||||
@Min(1) @Max(200) long size
|
||||
@Min(1) @Max(200) long size,
|
||||
@Schema(allowableValues = {"TAX_REFUND", "BUY_DECLARATION"})
|
||||
@jakarta.validation.constraints.Pattern(regexp = "TAX_REFUND|BUY_DECLARATION") String taxMode
|
||||
) {
|
||||
public UnifiedInboundQuery {
|
||||
if (page == 0) page = 1;
|
||||
|
||||
@@ -118,6 +118,7 @@ public class InboundOrderRepository {
|
||||
public Page<InboundOrderEntity> pageUnifiedEntities(UnifiedInboundQuery query, List<String> processStatuses) {
|
||||
LambdaQueryWrapper<InboundOrderEntity> wrapper = new LambdaQueryWrapper<InboundOrderEntity>()
|
||||
.eq(query.tenantId() != null, InboundOrderEntity::getTenantId, query.tenantId())
|
||||
.eq(query.taxMode() != null, InboundOrderEntity::getTaxMode, query.taxMode())
|
||||
.in(processStatuses != null && !processStatuses.isEmpty(), InboundOrderEntity::getProcessStatus, processStatuses)
|
||||
.like(StringUtils.hasText(query.orderNo()), InboundOrderEntity::getOrderNo, query.orderNo())
|
||||
.like(StringUtils.hasText(query.inboundCode()), InboundOrderEntity::getInboundCode, query.inboundCode())
|
||||
|
||||
@@ -222,6 +222,10 @@ public class UnifiedInboundQueryService {
|
||||
}
|
||||
|
||||
private void validate(UnifiedInboundQuery query) {
|
||||
if (query.taxMode() != null && !java.util.Set.of("TAX_REFUND", "BUY_DECLARATION").contains(query.taxMode())) {
|
||||
throw new com.baiyue.common.core.exception.BizException(
|
||||
com.baiyue.common.core.api.ResultCode.BAD_REQUEST, "taxMode仅支持TAX_REFUND或BUY_DECLARATION");
|
||||
}
|
||||
if (StringUtils.hasText(query.mainStatus())
|
||||
&& !MAIN_STATUSES.contains(query.mainStatus())
|
||||
) {
|
||||
|
||||
@@ -1557,6 +1557,7 @@ components:
|
||||
UnifiedInboundQuery:
|
||||
type: object
|
||||
properties:
|
||||
taxMode: {type: string, enum: [TAX_REFUND, BUY_DECLARATION], nullable: true, description: 按税务模式在数据库计数和分页前过滤;未传不限制}
|
||||
tenantId: {type: integer, format: int64, nullable: true}
|
||||
keyword: {type: string, maxLength: 64, nullable: true}
|
||||
orderNo: {type: string, maxLength: 64, nullable: true}
|
||||
|
||||
+1
-1
@@ -79,7 +79,7 @@ class InternalInboundOrderReferenceControllerTest {
|
||||
UnifiedInboundQuery request = new UnifiedInboundQuery(
|
||||
10001L, null, null, "WAIT_SPLIT", null, null, null,
|
||||
null, null, null, null, null, null, null, null, null, null, null, null,
|
||||
null, null, 1, 20);
|
||||
null, null, 1, 20, null);
|
||||
when(unifiedQueryService.page(request)).thenReturn(new PageResult<>(List.of(), 1, 20, 0, 0));
|
||||
|
||||
assertThat(controller.unifiedPage("internal-token", request).data().records()).isEmpty();
|
||||
|
||||
+1
-1
@@ -57,7 +57,7 @@ class InboundOrderRepositoryTest {
|
||||
10001L, null, null, "LOADING", 30001L, 40001L, 50001L,
|
||||
"RK-", "APPROVED", LocalDate.of(2026, 8, 6), "APPROVED", "美森",
|
||||
"MSCU", "CAB", LocalDate.of(2026, 8, 8), true, true, "UNRECORDED",
|
||||
List.of(77L, 78L), null, null, 1, 20);
|
||||
List.of(77L, 78L), null, null, 1, 20, null);
|
||||
|
||||
repository.pageUnifiedEntities(query, List.of("LOADING"));
|
||||
|
||||
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
package com.baiyue.firstleg.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
|
||||
import com.baiyue.firstleg.entity.InboundOrderEntity;
|
||||
import com.baiyue.firstleg.mapper.InboundOrderMapper;
|
||||
import com.baiyue.firstleg.dto.UnifiedInboundQuery;
|
||||
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.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/** Real mapper + pagination interceptor: tax filtering applies to COUNT and records before LIMIT. */
|
||||
class UnifiedInboundTaxModePaginationTest {
|
||||
private InboundOrderRepository repository;
|
||||
@BeforeEach
|
||||
void setup() throws Exception {
|
||||
var dataSource = new JdbcDataSource();
|
||||
dataSource.setURL("jdbc:h2:mem:tax-page-" + UUID.randomUUID() + ";MODE=MySQL;DB_CLOSE_DELAY=-1");
|
||||
var jdbc = new JdbcTemplate(dataSource);
|
||||
var columns = new ArrayList<String>();
|
||||
for (Class<?> type = InboundOrderEntity.class; type != Object.class; type = type.getSuperclass()) {
|
||||
for (var field : type.getDeclaredFields()) {
|
||||
var annotation = field.getAnnotation(TableField.class);
|
||||
if (Modifier.isStatic(field.getModifiers()) || annotation != null && !annotation.exist()) continue;
|
||||
String name = field.getName().replaceAll("([a-z0-9])([A-Z])", "$1_$2").toLowerCase();
|
||||
Class<?> valueType = field.getType();
|
||||
String sqlType = valueType == Long.class || valueType == long.class ? "BIGINT"
|
||||
: valueType == Integer.class || valueType == int.class || valueType == Boolean.class || valueType == boolean.class ? "INT"
|
||||
: valueType == java.math.BigDecimal.class ? "DECIMAL(20,6)"
|
||||
: valueType == java.time.LocalDateTime.class ? "TIMESTAMP"
|
||||
: valueType == java.time.LocalDate.class ? "DATE" : "VARCHAR(2000)";
|
||||
columns.add(name + " " + sqlType + (name.equals("id") ? " PRIMARY KEY" : ""));
|
||||
}
|
||||
}
|
||||
jdbc.execute("CREATE TABLE fl_inbound_order (" + String.join(",", columns) + ")");
|
||||
for (int id = 1; id <= 7; id++) {
|
||||
jdbc.update("INSERT INTO fl_inbound_order(id,tenant_id,process_status,tax_mode,deleted,created_at) VALUES(?,?,?,?,?,?)",
|
||||
id, id == 5 || id == 7 ? 22 : 11, "LOADED", id == 2 || id == 3 || id == 6 || id == 7 ? "TAX_REFUND" : "BUY_DECLARATION",
|
||||
id == 3 ? 1 : 0, java.time.LocalDateTime.of(2026, 10, 2, 12, 0));
|
||||
}
|
||||
var config = new MybatisConfiguration();
|
||||
config.setMapUnderscoreToCamelCase(true); config.addMapper(InboundOrderMapper.class);
|
||||
var pagination = new MybatisPlusInterceptor();
|
||||
pagination.addInnerInterceptor(new PaginationInnerInterceptor(DbType.H2));
|
||||
var factory = new MybatisSqlSessionFactoryBean();
|
||||
factory.setDataSource(dataSource); factory.setConfiguration(config); factory.setPlugins(pagination);
|
||||
var session = new SqlSessionTemplate(factory.getObject());
|
||||
repository = new InboundOrderRepository();
|
||||
ReflectionTestUtils.setField(repository, "orders", session.getMapper(InboundOrderMapper.class));
|
||||
}
|
||||
@Test
|
||||
void taxModeFiltersCountsAndPagesWithoutLeakingAnotherCustomerOrDeletedRows() {
|
||||
assertPage("TAX_REFUND", 1, 2, 6);
|
||||
assertPage("TAX_REFUND", 2, 2, 2);
|
||||
assertPage("BUY_DECLARATION", 1, 2, 4);
|
||||
assertPage("BUY_DECLARATION", 2, 2, 1);
|
||||
assertPage(null, 1, 4, 6);
|
||||
assertPage(null, 2, 4, 4);
|
||||
}
|
||||
private void assertPage(String taxMode, long page, long total, long expectedId) {
|
||||
var query = new UnifiedInboundQuery(11L, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, page, 1, taxMode);
|
||||
var result = repository.pageUnifiedEntities(query, List.of("LOADED"));
|
||||
assertThat(result.getTotal()).isEqualTo(total);
|
||||
assertThat(result.getRecords()).singleElement().satisfies(row -> {
|
||||
assertThat(row.getId()).isEqualTo(expectedId);
|
||||
assertThat(row.getTenantId()).isEqualTo(11L);
|
||||
if (taxMode != null) assertThat(row.getTaxMode()).isEqualTo(taxMode);
|
||||
});
|
||||
}
|
||||
}
|
||||
+13
-1
@@ -24,6 +24,18 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
class UnifiedInboundQueryServiceTest {
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"", " ", "tax_refund", "TAX_REFUND ", "OTHER", "TAX_INCLUDED"})
|
||||
void illegalTaxModeFailsBeforeRepository(String taxMode) {
|
||||
var repository = mock(InboundOrderRepository.class);
|
||||
var service = new UnifiedInboundQueryService();
|
||||
ReflectionTestUtils.setField(service, "inbounds", repository);
|
||||
var query = new UnifiedInboundQuery(10001L, null, null, null, null, null, null, null,
|
||||
null, null, null, null, null, null, null, null, null, null, null, null, null, 1, 20, taxMode);
|
||||
assertThatThrownBy(() -> service.page(query)).isInstanceOf(BizException.class).hasMessageContaining("taxMode");
|
||||
verifyNoInteractions(repository);
|
||||
}
|
||||
|
||||
@Test
|
||||
void mapsAllCompatibleInternalStatesToProductMainStatuses() {
|
||||
assertThat(UnifiedInboundQueryService.mainStatus("WAIT_MEASURE")).isEqualTo("WAIT_MEASURE");
|
||||
@@ -121,7 +133,7 @@ class UnifiedInboundQueryServiceTest {
|
||||
UnifiedInboundQuery query = new UnifiedInboundQuery(
|
||||
null, null, null, null, null, null, null, null,
|
||||
null, null, "UNKNOWN", null, null, null, null, null, null, "INVALID",
|
||||
null, null, null, 1, 20);
|
||||
null, null, null, 1, 20, null);
|
||||
|
||||
assertThatThrownBy(() -> new UnifiedInboundQueryService().page(query))
|
||||
.isInstanceOf(BizException.class);
|
||||
|
||||
Reference in New Issue
Block a user