fix qualification upload contract and test baseline

This commit is contained in:
2026-09-12 12:27:56 +08:00
parent dcd079b5b7
commit 2191bacbfe
9 changed files with 61 additions and 6 deletions
@@ -3896,9 +3896,38 @@ CREATE TABLE IF NOT EXISTS fl_price_book_version (
KEY idx_price_book_source_hash (tenant_id, source_sha256) KEY idx_price_book_source_hash (tenant_id, source_sha256)
) ENGINE=InnoDB COMMENT='平台公共船期价格整表版本'; ) ENGINE=InnoDB COMMENT='平台公共船期价格整表版本';
-- Warehouse 增量段已在自身结束时删除同名过程;Firstleg 价格版本的存量补列需在当前库重新定义。
DROP PROCEDURE IF EXISTS add_column_if_missing;
DELIMITER $$
CREATE PROCEDURE add_column_if_missing(
IN p_table_name VARCHAR(64),
IN p_column_name VARCHAR(64),
IN p_column_definition TEXT
)
BEGIN
IF NOT EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_schema = DATABASE()
AND table_name = p_table_name
AND column_name = p_column_name
) THEN
SET @add_column_sql = CONCAT(
'ALTER TABLE `', REPLACE(p_table_name, '`', '``'),
'` ADD COLUMN `', REPLACE(p_column_name, '`', '``'),
'` ', p_column_definition
);
PREPARE add_column_statement FROM @add_column_sql;
EXECUTE add_column_statement;
DEALLOCATE PREPARE add_column_statement;
END IF;
END$$
DELIMITER ;
CALL add_column_if_missing('fl_price_book_version', 'voided_by', 'BIGINT UNSIGNED NULL COMMENT ''作废操作人ID'' AFTER superseded_at'); CALL add_column_if_missing('fl_price_book_version', 'voided_by', 'BIGINT UNSIGNED NULL COMMENT ''作废操作人ID'' AFTER superseded_at');
CALL add_column_if_missing('fl_price_book_version', 'voided_at', 'DATETIME(3) NULL COMMENT ''作废时间'' AFTER voided_by'); CALL add_column_if_missing('fl_price_book_version', 'voided_at', 'DATETIME(3) NULL COMMENT ''作废时间'' AFTER voided_by');
CALL add_column_if_missing('fl_price_book_version', 'void_reason', 'VARCHAR(500) NULL COMMENT ''作废原因'' AFTER voided_at'); CALL add_column_if_missing('fl_price_book_version', 'void_reason', 'VARCHAR(500) NULL COMMENT ''作废原因'' AFTER voided_at');
DROP PROCEDURE IF EXISTS add_column_if_missing;
CREATE TABLE IF NOT EXISTS fl_price_book_channel ( CREATE TABLE IF NOT EXISTS fl_price_book_channel (
id BIGINT UNSIGNED NOT NULL COMMENT '版本渠道快照ID', id BIGINT UNSIGNED NOT NULL COMMENT '版本渠道快照ID',
@@ -31,6 +31,7 @@ public interface IamQualificationClient {
record QualificationRequest(Long tenantId, String qualificationType, record QualificationRequest(Long tenantId, String qualificationType,
String certificateNo, String documentUrl, String certificateNo, String documentUrl,
String originalFileName,
java.time.LocalDate validTo, boolean replace) { } java.time.LocalDate validTo, boolean replace) { }
record QualificationResponse(Long qualificationId, Long reviewId, String tenantStatus, record QualificationResponse(Long qualificationId, Long reviewId, String tenantStatus,
String replacedDocumentUrl) { } String replacedDocumentUrl) { }
@@ -38,12 +39,15 @@ public interface IamQualificationClient {
String replacedDocumentUrl) { } String replacedDocumentUrl) { }
record PlatformQualificationRequest(Long tenantId, String qualificationType, record PlatformQualificationRequest(Long tenantId, String qualificationType,
String certificateNo, String documentUrl, String certificateNo, String documentUrl,
String originalFileName,
java.time.LocalDate validTo, boolean replace) { } java.time.LocalDate validTo, boolean replace) { }
record RequiredPackageRequest(Long tenantId, String unifiedSocialCreditCode, record RequiredPackageRequest(Long tenantId, String unifiedSocialCreditCode,
String businessLicenseDocumentUrl, String businessLicenseDocumentUrl,
String businessLicenseOriginalFileName,
java.time.LocalDate businessLicenseValidTo, java.time.LocalDate businessLicenseValidTo,
boolean replaceBusinessLicense, boolean replaceBusinessLicense,
String legalPersonIdDocumentUrl, String legalPersonIdDocumentUrl,
String legalPersonIdOriginalFileName,
java.time.LocalDate legalPersonIdValidTo, java.time.LocalDate legalPersonIdValidTo,
boolean replaceLegalPersonId) { } boolean replaceLegalPersonId) { }
record RequiredPackageResponse(Long businessLicenseQualificationId, record RequiredPackageResponse(Long businessLicenseQualificationId,
@@ -53,7 +53,8 @@ public class PlatformQualificationFileController {
try { try {
var response = iamClient.submitPlatform(internalToken, var response = iamClient.submitPlatform(internalToken,
new IamQualificationClient.PlatformQualificationRequest( new IamQualificationClient.PlatformQualificationRequest(
tenantId, qualificationType, certificateNo, stored.documentUrl(), validTo, replace)); tenantId, qualificationType, certificateNo, stored.documentUrl(),
file.getOriginalFilename(), validTo, replace));
if (response == null) { if (response == null) {
throw new IllegalStateException("IAM qualification response is empty"); throw new IllegalStateException("IAM qualification response is empty");
} }
@@ -45,7 +45,7 @@ public class QualificationFileController {
try { try {
var response = iamClient.submit(internalToken, var response = iamClient.submit(internalToken,
new IamQualificationClient.QualificationRequest(tenantId, qualificationType, new IamQualificationClient.QualificationRequest(tenantId, qualificationType,
certificateNo, stored.documentUrl(), validTo, replace)); certificateNo, stored.documentUrl(), file.getOriginalFilename(), validTo, replace));
if (response == null) { if (response == null) {
throw new IllegalStateException("IAM qualification response is empty"); throw new IllegalStateException("IAM qualification response is empty");
} }
@@ -45,8 +45,10 @@ public class QualificationPackageService {
String legalPersonIdDocumentUrl = storedLegalPersonId == null ? null : storedLegalPersonId.documentUrl(); String legalPersonIdDocumentUrl = storedLegalPersonId == null ? null : storedLegalPersonId.documentUrl();
var request = new IamQualificationClient.RequiredPackageRequest( var request = new IamQualificationClient.RequiredPackageRequest(
tenantId, normalizedCreditCode, storedBusinessLicense.documentUrl(), businessLicenseValidTo, tenantId, normalizedCreditCode, storedBusinessLicense.documentUrl(), businessLicense.getOriginalFilename(),
replaceBusinessLicense, legalPersonIdDocumentUrl, legalPersonIdValidTo, replaceLegalPersonId); businessLicenseValidTo, replaceBusinessLicense, legalPersonIdDocumentUrl,
legalPersonId == null ? null : legalPersonId.getOriginalFilename(),
legalPersonIdValidTo, replaceLegalPersonId);
try { try {
var response = platform var response = platform
? iamClient.submitPlatformRequiredPackage(internalToken, request) ? iamClient.submitPlatformRequiredPackage(internalToken, request)
@@ -57,6 +57,8 @@ class QualificationPackageServiceTest {
var captor = ArgumentCaptor.forClass(IamQualificationClient.RequiredPackageRequest.class); var captor = ArgumentCaptor.forClass(IamQualificationClient.RequiredPackageRequest.class);
ordered.verify(fixture.iamClient).submitRequiredPackage(eq("internal-token"), captor.capture()); ordered.verify(fixture.iamClient).submitRequiredPackage(eq("internal-token"), captor.capture());
assertEquals("91310101ABCDE12345", captor.getValue().unifiedSocialCreditCode()); assertEquals("91310101ABCDE12345", captor.getValue().unifiedSocialCreditCode());
assertEquals("license.pdf", captor.getValue().businessLicenseOriginalFileName());
assertEquals("legal-person.png", captor.getValue().legalPersonIdOriginalFileName());
assertEquals(BUSINESS_URL, result.businessLicenseDocumentUrl()); assertEquals(BUSINESS_URL, result.businessLicenseDocumentUrl());
assertEquals(LEGAL_PERSON_URL, result.legalPersonIdDocumentUrl()); assertEquals(LEGAL_PERSON_URL, result.legalPersonIdDocumentUrl());
} }
@@ -74,6 +76,7 @@ class QualificationPackageServiceTest {
var captor = ArgumentCaptor.forClass(IamQualificationClient.RequiredPackageRequest.class); var captor = ArgumentCaptor.forClass(IamQualificationClient.RequiredPackageRequest.class);
verify(fixture.iamClient).submitRequiredPackage(eq("internal-token"), captor.capture()); verify(fixture.iamClient).submitRequiredPackage(eq("internal-token"), captor.capture());
assertNull(captor.getValue().legalPersonIdDocumentUrl()); assertNull(captor.getValue().legalPersonIdDocumentUrl());
assertNull(captor.getValue().legalPersonIdOriginalFileName());
assertNull(captor.getValue().legalPersonIdValidTo()); assertNull(captor.getValue().legalPersonIdValidTo());
assertNull(result.legalPersonIdQualificationId()); assertNull(result.legalPersonIdQualificationId());
assertNull(result.legalPersonIdDocumentUrl()); assertNull(result.legalPersonIdDocumentUrl());
@@ -67,6 +67,19 @@ test('国内统一初始化在注释门禁前创建订单申报快照表', () =>
} }
}) })
test('国内统一增量在 Firstleg 补列前重建已由 Warehouse 段删除的过程', () => {
const warehouseDrop = sql.indexOf('DROP PROCEDURE IF EXISTS add_column_if_missing;',
sql.indexOf('CALL assert_platform_public_resource_scope();'))
const firstlegCall = sql.indexOf("CALL add_column_if_missing('fl_price_book_version', 'voided_by'")
const firstlegCreate = sql.lastIndexOf('CREATE PROCEDURE add_column_if_missing(', firstlegCall)
const firstlegDrop = sql.indexOf('DROP PROCEDURE IF EXISTS add_column_if_missing;', firstlegCall)
assert.ok(warehouseDrop >= 0, 'Warehouse migration must clean up its helper procedure')
assert.ok(firstlegCreate > warehouseDrop, 'Firstleg must recreate the helper after the Warehouse cleanup')
assert.ok(firstlegCreate < firstlegCall, 'Firstleg helper must exist before the price-version ALTER calls')
assert.ok(firstlegDrop > firstlegCall, 'Firstleg must clean up the helper after its ALTER calls')
})
test('海外统一初始化内联空库基线且不依赖模块 SQL', () => { test('海外统一初始化内联空库基线且不依赖模块 SQL', () => {
assert.match(overseasSql, /BEGIN INLINE EMPTY-DATABASE BASELINE: LASTLEG/) assert.match(overseasSql, /BEGIN INLINE EMPTY-DATABASE BASELINE: LASTLEG/)
assert.match(overseasSql, /BEGIN INLINE EMPTY-DATABASE BASELINE: OVERSEAS WAREHOUSE/) assert.match(overseasSql, /BEGIN INLINE EMPTY-DATABASE BASELINE: OVERSEAS WAREHOUSE/)
@@ -15,6 +15,8 @@ test('默认全量验收先准备已审核商户再运行订单链路', () => {
test('商户夹具通过正式 File 和 IAM 审核流程建立下单资格', () => { test('商户夹具通过正式 File 和 IAM 审核流程建立下单资格', () => {
assert.match(fixture, /files\/qualifications\/\$\{encodeURIComponent\(tenantId\)\}\/required-package/) assert.match(fixture, /files\/qualifications\/\$\{encodeURIComponent\(tenantId\)\}\/required-package/)
assert.match(fixture, /required-package\?unifiedSocialCreditCode=\$\{encodeURIComponent\(unifiedSocialCreditCode\)\}/)
assert.doesNotMatch(fixture, /packageForm\.append\('unifiedSocialCreditCode'/)
assert.match(fixture, /iam\/tenants\/\$\{encodeURIComponent\(tenantId\)\}\/submit-review/) assert.match(fixture, /iam\/tenants\/\$\{encodeURIComponent\(tenantId\)\}\/submit-review/)
assert.match(fixture, /iam\/tenants\/\$\{encodeURIComponent\(tenantId\)\}\/dimension-initialization/) assert.match(fixture, /iam\/tenants\/\$\{encodeURIComponent\(tenantId\)\}\/dimension-initialization/)
assert.match(fixture, /status === 'READY'/) assert.match(fixture, /status === 'READY'/)
@@ -62,9 +62,10 @@ export async function run(context) {
const packageForm = new FormData() const packageForm = new FormData()
packageForm.append('businessLicense', new Blob([QUALIFICATION_IMAGE], { type: 'image/png' }), `business-${suffix}.png`) packageForm.append('businessLicense', new Blob([QUALIFICATION_IMAGE], { type: 'image/png' }), `business-${suffix}.png`)
packageForm.append('legalPersonId', new Blob([QUALIFICATION_IMAGE], { type: 'image/png' }), `legal-person-${suffix}.png`) packageForm.append('legalPersonId', new Blob([QUALIFICATION_IMAGE], { type: 'image/png' }), `legal-person-${suffix}.png`)
packageForm.append('unifiedSocialCreditCode', `91440300${suffix}`) const unifiedSocialCreditCode = `91440300${suffix}`
const packageResponse = await context.requestWithBody('POST', const packageResponse = await context.requestWithBody('POST',
`/api/platform/v1/files/qualifications/${encodeURIComponent(tenantId)}/required-package`, packageForm) `/api/platform/v1/files/qualifications/${encodeURIComponent(tenantId)}/required-package?unifiedSocialCreditCode=${encodeURIComponent(unifiedSocialCreditCode)}`,
packageForm)
context.apiSuccess(MODULE, '通过 File 上传真实必填资质包', packageResponse, context.apiSuccess(MODULE, '通过 File 上传真实必填资质包', packageResponse,
`tenantId=${tenantId}`, { deploymentBlocker: true }) `tenantId=${tenantId}`, { deploymentBlocker: true })