From 9b7b08665e5eaa3122b754e232f0c3cc84d28a20 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Tue, 4 Aug 2026 21:01:53 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/baiyue/iam/dto/TenantDtos.java | 2 +- .../iam/service/ProfileResourceService.java | 5 ++ .../com/baiyue/iam/service/TenantService.java | 10 ++- iam/src/main/resources/openapi/iam-v1.yaml | 4 +- .../MerchantQualificationUploadTest.java | 22 ++++++ .../baiyue/iam/service/TenantServiceTest.java | 15 ++++ .../PlatformLocationController.java | 8 +- .../controller/PlatformPalletController.java | 6 +- .../baiyue/warehouse/dto/LocationDtos.java | 6 +- .../baiyue/warehouse/dto/PalletRequests.java | 2 +- .../warehouse/mapper/LocationMapper.java | 12 ++- .../repository/LocationRepository.java | 4 +- .../repository/PalletRepository.java | 6 +- .../service/LocationApplicationService.java | 5 +- .../service/PalletApplicationService.java | 2 +- ...latformLocationControllerContractTest.java | 24 +++++- .../PlatformPalletControllerContractTest.java | 18 ++++- .../mapper/LocationMapperContractTest.java | 17 ++++ .../repository/PalletRepositoryTest.java | 78 +++++++++++++++++++ .../LocationApplicationServiceTest.java | 40 ++++++++++ .../service/PalletApplicationServiceTest.java | 14 ++++ 21 files changed, 268 insertions(+), 32 deletions(-) create mode 100644 warehouse/src/test/java/com/baiyue/warehouse/repository/PalletRepositoryTest.java diff --git a/iam/src/main/java/com/baiyue/iam/dto/TenantDtos.java b/iam/src/main/java/com/baiyue/iam/dto/TenantDtos.java index f1303e72e..3c948f97d 100644 --- a/iam/src/main/java/com/baiyue/iam/dto/TenantDtos.java +++ b/iam/src/main/java/com/baiyue/iam/dto/TenantDtos.java @@ -26,7 +26,7 @@ public final class TenantDtos { @Schema(description = "Enterprise display name / 企业名称", example = "深圳虫虫出海科技有限公司") @NotBlank String tenantName, @Schema(description = "Legal entity name / 企业法定名称", example = "深圳虫虫出海科技有限公司") @NotBlank String legalName, @Schema(description = "Legal representative / 法定代表人", example = "张三") @NotBlank @Size(max = 64) String legalRepresentative, - @Schema(description = "18-character unified social credit code / 18 位统一社会信用代码", example = "91440300MA5EXAMPLE") @NotBlank @Pattern(regexp = "[0-9A-Z]{18}") String unifiedSocialCreditCode, + @Schema(description = "Optional while creating a draft; required before review / 创建草稿时选填,送审前必填", example = "91440300MA5EXAMPLE", nullable = true) @Pattern(regexp = "[0-9A-Za-z]{18}") String unifiedSocialCreditCode, @Schema(description = "Primary contact / 联系人", example = "张三") @NotBlank String contactName, @Schema(description = "Contact phone / 联系电话", example = "13800138000") @NotBlank String contactPhone, @Schema(description = "Contact email / 联系邮箱", example = "ops@example.com") @Email String contactEmail, diff --git a/iam/src/main/java/com/baiyue/iam/service/ProfileResourceService.java b/iam/src/main/java/com/baiyue/iam/service/ProfileResourceService.java index 68d55d7f3..701dd3460 100644 --- a/iam/src/main/java/com/baiyue/iam/service/ProfileResourceService.java +++ b/iam/src/main/java/com/baiyue/iam/service/ProfileResourceService.java @@ -150,6 +150,11 @@ public class ProfileResourceService { throw new BizException(ResultCode.CONFLICT, "Unified social credit code already exists / 统一社会信用代码已存在"); } + if (StringUtils.hasText(tenant.getUnifiedSocialCreditCode()) + && !certificateNo.equals(tenant.getUnifiedSocialCreditCode().trim().toUpperCase())) { + throw new BizException(ResultCode.CONFLICT, + "Unified social credit code cannot be changed / 统一社会信用代码已登记,营业执照代码必须与企业主档一致"); + } return certificateNo; } diff --git a/iam/src/main/java/com/baiyue/iam/service/TenantService.java b/iam/src/main/java/com/baiyue/iam/service/TenantService.java index caa60f987..fc4a2691a 100644 --- a/iam/src/main/java/com/baiyue/iam/service/TenantService.java +++ b/iam/src/main/java/com/baiyue/iam/service/TenantService.java @@ -24,6 +24,7 @@ import com.baiyue.iam.dto.CustomerExportDtos.CustomerExportRow; import com.baomidou.mybatisplus.core.toolkit.IdWorker; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; import java.time.LocalDateTime; import java.util.List; @@ -53,7 +54,8 @@ public class TenantService { ) @Transactional public TenantResponse register(RegisterRequest request, long operatorId) { - if (repository.existsByCreditCode(request.unifiedSocialCreditCode())) { + String creditCode = normalizeCreditCode(request.unifiedSocialCreditCode()); + if (creditCode != null && repository.existsByCreditCode(creditCode)) { throw new BizException(ResultCode.CONFLICT, "Enterprise already exists / 企业已存在"); } @@ -65,7 +67,7 @@ public class TenantService { tenant.setStatus("PROFILE_REQUIRED"); tenant.setLegalName(request.legalName()); tenant.setLegalRepresentative(request.legalRepresentative()); - tenant.setUnifiedSocialCreditCode(request.unifiedSocialCreditCode()); + tenant.setUnifiedSocialCreditCode(creditCode); tenant.setContactName(request.contactName()); tenant.setContactPhone(request.contactPhone()); tenant.setContactEmail(request.contactEmail()); @@ -332,6 +334,10 @@ public class TenantService { return tenant; } + private String normalizeCreditCode(String creditCode) { + return StringUtils.hasText(creditCode) ? creditCode.trim().toUpperCase() : null; + } + private TenantResponse response(IamTenant tenant) { return new TenantResponse( tenant.getId(), tenant.getTenantCode(), tenant.getTenantName(), tenant.getStatus(), diff --git a/iam/src/main/resources/openapi/iam-v1.yaml b/iam/src/main/resources/openapi/iam-v1.yaml index a757efab6..1d9d3fd83 100644 --- a/iam/src/main/resources/openapi/iam-v1.yaml +++ b/iam/src/main/resources/openapi/iam-v1.yaml @@ -840,12 +840,12 @@ components: reviewComment: {type: string} RegisterRequest: type: object - required: [tenantName, legalName, legalRepresentative, unifiedSocialCreditCode, contactName, contactPhone, servicePackage] + required: [tenantName, legalName, legalRepresentative, contactName, contactPhone, servicePackage] properties: tenantName: {type: string, example: 深圳虫虫出海科技有限公司} legalName: {type: string, example: 深圳虫虫出海科技有限公司} legalRepresentative: {type: string, maxLength: 64, example: 张三} - unifiedSocialCreditCode: {type: string, pattern: '^[0-9A-Z]{18}$', example: 91440300MA5ABCDE12} + unifiedSocialCreditCode: {type: string, nullable: true, pattern: '^[0-9A-Za-z]{18}$', example: 91440300MA5ABCDE12, description: 创建草稿时选填,送审前必填} contactName: {type: string, example: 李四} contactPhone: {type: string, example: '13800138000'} contactEmail: {type: string, format: email, example: contact@example.com} diff --git a/iam/src/test/java/com/baiyue/iam/service/MerchantQualificationUploadTest.java b/iam/src/test/java/com/baiyue/iam/service/MerchantQualificationUploadTest.java index e2ecbf970..87aec3b5d 100644 --- a/iam/src/test/java/com/baiyue/iam/service/MerchantQualificationUploadTest.java +++ b/iam/src/test/java/com/baiyue/iam/service/MerchantQualificationUploadTest.java @@ -72,4 +72,26 @@ class MerchantQualificationUploadTest { assertEquals("91310101ABCDE12345", tenant.getUnifiedSocialCreditCode()); verify(tenantRepository).saveTenant(tenant); } + + @Test + void businessLicenseCannotOverwriteAnEstablishedCreditCode() { + TenantRepository tenantRepository = mock(TenantRepository.class); + ProfileResourceRepository profileRepository = mock(ProfileResourceRepository.class); + ProfileResourceService service = new ProfileResourceService(); + ReflectionTestUtils.setField(service, "tenantRepository", tenantRepository); + ReflectionTestUtils.setField(service, "repository", profileRepository); + IamTenant tenant = new IamTenant(); + tenant.setId(10001L); + tenant.setTenantType("MERCHANT"); + tenant.setStatus("REJECTED"); + tenant.setUnifiedSocialCreditCode("91330100EXAMPLE001"); + when(tenantRepository.findTenantForUpdate(10001L)).thenReturn(tenant); + + assertThrows(BizException.class, () -> service.submitQualification(new QualificationSubmissionRequest( + 10001L, "BUSINESS_LICENSE", "91310101ABCDE12345", + "/api/merchant/v1/files/qualifications/license.png", null, true))); + + verify(profileRepository, never()).insertQualification(any()); + verify(tenantRepository, never()).saveTenant(tenant); + } } diff --git a/iam/src/test/java/com/baiyue/iam/service/TenantServiceTest.java b/iam/src/test/java/com/baiyue/iam/service/TenantServiceTest.java index 66f336fdd..979b8257c 100644 --- a/iam/src/test/java/com/baiyue/iam/service/TenantServiceTest.java +++ b/iam/src/test/java/com/baiyue/iam/service/TenantServiceTest.java @@ -133,6 +133,21 @@ class TenantServiceTest { verify(repository, never()).insertReview(any()); } + @Test + void platformRegistrationAllowsCreditCodeToBeCompletedWithQualificationsLater() { + TenantRepository repository = mock(TenantRepository.class); + TenantService service = new TenantService(); + ReflectionTestUtils.setField(service, "repository", repository); + + var created = service.register(new RegisterRequest( + "Example Merchant", "Example Merchant Ltd", "Bob", null, "Alice", "13800138000", + "alice@example.com", "基础版", null, null, 30, null, + null, null, "CNY", null, null), 9001L); + + assertEquals(null, created.unifiedSocialCreditCode()); + verify(repository).insertTenant(argThat(tenant -> tenant.getUnifiedSocialCreditCode() == null)); + } + @Test void explicitSubmissionRequiresOnlyBusinessLicenseAndLegalPersonIdAndCreatesReview() { TenantRepository repository = mock(TenantRepository.class); diff --git a/warehouse/src/main/java/com/baiyue/warehouse/controller/PlatformLocationController.java b/warehouse/src/main/java/com/baiyue/warehouse/controller/PlatformLocationController.java index 1db483310..84302d65c 100644 --- a/warehouse/src/main/java/com/baiyue/warehouse/controller/PlatformLocationController.java +++ b/warehouse/src/main/java/com/baiyue/warehouse/controller/PlatformLocationController.java @@ -35,7 +35,7 @@ import org.springframework.web.bind.annotation.RestController; import java.util.List; -/** 平台库位管理接口;当前数据模型中企业边界即目标 tenantId。 */ +/** 平台库位管理接口;分页支持跨企业查询,写操作仍以目标 tenantId 隔离。 */ @Validated @RestController @RequestMapping("/api/platform/v1/locations") @@ -49,14 +49,16 @@ public class PlatformLocationController { @GetMapping @PreAuthorize("hasAuthority('platform:location:list')") - @Operation(summary = "分页查询库位", description = "按目标企业租户、仓库、库区、编码、类型和状态分页查询") + @Operation(summary = "分页查询库位", + description = "默认分页查询全部企业库位;可按目标企业租户、仓库、库区、编码、类型和状态筛选") @ApiResponses({ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "查询成功"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "需要 platform:location:list 权限,且仓库须属于目标企业租户") }) public ApiResponse> page( @AuthenticationPrincipal AuthenticatedUser user, - @RequestParam @Min(1) long tenantId, + @Parameter(description = "可选目标企业租户ID;不传时查询全部企业") + @RequestParam(required = false) @Min(1) Long tenantId, @Valid @ParameterObject PageQuery query) { requirePlatform(user); return ApiResponse.success(service.page(tenantId, query)); diff --git a/warehouse/src/main/java/com/baiyue/warehouse/controller/PlatformPalletController.java b/warehouse/src/main/java/com/baiyue/warehouse/controller/PlatformPalletController.java index 456e0bd66..6da37badd 100644 --- a/warehouse/src/main/java/com/baiyue/warehouse/controller/PlatformPalletController.java +++ b/warehouse/src/main/java/com/baiyue/warehouse/controller/PlatformPalletController.java @@ -8,6 +8,7 @@ import com.baiyue.common.security.AuthenticatedUser; import com.baiyue.warehouse.dto.PalletRequests; import com.baiyue.warehouse.service.PalletApplicationService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; @@ -40,10 +41,11 @@ public class PlatformPalletController { @GetMapping @PreAuthorize("hasAuthority('platform:pallet:list')") - @Operation(summary = "分页查询托盘") + @Operation(summary = "分页查询托盘", description = "默认查询全部企业;可按企业、仓库、托盘编码和类型筛选") @ApiResponses(@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "需要 platform:pallet:list 权限")) public ApiResponse> page(@AuthenticationPrincipal AuthenticatedUser user, - @RequestParam @Min(1) long tenantId, + @Parameter(description = "目标企业租户ID;不传时查询全部企业") + @RequestParam(required = false) @Min(1) Long tenantId, @Valid @ParameterObject PalletRequests.PageQuery query) { requirePlatform(user); return ApiResponse.success(service.page(tenantId, query)); diff --git a/warehouse/src/main/java/com/baiyue/warehouse/dto/LocationDtos.java b/warehouse/src/main/java/com/baiyue/warehouse/dto/LocationDtos.java index 44462941c..b512b0e20 100644 --- a/warehouse/src/main/java/com/baiyue/warehouse/dto/LocationDtos.java +++ b/warehouse/src/main/java/com/baiyue/warehouse/dto/LocationDtos.java @@ -23,9 +23,9 @@ public final class LocationDtos { @Schema(name = "LocationPageQuery", description = "库位分页查询条件") public record PageQuery( - @Min(1) long page, - @Min(1) @Max(200) long size, - @NotNull @Min(1) Long warehouseId, + @Min(1) @Schema(description = "页码", defaultValue = "1") long page, + @Min(1) @Max(200) @Schema(description = "每页条数", defaultValue = "20") long size, + @Min(1) @Schema(description = "可选仓库ID;不传时不限制仓库") Long warehouseId, @Size(max = 50) String zoneCode, @Size(max = 50) String locationCode, @Pattern(regexp = "^(SHELF|PALLET|FLOOR|FLOW_RACK)$") String locationType, diff --git a/warehouse/src/main/java/com/baiyue/warehouse/dto/PalletRequests.java b/warehouse/src/main/java/com/baiyue/warehouse/dto/PalletRequests.java index 68fd6142c..3b5e567bb 100644 --- a/warehouse/src/main/java/com/baiyue/warehouse/dto/PalletRequests.java +++ b/warehouse/src/main/java/com/baiyue/warehouse/dto/PalletRequests.java @@ -41,7 +41,7 @@ public final class PalletRequests { public record PageQuery( @Min(1) @Schema(description = "页码", defaultValue = "1") long page, @Min(1) @Max(200) @Schema(description = "每页条数", defaultValue = "20") long size, - @Min(1) @Schema(description = "仓库ID") Long warehouseId, + @Min(1) @Schema(description = "仓库ID;不传时不限制仓库") Long warehouseId, @Size(max = 50) @Schema(description = "托盘编码,支持模糊查询") String palletCode, @Size(max = 32) @Schema(description = "托盘类型") String palletType ) { diff --git a/warehouse/src/main/java/com/baiyue/warehouse/mapper/LocationMapper.java b/warehouse/src/main/java/com/baiyue/warehouse/mapper/LocationMapper.java index 6d1e13851..929d465e4 100644 --- a/warehouse/src/main/java/com/baiyue/warehouse/mapper/LocationMapper.java +++ b/warehouse/src/main/java/com/baiyue/warehouse/mapper/LocationMapper.java @@ -20,9 +20,13 @@ public interface LocationMapper extends BaseMapper { FROM wh_location l JOIN wh_warehouse w ON w.id = l.warehouse_id AND w.tenant_id = l.tenant_id AND w.deleted = 0 - WHERE l.tenant_id = #{tenantId} + WHERE l.deleted = 0 + + AND l.tenant_id = #{tenantId} + + AND l.warehouse_id = #{warehouseId} - AND l.deleted = 0 + AND l.zone_code = #{zoneCode} @@ -39,8 +43,8 @@ public interface LocationMapper extends BaseMapper { """) Page selectLocationPage(Page page, - @Param("tenantId") long tenantId, - @Param("warehouseId") long warehouseId, + @Param("tenantId") Long tenantId, + @Param("warehouseId") Long warehouseId, @Param("zoneCode") String zoneCode, @Param("locationCode") String locationCode, @Param("locationType") String locationType, diff --git a/warehouse/src/main/java/com/baiyue/warehouse/repository/LocationRepository.java b/warehouse/src/main/java/com/baiyue/warehouse/repository/LocationRepository.java index 22c169d62..d81887996 100644 --- a/warehouse/src/main/java/com/baiyue/warehouse/repository/LocationRepository.java +++ b/warehouse/src/main/java/com/baiyue/warehouse/repository/LocationRepository.java @@ -18,7 +18,7 @@ import org.springframework.stereotype.Repository; import java.util.Collection; import java.util.List; -/** 强制使用租户、仓库联合条件的库位持久化边界。 */ +/** 库位持久化边界;平台分页条件可选,资源读写继续强制租户、仓库联合条件。 */ @Repository public class LocationRepository { private final LocationMapper locations; @@ -47,7 +47,7 @@ public class LocationRepository { .eq(LocationEntity::getId, id)); } - public PageResult page(long tenantId, PageQuery query) { + public PageResult page(Long tenantId, PageQuery query) { Page result = locations.selectLocationPage(new Page<>(query.page(), query.size()), tenantId, query.warehouseId(), query.zoneCode(), query.locationCode(), query.locationType(), query.status()); return PageResult.from(result); diff --git a/warehouse/src/main/java/com/baiyue/warehouse/repository/PalletRepository.java b/warehouse/src/main/java/com/baiyue/warehouse/repository/PalletRepository.java index 9576ddfd3..b3f64d68e 100644 --- a/warehouse/src/main/java/com/baiyue/warehouse/repository/PalletRepository.java +++ b/warehouse/src/main/java/com/baiyue/warehouse/repository/PalletRepository.java @@ -13,7 +13,7 @@ import org.springframework.util.StringUtils; import java.util.Collection; import java.util.List; -/** 托盘持久化仓储,所有业务查询均显式包含租户和仓库边界。 */ +/** 托盘持久化仓储;平台分页支持跨租户筛选,资源操作始终包含租户和仓库边界。 */ @Repository public class PalletRepository { private final PalletMapper mapper; @@ -22,9 +22,9 @@ public class PalletRepository { this.mapper = mapper; } - public PageResult page(long tenantId, PalletRequests.PageQuery query) { + public PageResult page(Long tenantId, PalletRequests.PageQuery query) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper() - .eq(PalletEntity::getTenantId, tenantId) + .eq(tenantId != null, PalletEntity::getTenantId, tenantId) .eq(query.warehouseId() != null, PalletEntity::getWarehouseId, query.warehouseId()) .like(StringUtils.hasText(query.palletCode()), PalletEntity::getPalletCode, query.palletCode()) .eq(StringUtils.hasText(query.palletType()), PalletEntity::getPalletType, query.palletType()) diff --git a/warehouse/src/main/java/com/baiyue/warehouse/service/LocationApplicationService.java b/warehouse/src/main/java/com/baiyue/warehouse/service/LocationApplicationService.java index e4ff3b52a..d17c2f6af 100644 --- a/warehouse/src/main/java/com/baiyue/warehouse/service/LocationApplicationService.java +++ b/warehouse/src/main/java/com/baiyue/warehouse/service/LocationApplicationService.java @@ -16,7 +16,7 @@ import org.springframework.transaction.annotation.Transactional; import java.util.List; -/** 库位基础资料应用服务,负责 CRUD、租户仓库隔离和启停/删除约束。 */ +/** 库位基础资料应用服务,负责平台分页、CRUD、租户仓库隔离和启停/删除约束。 */ @Service public class LocationApplicationService { private final LocationRepository repository; @@ -25,8 +25,7 @@ public class LocationApplicationService { this.repository = repository; } - public PageResult page(long tenantId, PageQuery query) { - requireWarehouse(tenantId, query.warehouseId(), false); + public PageResult page(Long tenantId, PageQuery query) { return repository.page(tenantId, query); } diff --git a/warehouse/src/main/java/com/baiyue/warehouse/service/PalletApplicationService.java b/warehouse/src/main/java/com/baiyue/warehouse/service/PalletApplicationService.java index 1cdf915d0..fa3941b16 100644 --- a/warehouse/src/main/java/com/baiyue/warehouse/service/PalletApplicationService.java +++ b/warehouse/src/main/java/com/baiyue/warehouse/service/PalletApplicationService.java @@ -23,7 +23,7 @@ public class PalletApplicationService { this.warehouseRepository = warehouseRepository; } - public PageResult page(long tenantId, PalletRequests.PageQuery query) { + public PageResult page(Long tenantId, PalletRequests.PageQuery query) { PageResult source = repository.page(tenantId, query); return new PageResult<>(source.records().stream().map(PalletApplicationService::view).toList(), source.page(), source.size(), source.total(), source.pages()); diff --git a/warehouse/src/test/java/com/baiyue/warehouse/controller/PlatformLocationControllerContractTest.java b/warehouse/src/test/java/com/baiyue/warehouse/controller/PlatformLocationControllerContractTest.java index 2f540deb5..fcb4d2d1b 100644 --- a/warehouse/src/test/java/com/baiyue/warehouse/controller/PlatformLocationControllerContractTest.java +++ b/warehouse/src/test/java/com/baiyue/warehouse/controller/PlatformLocationControllerContractTest.java @@ -9,6 +9,7 @@ import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import java.lang.annotation.Annotation; import java.lang.reflect.Method; @@ -29,13 +30,30 @@ class PlatformLocationControllerContractTest { assertMethod("delete", DeleteMapping.class, "platform:location:delete"); } + @Test + void 分页查询的企业租户参数可选但写接口仍然必填() { + Method page = method("page"); + assertThat(page.getParameterTypes()[1]).isEqualTo(Long.class); + assertThat(page.getParameters()[1].getAnnotation(RequestParam.class).required()).isFalse(); + + for (String writeMethod : new String[] {"create", "update", "changeStatus", "delete"}) { + Method method = method(writeMethod); + assertThat(method.getParameterTypes()[1]).isEqualTo(long.class); + assertThat(method.getParameters()[1].getAnnotation(RequestParam.class).required()).isTrue(); + } + } + private static void assertMethod(String name, Class mapping, String permission) { - Method method = java.util.Arrays.stream(PlatformLocationController.class.getDeclaredMethods()) - .filter(candidate -> candidate.getName().equals(name)) - .findFirst().orElseThrow(); + Method method = method(name); assertThat(method.getAnnotation(mapping)).isNotNull(); assertThat(method.getAnnotation(PreAuthorize.class).value()) .isEqualTo("hasAuthority('" + permission + "')"); assertThat(method.getAnnotation(Operation.class).summary()).isNotBlank(); } + + private static Method method(String name) { + return java.util.Arrays.stream(PlatformLocationController.class.getDeclaredMethods()) + .filter(candidate -> candidate.getName().equals(name)) + .findFirst().orElseThrow(); + } } diff --git a/warehouse/src/test/java/com/baiyue/warehouse/controller/PlatformPalletControllerContractTest.java b/warehouse/src/test/java/com/baiyue/warehouse/controller/PlatformPalletControllerContractTest.java index b1d0a3183..b187858ef 100644 --- a/warehouse/src/test/java/com/baiyue/warehouse/controller/PlatformPalletControllerContractTest.java +++ b/warehouse/src/test/java/com/baiyue/warehouse/controller/PlatformPalletControllerContractTest.java @@ -7,6 +7,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import java.lang.annotation.Annotation; import java.lang.reflect.Method; @@ -24,15 +25,28 @@ class PlatformPalletControllerContractTest { assertMethod("create", PostMapping.class, "platform:pallet:create"); assertMethod("update", PutMapping.class, "platform:pallet:edit"); assertMethod("delete", DeleteMapping.class, "platform:pallet:delete"); + Method page = method("page"); + assertThat(page.getParameterTypes()[1]).isEqualTo(Long.class); + assertThat(page.getParameters()[1].getAnnotation(RequestParam.class).required()).isFalse(); + + for (String writeMethod : new String[]{"detail", "create", "update", "delete"}) { + Method method = method(writeMethod); + assertThat(method.getParameterTypes()[1]).isEqualTo(long.class); + assertThat(method.getParameters()[1].getAnnotation(RequestParam.class).required()).isTrue(); + } assertThat(Arrays.stream(PlatformPalletController.class.getDeclaredMethods()) .noneMatch(method -> method.getName().equals("status"))).isTrue(); } private static void assertMethod(String name, Class mapping, String authority) { - Method method = Arrays.stream(PlatformPalletController.class.getDeclaredMethods()) - .filter(candidate -> candidate.getName().equals(name)).findFirst().orElseThrow(); + Method method = method(name); assertThat(method.getAnnotation(mapping)).isNotNull(); assertThat(method.getAnnotation(PreAuthorize.class).value()) .isEqualTo("hasAuthority('" + authority + "')"); } + + private static Method method(String name) { + return Arrays.stream(PlatformPalletController.class.getDeclaredMethods()) + .filter(candidate -> candidate.getName().equals(name)).findFirst().orElseThrow(); + } } diff --git a/warehouse/src/test/java/com/baiyue/warehouse/mapper/LocationMapperContractTest.java b/warehouse/src/test/java/com/baiyue/warehouse/mapper/LocationMapperContractTest.java index 2c5fdddb6..5f7f95484 100644 --- a/warehouse/src/test/java/com/baiyue/warehouse/mapper/LocationMapperContractTest.java +++ b/warehouse/src/test/java/com/baiyue/warehouse/mapper/LocationMapperContractTest.java @@ -1,5 +1,6 @@ package com.baiyue.warehouse.mapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Select; import org.junit.jupiter.api.Test; @@ -9,6 +10,22 @@ import java.lang.reflect.Method; import static org.assertj.core.api.Assertions.assertThat; class LocationMapperContractTest { + @Test + void 平台分页的企业和仓库条件均为可选且仍返回仓库快照() throws NoSuchMethodException { + Method page = LocationMapper.class.getMethod("selectLocationPage", Page.class, + Long.class, Long.class, String.class, String.class, String.class, String.class); + String pageSql = sql(page.getAnnotation(Select.class).value()); + + assertThat(pageSql) + .contains("SELECT l.*, w.warehouse_code, w.warehouse_name") + .contains("w.id = l.warehouse_id AND w.tenant_id = l.tenant_id") + .contains("") + .contains("AND l.tenant_id = #{tenantId}") + .contains("") + .contains("AND l.warehouse_id = #{warehouseId}") + .contains("ORDER BY l.created_at DESC, l.id DESC"); + } + @Test void 可用库位按库区和库位数字自然顺序临近分配() throws NoSuchMethodException { String selectSql = sql(LocationMapper.class.getMethod( diff --git a/warehouse/src/test/java/com/baiyue/warehouse/repository/PalletRepositoryTest.java b/warehouse/src/test/java/com/baiyue/warehouse/repository/PalletRepositoryTest.java new file mode 100644 index 000000000..1e79cc23d --- /dev/null +++ b/warehouse/src/test/java/com/baiyue/warehouse/repository/PalletRepositoryTest.java @@ -0,0 +1,78 @@ +package com.baiyue.warehouse.repository; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baiyue.warehouse.dto.PalletRequests; +import com.baiyue.warehouse.entity.PalletEntity; +import com.baiyue.warehouse.mapper.PalletMapper; +import org.apache.ibatis.builder.MapperBuilderAssistant; +import org.apache.ibatis.session.Configuration; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import java.util.Collection; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class PalletRepositoryTest { + @BeforeAll + static void 初始化实体字段缓存() { + TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new Configuration(), "test"), PalletEntity.class); + } + + @Test + void 平台分页默认不限制企业和仓库() { + Collection values = queryValues(null, null); + assertThat(values).isEmpty(); + } + + @Test + void 平台分页可单独按企业筛选() { + Collection values = queryValues(10001L, null); + assertThat(values).containsExactly(10001L); + } + + @Test + void 平台分页可同时按企业和仓库筛选() { + Collection values = queryValues(10001L, 20L); + assertThat(values).containsExactlyInAnyOrder(10001L, 20L); + } + + @Test + void 平台分页可按全局唯一仓库筛选且写查询仍保留联合边界() { + Collection pageValues = queryValues(null, 20L); + assertThat(pageValues).containsExactly(20L); + + PalletMapper mapper = mapper(); + new PalletRepository(mapper).findOwned(10001L, 20L, 30L); + @SuppressWarnings("unchecked") + ArgumentCaptor> wrapper = ArgumentCaptor.forClass(LambdaQueryWrapper.class); + verify(mapper).selectOne(wrapper.capture()); + wrapper.getValue().getSqlSegment(); + assertThat(wrapper.getValue().getParamNameValuePairs().values()) + .containsExactlyInAnyOrder(10001L, 20L, 30L); + } + + private static Collection queryValues(Long tenantId, Long warehouseId) { + PalletMapper mapper = mapper(); + new PalletRepository(mapper).page(tenantId, + new PalletRequests.PageQuery(1, 20, warehouseId, null, null)); + @SuppressWarnings("unchecked") + ArgumentCaptor> wrapper = ArgumentCaptor.forClass(LambdaQueryWrapper.class); + verify(mapper).selectPage(any(Page.class), wrapper.capture()); + wrapper.getValue().getSqlSegment(); + return wrapper.getValue().getParamNameValuePairs().values(); + } + + private static PalletMapper mapper() { + PalletMapper mapper = mock(PalletMapper.class); + when(mapper.selectPage(any(Page.class), any())).thenAnswer(invocation -> invocation.getArgument(0)); + return mapper; + } +} diff --git a/warehouse/src/test/java/com/baiyue/warehouse/service/LocationApplicationServiceTest.java b/warehouse/src/test/java/com/baiyue/warehouse/service/LocationApplicationServiceTest.java index c8671da6d..23a94a9ab 100644 --- a/warehouse/src/test/java/com/baiyue/warehouse/service/LocationApplicationServiceTest.java +++ b/warehouse/src/test/java/com/baiyue/warehouse/service/LocationApplicationServiceTest.java @@ -1,6 +1,7 @@ package com.baiyue.warehouse.service; import com.baiyue.common.core.exception.BizException; +import com.baiyue.warehouse.dto.LocationDtos.PageQuery; import com.baiyue.warehouse.dto.LocationDtos.SaveRequest; import com.baiyue.warehouse.entity.LocationEntity; import com.baiyue.warehouse.entity.WarehouseEntity; @@ -14,9 +15,44 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; class LocationApplicationServiceTest { + @Test + void 平台分页不传企业和仓库时查询全部库位() { + LocationRepository repository = mock(LocationRepository.class); + LocationApplicationService service = new LocationApplicationService(repository); + PageQuery query = pageQuery(null); + + service.page(null, query); + + verify(repository).page(null, query); + verifyNoMoreInteractions(repository); + } + + @Test + void 平台分页可只按企业筛选全部仓库() { + LocationRepository repository = mock(LocationRepository.class); + LocationApplicationService service = new LocationApplicationService(repository); + PageQuery query = pageQuery(null); + + service.page(10001L, query); + + verify(repository).page(10001L, query); + } + + @Test + void 平台分页可同时按企业和仓库筛选() { + LocationRepository repository = mock(LocationRepository.class); + LocationApplicationService service = new LocationApplicationService(repository); + PageQuery query = pageQuery(88L); + + service.page(10001L, query); + + verify(repository).page(10001L, query); + } + @Test void 新增时仓库必须属于当前企业租户() { LocationRepository repository = mock(LocationRepository.class); @@ -79,6 +115,10 @@ class LocationApplicationServiceTest { "PALLET", new BigDecimal("500"), status, "备注", version); } + private static PageQuery pageQuery(Long warehouseId) { + return new PageQuery(1, 20, warehouseId, null, null, null, null); + } + private static WarehouseEntity warehouse(String status) { WarehouseEntity warehouse = new WarehouseEntity(); warehouse.setId(88L); diff --git a/warehouse/src/test/java/com/baiyue/warehouse/service/PalletApplicationServiceTest.java b/warehouse/src/test/java/com/baiyue/warehouse/service/PalletApplicationServiceTest.java index 03233e202..db6e13a46 100644 --- a/warehouse/src/test/java/com/baiyue/warehouse/service/PalletApplicationServiceTest.java +++ b/warehouse/src/test/java/com/baiyue/warehouse/service/PalletApplicationServiceTest.java @@ -1,6 +1,8 @@ package com.baiyue.warehouse.service; import com.baiyue.common.core.exception.BizException; +import com.baiyue.common.core.api.PageResult; +import com.baiyue.warehouse.dto.PalletRequests; import com.baiyue.warehouse.entity.PalletEntity; import com.baiyue.warehouse.repository.PalletRepository; import com.baiyue.warehouse.repository.WarehouseRepository; @@ -8,6 +10,7 @@ import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; class PalletApplicationServiceTest { @@ -28,6 +31,17 @@ class PalletApplicationServiceTest { assertThatThrownBy(() -> service.detail(10001, 21, 30)).isInstanceOf(BizException.class); } + @Test + void 平台分页允许不指定企业并保留分页结果() { + PalletRequests.PageQuery query = new PalletRequests.PageQuery(1, 20, null, null, null); + when(repository.page(null, query)).thenReturn(new PageResult<>(java.util.List.of(pallet("AVAILABLE")), + 1, 20, 1, 1)); + + org.assertj.core.api.Assertions.assertThat(service.page(null, query).records()) + .singleElement().extracting("tenantId", "warehouseId").containsExactly(10001L, 20L); + verify(repository).page(null, query); + } + private static PalletEntity pallet(String status) { PalletEntity entity = new PalletEntity(); entity.setId(30L);