优化
This commit is contained in:
+2
-2
@@ -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` 指定的统一迁移为准。
|
||||
|
||||
@@ -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<TenantResponse> submitQualificationReview(@AuthenticationPrincipal AuthenticatedUser user) {
|
||||
return ApiResponse.success(tenantService.submitMerchantReview(TenantGuard.tenant(user, "MERCHANT")));
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ import java.util.Set;
|
||||
@Service
|
||||
public class TenantService {
|
||||
private static final Set<String> 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);
|
||||
|
||||
@@ -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'}
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user