优化
This commit is contained in:
Vendored
-1
@@ -165,7 +165,6 @@ pipeline {
|
||||
wait: true,
|
||||
propagate: false,
|
||||
parameters: [
|
||||
string(name: 'SERVICE', value: service),
|
||||
string(name: 'SOURCE_COMMIT', value: env.SOURCE_COMMIT),
|
||||
string(name: 'IMAGE_TAG', value: env.IMAGE_TAG),
|
||||
string(name: 'ENVIRONMENT', value: params.ENVIRONMENT),
|
||||
|
||||
+13
-4
@@ -33,7 +33,6 @@ pipeline {
|
||||
}
|
||||
|
||||
parameters {
|
||||
choice(name: 'SERVICE', choices: serviceModules, description: '本 Job 负责构建和部署的单个服务')
|
||||
string(name: 'SOURCE_COMMIT', defaultValue: '', description: '必须构建的 Git commit;通常由总控 Job 传入')
|
||||
string(name: 'IMAGE_TAG', defaultValue: '', description: '不可变镜像标签;通常使用 Git commit 前 12 位')
|
||||
choice(name: 'ENVIRONMENT', choices: ['test', 'staging', 'production'], description: '部署环境')
|
||||
@@ -52,14 +51,25 @@ pipeline {
|
||||
stages {
|
||||
stage('校验参数并检出指定提交') {
|
||||
steps {
|
||||
checkout scm
|
||||
|
||||
script {
|
||||
String service = params.SERVICE?.trim()
|
||||
String service = serviceModules.find { candidate ->
|
||||
env.JOB_BASE_NAME?.endsWith("-${candidate}")
|
||||
}
|
||||
String sourceCommit = params.SOURCE_COMMIT?.trim()
|
||||
String imageTag = params.IMAGE_TAG?.trim()
|
||||
String imageRepository = params.IMAGE_REPOSITORY?.trim()
|
||||
|
||||
if (!sourceCommit) {
|
||||
sourceCommit = sh(script: 'git rev-parse HEAD', returnStdout: true).trim()
|
||||
}
|
||||
if (!imageTag) {
|
||||
imageTag = sh(script: 'git rev-parse --short=12 HEAD', returnStdout: true).trim()
|
||||
}
|
||||
|
||||
if (!serviceModules.contains(service)) {
|
||||
error("未知服务: ${service};允许值: ${serviceModules.join(', ')}")
|
||||
error("无法从 Job 名称 ${env.JOB_BASE_NAME} 识别服务;名称必须以以下服务之一结尾: ${serviceModules.join(', ')}")
|
||||
}
|
||||
if (!sourceCommit || !(sourceCommit ==~ /(?i)[0-9a-f]{7,40}/)) {
|
||||
error('SOURCE_COMMIT 必须是 7 到 40 位十六进制 Git commit')
|
||||
@@ -80,7 +90,6 @@ pipeline {
|
||||
env.RESOLVED_IMAGE_REPOSITORY = imageRepository
|
||||
}
|
||||
|
||||
checkout scm
|
||||
sh '''
|
||||
if ! git cat-file -e "${RESOLVED_SOURCE_COMMIT}^{commit}" 2>/dev/null; then
|
||||
git fetch --no-tags origin
|
||||
|
||||
@@ -14,7 +14,15 @@ public interface IamQualificationClient {
|
||||
@RequestHeader("X-Internal-Service-Token") String internalToken,
|
||||
@RequestBody QualificationRequest request);
|
||||
|
||||
@PostMapping("/platform-qualifications")
|
||||
ApiResponse<QualificationResponse> submitPlatform(
|
||||
@RequestHeader("X-Internal-Service-Token") String internalToken,
|
||||
@RequestBody PlatformQualificationRequest request);
|
||||
|
||||
record QualificationRequest(Long tenantId, String qualificationType,
|
||||
String certificateNo, String documentUrl) { }
|
||||
record QualificationResponse(Long qualificationId, Long reviewId, String tenantStatus) { }
|
||||
record PlatformQualificationRequest(Long tenantId, String qualificationType,
|
||||
String certificateNo, String documentUrl,
|
||||
java.time.LocalDate validTo) { }
|
||||
}
|
||||
|
||||
+50
-4
@@ -1,8 +1,10 @@
|
||||
package com.baiyue.file.controller;
|
||||
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
import com.baiyue.file.client.IamQualificationClient;
|
||||
import com.baiyue.file.service.LocalQualificationStorage;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.http.CacheControl;
|
||||
@@ -14,23 +16,67 @@ import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestPart;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/platform/v1/files/qualifications")
|
||||
public class PlatformQualificationFileController {
|
||||
@Resource private LocalQualificationStorage storage;
|
||||
@Resource private IamQualificationClient iamClient;
|
||||
@Value("${baiyue.security.internal-service-token}") private String internalToken;
|
||||
|
||||
@PostMapping(value = "/{tenantId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
@PreAuthorize("hasAnyAuthority('platform:customer:create','platform:customer:edit')")
|
||||
public ApiResponse<QualificationFileController.UploadResponse> upload(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable long tenantId,
|
||||
@RequestPart("file") MultipartFile file,
|
||||
@RequestParam String qualificationType,
|
||||
@RequestParam(required = false) String certificateNo,
|
||||
@RequestParam(required = false) LocalDate validTo) {
|
||||
requirePlatform(user);
|
||||
var stored = storage.store(tenantId, file);
|
||||
try {
|
||||
var response = iamClient.submitPlatform(internalToken,
|
||||
new IamQualificationClient.PlatformQualificationRequest(
|
||||
tenantId, qualificationType, certificateNo, stored.documentUrl(), validTo));
|
||||
if (response == null || !response.successful() || response.data() == null) {
|
||||
String message = response == null || response.message() == null || response.message().isBlank()
|
||||
? "IAM qualification service is unavailable" : response.message();
|
||||
throw new BizException(response == null ? ResultCode.SERVICE_UNAVAILABLE : ResultCode.of(response.code()), message);
|
||||
}
|
||||
var data = response.data();
|
||||
return ApiResponse.success(new QualificationFileController.UploadResponse(
|
||||
data.qualificationId(), data.reviewId(), data.tenantStatus(), stored.documentUrl()));
|
||||
} catch (RuntimeException exception) {
|
||||
storage.delete(stored);
|
||||
if (exception instanceof BizException business) throw business;
|
||||
throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "IAM qualification service is unavailable");
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/{tenantId}/{fileName}")
|
||||
@PreAuthorize("hasAuthority('platform:customer:review')")
|
||||
public ResponseEntity<org.springframework.core.io.Resource> download(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable long tenantId, @PathVariable String fileName) {
|
||||
if (user == null || !"PLATFORM".equalsIgnoreCase(user.appCode())) {
|
||||
throw new BizException(ResultCode.FORBIDDEN, "Platform identity is required");
|
||||
}
|
||||
requirePlatform(user);
|
||||
var resource = storage.load(tenantId, fileName);
|
||||
String mediaType = fileName.endsWith(".png") ? MediaType.IMAGE_PNG_VALUE : MediaType.IMAGE_JPEG_VALUE;
|
||||
String mediaType = fileName.endsWith(".png") ? MediaType.IMAGE_PNG_VALUE
|
||||
: (fileName.endsWith(".pdf") ? MediaType.APPLICATION_PDF_VALUE : MediaType.IMAGE_JPEG_VALUE);
|
||||
return ResponseEntity.ok().cacheControl(CacheControl.noStore())
|
||||
.contentType(MediaType.parseMediaType(mediaType)).body(resource);
|
||||
}
|
||||
|
||||
private void requirePlatform(AuthenticatedUser user) {
|
||||
if (user == null || !"PLATFORM".equalsIgnoreCase(user.appCode())) {
|
||||
throw new BizException(ResultCode.FORBIDDEN, "Platform identity is required");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,8 @@ public class QualificationFileController {
|
||||
public ResponseEntity<org.springframework.core.io.Resource> download(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable String fileName) {
|
||||
var resource = storage.load(merchantTenant(user), fileName);
|
||||
String mediaType = fileName.endsWith(".png") ? MediaType.IMAGE_PNG_VALUE : MediaType.IMAGE_JPEG_VALUE;
|
||||
String mediaType = fileName.endsWith(".png") ? MediaType.IMAGE_PNG_VALUE
|
||||
: (fileName.endsWith(".pdf") ? MediaType.APPLICATION_PDF_VALUE : MediaType.IMAGE_JPEG_VALUE);
|
||||
return ResponseEntity.ok().cacheControl(CacheControl.noStore())
|
||||
.contentType(MediaType.parseMediaType(mediaType)).body(resource);
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ import java.util.UUID;
|
||||
@Service
|
||||
public class LocalQualificationStorage {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(LocalQualificationStorage.class);
|
||||
private static final long MAX_SIZE = 5L * 1024 * 1024;
|
||||
private static final Set<String> TYPES = Set.of("image/jpeg", "image/png");
|
||||
private static final long MAX_SIZE = 10L * 1024 * 1024;
|
||||
private static final Set<String> TYPES = Set.of("image/jpeg", "image/png", "application/pdf");
|
||||
private final Path root;
|
||||
private final Path qualificationRoot;
|
||||
|
||||
@@ -52,14 +52,14 @@ public class LocalQualificationStorage {
|
||||
}
|
||||
|
||||
public StoredFile store(long tenantId, MultipartFile file) {
|
||||
if (file == null || file.isEmpty()) throw new BizException("One business-license image is required");
|
||||
if (file.getSize() > MAX_SIZE) throw new BizException("Business-license image must not exceed 5 MB");
|
||||
if (file == null || file.isEmpty()) throw new BizException("One qualification file is required / 请选择资质文件");
|
||||
if (file.getSize() > MAX_SIZE) throw new BizException("Qualification file must not exceed 10 MB / 资质文件不能超过 10MB");
|
||||
String contentType = file.getContentType() == null ? "" : file.getContentType().toLowerCase(Locale.ROOT);
|
||||
if (!TYPES.contains(contentType)) throw new BizException("Only JPG and PNG images are supported");
|
||||
if (!matchesImageSignature(file, contentType)) {
|
||||
throw new BizException("The uploaded file content is not a valid JPG or PNG image");
|
||||
if (!TYPES.contains(contentType)) throw new BizException("Only PDF, JPG and PNG files are supported / 仅支持 PDF、JPG、PNG 文件");
|
||||
if (!matchesSignature(file, contentType)) {
|
||||
throw new BizException("The uploaded file content does not match its type / 文件内容与格式不匹配");
|
||||
}
|
||||
String extension = "image/png".equals(contentType) ? ".png" : ".jpg";
|
||||
String extension = "image/png".equals(contentType) ? ".png" : ("application/pdf".equals(contentType) ? ".pdf" : ".jpg");
|
||||
String fileName = UUID.randomUUID() + extension;
|
||||
Path directory = tenantDirectory(tenantId);
|
||||
Path target = directory.resolve(fileName).normalize();
|
||||
@@ -82,7 +82,7 @@ public class LocalQualificationStorage {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean matchesImageSignature(MultipartFile file, String contentType) {
|
||||
private boolean matchesSignature(MultipartFile file, String contentType) {
|
||||
byte[] header = new byte[8];
|
||||
try (InputStream input = file.getInputStream()) {
|
||||
int read = input.read(header);
|
||||
@@ -90,6 +90,10 @@ public class LocalQualificationStorage {
|
||||
return read >= 3 && (header[0] & 0xff) == 0xff && (header[1] & 0xff) == 0xd8
|
||||
&& (header[2] & 0xff) == 0xff;
|
||||
}
|
||||
if ("application/pdf".equals(contentType)) {
|
||||
return read >= 5 && header[0] == 0x25 && header[1] == 0x50 && header[2] == 0x44
|
||||
&& header[3] == 0x46 && header[4] == 0x2d;
|
||||
}
|
||||
return read >= 8 && (header[0] & 0xff) == 0x89 && header[1] == 0x50
|
||||
&& header[2] == 0x4e && header[3] == 0x47 && header[4] == 0x0d
|
||||
&& header[5] == 0x0a && header[6] == 0x1a && header[7] == 0x0a;
|
||||
@@ -99,7 +103,7 @@ public class LocalQualificationStorage {
|
||||
}
|
||||
|
||||
public Resource load(long tenantId, String fileName) {
|
||||
if (!fileName.matches("[0-9a-fA-F-]{36}\\.(jpg|png)")) throw new BizException("Invalid file name");
|
||||
if (!fileName.matches("[0-9a-fA-F-]{36}\\.(jpg|png|pdf)")) throw new BizException("Invalid file name");
|
||||
Path directory = tenantDirectory(tenantId);
|
||||
Path target = directory.resolve(fileName).normalize();
|
||||
if (!target.startsWith(directory) || !Files.isRegularFile(target)) throw new BizException("File not found");
|
||||
|
||||
@@ -22,8 +22,8 @@ spring:
|
||||
config.namespace: ${NACOS_NAMESPACE:public}
|
||||
servlet:
|
||||
multipart:
|
||||
max-file-size: 5MB
|
||||
max-request-size: 6MB
|
||||
max-file-size: 10MB
|
||||
max-request-size: 11MB
|
||||
baiyue:
|
||||
security:
|
||||
jwt-secret: ${JWT_SECRET:change-me-to-at-least-32-bytes-long-before-start}
|
||||
|
||||
@@ -27,4 +27,16 @@ class QualificationFileContractTest {
|
||||
|
||||
assertTrue(hasMultipartPost, "企业资质真实上传入口必须保持 multipart/form-data 契约");
|
||||
}
|
||||
|
||||
@Test
|
||||
void platformControllerExposesMultipartUploadForCustomerDrafts() {
|
||||
RequestMapping mapping = PlatformQualificationFileController.class.getAnnotation(RequestMapping.class);
|
||||
assertTrue(Arrays.asList(mapping.value()).contains("/api/platform/v1/files/qualifications"));
|
||||
boolean hasMultipartPost = Arrays.stream(PlatformQualificationFileController.class.getDeclaredMethods())
|
||||
.map(method -> method.getAnnotation(PostMapping.class))
|
||||
.filter(java.util.Objects::nonNull)
|
||||
.flatMap(post -> Arrays.stream(post.consumes()))
|
||||
.anyMatch(MediaType.MULTIPART_FORM_DATA_VALUE::equals);
|
||||
assertTrue(hasMultipartPost);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,19 @@ class LocalQualificationStorageTest {
|
||||
assertThrows(BizException.class, () -> storage.load(10002L, stored.fileName()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void storesPdfQualificationAfterVerifyingItsSignature() throws Exception {
|
||||
var storage = new LocalQualificationStorage(storageRoot.toString());
|
||||
storage.initialize();
|
||||
var upload = new MockMultipartFile("file", "license.pdf", "application/pdf", "%PDF-1.4\ncontent".getBytes());
|
||||
|
||||
var stored = storage.store(10001L, upload);
|
||||
|
||||
assertTrue(stored.fileName().endsWith(".pdf"));
|
||||
assertEquals(stored.path().toAbsolutePath(),
|
||||
storage.load(10001L, stored.fileName()).getFile().toPath().toAbsolutePath());
|
||||
}
|
||||
|
||||
@Test
|
||||
void legacySeedPathCannotBePretendedToBeAStoredFile() {
|
||||
var storage = new LocalQualificationStorage(storageRoot.toString());
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.baiyue.iam.dto.OnboardingDtos.MerchantRegistrationResponse;
|
||||
import com.baiyue.iam.dto.OnboardingDtos.OrderEligibilityResponse;
|
||||
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.service.MerchantOnboardingService;
|
||||
import com.baiyue.iam.service.ProfileResourceService;
|
||||
import com.baiyue.iam.service.TenantService;
|
||||
@@ -48,6 +49,14 @@ public class InternalTenantController {
|
||||
return ApiResponse.success(profileResourceService.submitQualification(request));
|
||||
}
|
||||
|
||||
@PostMapping("/platform-qualifications")
|
||||
public ApiResponse<QualificationSubmissionResponse> registerPlatformQualification(
|
||||
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
|
||||
@Valid @RequestBody PlatformQualificationRegistrationRequest request) {
|
||||
verify(token);
|
||||
return ApiResponse.success(profileResourceService.registerPlatformQualification(request));
|
||||
}
|
||||
|
||||
@GetMapping("/tenants/{tenantId}/order-eligibility")
|
||||
public ApiResponse<OrderEligibilityResponse> orderEligibility(
|
||||
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
|
||||
|
||||
@@ -70,7 +70,7 @@ public class PlatformTenantController {
|
||||
|
||||
@PostMapping("/tenants")
|
||||
@PreAuthorize("hasAuthority('platform:customer:create')")
|
||||
@Operation(summary = "Register enterprise tenant / 提交企业注册", description = "Create a pending merchant tenant and its registration review ticket. Platform token required. / 创建待审核商户租户及注册审核单,需要平台端令牌。")
|
||||
@Operation(summary = "Register enterprise tenant / 创建企业客户草稿", description = "Create a PROFILE_REQUIRED merchant draft only. Saving the customer does not create a review ticket. / 仅创建待完善企业客户草稿,保存客户不会创建审核单。")
|
||||
@ApiResponses({
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "Registration created / 注册已创建"),
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "Invalid enterprise data / 企业资料不合法"),
|
||||
@@ -103,6 +103,15 @@ public class PlatformTenantController {
|
||||
return ApiResponse.success(service.changeMerchantStatus(tenantId, request));
|
||||
}
|
||||
|
||||
@PostMapping("/tenants/{tenantId:\\d+}/submit-review")
|
||||
@PreAuthorize("hasAnyAuthority('platform:customer:create','platform:customer:edit')")
|
||||
@Operation(summary = "提交企业客户审核", description = "校验客户基础资料与必填资质后创建审核单;保存客户或上传文件不会自动进入审核。")
|
||||
public ApiResponse<TenantResponse> submitTenantReview(@PathVariable long tenantId,
|
||||
@AuthenticationPrincipal AuthenticatedUser user) {
|
||||
TenantGuard.tenant(user, "PLATFORM");
|
||||
return ApiResponse.success(service.submitMerchantReview(tenantId));
|
||||
}
|
||||
|
||||
@GetMapping("/reviews")
|
||||
@PreAuthorize("hasAuthority('platform:customer:review')")
|
||||
@Operation(summary = "List pending enterprise reviews / 查询待审核企业", description = "Return pending enterprise registration review tickets ordered by creation. / 按创建顺序返回待审核企业注册单。")
|
||||
|
||||
@@ -2,6 +2,9 @@ package com.baiyue.iam.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
public final class OnboardingDtos {
|
||||
private OnboardingDtos() { }
|
||||
@@ -28,5 +31,13 @@ public final class OnboardingDtos {
|
||||
|
||||
public record QualificationSubmissionResponse(Long qualificationId, Long reviewId, String tenantStatus) { }
|
||||
|
||||
public record PlatformQualificationRegistrationRequest(
|
||||
@NotNull Long tenantId,
|
||||
@NotBlank @Pattern(regexp = "BUSINESS_LICENSE|LEGAL_PERSON_ID|FREIGHT_FORWARDER_CERTIFICATE|BANK_ACCOUNT_LICENSE") String qualificationType,
|
||||
String certificateNo,
|
||||
@NotBlank String documentUrl,
|
||||
LocalDate validTo
|
||||
) { }
|
||||
|
||||
public record OrderEligibilityResponse(boolean eligible, String tenantStatus, String reason) { }
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ public final class TenantDtos {
|
||||
@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,
|
||||
@NotBlank @Size(max = 64) String servicePackage,
|
||||
@Size(max = 64) String contactWechat,
|
||||
@Size(max = 64) String salesOwnerName,
|
||||
@Min(0) @Max(365) Integer paymentTermDays,
|
||||
@@ -57,6 +58,7 @@ public final class TenantDtos {
|
||||
@NotBlank String contactName,
|
||||
@NotBlank String contactPhone,
|
||||
@Email String contactEmail,
|
||||
@NotBlank @Size(max = 64) String servicePackage,
|
||||
@Size(max = 64) String contactWechat,
|
||||
@Size(max = 64) String salesOwnerName,
|
||||
@Min(0) @Max(365) Integer paymentTermDays,
|
||||
@@ -82,7 +84,7 @@ public final class TenantDtos {
|
||||
public record TenantResponse(Long id, String tenantCode, String tenantName, String status,
|
||||
String legalName, String unifiedSocialCreditCode, String contactName,
|
||||
String contactPhone, String contactEmail, String contactWechat,
|
||||
String salesOwnerName, Integer paymentTermDays, String enterpriseType,
|
||||
String servicePackage, String salesOwnerName, Integer paymentTermDays, String enterpriseType,
|
||||
LocalDate establishedDate, BigDecimal registeredCapital,
|
||||
String registeredCapitalCurrency, String registeredAddress,
|
||||
String internalRemark, LocalDateTime createdAt, LocalDateTime approvedAt) { }
|
||||
|
||||
@@ -27,6 +27,7 @@ public class IamTenant extends BaseEntity {
|
||||
private String contactName;
|
||||
private String contactPhone;
|
||||
private String contactEmail;
|
||||
private String servicePackage;
|
||||
private String contactWechat;
|
||||
private String salesOwnerName;
|
||||
private Integer paymentTermDays;
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.baiyue.common.audit.BusinessAudit;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
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.ProfileDtos.AddressRequest;
|
||||
import com.baiyue.iam.entity.IamTenant;
|
||||
import com.baiyue.iam.entity.IamTenantAddress;
|
||||
@@ -88,6 +89,28 @@ public class ProfileResourceService {
|
||||
return new QualificationSubmissionResponse(qualification.getId(), review.getId(), tenant.getStatus());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@BusinessAudit(event = "PLATFORM_TENANT_QUALIFICATION_UPLOAD", resourceType = "TENANT_QUALIFICATION",
|
||||
resourceId = "#request.tenantId", description = "平台代客户上传资质", captureAfter = false)
|
||||
public QualificationSubmissionResponse registerPlatformQualification(PlatformQualificationRegistrationRequest request) {
|
||||
IamTenant tenant = tenantRepository.findTenant(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 / 仅待完善或已驳回客户可上传资质");
|
||||
}
|
||||
if (repository.findQualifications(tenant.getId()).stream().anyMatch(item ->
|
||||
request.qualificationType().equals(item.getQualificationType()) && !"REJECTED".equals(item.getStatus()))) {
|
||||
throw new BizException(ResultCode.CONFLICT, "Qualification type already uploaded / 该类资质已上传");
|
||||
}
|
||||
IamTenantQualification qualification = createQualification(tenant.getId(), request.qualificationType(),
|
||||
request.certificateNo(), request.documentUrl());
|
||||
qualification.setValidTo(request.validTo());
|
||||
repository.saveQualification(qualification);
|
||||
return new QualificationSubmissionResponse(qualification.getId(), null, tenant.getStatus());
|
||||
}
|
||||
|
||||
private IamTenantQualification createQualification(long tenant, String type, String certificateNo, String url) {
|
||||
IamTenantQualification x = new IamTenantQualification();
|
||||
x.setId(IdWorker.getId()); x.setTenantId(tenant); x.setQualificationType(type);
|
||||
|
||||
@@ -53,12 +53,13 @@ public class TenantService {
|
||||
tenant.setTenantCode("TEN" + tenant.getId());
|
||||
tenant.setTenantName(request.tenantName());
|
||||
tenant.setTenantType("MERCHANT");
|
||||
tenant.setStatus("PENDING");
|
||||
tenant.setStatus("PROFILE_REQUIRED");
|
||||
tenant.setLegalName(request.legalName());
|
||||
tenant.setUnifiedSocialCreditCode(request.unifiedSocialCreditCode());
|
||||
tenant.setContactName(request.contactName());
|
||||
tenant.setContactPhone(request.contactPhone());
|
||||
tenant.setContactEmail(request.contactEmail());
|
||||
tenant.setServicePackage(request.servicePackage());
|
||||
tenant.setContactWechat(request.contactWechat());
|
||||
tenant.setSalesOwnerName(request.salesOwnerName());
|
||||
tenant.setPaymentTermDays(request.paymentTermDays());
|
||||
@@ -70,13 +71,6 @@ public class TenantService {
|
||||
tenant.setInternalRemark(request.internalRemark());
|
||||
repository.insertTenant(tenant);
|
||||
|
||||
IamTenantReview review = new IamTenantReview();
|
||||
review.setId(IdWorker.getId());
|
||||
review.setTenantId(tenant.getId());
|
||||
review.setReviewType("REGISTER");
|
||||
review.setStatus("PENDING");
|
||||
review.setCreatedAt(LocalDateTime.now());
|
||||
repository.insertReview(review);
|
||||
return response(tenant);
|
||||
}
|
||||
|
||||
@@ -108,6 +102,15 @@ public class TenantService {
|
||||
throw new BizException(ResultCode.CONFLICT, "Review already completed / 审核已完成");
|
||||
}
|
||||
|
||||
if (approve) {
|
||||
var qualifications = profileRepository.findQualifications(review.getTenantId());
|
||||
boolean hasLicense = qualifications.stream().anyMatch(item -> "BUSINESS_LICENSE".equals(item.getQualificationType()) && "PENDING".equals(item.getStatus()));
|
||||
boolean hasLegalId = qualifications.stream().anyMatch(item -> "LEGAL_PERSON_ID".equals(item.getQualificationType()) && "PENDING".equals(item.getStatus()));
|
||||
if (!hasLicense || !hasLegalId) {
|
||||
throw new BizException(ResultCode.CONFLICT, "Required qualifications are incomplete / 必填资质不完整,不能审核通过");
|
||||
}
|
||||
}
|
||||
|
||||
review.setStatus(approve ? "APPROVED" : "REJECTED");
|
||||
review.setReviewerId(reviewerId);
|
||||
review.setReviewComment(comment);
|
||||
@@ -171,6 +174,7 @@ public class TenantService {
|
||||
tenant.setContactName(request.contactName());
|
||||
tenant.setContactPhone(request.contactPhone());
|
||||
tenant.setContactEmail(request.contactEmail());
|
||||
tenant.setServicePackage(request.servicePackage());
|
||||
tenant.setContactWechat(request.contactWechat());
|
||||
tenant.setSalesOwnerName(request.salesOwnerName());
|
||||
tenant.setPaymentTermDays(request.paymentTermDays());
|
||||
@@ -198,6 +202,33 @@ public class TenantService {
|
||||
return response(tenant);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@BusinessAudit(event = "PLATFORM_TENANT_SUBMIT_REVIEW", resourceType = "TENANT", resourceId = "#tenantId",
|
||||
description = "平台提交企业客户审核")
|
||||
public TenantResponse submitMerchantReview(long tenantId) {
|
||||
IamTenant tenant = requireMerchant(tenantId);
|
||||
if (!"PROFILE_REQUIRED".equals(tenant.getStatus()) && !"REJECTED".equals(tenant.getStatus())) {
|
||||
throw new BizException(ResultCode.CONFLICT, "Only draft or rejected enterprise can be submitted / 仅待完善或已驳回客户可提交审核");
|
||||
}
|
||||
if (repository.findPendingReviewByTenant(tenantId) != null) {
|
||||
throw new BizException(ResultCode.CONFLICT, "Enterprise information is already under review / 客户资料已在审核中");
|
||||
}
|
||||
var qualifications = profileRepository.findQualifications(tenantId);
|
||||
boolean hasLicense = qualifications.stream().anyMatch(item -> "BUSINESS_LICENSE".equals(item.getQualificationType()) && !"REJECTED".equals(item.getStatus()));
|
||||
boolean hasLegalId = qualifications.stream().anyMatch(item -> "LEGAL_PERSON_ID".equals(item.getQualificationType()) && !"REJECTED".equals(item.getStatus()));
|
||||
if (!hasLicense || !hasLegalId) {
|
||||
throw new BizException(ResultCode.BAD_REQUEST, "Business license and legal-person ID are required / 营业执照和法人身份证为必填资质");
|
||||
}
|
||||
IamTenantReview review = new IamTenantReview();
|
||||
review.setId(IdWorker.getId()); review.setTenantId(tenantId);
|
||||
review.setReviewType("REGISTER"); review.setStatus("PENDING");
|
||||
review.setCreatedAt(LocalDateTime.now());
|
||||
repository.insertReview(review);
|
||||
tenant.setStatus("PENDING"); tenant.setRejectReason(null);
|
||||
repository.saveTenant(tenant);
|
||||
return response(tenant);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@BusinessAudit(event = "TENANT_PROFILE_UPDATE", resourceType = "TENANT",
|
||||
resourceId = "#tenantId", description = "编辑企业资料")
|
||||
@@ -232,7 +263,7 @@ public class TenantService {
|
||||
tenant.getId(), tenant.getTenantCode(), tenant.getTenantName(), tenant.getStatus(),
|
||||
tenant.getLegalName(), tenant.getUnifiedSocialCreditCode(), tenant.getContactName(),
|
||||
tenant.getContactPhone(), tenant.getContactEmail(), tenant.getContactWechat(),
|
||||
tenant.getSalesOwnerName(), tenant.getPaymentTermDays(), tenant.getEnterpriseType(),
|
||||
tenant.getServicePackage(), tenant.getSalesOwnerName(), tenant.getPaymentTermDays(), tenant.getEnterpriseType(),
|
||||
tenant.getEstablishedDate(), tenant.getRegisteredCapital(), tenant.getRegisteredCapitalCurrency(),
|
||||
tenant.getRegisteredAddress(), tenant.getInternalRemark(), tenant.getCreatedAt(), tenant.getApprovedAt()
|
||||
);
|
||||
@@ -243,7 +274,7 @@ public class TenantService {
|
||||
return new TenantResponse(response.id(), response.tenantCode(), response.tenantName(), response.status(),
|
||||
response.legalName(), response.unifiedSocialCreditCode(), response.contactName(),
|
||||
maskPhone(response.contactPhone()), maskEmail(response.contactEmail()), response.contactWechat(),
|
||||
response.salesOwnerName(), response.paymentTermDays(), response.enterpriseType(),
|
||||
response.servicePackage(), response.salesOwnerName(), response.paymentTermDays(), response.enterpriseType(),
|
||||
response.establishedDate(), response.registeredCapital(), response.registeredCapitalCurrency(),
|
||||
response.registeredAddress(), response.internalRemark(), response.createdAt(), response.approvedAt());
|
||||
}
|
||||
@@ -252,7 +283,7 @@ public class TenantService {
|
||||
TenantResponse response = response(tenant);
|
||||
return new TenantResponse(response.id(), response.tenantCode(), response.tenantName(), response.status(),
|
||||
response.legalName(), response.unifiedSocialCreditCode(), response.contactName(),
|
||||
response.contactPhone(), response.contactEmail(), response.contactWechat(), response.salesOwnerName(),
|
||||
response.contactPhone(), response.contactEmail(), response.contactWechat(), response.servicePackage(), response.salesOwnerName(),
|
||||
response.paymentTermDays(), response.enterpriseType(), response.establishedDate(),
|
||||
response.registeredCapital(), response.registeredCapitalCurrency(), response.registeredAddress(),
|
||||
null, response.createdAt(), response.approvedAt());
|
||||
|
||||
@@ -16,6 +16,7 @@ CREATE TABLE IF NOT EXISTS iam_tenant (
|
||||
contact_name VARCHAR(64) NULL COMMENT '企业联系人',
|
||||
contact_phone VARCHAR(32) NULL COMMENT '联系电话',
|
||||
contact_email VARCHAR(128) NULL COMMENT '联系邮箱',
|
||||
service_package VARCHAR(64) NOT NULL DEFAULT '基础版' COMMENT '客户服务套餐',
|
||||
contact_wechat VARCHAR(64) NULL COMMENT '联系微信',
|
||||
sales_owner_name VARCHAR(64) NULL COMMENT '销售负责人姓名',
|
||||
payment_term_days SMALLINT UNSIGNED NULL COMMENT '账期天数',
|
||||
|
||||
@@ -39,8 +39,8 @@ paths:
|
||||
'422': {$ref: '#/components/responses/UnprocessableEntity'}
|
||||
post:
|
||||
tags: [Platform Tenant / 平台租户]
|
||||
summary: Register enterprise tenant / 创建企业租户
|
||||
description: Create a pending enterprise tenant and review task. Platform identity is required. / 创建待审核企业租户及审核任务,要求平台端身份。
|
||||
summary: Register enterprise tenant / 创建企业客户草稿
|
||||
description: Create a `PROFILE_REQUIRED` enterprise draft only. Saving the customer does not create a review task. / 仅创建待完善企业客户草稿,保存客户不会创建审核单。
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
@@ -88,6 +88,19 @@ paths:
|
||||
'400': {$ref: '#/components/responses/BadRequest'}
|
||||
'403': {$ref: '#/components/responses/Forbidden'}
|
||||
'404': {$ref: '#/components/responses/NotFound'}
|
||||
/api/platform/v1/iam/tenants/{tenantId}/submit-review:
|
||||
parameters:
|
||||
- {$ref: '#/components/parameters/TenantId'}
|
||||
post:
|
||||
tags: [Platform Tenant / 平台租户]
|
||||
summary: 提交企业客户审核
|
||||
description: 校验营业执照和法人身份证已上传后创建审核单,并把客户状态改为 `PENDING`;保存资料和上传文件均不会自动调用本接口。
|
||||
responses:
|
||||
'200': {$ref: '#/components/responses/TenantSuccess'}
|
||||
'400': {$ref: '#/components/responses/BadRequest'}
|
||||
'403': {$ref: '#/components/responses/Forbidden'}
|
||||
'404': {$ref: '#/components/responses/NotFound'}
|
||||
'409': {$ref: '#/components/responses/Conflict'}
|
||||
/api/platform/v1/iam/tenants/{tenantId}/addresses:
|
||||
parameters:
|
||||
- {$ref: '#/components/parameters/TenantId'}
|
||||
@@ -674,7 +687,7 @@ components:
|
||||
data: {$ref: '#/components/schemas/InternalUserResponse'}
|
||||
TenantResponse:
|
||||
type: object
|
||||
required: [id, tenantCode, tenantName, status]
|
||||
required: [id, tenantCode, tenantName, status, servicePackage]
|
||||
properties:
|
||||
id: {type: string, pattern: '^\d+$', example: '190000000000000001'}
|
||||
tenantCode: {type: string}
|
||||
@@ -686,6 +699,7 @@ components:
|
||||
contactPhone: {type: string}
|
||||
contactEmail: {type: string, format: email}
|
||||
contactWechat: {type: string, maxLength: 64}
|
||||
servicePackage: {type: string, maxLength: 64}
|
||||
salesOwnerName: {type: string, maxLength: 64}
|
||||
paymentTermDays: {type: integer, minimum: 0, maximum: 365}
|
||||
enterpriseType: {type: string, maxLength: 64}
|
||||
@@ -730,16 +744,16 @@ components:
|
||||
properties:
|
||||
id: {type: string, pattern: '^\d+$'}
|
||||
tenantId: {type: string, pattern: '^\d+$'}
|
||||
qualificationType: {type: string}
|
||||
qualificationType: {type: string, enum: [BUSINESS_LICENSE, LEGAL_PERSON_ID, FREIGHT_FORWARDER_CERTIFICATE, BANK_ACCOUNT_LICENSE]}
|
||||
certificateNo: {type: string}
|
||||
documentUrl: {type: string}
|
||||
validFrom: {type: string, format: date}
|
||||
validTo: {type: string, format: date}
|
||||
status: {type: string}
|
||||
validTo: {type: string, format: date, nullable: true}
|
||||
status: {type: string, enum: [PENDING, APPROVED, REJECTED]}
|
||||
reviewComment: {type: string}
|
||||
RegisterRequest:
|
||||
type: object
|
||||
required: [tenantName, legalName, unifiedSocialCreditCode, contactName, contactPhone]
|
||||
required: [tenantName, legalName, unifiedSocialCreditCode, contactName, contactPhone, servicePackage]
|
||||
properties:
|
||||
tenantName: {type: string, example: 深圳虫虫出海科技有限公司}
|
||||
legalName: {type: string, example: 张三}
|
||||
@@ -748,6 +762,7 @@ components:
|
||||
contactPhone: {type: string, example: '13800138000'}
|
||||
contactEmail: {type: string, format: email, example: contact@example.com}
|
||||
contactWechat: {type: string, maxLength: 64}
|
||||
servicePackage: {type: string, maxLength: 64, example: 基础版}
|
||||
salesOwnerName: {type: string, maxLength: 64}
|
||||
paymentTermDays: {type: integer, minimum: 0, maximum: 365}
|
||||
enterpriseType: {type: string, maxLength: 64}
|
||||
@@ -758,7 +773,7 @@ components:
|
||||
internalRemark: {type: string, maxLength: 1000}
|
||||
UpdateProfileRequest:
|
||||
type: object
|
||||
required: [tenantName, legalName, contactName, contactPhone]
|
||||
required: [tenantName, legalName, contactName, contactPhone, servicePackage]
|
||||
properties:
|
||||
tenantName: {type: string}
|
||||
legalName: {type: string}
|
||||
@@ -775,6 +790,7 @@ components:
|
||||
contactPhone: {type: string}
|
||||
contactEmail: {type: string, format: email}
|
||||
contactWechat: {type: string, maxLength: 64}
|
||||
servicePackage: {type: string, maxLength: 64, example: 基础版}
|
||||
salesOwnerName: {type: string, maxLength: 64}
|
||||
paymentTermDays: {type: integer, minimum: 0, maximum: 365}
|
||||
enterpriseType: {type: string, maxLength: 64}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.baiyue.iam.service;
|
||||
|
||||
import com.baiyue.iam.dto.OnboardingDtos.PlatformQualificationRegistrationRequest;
|
||||
import com.baiyue.iam.entity.IamTenant;
|
||||
import com.baiyue.iam.entity.IamTenantQualification;
|
||||
import com.baiyue.iam.repository.ProfileResourceRepository;
|
||||
import com.baiyue.iam.repository.TenantRepository;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class PlatformQualificationRegistrationTest {
|
||||
@Test
|
||||
void platformUploadRegistersMetadataWithoutCreatingReviewTicket() {
|
||||
ProfileResourceRepository repository = mock(ProfileResourceRepository.class);
|
||||
TenantRepository tenantRepository = mock(TenantRepository.class);
|
||||
ProfileResourceService service = new ProfileResourceService();
|
||||
ReflectionTestUtils.setField(service, "repository", repository);
|
||||
ReflectionTestUtils.setField(service, "tenantRepository", tenantRepository);
|
||||
IamTenant tenant = new IamTenant();
|
||||
tenant.setId(10001L); tenant.setTenantType("MERCHANT"); tenant.setStatus("PROFILE_REQUIRED");
|
||||
when(tenantRepository.findTenant(10001L)).thenReturn(tenant);
|
||||
when(repository.findQualifications(10001L)).thenReturn(List.of());
|
||||
|
||||
var response = service.registerPlatformQualification(new PlatformQualificationRegistrationRequest(
|
||||
10001L, "LEGAL_PERSON_ID", null, "/api/merchant/v1/files/qualifications/file.pdf",
|
||||
LocalDate.of(2030, 12, 31)));
|
||||
|
||||
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(LocalDate.of(2030, 12, 31), captor.getValue().getValidTo());
|
||||
assertEquals("PROFILE_REQUIRED", response.tenantStatus());
|
||||
assertNull(response.reviewId());
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import com.baiyue.common.core.api.PageResult;
|
||||
import com.baiyue.iam.entity.IamTenant;
|
||||
import com.baiyue.iam.dto.TenantDtos.ChangeTenantStatusRequest;
|
||||
import com.baiyue.iam.dto.TenantDtos.UpdateTenantRequest;
|
||||
import com.baiyue.iam.dto.TenantDtos.RegisterRequest;
|
||||
import com.baiyue.iam.entity.IamTenantQualification;
|
||||
import com.baiyue.iam.repository.TenantRepository;
|
||||
import com.baiyue.iam.repository.ProfileResourceRepository;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -18,6 +20,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
@@ -72,12 +76,13 @@ class TenantServiceTest {
|
||||
|
||||
var updated = service.updateMerchant(10001L,
|
||||
new UpdateTenantRequest("New name", "New legal", "Alice", "13900139000", "alice@example.com",
|
||||
"alice_wechat", "Sales Zhang", 45, "LIMITED_COMPANY", LocalDate.of(2020, 1, 2),
|
||||
"专业版", "alice_wechat", "Sales Zhang", 45, "LIMITED_COMPANY", LocalDate.of(2020, 1, 2),
|
||||
new BigDecimal("5000000.00"), "CNY", "深圳市南山区示例路 1 号", "一期重点客户"));
|
||||
var disabled = service.changeMerchantStatus(10001L, new ChangeTenantStatusRequest("DISABLED"));
|
||||
|
||||
assertEquals("New name", updated.tenantName());
|
||||
assertEquals("alice_wechat", updated.contactWechat());
|
||||
assertEquals("专业版", updated.servicePackage());
|
||||
assertEquals("Sales Zhang", updated.salesOwnerName());
|
||||
assertEquals(45, updated.paymentTermDays());
|
||||
assertEquals("LIMITED_COMPANY", updated.enterpriseType());
|
||||
@@ -103,6 +108,41 @@ class TenantServiceTest {
|
||||
assertEquals(ResultCode.CONFLICT, exception.getResultCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void platformRegistrationCreatesDraftWithoutCreatingReviewTicket() {
|
||||
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", "91330100EXAMPLE001", "Alice", "13800138000",
|
||||
"alice@example.com", "基础版", "alice_wechat", "Sales Zhang", 30, "LIMITED_COMPANY",
|
||||
LocalDate.of(2020, 1, 2), new BigDecimal("5000000.00"), "CNY", "深圳市南山区", "草稿客户"));
|
||||
|
||||
assertEquals("PROFILE_REQUIRED", created.status());
|
||||
assertEquals("基础版", created.servicePackage());
|
||||
verify(repository, never()).insertReview(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void explicitSubmissionRequiresBothMandatoryQualificationsAndCreatesReview() {
|
||||
TenantRepository repository = mock(TenantRepository.class);
|
||||
ProfileResourceRepository profileRepository = mock(ProfileResourceRepository.class);
|
||||
TenantService service = new TenantService();
|
||||
ReflectionTestUtils.setField(service, "repository", repository);
|
||||
ReflectionTestUtils.setField(service, "profileRepository", profileRepository);
|
||||
IamTenant tenant = merchant(10001L, "PROFILE_REQUIRED");
|
||||
when(repository.findTenant(10001L)).thenReturn(tenant);
|
||||
when(profileRepository.findQualifications(10001L)).thenReturn(List.of(
|
||||
qualification("BUSINESS_LICENSE"), qualification("LEGAL_PERSON_ID")));
|
||||
|
||||
var submitted = service.submitMerchantReview(10001L);
|
||||
|
||||
assertEquals("PENDING", submitted.status());
|
||||
verify(repository).insertReview(any());
|
||||
verify(repository).saveTenant(tenant);
|
||||
}
|
||||
|
||||
@Test
|
||||
void 企业激活不等于具备下单资格仍需已审核资质() {
|
||||
TenantRepository repository = mock(TenantRepository.class);
|
||||
@@ -134,4 +174,10 @@ class TenantServiceTest {
|
||||
tenant.setContactPhone("13800138000");
|
||||
return tenant;
|
||||
}
|
||||
|
||||
private IamTenantQualification qualification(String type) {
|
||||
IamTenantQualification item = new IamTenantQualification();
|
||||
item.setQualificationType(type); item.setStatus("PENDING");
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user