优化
This commit is contained in:
@@ -10,6 +10,8 @@ import com.baiyue.iam.dto.OnboardingDtos.QualificationSubmissionRequest;
|
||||
import com.baiyue.iam.dto.OnboardingDtos.QualificationSubmissionResponse;
|
||||
import com.baiyue.iam.dto.OnboardingDtos.PlatformQualificationRegistrationRequest;
|
||||
import com.baiyue.iam.dto.OnboardingDtos.PlatformQualificationRegistrationResponse;
|
||||
import com.baiyue.iam.dto.OnboardingDtos.RequiredQualificationPackageRequest;
|
||||
import com.baiyue.iam.dto.OnboardingDtos.RequiredQualificationPackageResponse;
|
||||
import com.baiyue.iam.service.MerchantOnboardingService;
|
||||
import com.baiyue.iam.service.ProfileResourceService;
|
||||
import com.baiyue.iam.service.TenantService;
|
||||
@@ -73,6 +75,22 @@ public class InternalTenantController {
|
||||
return ApiResponse.success(profileResourceService.registerPlatformQualification(request));
|
||||
}
|
||||
|
||||
@PostMapping("/required-qualification-packages")
|
||||
public ApiResponse<RequiredQualificationPackageResponse> submitRequiredQualificationPackage(
|
||||
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
|
||||
@Valid @RequestBody RequiredQualificationPackageRequest request) {
|
||||
verify(token);
|
||||
return ApiResponse.success(profileResourceService.submitRequiredQualificationPackage(request));
|
||||
}
|
||||
|
||||
@PostMapping("/platform-required-qualification-packages")
|
||||
public ApiResponse<RequiredQualificationPackageResponse> registerPlatformRequiredQualificationPackage(
|
||||
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
|
||||
@Valid @RequestBody RequiredQualificationPackageRequest request) {
|
||||
verify(token);
|
||||
return ApiResponse.success(profileResourceService.registerPlatformRequiredQualificationPackage(request));
|
||||
}
|
||||
|
||||
@GetMapping("/tenants/{tenantId}/order-eligibility")
|
||||
public ApiResponse<OrderEligibilityResponse> orderEligibility(
|
||||
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
|
||||
|
||||
@@ -50,5 +50,24 @@ public final class OnboardingDtos {
|
||||
String replacedDocumentUrl
|
||||
) { }
|
||||
|
||||
public record RequiredQualificationPackageRequest(
|
||||
@NotNull Long tenantId,
|
||||
@NotBlank @Pattern(regexp = "[0-9A-Za-z]{18}") String unifiedSocialCreditCode,
|
||||
@NotBlank String businessLicenseDocumentUrl,
|
||||
LocalDate businessLicenseValidTo,
|
||||
boolean replaceBusinessLicense,
|
||||
@NotBlank String legalPersonIdDocumentUrl,
|
||||
LocalDate legalPersonIdValidTo,
|
||||
boolean replaceLegalPersonId
|
||||
) { }
|
||||
|
||||
public record RequiredQualificationPackageResponse(
|
||||
Long businessLicenseQualificationId,
|
||||
Long legalPersonIdQualificationId,
|
||||
String tenantStatus,
|
||||
String replacedBusinessLicenseDocumentUrl,
|
||||
String replacedLegalPersonIdDocumentUrl
|
||||
) { }
|
||||
|
||||
public record OrderEligibilityResponse(boolean eligible, String tenantStatus, String reason) { }
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ 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 = "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,
|
||||
|
||||
@@ -7,6 +7,8 @@ import com.baiyue.iam.dto.OnboardingDtos.QualificationSubmissionRequest;
|
||||
import com.baiyue.iam.dto.OnboardingDtos.QualificationSubmissionResponse;
|
||||
import com.baiyue.iam.dto.OnboardingDtos.PlatformQualificationRegistrationRequest;
|
||||
import com.baiyue.iam.dto.OnboardingDtos.PlatformQualificationRegistrationResponse;
|
||||
import com.baiyue.iam.dto.OnboardingDtos.RequiredQualificationPackageRequest;
|
||||
import com.baiyue.iam.dto.OnboardingDtos.RequiredQualificationPackageResponse;
|
||||
import com.baiyue.iam.dto.ProfileDtos.AddressRequest;
|
||||
import com.baiyue.iam.entity.IamTenant;
|
||||
import com.baiyue.iam.entity.IamTenantAddress;
|
||||
@@ -65,6 +67,7 @@ public class ProfileResourceService {
|
||||
throw new BizException(ResultCode.CONFLICT,
|
||||
"Qualifications can only be uploaded before review / 仅待完善或已驳回客户可上传资质");
|
||||
}
|
||||
requireOptionalQualificationType(request.qualificationType());
|
||||
String certificateNo = resolveCertificateNo(tenant, request.qualificationType(), request.certificateNo());
|
||||
List<IamTenantQualification> activeQualifications = repository.findQualificationsForUpdate(tenant.getId()).stream()
|
||||
.filter(item -> request.qualificationType().equals(item.getQualificationType())
|
||||
@@ -86,11 +89,6 @@ public class ProfileResourceService {
|
||||
request.qualificationType(), certificateNo, request.documentUrl());
|
||||
qualification.setValidTo(request.validTo());
|
||||
repository.saveQualification(qualification);
|
||||
if ("BUSINESS_LICENSE".equals(request.qualificationType())
|
||||
&& !certificateNo.equals(tenant.getUnifiedSocialCreditCode())) {
|
||||
tenant.setUnifiedSocialCreditCode(certificateNo);
|
||||
tenantRepository.saveTenant(tenant);
|
||||
}
|
||||
return new QualificationSubmissionResponse(qualification.getId(), null, tenant.getStatus(), replacedDocumentUrl);
|
||||
}
|
||||
|
||||
@@ -105,6 +103,7 @@ public class ProfileResourceService {
|
||||
if (!"PROFILE_REQUIRED".equals(tenant.getStatus()) && !"REJECTED".equals(tenant.getStatus())) {
|
||||
throw new BizException(ResultCode.CONFLICT, "Qualifications can only be uploaded before review / 仅待完善或已驳回客户可上传资质");
|
||||
}
|
||||
requireOptionalQualificationType(request.qualificationType());
|
||||
String certificateNo = resolveCertificateNo(tenant, request.qualificationType(), request.certificateNo());
|
||||
List<IamTenantQualification> activeQualifications = repository.findQualificationsForUpdate(tenant.getId()).stream()
|
||||
.filter(item -> request.qualificationType().equals(item.getQualificationType())
|
||||
@@ -126,15 +125,93 @@ public class ProfileResourceService {
|
||||
certificateNo, request.documentUrl());
|
||||
qualification.setValidTo(request.validTo());
|
||||
repository.saveQualification(qualification);
|
||||
if ("BUSINESS_LICENSE".equals(request.qualificationType())
|
||||
&& !certificateNo.equals(tenant.getUnifiedSocialCreditCode())) {
|
||||
tenant.setUnifiedSocialCreditCode(certificateNo);
|
||||
tenantRepository.saveTenant(tenant);
|
||||
}
|
||||
return new PlatformQualificationRegistrationResponse(
|
||||
qualification.getId(), null, tenant.getStatus(), replacedDocumentUrl);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@BusinessAudit(event = "TENANT_REQUIRED_QUALIFICATION_PACKAGE_UPLOAD", resourceType = "TENANT_QUALIFICATION",
|
||||
resourceId = "#request.tenantId", description = "上传企业必填资质包", captureAfter = false)
|
||||
public RequiredQualificationPackageResponse submitRequiredQualificationPackage(
|
||||
RequiredQualificationPackageRequest request) {
|
||||
return registerRequiredQualificationPackage(request, "商户在送审前替换文件");
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@BusinessAudit(event = "PLATFORM_REQUIRED_QUALIFICATION_PACKAGE_UPLOAD", resourceType = "TENANT_QUALIFICATION",
|
||||
resourceId = "#request.tenantId", description = "平台代客户上传必填资质包", captureAfter = false)
|
||||
public RequiredQualificationPackageResponse registerPlatformRequiredQualificationPackage(
|
||||
RequiredQualificationPackageRequest request) {
|
||||
return registerRequiredQualificationPackage(request, "平台在送审前替换文件");
|
||||
}
|
||||
|
||||
private RequiredQualificationPackageResponse registerRequiredQualificationPackage(
|
||||
RequiredQualificationPackageRequest request, String replacementComment) {
|
||||
IamTenant tenant = tenantRepository.findTenantForUpdate(request.tenantId());
|
||||
if (tenant == null || !"MERCHANT".equals(tenant.getTenantType())) {
|
||||
throw new BizException(ResultCode.NOT_FOUND, "Enterprise not found / 企业客户不存在");
|
||||
}
|
||||
if (!"PROFILE_REQUIRED".equals(tenant.getStatus()) && !"REJECTED".equals(tenant.getStatus())) {
|
||||
throw new BizException(ResultCode.CONFLICT,
|
||||
"Qualifications can only be uploaded before review / 仅待完善或已驳回客户可上传资质");
|
||||
}
|
||||
String creditCode = resolveCertificateNo(tenant, "BUSINESS_LICENSE", request.unifiedSocialCreditCode());
|
||||
List<IamTenantQualification> locked = repository.findQualificationsForUpdate(tenant.getId());
|
||||
List<IamTenantQualification> businessLicenses = currentQualifications(locked, "BUSINESS_LICENSE");
|
||||
List<IamTenantQualification> legalPersonIds = currentQualifications(locked, "LEGAL_PERSON_ID");
|
||||
requireReplaceAllowed(businessLicenses, request.replaceBusinessLicense(), "营业执照");
|
||||
requireReplaceAllowed(legalPersonIds, request.replaceLegalPersonId(), "法人身份证");
|
||||
|
||||
String replacedBusinessLicenseUrl = supersede(businessLicenses, replacementComment);
|
||||
String replacedLegalPersonIdUrl = supersede(legalPersonIds, replacementComment);
|
||||
IamTenantQualification businessLicense = createQualification(tenant.getId(), "BUSINESS_LICENSE",
|
||||
creditCode, request.businessLicenseDocumentUrl());
|
||||
businessLicense.setValidTo(request.businessLicenseValidTo());
|
||||
repository.saveQualification(businessLicense);
|
||||
IamTenantQualification legalPersonId = createQualification(tenant.getId(), "LEGAL_PERSON_ID",
|
||||
null, request.legalPersonIdDocumentUrl());
|
||||
legalPersonId.setValidTo(request.legalPersonIdValidTo());
|
||||
repository.saveQualification(legalPersonId);
|
||||
if (!creditCode.equals(tenant.getUnifiedSocialCreditCode())) {
|
||||
tenant.setUnifiedSocialCreditCode(creditCode);
|
||||
tenantRepository.saveTenant(tenant);
|
||||
}
|
||||
return new RequiredQualificationPackageResponse(
|
||||
businessLicense.getId(), legalPersonId.getId(), tenant.getStatus(),
|
||||
replacedBusinessLicenseUrl, replacedLegalPersonIdUrl);
|
||||
}
|
||||
|
||||
private List<IamTenantQualification> currentQualifications(
|
||||
List<IamTenantQualification> qualifications, String type) {
|
||||
return qualifications.stream()
|
||||
.filter(item -> type.equals(item.getQualificationType()) && isCurrentQualification(item))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private void requireReplaceAllowed(List<IamTenantQualification> current, boolean replace, String label) {
|
||||
if (!current.isEmpty() && !replace) {
|
||||
throw new BizException(ResultCode.CONFLICT, label + "已上传,请明确选择替换");
|
||||
}
|
||||
}
|
||||
|
||||
private String supersede(List<IamTenantQualification> current, String comment) {
|
||||
String replacedUrl = null;
|
||||
for (IamTenantQualification existing : current) {
|
||||
if (replacedUrl == null) replacedUrl = existing.getDocumentUrl();
|
||||
existing.setStatus("SUPERSEDED");
|
||||
existing.setReviewComment(comment);
|
||||
repository.saveQualification(existing);
|
||||
}
|
||||
return replacedUrl;
|
||||
}
|
||||
|
||||
private void requireOptionalQualificationType(String type) {
|
||||
if ("BUSINESS_LICENSE".equals(type) || "LEGAL_PERSON_ID".equals(type)) {
|
||||
throw new BizException(ResultCode.BAD_REQUEST,
|
||||
"Required qualifications must be uploaded as one package / 营业执照、统一社会信用代码和法人身份证必须整包上传");
|
||||
}
|
||||
}
|
||||
|
||||
private String resolveCertificateNo(IamTenant tenant, String qualificationType, String submittedCertificateNo) {
|
||||
if (!"BUSINESS_LICENSE".equals(qualificationType)) {
|
||||
return submittedCertificateNo;
|
||||
|
||||
@@ -24,7 +24,6 @@ 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;
|
||||
@@ -54,11 +53,6 @@ public class TenantService {
|
||||
)
|
||||
@Transactional
|
||||
public TenantResponse register(RegisterRequest request, long operatorId) {
|
||||
String creditCode = normalizeCreditCode(request.unifiedSocialCreditCode());
|
||||
if (creditCode != null && repository.existsByCreditCode(creditCode)) {
|
||||
throw new BizException(ResultCode.CONFLICT, "Enterprise already exists / 企业已存在");
|
||||
}
|
||||
|
||||
IamTenant tenant = new IamTenant();
|
||||
tenant.setId(IdWorker.getId());
|
||||
tenant.setTenantCode("TEN" + tenant.getId());
|
||||
@@ -67,7 +61,7 @@ public class TenantService {
|
||||
tenant.setStatus("PROFILE_REQUIRED");
|
||||
tenant.setLegalName(request.legalName());
|
||||
tenant.setLegalRepresentative(request.legalRepresentative());
|
||||
tenant.setUnifiedSocialCreditCode(creditCode);
|
||||
tenant.setUnifiedSocialCreditCode(null);
|
||||
tenant.setContactName(request.contactName());
|
||||
tenant.setContactPhone(request.contactPhone());
|
||||
tenant.setContactEmail(request.contactEmail());
|
||||
@@ -334,10 +328,6 @@ 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(),
|
||||
|
||||
@@ -608,6 +608,46 @@ paths:
|
||||
'400': {$ref: '#/components/responses/BadRequest'}
|
||||
'403': {$ref: '#/components/responses/Forbidden'}
|
||||
'409': {$ref: '#/components/responses/Conflict'}
|
||||
/internal/iam/required-qualification-packages:
|
||||
post:
|
||||
tags: [IAM Resources / IAM 资源]
|
||||
summary: 商户必填资质包登记
|
||||
description: 仅供 File Service 调用;在一个 IAM 事务中登记营业执照、统一社会信用代码和法人身份证。
|
||||
security: []
|
||||
parameters:
|
||||
- {$ref: '#/components/parameters/InternalServiceToken'}
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: {$ref: '#/components/schemas/RequiredQualificationPackageRequest'}
|
||||
responses:
|
||||
'200':
|
||||
description: 统一响应;仅 data 非空且 code 为 200 表示资质包已完整登记
|
||||
content:
|
||||
application/json:
|
||||
schema: {$ref: '#/components/schemas/RequiredQualificationPackageApiResponse'}
|
||||
'403': {$ref: '#/components/responses/Forbidden'}
|
||||
/internal/iam/platform-required-qualification-packages:
|
||||
post:
|
||||
tags: [IAM Resources / IAM 资源]
|
||||
summary: 平台代客户必填资质包登记
|
||||
description: 仅供 File Service 调用;与商户入口使用相同的三项事务保存规则。
|
||||
security: []
|
||||
parameters:
|
||||
- {$ref: '#/components/parameters/InternalServiceToken'}
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: {$ref: '#/components/schemas/RequiredQualificationPackageRequest'}
|
||||
responses:
|
||||
'200':
|
||||
description: 统一响应;仅 data 非空且 code 为 200 表示资质包已完整登记
|
||||
content:
|
||||
application/json:
|
||||
schema: {$ref: '#/components/schemas/RequiredQualificationPackageApiResponse'}
|
||||
'403': {$ref: '#/components/responses/Forbidden'}
|
||||
/internal/exports/v1/CUSTOMER_LIST/batches:
|
||||
post:
|
||||
tags: [Internal Export Provider]
|
||||
@@ -650,6 +690,11 @@ components:
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
parameters:
|
||||
InternalServiceToken:
|
||||
name: X-Internal-Service-Token
|
||||
in: header
|
||||
required: true
|
||||
schema: {type: string}
|
||||
Id:
|
||||
name: id
|
||||
in: path
|
||||
@@ -838,6 +883,33 @@ components:
|
||||
validTo: {type: string, format: date, nullable: true}
|
||||
status: {type: string, enum: [PENDING, APPROVED, REJECTED, SUPERSEDED]}
|
||||
reviewComment: {type: string}
|
||||
RequiredQualificationPackageRequest:
|
||||
type: object
|
||||
required: [tenantId, unifiedSocialCreditCode, businessLicenseDocumentUrl, replaceBusinessLicense, legalPersonIdDocumentUrl, replaceLegalPersonId]
|
||||
properties:
|
||||
tenantId: {type: string, pattern: '^\d+$'}
|
||||
unifiedSocialCreditCode: {type: string, pattern: '^[0-9A-Za-z]{18}$'}
|
||||
businessLicenseDocumentUrl: {type: string, minLength: 1}
|
||||
businessLicenseValidTo: {type: string, format: date, nullable: true}
|
||||
replaceBusinessLicense: {type: boolean}
|
||||
legalPersonIdDocumentUrl: {type: string, minLength: 1}
|
||||
legalPersonIdValidTo: {type: string, format: date, nullable: true}
|
||||
replaceLegalPersonId: {type: boolean}
|
||||
RequiredQualificationPackageResponse:
|
||||
type: object
|
||||
required: [businessLicenseQualificationId, legalPersonIdQualificationId, tenantStatus]
|
||||
properties:
|
||||
businessLicenseQualificationId: {type: string, pattern: '^\d+$'}
|
||||
legalPersonIdQualificationId: {type: string, pattern: '^\d+$'}
|
||||
tenantStatus: {type: string, enum: [PROFILE_REQUIRED, REJECTED]}
|
||||
replacedBusinessLicenseDocumentUrl: {type: string, nullable: true}
|
||||
replacedLegalPersonIdDocumentUrl: {type: string, nullable: true}
|
||||
RequiredQualificationPackageApiResponse:
|
||||
allOf:
|
||||
- {$ref: '#/components/schemas/ApiResponse'}
|
||||
- type: object
|
||||
properties:
|
||||
data: {$ref: '#/components/schemas/RequiredQualificationPackageResponse'}
|
||||
RegisterRequest:
|
||||
type: object
|
||||
required: [tenantName, legalName, legalRepresentative, contactName, contactPhone, servicePackage]
|
||||
@@ -845,7 +917,6 @@ components:
|
||||
tenantName: {type: string, example: 深圳虫虫出海科技有限公司}
|
||||
legalName: {type: string, example: 深圳虫虫出海科技有限公司}
|
||||
legalRepresentative: {type: string, maxLength: 64, example: 张三}
|
||||
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}
|
||||
|
||||
@@ -38,4 +38,28 @@ class QualificationUploadContractTest {
|
||||
assertTrue(yaml.contains(" /api/merchant/v1/iam/tenant/qualifications/submit-review:"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void internalControllerExposesBothRequiredPackageRoutes() {
|
||||
var routes = java.util.Arrays.stream(InternalTenantController.class.getDeclaredMethods())
|
||||
.map(method -> method.getAnnotation(PostMapping.class))
|
||||
.filter(java.util.Objects::nonNull)
|
||||
.flatMap(mapping -> java.util.Arrays.stream(mapping.value()))
|
||||
.toList();
|
||||
assertTrue(routes.contains("/required-qualification-packages"));
|
||||
assertTrue(routes.contains("/platform-required-qualification-packages"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void staticOpenApiDocumentsTheRequiredPackageContract() throws IOException {
|
||||
try (var input = getClass().getClassLoader().getResourceAsStream("openapi/iam-v1.yaml")) {
|
||||
assertNotNull(input);
|
||||
String yaml = new String(input.readAllBytes(), StandardCharsets.UTF_8);
|
||||
assertTrue(yaml.contains("/internal/iam/required-qualification-packages:"));
|
||||
assertTrue(yaml.contains("/internal/iam/platform-required-qualification-packages:"));
|
||||
assertTrue(yaml.contains("RequiredQualificationPackageRequest:"));
|
||||
assertTrue(yaml.contains("replaceBusinessLicense"));
|
||||
assertTrue(yaml.contains("replaceLegalPersonId"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.baiyue.iam.service;
|
||||
|
||||
import com.baiyue.iam.dto.OnboardingDtos.QualificationSubmissionRequest;
|
||||
import com.baiyue.iam.dto.OnboardingDtos.RequiredQualificationPackageRequest;
|
||||
import com.baiyue.iam.entity.IamTenant;
|
||||
import com.baiyue.iam.repository.ProfileResourceRepository;
|
||||
import com.baiyue.iam.repository.TenantRepository;
|
||||
@@ -18,12 +19,13 @@ import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.Mockito.times;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
|
||||
class MerchantQualificationUploadTest {
|
||||
|
||||
@Test
|
||||
void uploadsOneDraftQualificationWithoutCreatingAReview() {
|
||||
void uploadsOneOptionalDraftQualificationWithoutCreatingAReview() {
|
||||
TenantRepository tenantRepository = mock(TenantRepository.class);
|
||||
ProfileResourceRepository profileRepository = mock(ProfileResourceRepository.class);
|
||||
ProfileResourceService service = new ProfileResourceService();
|
||||
@@ -37,8 +39,8 @@ class MerchantQualificationUploadTest {
|
||||
when(profileRepository.findQualificationsForUpdate(10001L)).thenReturn(List.of());
|
||||
|
||||
var response = service.submitQualification(new QualificationSubmissionRequest(
|
||||
10001L, "LEGAL_PERSON_ID", null,
|
||||
"/api/merchant/v1/files/qualifications/legal-person.png", LocalDate.of(2030, 1, 1), false));
|
||||
10001L, "FREIGHT_FORWARDER_CERTIFICATE", null,
|
||||
"/api/merchant/v1/files/qualifications/freight-forwarder.png", LocalDate.of(2030, 1, 1), false));
|
||||
|
||||
assertEquals("PROFILE_REQUIRED", response.tenantStatus());
|
||||
assertNull(response.reviewId());
|
||||
@@ -47,7 +49,26 @@ class MerchantQualificationUploadTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void businessLicenseRequiresAndPersistsAUniqueUnifiedSocialCreditCode() {
|
||||
void requiredQualificationsRejectTheLegacySingleFilePath() {
|
||||
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("PROFILE_REQUIRED");
|
||||
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, false)));
|
||||
|
||||
verify(profileRepository, never()).insertQualification(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void requiredPackagePersistsBothFilesAndCreditCodeTogether() {
|
||||
TenantRepository tenantRepository = mock(TenantRepository.class);
|
||||
ProfileResourceRepository profileRepository = mock(ProfileResourceRepository.class);
|
||||
ProfileResourceService service = new ProfileResourceService();
|
||||
@@ -60,21 +81,19 @@ class MerchantQualificationUploadTest {
|
||||
when(tenantRepository.findTenantForUpdate(10001L)).thenReturn(tenant);
|
||||
when(profileRepository.findQualificationsForUpdate(10001L)).thenReturn(List.of());
|
||||
|
||||
assertThrows(BizException.class, () -> service.submitQualification(new QualificationSubmissionRequest(
|
||||
10001L, "BUSINESS_LICENSE", null,
|
||||
"/api/merchant/v1/files/qualifications/license.png", null, false)));
|
||||
|
||||
var response = service.submitQualification(new QualificationSubmissionRequest(
|
||||
10001L, "BUSINESS_LICENSE", "91310101abcde12345",
|
||||
"/api/merchant/v1/files/qualifications/license.png", null, false));
|
||||
var response = service.submitRequiredQualificationPackage(new RequiredQualificationPackageRequest(
|
||||
10001L, "91310101abcde12345",
|
||||
"/api/merchant/v1/files/qualifications/license.png", null, false,
|
||||
"/api/merchant/v1/files/qualifications/legal-person.png", null, false));
|
||||
|
||||
assertEquals("PROFILE_REQUIRED", response.tenantStatus());
|
||||
assertEquals("91310101ABCDE12345", tenant.getUnifiedSocialCreditCode());
|
||||
verify(profileRepository, times(2)).insertQualification(any());
|
||||
verify(tenantRepository).saveTenant(tenant);
|
||||
}
|
||||
|
||||
@Test
|
||||
void businessLicenseCannotOverwriteAnEstablishedCreditCode() {
|
||||
void requiredPackageCannotOverwriteAnEstablishedCreditCode() {
|
||||
TenantRepository tenantRepository = mock(TenantRepository.class);
|
||||
ProfileResourceRepository profileRepository = mock(ProfileResourceRepository.class);
|
||||
ProfileResourceService service = new ProfileResourceService();
|
||||
@@ -87,11 +106,11 @@ class MerchantQualificationUploadTest {
|
||||
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)));
|
||||
assertThrows(BizException.class, () -> service.submitRequiredQualificationPackage(
|
||||
new RequiredQualificationPackageRequest(10001L, "91310101ABCDE12345",
|
||||
"/api/merchant/v1/files/qualifications/license.png", null, true,
|
||||
"/api/merchant/v1/files/qualifications/legal-person.png", null, true)));
|
||||
|
||||
verify(profileRepository, never()).insertQualification(any());
|
||||
verify(tenantRepository, never()).saveTenant(tenant);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import static org.mockito.Mockito.when;
|
||||
|
||||
class PlatformQualificationRegistrationTest {
|
||||
@Test
|
||||
void platformUploadRegistersMetadataWithoutCreatingReviewTicket() {
|
||||
void platformUploadRegistersOptionalMetadataWithoutCreatingReviewTicket() {
|
||||
ProfileResourceRepository repository = mock(ProfileResourceRepository.class);
|
||||
TenantRepository tenantRepository = mock(TenantRepository.class);
|
||||
ProfileResourceService service = new ProfileResourceService();
|
||||
@@ -33,13 +33,13 @@ class PlatformQualificationRegistrationTest {
|
||||
when(repository.findQualificationsForUpdate(10001L)).thenReturn(List.of());
|
||||
|
||||
var response = service.registerPlatformQualification(new PlatformQualificationRegistrationRequest(
|
||||
10001L, "LEGAL_PERSON_ID", null, "/api/merchant/v1/files/qualifications/file.pdf",
|
||||
10001L, "BANK_ACCOUNT_LICENSE", null, "/api/merchant/v1/files/qualifications/file.pdf",
|
||||
LocalDate.of(2030, 12, 31), false));
|
||||
|
||||
ArgumentCaptor<IamTenantQualification> captor = ArgumentCaptor.forClass(IamTenantQualification.class);
|
||||
verify(repository).insertQualification(captor.capture());
|
||||
verify(repository).saveQualification(captor.getValue());
|
||||
assertEquals("LEGAL_PERSON_ID", captor.getValue().getQualificationType());
|
||||
assertEquals("BANK_ACCOUNT_LICENSE", captor.getValue().getQualificationType());
|
||||
assertEquals(LocalDate.of(2030, 12, 31), captor.getValue().getValidTo());
|
||||
assertEquals("PROFILE_REQUIRED", response.tenantStatus());
|
||||
assertNull(response.reviewId());
|
||||
@@ -56,14 +56,14 @@ class PlatformQualificationRegistrationTest {
|
||||
IamTenant tenant = new IamTenant();
|
||||
tenant.setId(10001L); tenant.setTenantType("MERCHANT"); tenant.setStatus("PROFILE_REQUIRED");
|
||||
IamTenantQualification existing = new IamTenantQualification();
|
||||
existing.setId(88L); existing.setTenantId(10001L); existing.setQualificationType("LEGAL_PERSON_ID");
|
||||
existing.setId(88L); existing.setTenantId(10001L); existing.setQualificationType("BANK_ACCOUNT_LICENSE");
|
||||
existing.setStatus("PENDING");
|
||||
existing.setDocumentUrl("/api/merchant/v1/files/qualifications/old-file.pdf");
|
||||
when(tenantRepository.findTenantForUpdate(10001L)).thenReturn(tenant);
|
||||
when(repository.findQualificationsForUpdate(10001L)).thenReturn(List.of(existing));
|
||||
|
||||
var response = service.registerPlatformQualification(new PlatformQualificationRegistrationRequest(
|
||||
10001L, "LEGAL_PERSON_ID", null, "/api/merchant/v1/files/qualifications/new-file.pdf",
|
||||
10001L, "BANK_ACCOUNT_LICENSE", null, "/api/merchant/v1/files/qualifications/new-file.pdf",
|
||||
LocalDate.of(2031, 12, 31), true));
|
||||
|
||||
ArgumentCaptor<IamTenantQualification> captor = ArgumentCaptor.forClass(IamTenantQualification.class);
|
||||
|
||||
@@ -120,7 +120,7 @@ class TenantServiceTest {
|
||||
ReflectionTestUtils.setField(service, "repository", repository);
|
||||
|
||||
var created = service.register(new RegisterRequest(
|
||||
"Example Merchant", "Example Merchant Ltd", "Bob", "91330100EXAMPLE001", "Alice", "13800138000",
|
||||
"Example Merchant", "Example Merchant Ltd", "Bob", "Alice", "13800138000",
|
||||
"alice@example.com", "基础版", "alice_wechat", "Sales Zhang", 30, "LIMITED_COMPANY",
|
||||
LocalDate.of(2020, 1, 2), new BigDecimal("5000000.00"), "CNY", "深圳市南山区", "草稿客户"),
|
||||
9001L);
|
||||
@@ -134,13 +134,13 @@ class TenantServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void platformRegistrationAllowsCreditCodeToBeCompletedWithQualificationsLater() {
|
||||
void platformRegistrationAlwaysCreatesTheDraftWithoutACreditCode() {
|
||||
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",
|
||||
"Example Merchant", "Example Merchant Ltd", "Bob", "Alice", "13800138000",
|
||||
"alice@example.com", "基础版", null, null, 30, null,
|
||||
null, null, "CNY", null, null), 9001L);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user