From cd540f9b3ef63ff9d6464f4803704e045380eb12 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Tue, 4 Aug 2026 14:15:55 +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 --- .../auth/controller/AuthController.java | 2 +- iam/AGENTS.md | 4 ++-- .../controller/MerchantTenantController.java | 2 +- .../com/baiyue/iam/service/TenantService.java | 4 ++-- iam/src/main/resources/openapi/iam-v1.yaml | 2 +- .../baiyue/iam/service/TenantServiceTest.java | 21 +++++++++++-------- 6 files changed, 19 insertions(+), 16 deletions(-) diff --git a/auth/src/main/java/com/baiyue/auth/controller/AuthController.java b/auth/src/main/java/com/baiyue/auth/controller/AuthController.java index 72c8edf2b..601ecb42b 100644 --- a/auth/src/main/java/com/baiyue/auth/controller/AuthController.java +++ b/auth/src/main/java/com/baiyue/auth/controller/AuthController.java @@ -47,7 +47,7 @@ public class AuthController { @SecurityRequirements @PostMapping("/register") @Operation(summary = "Merchant registration / 客户注册", - description = "Create a login account and an enterprise profile awaiting four required qualification files.") + description = "Create a login account and an enterprise profile awaiting the required business license and legal-person ID; other qualification files are optional.") public ApiResponse register(@Valid @RequestBody MerchantRegisterRequest request) { return ApiResponse.success(registrationService.register(request)); } diff --git a/iam/AGENTS.md b/iam/AGENTS.md index ec558a648..b79bfdae9 100644 --- a/iam/AGENTS.md +++ b/iam/AGENTS.md @@ -35,7 +35,7 @@ - 平台客户档案维护使用 `PlatformTenantController` 的列表、详情、新建、编辑、启停接口;详情和维护仅接受 `tenant_type=MERCHANT`。PENDING/REJECTED 只能由审核流程变更,不得由启停接口绕过。 - 平台代客户上传资质的公开 multipart 入口在 File 服务;IAM 的 `/internal/iam/platform-qualifications` 只登记元数据。当前仅 `PROFILE_REQUIRED/REJECTED` 可上传,同类型重复需显式 `replace=true`;替换时旧记录标记 REJECTED 并把旧 `documentUrl` 返回 File 清理。 - 平台资质登记在事务内通过 `ProfileResourceRepository.findQualificationsForUpdate` 锁定目标租户现有资质,避免并发上传或替换产生多个有效同类型记录;该查询不得改回普通无锁列表。 -- 企业送审和审核通过都要求 `BUSINESS_LICENSE`、`LEGAL_PERSON_ID`、`FREIGHT_FORWARDER_CERTIFICATE` 与 `BANK_ACCOUNT_LICENSE` 四类资质均为 `PENDING`;审核由 `TenantService.review()` 同事务更新审核单、租户和所有 PENDING 资质,行锁负责并发防重。 +- 企业送审和审核通过只要求 `BUSINESS_LICENSE`、`LEGAL_PERSON_ID` 两类必填资质均为 `PENDING`;`FREIGHT_FORWARDER_CERTIFICATE` 与 `BANK_ACCOUNT_LICENSE` 为选填,已上传时仍随审核统一处理。审核由 `TenantService.review()` 同事务更新审核单、租户和所有 PENDING 资质,行锁负责并发防重。 - 平台客户列表使用 `platform:customer:list` 并脱敏电话、邮箱;完整详情、平台地址和平台资质只读查询统一使用独立的 `platform:customer:view`,不得复用 `platform:customer:review`。 - 权限资源的 code/type 不可变;只有 `built_in=0` 的自定义资源可编辑、启停或删除,删除前必须无子节点且无角色绑定。 @@ -47,7 +47,7 @@ - 平台内部用户的公开主键是 `membershipId`;列表必须回显 `roleIds`,密码只允许通过 DTO 转交 Auth,禁止进入 IAM 实体、日志或审计快照。 - 平台内部用户角色分配使用 `PUT /api/platform/v1/iam/internal-users/{membershipId}/roles`,仅允许平台租户下已启用的平台角色;空 `roleIds` 表示清空。 - 商户成员新增、编辑采用字段级授权:基础资料分别校验 `merchant:member:create`、`merchant:member:edit`;创建时非空角色、编辑时角色集合发生任何变化(含空数组清空)还必须校验 `merchant:member:grant-role`。详情和列表实时回显当前 `roleIds`,比较和写入均限定当前租户。 -- 企业 `ACTIVE` 只表示平台审核已通过;下单资格还必须四类必填资质全部为 `APPROVED`。任意一类缺失、驳回、已替换或过期都必须由服务端阻止提交订单。 +- 企业 `ACTIVE` 只表示平台审核已通过;下单资格还必须营业执照和法人身份证两类必填资质均为 `APPROVED`。任一必填资质缺失、驳回、已替换或过期都必须由服务端阻止提交订单;选填资质不影响下单资格。 - SQL 增量保持可重复部署顺序,修改 Entity 时同步检查 Mapper、Repository、DTO 与种子数据。`phase1-platform-master-data-and-permission-management.sql` 必须在 `phase1-admin-rbac.sql` 和 `rbac-role-management-v2.sql` 之后执行;它标记既有系统资源并补齐平台客户、快递公司、往来单位与权限资源管理权限。 - 存量库新增一期客户档案字段时执行 `phase1-customer-profile.sql`;该脚本只扩展 `iam_tenant`,可重复执行,不得把运单数、财务金额、套餐计费、代客登录或物理删除能力并入 IAM。 - `deploy/scripts/initialize-test-databases.sh` 的 IAM 客户档案和 Warehouse 主数据脚本顺序必须与当前文件保持一致;测试环境正式增量仍以 `../deploy/sql/README.md` 指定的统一迁移为准。 diff --git a/iam/src/main/java/com/baiyue/iam/controller/MerchantTenantController.java b/iam/src/main/java/com/baiyue/iam/controller/MerchantTenantController.java index e3fe3d048..e41b8daa4 100644 --- a/iam/src/main/java/com/baiyue/iam/controller/MerchantTenantController.java +++ b/iam/src/main/java/com/baiyue/iam/controller/MerchantTenantController.java @@ -72,7 +72,7 @@ public class MerchantTenantController { @PostMapping("/tenant/qualifications/submit-review") @PreAuthorize("hasAuthority('merchant:qualification:upload')") - @Operation(summary = "提交当前企业资质审核", description = "校验四类必填资质齐全后创建审核单。上传或替换文件不会自动送审。") + @Operation(summary = "提交当前企业资质审核", description = "校验营业执照和法人身份证齐全后创建审核单;货代资质证书和银行开户许可证选填。上传或替换文件不会自动送审。") public ApiResponse submitQualificationReview(@AuthenticationPrincipal AuthenticatedUser user) { return ApiResponse.success(tenantService.submitMerchantReview(TenantGuard.tenant(user, "MERCHANT"))); } 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 43f345701..caa60f987 100644 --- a/iam/src/main/java/com/baiyue/iam/service/TenantService.java +++ b/iam/src/main/java/com/baiyue/iam/service/TenantService.java @@ -35,7 +35,7 @@ import java.util.Set; @Service public class TenantService { private static final Set REQUIRED_QUALIFICATION_TYPES = Set.of( - "BUSINESS_LICENSE", "LEGAL_PERSON_ID", "FREIGHT_FORWARDER_CERTIFICATE", "BANK_ACCOUNT_LICENSE"); + "BUSINESS_LICENSE", "LEGAL_PERSON_ID"); @Resource private TenantRepository repository; @Resource @@ -273,7 +273,7 @@ public class TenantService { var qualifications = profileRepository.findQualifications(tenantId); if (!hasAllPendingQualifications(qualifications)) { throw new BizException(ResultCode.BAD_REQUEST, - "Business license, legal-person ID, freight-forwarder certificate and bank account license are required / 营业执照、法人身份证、货代资质证书和银行开户许可证均为必填资质"); + "Business license and legal-person ID are required / 营业执照和法人身份证为必填资质"); } IamTenantReview review = new IamTenantReview(); review.setId(IdWorker.getId()); review.setTenantId(tenantId); diff --git a/iam/src/main/resources/openapi/iam-v1.yaml b/iam/src/main/resources/openapi/iam-v1.yaml index 7ecc1c6f2..a757efab6 100644 --- a/iam/src/main/resources/openapi/iam-v1.yaml +++ b/iam/src/main/resources/openapi/iam-v1.yaml @@ -481,7 +481,7 @@ paths: post: tags: [IAM Resources / IAM 资源] summary: Submit enterprise qualifications for review / 提交企业资质审核 - description: Uploading or replacing a qualification keeps the tenant in draft state. This endpoint creates the review only after all four required qualification types have been provided. / 上传或替换资质不会自动送审;四类必填资质齐全后由此接口创建审核单。 + description: Uploading or replacing a qualification keeps the tenant in draft state. This endpoint creates the review after the business license and legal-person ID have been provided; the freight-forwarder certificate and bank account license are optional. / 上传或替换资质不会自动送审;营业执照和法人身份证齐全后由此接口创建审核单,货代资质证书和银行开户许可证选填。 responses: '200': {$ref: '#/components/responses/TenantSuccess'} '400': {$ref: '#/components/responses/BadRequest'} 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 535486d6b..66f336fdd 100644 --- a/iam/src/test/java/com/baiyue/iam/service/TenantServiceTest.java +++ b/iam/src/test/java/com/baiyue/iam/service/TenantServiceTest.java @@ -134,7 +134,7 @@ class TenantServiceTest { } @Test - void explicitSubmissionRequiresAllPrototypeQualificationsAndCreatesReview() { + void explicitSubmissionRequiresOnlyBusinessLicenseAndLegalPersonIdAndCreatesReview() { TenantRepository repository = mock(TenantRepository.class); ProfileResourceRepository profileRepository = mock(ProfileResourceRepository.class); TenantService service = new TenantService(); @@ -143,8 +143,7 @@ class TenantServiceTest { IamTenant tenant = merchant(10001L, "PROFILE_REQUIRED"); when(repository.findTenantForUpdate(10001L)).thenReturn(tenant); when(profileRepository.findQualifications(10001L)).thenReturn(List.of( - qualification("BUSINESS_LICENSE"), qualification("LEGAL_PERSON_ID"), - qualification("FREIGHT_FORWARDER_CERTIFICATE"), qualification("BANK_ACCOUNT_LICENSE"))); + qualification("BUSINESS_LICENSE"), qualification("LEGAL_PERSON_ID"))); var submitted = service.submitMerchantReview(10001L); @@ -162,7 +161,7 @@ class TenantServiceTest { ReflectionTestUtils.setField(service, "profileRepository", profileRepository); when(repository.findTenantForUpdate(10001L)).thenReturn(merchant(10001L, "PROFILE_REQUIRED")); when(profileRepository.findQualifications(10001L)).thenReturn(List.of( - qualification("BUSINESS_LICENSE"), qualification("LEGAL_PERSON_ID"))); + qualification("BUSINESS_LICENSE"))); BizException exception = assertThrows(BizException.class, () -> service.submitMerchantReview(10001L)); @@ -205,8 +204,7 @@ class TenantServiceTest { when(repository.findReviewForUpdate(71L)).thenReturn(review); when(repository.findTenant(10001L)).thenReturn(tenant); when(profileRepository.findQualifications(10001L)).thenReturn(List.of( - qualification("BUSINESS_LICENSE"), qualification("LEGAL_PERSON_ID"), - qualification("FREIGHT_FORWARDER_CERTIFICATE"), qualification("BANK_ACCOUNT_LICENSE"))); + qualification("BUSINESS_LICENSE"), qualification("LEGAL_PERSON_ID"))); when(provisioning.initialize(any())).thenReturn( TenantAdministratorProvisioningService.ProvisioningResult.created( 81L, "merchant_10001", "Password1234")); @@ -285,7 +283,7 @@ class TenantServiceTest { } @Test - void 企业激活不等于具备下单资格仍需四类资质全部审核通过() { + void 企业激活后仅需两类必填资质审核通过即可下单() { TenantRepository repository = mock(TenantRepository.class); ProfileResourceRepository profileRepository = mock(ProfileResourceRepository.class); TenantService service = new TenantService(); @@ -295,8 +293,7 @@ class TenantServiceTest { when(profileRepository.findQualifications(10001L)).thenReturn( List.of(approvedQualification("BUSINESS_LICENSE")), List.of(approvedQualification("BUSINESS_LICENSE"), approvedQualification("LEGAL_PERSON_ID"), - approvedQualification("FREIGHT_FORWARDER_CERTIFICATE"), - approvedQualification("BANK_ACCOUNT_LICENSE"))); + rejectedQualification("FREIGHT_FORWARDER_CERTIFICATE"))); var withoutQualification = service.orderEligibility(10001L); var withQualification = service.orderEligibility(10001L); @@ -332,6 +329,12 @@ class TenantServiceTest { return item; } + private IamTenantQualification rejectedQualification(String type) { + IamTenantQualification item = qualification(type); + item.setStatus("REJECTED"); + return item; + } + private IamTenantReview review(long id, long tenantId) { IamTenantReview review = new IamTenantReview(); review.setId(id); review.setTenantId(tenantId); review.setStatus("PENDING");