优化
This commit is contained in:
@@ -13,7 +13,7 @@ import com.baiyue.iam.dto.TenantDtos.ApprovalResponse;
|
||||
import com.baiyue.iam.dto.TenantDtos.CreationResponse;
|
||||
import com.baiyue.iam.dto.TenantDtos.CustomerImpersonationRequest;
|
||||
import com.baiyue.iam.dto.TenantDtos.CustomerImpersonationResponse;
|
||||
import com.baiyue.iam.entity.IamTenantReview;
|
||||
import com.baiyue.iam.dto.TenantDtos.PendingReviewResponse;
|
||||
import com.baiyue.iam.service.TenantGuard;
|
||||
import com.baiyue.iam.service.TenantService;
|
||||
import com.baiyue.iam.service.MemberService;
|
||||
@@ -179,7 +179,7 @@ public class PlatformTenantController {
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "Review list returned / 查询成功"),
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "Platform scope required / 需要平台端权限域")
|
||||
})
|
||||
public ApiResponse<List<IamTenantReview>> reviews(@AuthenticationPrincipal AuthenticatedUser user) {
|
||||
public ApiResponse<List<PendingReviewResponse>> reviews(@AuthenticationPrincipal AuthenticatedUser user) {
|
||||
TenantGuard.tenant(user, "PLATFORM");
|
||||
return ApiResponse.success(service.pendingReviews());
|
||||
}
|
||||
|
||||
@@ -101,6 +101,21 @@ public final class TenantDtos {
|
||||
@Schema(description = "Approval or rejection comment / 审核意见", example = "资料完整,审核通过") String comment
|
||||
) { }
|
||||
|
||||
@Schema(name = "PendingEnterpriseReviewResponse", description = "Pending enterprise review with the customer summary required by platform reviewers / 待审核企业及客户摘要")
|
||||
public record PendingReviewResponse(
|
||||
@Schema(description = "Review ID / 审核单 ID", example = "190000000000000001") Long id,
|
||||
@Schema(description = "Customer ID / 客户 ID", example = "190000000000000002") Long tenantId,
|
||||
@Schema(description = "Review type / 审核类型", example = "REGISTER") String reviewType,
|
||||
@Schema(description = "Review status / 审核状态", example = "PENDING") String status,
|
||||
@Schema(description = "Customer name / 客户名称", example = "深圳虫虫出海科技有限公司") String tenantName,
|
||||
@Schema(description = "Customer code / 客户编号", example = "TEN190000000000000002") String tenantCode,
|
||||
@Schema(description = "Primary contact / 联系人", example = "张三") String contactName,
|
||||
@Schema(description = "Contact phone / 联系电话", example = "13800138000") String contactPhone,
|
||||
@Schema(description = "Contact email / 联系邮箱", example = "ops@example.com") String contactEmail,
|
||||
@Schema(description = "Review submission time / 提交审核时间") LocalDateTime submittedAt,
|
||||
@Schema(description = "Review comment / 审核意见", nullable = true) String reviewComment
|
||||
) { }
|
||||
|
||||
@Schema(name = "TenantApprovalResponse", description = "Enterprise approval result and one-time administrator credentials / 企业审核结果及一次性管理员凭据")
|
||||
public record ApprovalResponse(
|
||||
@Schema(description = "Tenant ID / 企业租户 ID", example = "190000000000000001") Long tenantId,
|
||||
|
||||
@@ -12,6 +12,7 @@ import org.springframework.stereotype.Repository;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* 租户仓储,封装租户及审核单的唯一性校验、查询、创建、更新和待审列表操作。
|
||||
@@ -48,6 +49,12 @@ public class TenantRepository {
|
||||
}
|
||||
|
||||
public IamTenant findTenant(Long tenantId) { return tenantMapper.selectById(tenantId); }
|
||||
public List<IamTenant> findMerchantsByIds(Collection<Long> tenantIds) {
|
||||
if (tenantIds == null || tenantIds.isEmpty()) return List.of();
|
||||
return tenantMapper.selectList(new LambdaQueryWrapper<IamTenant>()
|
||||
.in(IamTenant::getId, tenantIds)
|
||||
.eq(IamTenant::getTenantType, "MERCHANT"));
|
||||
}
|
||||
/**
|
||||
* Serializes the draft qualification workflow for one enterprise. The file service and the
|
||||
* merchant submit action run in separate requests, so qualification-row locks alone cannot
|
||||
|
||||
@@ -22,6 +22,7 @@ import com.baiyue.iam.dto.OnboardingDtos.OrderEligibilityResponse;
|
||||
import com.baiyue.iam.dto.TenantDtos.RegisterRequest;
|
||||
import com.baiyue.iam.dto.TenantDtos.TenantResponse;
|
||||
import com.baiyue.iam.dto.TenantDtos.FinanceCustomerSummary;
|
||||
import com.baiyue.iam.dto.TenantDtos.PendingReviewResponse;
|
||||
import com.baiyue.iam.dto.TenantDtos.UpdateProfileRequest;
|
||||
import com.baiyue.iam.dto.TenantDtos.UpdateTenantRequest;
|
||||
import com.baiyue.iam.dto.TenantDtos.ChangeTenantStatusRequest;
|
||||
@@ -40,7 +41,10 @@ import feign.FeignException;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 租户生命周期应用服务,编排商户入驻、资料维护、审核流转和租户状态变更事务。
|
||||
@@ -189,8 +193,17 @@ public class TenantService {
|
||||
return new CreationResponse(response(tenant), administrator.username(), administrator.temporaryPassword());
|
||||
}
|
||||
|
||||
public List<IamTenantReview> pendingReviews() {
|
||||
return repository.findPendingReviews();
|
||||
public List<PendingReviewResponse> pendingReviews() {
|
||||
List<IamTenantReview> reviews = repository.findPendingReviews();
|
||||
if (reviews.isEmpty()) return List.of();
|
||||
Map<Long, IamTenant> tenants = repository.findMerchantsByIds(reviews.stream()
|
||||
.map(IamTenantReview::getTenantId)
|
||||
.collect(Collectors.toSet()))
|
||||
.stream()
|
||||
.collect(Collectors.toMap(IamTenant::getId, Function.identity()));
|
||||
return reviews.stream()
|
||||
.map(review -> pendingReviewResponse(review, tenants.get(review.getTenantId())))
|
||||
.toList();
|
||||
}
|
||||
|
||||
public PageResult<TenantResponse> pageMerchants(long page, long size, String keyword, String status) {
|
||||
@@ -497,6 +510,17 @@ public class TenantService {
|
||||
);
|
||||
}
|
||||
|
||||
private PendingReviewResponse pendingReviewResponse(IamTenantReview review, IamTenant tenant) {
|
||||
return new PendingReviewResponse(
|
||||
review.getId(), review.getTenantId(), review.getReviewType(), review.getStatus(),
|
||||
tenant == null ? null : tenant.getTenantName(),
|
||||
tenant == null ? null : tenant.getTenantCode(),
|
||||
tenant == null ? null : tenant.getContactName(),
|
||||
tenant == null ? null : tenant.getContactPhone(),
|
||||
tenant == null ? null : tenant.getContactEmail(),
|
||||
review.getCreatedAt(), review.getReviewComment());
|
||||
}
|
||||
|
||||
private TenantResponse summaryResponse(IamTenant tenant) {
|
||||
TenantResponse response = response(tenant);
|
||||
return new TenantResponse(response.id(), response.tenantCode(), response.tenantName(), response.status(),
|
||||
|
||||
@@ -259,7 +259,11 @@ paths:
|
||||
summary: List pending tenant reviews / 查询待审核企业
|
||||
description: Return enterprise registrations waiting for a platform decision. / 返回等待平台处理的企业注册审核记录。
|
||||
responses:
|
||||
'200': {$ref: '#/components/responses/Success'}
|
||||
'200':
|
||||
description: 待审核企业及客户摘要查询成功
|
||||
content:
|
||||
application/json:
|
||||
schema: {$ref: '#/components/schemas/PendingEnterpriseReviewListApiResponse'}
|
||||
'403': {$ref: '#/components/responses/Forbidden'}
|
||||
/api/platform/v1/iam/reviews/{id}/approve:
|
||||
post:
|
||||
@@ -903,6 +907,29 @@ components:
|
||||
- type: object
|
||||
properties:
|
||||
data: {$ref: '#/components/schemas/TenantApprovalResponse'}
|
||||
PendingEnterpriseReviewListApiResponse:
|
||||
allOf:
|
||||
- {$ref: '#/components/schemas/ApiResponse'}
|
||||
- type: object
|
||||
properties:
|
||||
data:
|
||||
type: array
|
||||
items: {$ref: '#/components/schemas/PendingEnterpriseReviewResponse'}
|
||||
PendingEnterpriseReviewResponse:
|
||||
type: object
|
||||
required: [id, tenantId, reviewType, status, tenantName, tenantCode, contactName, submittedAt]
|
||||
properties:
|
||||
id: {type: string, pattern: '^\d+$', description: 审核单 ID}
|
||||
tenantId: {type: string, pattern: '^\d+$', description: 客户 ID}
|
||||
reviewType: {type: string, example: REGISTER}
|
||||
status: {type: string, enum: [PENDING]}
|
||||
tenantName: {type: string, description: 客户名称}
|
||||
tenantCode: {type: string, description: 客户编号}
|
||||
contactName: {type: string, description: 联系人}
|
||||
contactPhone: {type: string, nullable: true, description: 联系电话}
|
||||
contactEmail: {type: string, format: email, nullable: true, description: 联系邮箱}
|
||||
submittedAt: {type: string, format: date-time, description: 提交审核时间}
|
||||
reviewComment: {type: string, nullable: true, description: 审核意见}
|
||||
TenantCreationApiResponse:
|
||||
allOf:
|
||||
- {$ref: '#/components/schemas/ApiResponse'}
|
||||
|
||||
@@ -23,6 +23,10 @@ class IamOpenApiContractTest {
|
||||
.contains("PermissionCodesApiResponse:")
|
||||
.contains("TenantPageApiResponse:")
|
||||
.contains("TenantApiResponse:")
|
||||
.contains("PendingEnterpriseReviewListApiResponse:")
|
||||
.contains("PendingEnterpriseReviewResponse:")
|
||||
.contains("required: [id, tenantId, reviewType, status, tenantName, tenantCode, contactName, submittedAt]")
|
||||
.contains("submittedAt: {type: string, format: date-time, description: 提交审核时间}")
|
||||
.contains("TenantSuccess:")
|
||||
.contains("salesOwnerName:")
|
||||
.contains("paymentTermDays:")
|
||||
|
||||
@@ -46,6 +46,11 @@ class PlatformTenantControllerContractTest {
|
||||
|
||||
assertEquals("hasAuthority('platform:customer:review')",
|
||||
method.getAnnotation(PreAuthorize.class).value());
|
||||
ParameterizedType apiResponse = (ParameterizedType) method.getGenericReturnType();
|
||||
ParameterizedType list = (ParameterizedType) apiResponse.getActualTypeArguments()[0];
|
||||
assertEquals(java.util.List.class, list.getRawType());
|
||||
assertEquals(com.baiyue.iam.dto.TenantDtos.PendingReviewResponse.class,
|
||||
list.getActualTypeArguments()[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -30,6 +30,7 @@ import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.verify;
|
||||
@@ -93,6 +94,37 @@ class TenantServiceTest {
|
||||
verify(repository).pageMerchants(1, 10, "Example", "ACTIVE");
|
||||
}
|
||||
|
||||
@Test
|
||||
void pendingReviewsIncludeCustomerSummaryWithoutPerReviewQueries() {
|
||||
IamTenantReview review = review(71L, 10001L);
|
||||
review.setReviewType("REGISTER");
|
||||
review.setCreatedAt(LocalDateTime.of(2026, 8, 17, 10, 27));
|
||||
IamTenant tenant = merchant(10001L, "PENDING");
|
||||
tenant.setTenantCode("TEN10001");
|
||||
tenant.setTenantName("Example Merchant");
|
||||
tenant.setContactName("Alice");
|
||||
tenant.setContactPhone("13800138000");
|
||||
tenant.setContactEmail("alice@example.com");
|
||||
when(repository.findPendingReviews()).thenReturn(List.of(review));
|
||||
when(repository.findMerchantsByIds(Set.of(10001L))).thenReturn(List.of(tenant));
|
||||
|
||||
var result = service.pendingReviews();
|
||||
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(71L, result.get(0).id());
|
||||
assertEquals(10001L, result.get(0).tenantId());
|
||||
assertEquals("REGISTER", result.get(0).reviewType());
|
||||
assertEquals("PENDING", result.get(0).status());
|
||||
assertEquals("Example Merchant", result.get(0).tenantName());
|
||||
assertEquals("TEN10001", result.get(0).tenantCode());
|
||||
assertEquals("Alice", result.get(0).contactName());
|
||||
assertEquals("13800138000", result.get(0).contactPhone());
|
||||
assertEquals("alice@example.com", result.get(0).contactEmail());
|
||||
assertEquals(LocalDateTime.of(2026, 8, 17, 10, 27), result.get(0).submittedAt());
|
||||
verify(repository).findMerchantsByIds(Set.of(10001L));
|
||||
verify(repository, never()).findTenant(10001L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void pageFinanceCustomersKeepsFullContactSummaryForInternalFinance() {
|
||||
IamTenant tenant = merchant(10001L, "ACTIVE");
|
||||
|
||||
Reference in New Issue
Block a user