feat(client): align account and order foundations
This commit is contained in:
@@ -42,11 +42,18 @@ test('supports tenant profile updates and authenticated qualification downloads'
|
||||
assert.match(storeSource, /enterpriseType: profile\.enterpriseType \?\? ''/)
|
||||
})
|
||||
|
||||
test('uploads the three required qualification fields through one multipart package request', () => {
|
||||
test('uploads the required license and omits the optional legal-person file when absent', () => {
|
||||
assert.match(source, /url: '\/merchant\/v1\/files\/qualifications\/required-package'/)
|
||||
assert.match(source, /data\.append\('businessLicense', businessLicense\)/)
|
||||
assert.match(source, /data\.append\('legalPersonId', legalPersonId\)/)
|
||||
assert.match(source, /params: \{ unifiedSocialCreditCode, replaceBusinessLicense, replaceLegalPersonId \}/)
|
||||
assert.match(source, /if \(legalPersonId\) data\.append\('legalPersonId', legalPersonId\)/)
|
||||
assert.match(source, /\.\.\.\(legalPersonId \? \{ replaceLegalPersonId: Boolean\(replaceLegalPersonId\) \} : \{\}\)/)
|
||||
assert.match(source, /legalPersonIdQualificationId: number \| string \| null/)
|
||||
})
|
||||
|
||||
test('uses the dedicated login-identifiers endpoint and keeps phone out of ordinary profile updates', () => {
|
||||
assert.match(source, /url: '\/merchant\/v1\/iam\/profile\/login-identifiers'/)
|
||||
assert.match(source, /ChangeMerchantLoginIdentifiersRequest/)
|
||||
assert.match(source, /Pick<MerchantPersonalProfile, 'displayName' \| 'email'>/)
|
||||
})
|
||||
|
||||
test('uses membershipId and full replacement arrays in authorization writes', () => {
|
||||
|
||||
@@ -65,10 +65,10 @@ export interface QualificationUploadResult {
|
||||
|
||||
export interface RequiredQualificationPackageUploadResult {
|
||||
businessLicenseQualificationId: number | string
|
||||
legalPersonIdQualificationId: number | string
|
||||
legalPersonIdQualificationId: number | string | null
|
||||
tenantStatus: MerchantTenantStatus
|
||||
businessLicenseDocumentUrl: string
|
||||
legalPersonIdDocumentUrl: string
|
||||
legalPersonIdDocumentUrl: string | null
|
||||
}
|
||||
|
||||
export interface MerchantRole {
|
||||
@@ -119,6 +119,12 @@ export interface MerchantPersonalProfile {
|
||||
email: string | null
|
||||
}
|
||||
|
||||
export interface ChangeMerchantLoginIdentifiersRequest {
|
||||
username: string
|
||||
phone: string
|
||||
currentPassword: string
|
||||
}
|
||||
|
||||
export interface CreateMerchantMemberRequest {
|
||||
username: string
|
||||
password: string
|
||||
@@ -163,10 +169,18 @@ export function getMerchantPersonalProfile() {
|
||||
return apiRequest<MerchantPersonalProfile>({ method: 'GET', url: '/merchant/v1/iam/profile' })
|
||||
}
|
||||
|
||||
export function updateMerchantPersonalProfile(payload: Pick<MerchantPersonalProfile, 'displayName' | 'phone' | 'email'>) {
|
||||
export function updateMerchantPersonalProfile(payload: Pick<MerchantPersonalProfile, 'displayName' | 'email'>) {
|
||||
return apiRequest<MerchantPersonalProfile>({ method: 'PUT', url: '/merchant/v1/iam/profile', data: payload })
|
||||
}
|
||||
|
||||
export function updateMerchantLoginIdentifiers(payload: ChangeMerchantLoginIdentifiersRequest) {
|
||||
return apiRequest<MerchantPersonalProfile>({
|
||||
method: 'PUT',
|
||||
url: '/merchant/v1/iam/profile/login-identifiers',
|
||||
data: payload,
|
||||
})
|
||||
}
|
||||
|
||||
export function uploadMerchantQualification(
|
||||
file: File,
|
||||
qualificationType: MerchantQualificationType,
|
||||
@@ -185,18 +199,22 @@ export function uploadMerchantQualification(
|
||||
|
||||
export function uploadMerchantRequiredQualificationPackage(
|
||||
businessLicense: File,
|
||||
legalPersonId: File,
|
||||
legalPersonId: File | undefined,
|
||||
unifiedSocialCreditCode: string,
|
||||
replaceBusinessLicense = false,
|
||||
replaceLegalPersonId = false,
|
||||
replaceLegalPersonId?: boolean,
|
||||
) {
|
||||
const data = new FormData()
|
||||
data.append('businessLicense', businessLicense)
|
||||
data.append('legalPersonId', legalPersonId)
|
||||
if (legalPersonId) data.append('legalPersonId', legalPersonId)
|
||||
return apiRequest<RequiredQualificationPackageUploadResult>({
|
||||
method: 'POST',
|
||||
url: '/merchant/v1/files/qualifications/required-package',
|
||||
params: { unifiedSocialCreditCode, replaceBusinessLicense, replaceLegalPersonId },
|
||||
params: {
|
||||
unifiedSocialCreditCode,
|
||||
replaceBusinessLicense,
|
||||
...(legalPersonId ? { replaceLegalPersonId: Boolean(replaceLegalPersonId) } : {}),
|
||||
},
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@ export {
|
||||
getCurrentMerchantPermissionCodes,
|
||||
getMerchantPersonalProfile,
|
||||
getTenantProfile,
|
||||
updateMerchantLoginIdentifiers,
|
||||
updateMerchantPersonalProfile,
|
||||
type ChangeMerchantLoginIdentifiersRequest,
|
||||
type MerchantPermissionResource,
|
||||
} from './api/merchantIam'
|
||||
export {
|
||||
|
||||
@@ -20,7 +20,7 @@ const store = useTenantAdministrationStore()
|
||||
const auth = useAuthStore()
|
||||
const companyApproved = computed(() => store.company.auditStatus === 'ACTIVE')
|
||||
const auditStatusCopy = computed(() => ({
|
||||
PROFILE_REQUIRED: { title: '请完善企业资质', detail: '上传营业执照和法人身份证并提交审核后,才能提交头程订单。' },
|
||||
PROFILE_REQUIRED: { title: '请完善企业资质', detail: '上传营业执照并提交审核后,才能提交头程订单;法人身份证可按需补充。' },
|
||||
PENDING: { title: '企业资料正在等待平台审核', detail: '审核期间仍可维护资料和保存订单草稿;审核通过前不能提交订单。' },
|
||||
ACTIVE: { title: '企业资料已审核,下单资格已开通', detail: '您可以创建、保存并提交头程订单。' },
|
||||
REJECTED: { title: '企业资料审核未通过', detail: '请根据平台反馈替换对应资质,必填文件齐全后可重新提交审核。' },
|
||||
@@ -60,7 +60,7 @@ type QualificationDraftItem = QualificationTypeDefinition & {
|
||||
|
||||
const qualificationTypes: QualificationTypeDefinition[] = [
|
||||
{ value: 'BUSINESS_LICENSE', label: '营业执照', description: '上传清晰完整的营业执照文件。', required: true },
|
||||
{ value: 'LEGAL_PERSON_ID', label: '法人身份证', description: '正反面请合并为一个 PDF、JPG 或 PNG 文件。', required: true },
|
||||
{ value: 'LEGAL_PERSON_ID', label: '法人身份证', description: '选填;如上传,正反面请合并为一个 PDF、JPG 或 PNG 文件。', required: false },
|
||||
{ value: 'FREIGHT_FORWARDER_CERTIFICATE', label: '货代资质证书', description: '如有,可上传当前有效的货代资质证明。', required: false },
|
||||
{ value: 'BANK_ACCOUNT_LICENSE', label: '银行开户许可证', description: '如有,可上传企业对公账户开户证明。', required: false },
|
||||
]
|
||||
@@ -83,14 +83,15 @@ const normalizedQualificationCertificateNo = computed(() => qualificationCertifi
|
||||
const draftCreditCodeValid = computed(() => unifiedSocialCreditCodePattern.test(normalizedQualificationCertificateNo.value))
|
||||
const businessLicenseFileSelected = computed(() => Boolean(qualificationDraftItem('BUSINESS_LICENSE')?.file))
|
||||
const legalPersonIdFileSelected = computed(() => Boolean(qualificationDraftItem('LEGAL_PERSON_ID')?.file))
|
||||
const requiredPackageChangeSelected = computed(() => businessLicenseFileSelected.value || legalPersonIdFileSelected.value)
|
||||
const requiredPackageFilesSelected = computed(() => businessLicenseFileSelected.value && legalPersonIdFileSelected.value)
|
||||
const requiredPackageChangeSelected = computed(() => businessLicenseFileSelected.value)
|
||||
const requiredPackageFilesSelected = computed(() => businessLicenseFileSelected.value)
|
||||
const qualificationCreditCodeReady = computed(() => requiredPackageChangeSelected.value
|
||||
? requiredPackageFilesSelected.value && (companyCreditCodeValid.value || draftCreditCodeValid.value)
|
||||
: companyCreditCodeValid.value)
|
||||
const qualificationCreditCodeError = computed(() => {
|
||||
if (qualificationCreditCodeReady.value) return ''
|
||||
if (draftCreditCodeValid.value && !requiredPackageFilesSelected.value) return '统一社会信用代码必须随营业执照和法人身份证共同保存'
|
||||
if (legalPersonIdFileSelected.value && !businessLicenseFileSelected.value) return '法人身份证需与本次营业执照一起保存'
|
||||
if (draftCreditCodeValid.value && !requiredPackageFilesSelected.value) return '统一社会信用代码必须随营业执照共同保存'
|
||||
return '请输入18位字母或数字组成的统一社会信用代码'
|
||||
})
|
||||
const canUploadQualification = computed(() => ['PROFILE_REQUIRED', 'REJECTED'].includes(store.company.auditStatus))
|
||||
@@ -121,9 +122,11 @@ const qualificationUploadDisabledReason = computed(() => {
|
||||
const selectedQualificationCount = computed(() => qualificationDrafts.filter((item) => Boolean(item.file)).length)
|
||||
const canSaveQualificationBatch = computed(() => canUploadQualification.value
|
||||
&& selectedQualificationCount.value > 0
|
||||
&& (!legalPersonIdFileSelected.value || businessLicenseFileSelected.value)
|
||||
&& requiredPackageComplete.value)
|
||||
const qualificationSaveDisabledReason = computed(() => {
|
||||
if (!canUploadQualification.value) return qualificationUploadDisabledReason.value
|
||||
if (legalPersonIdFileSelected.value && !businessLicenseFileSelected.value) return '法人身份证需与本次营业执照一起保存'
|
||||
if (!requiredPackageComplete.value) return `请完整保存必填业务项:${missingRequiredPackageItems.value.join('、')}`
|
||||
if (!selectedQualificationCount.value) return '当前没有需要保存的变更'
|
||||
return ''
|
||||
@@ -141,7 +144,7 @@ const qualificationReviewDisabledReason = computed(() => {
|
||||
return missing.length ? `请先上传:${missing.join('、')}` : ''
|
||||
})
|
||||
const qualificationPanelHint = computed(() => canUploadQualification.value
|
||||
? '营业执照、法人身份证为必填;货代资质证书、银行开户许可证可选填。文件保存后需另行提交审核。'
|
||||
? '营业执照为必填;法人身份证、货代资质证书、银行开户许可证可选填。文件保存后需另行提交审核。'
|
||||
: qualificationUploadDisabledReason.value)
|
||||
const profileFormRules: FormRules<TenantCompanyProfile> = {
|
||||
companyName: [{ required: true, whitespace: true, message: '请输入公司名称', trigger: 'blur' }],
|
||||
@@ -340,7 +343,8 @@ async function submitQualification() {
|
||||
ElMessage.warning(`请完整保存必填业务项:${missingRequiredPackageItems.value.join('、')}`)
|
||||
return
|
||||
}
|
||||
const optionalItems = qualificationDrafts.filter((item) => !item.required && item.file)
|
||||
const optionalItems = qualificationDrafts.filter((item) => !item.required && item.value !== 'LEGAL_PERSON_ID' && item.file)
|
||||
const includedLegalPersonId = legalPersonIdFileSelected.value
|
||||
qualificationSubmitting.value = true
|
||||
let uploadedCount = 0
|
||||
const failures: string[] = []
|
||||
@@ -348,7 +352,7 @@ async function submitQualification() {
|
||||
if (requiredPackageChangeSelected.value) {
|
||||
const businessLicense = qualificationDraftItem('BUSINESS_LICENSE')
|
||||
const legalPersonId = qualificationDraftItem('LEGAL_PERSON_ID')
|
||||
for (const item of [businessLicense, legalPersonId]) {
|
||||
for (const item of [businessLicense, ...(legalPersonId?.file ? [legalPersonId] : [])]) {
|
||||
if (!item) continue
|
||||
item.error = ''
|
||||
item.uploadState = 'uploading'
|
||||
@@ -356,12 +360,13 @@ async function submitQualification() {
|
||||
try {
|
||||
await uploadMerchantRequiredQualificationPackage(
|
||||
businessLicense?.file as File,
|
||||
legalPersonId?.file as File,
|
||||
legalPersonId?.file || undefined,
|
||||
normalizedQualificationCertificateNo.value,
|
||||
Boolean(replaceableQualification('BUSINESS_LICENSE')),
|
||||
Boolean(replaceableQualification('LEGAL_PERSON_ID')),
|
||||
legalPersonId?.file ? Boolean(replaceableQualification('LEGAL_PERSON_ID')) : undefined,
|
||||
)
|
||||
for (const item of [businessLicense, legalPersonId]) {
|
||||
const uploadedRequiredItems = [businessLicense, ...(legalPersonId?.file ? [legalPersonId] : [])]
|
||||
for (const item of uploadedRequiredItems) {
|
||||
if (!item) continue
|
||||
releaseQualificationPreview(item)
|
||||
item.file = null
|
||||
@@ -371,11 +376,11 @@ async function submitQualification() {
|
||||
item.inputVersion += 1
|
||||
item.uploadState = 'uploaded'
|
||||
}
|
||||
uploadedCount += 2
|
||||
uploadedCount += uploadedRequiredItems.filter(Boolean).length
|
||||
} catch (error) {
|
||||
const message = qualificationUploadErrorMessage(error)
|
||||
if (businessLicense) { businessLicense.error = message; businessLicense.uploadState = 'error' }
|
||||
if (legalPersonId) { legalPersonId.error = message; legalPersonId.uploadState = 'error' }
|
||||
if (legalPersonId?.file) { legalPersonId.error = message; legalPersonId.uploadState = 'error' }
|
||||
failures.push(`必填资质包:${message}`)
|
||||
}
|
||||
}
|
||||
@@ -414,7 +419,9 @@ async function submitQualification() {
|
||||
return
|
||||
}
|
||||
qualificationOpen.value = false
|
||||
if (refreshed) ElMessage.success('营业执照、统一社会信用代码和法人身份证已完整保存,尚未提交审核')
|
||||
if (refreshed) ElMessage.success(includedLegalPersonId
|
||||
? '营业执照、统一社会信用代码和法人身份证已保存,尚未提交审核'
|
||||
: '营业执照和统一社会信用代码已保存,尚未提交审核')
|
||||
else ElMessage.warning('资质文件已保存,但最新状态刷新失败,请点击“重新加载”确认状态')
|
||||
} finally {
|
||||
qualificationSubmitting.value = false
|
||||
@@ -582,7 +589,7 @@ function closeQualificationPreview() {
|
||||
>
|
||||
<div class="qualification-dialog-intro">
|
||||
<strong>企业认证必填业务项</strong>
|
||||
<span>营业执照、18位统一社会信用代码、法人身份证需共同完整保存;其余两项可按需上传。</span>
|
||||
<span>营业执照和18位统一社会信用代码需共同保存;法人身份证及其他资质可按需上传。</span>
|
||||
</div>
|
||||
<div class="qualification-upload-grid">
|
||||
<article v-for="item in qualificationDrafts" :key="item.value" class="qualification-upload-item">
|
||||
|
||||
@@ -51,7 +51,7 @@ test('implements company details, contact information and qualification workflow
|
||||
assert.match(profileSource, /:close-on-press-escape="!qualificationSubmitting"/)
|
||||
assert.match(profileSource, /:show-close="!qualificationSubmitting"/)
|
||||
assert.match(profileSource, /const requiredPackageComplete = computed/)
|
||||
assert.match(profileSource, /营业执照、18位统一社会信用代码、法人身份证需共同完整保存/)
|
||||
assert.match(profileSource, /营业执照和18位统一社会信用代码需共同保存;法人身份证及其他资质可按需上传/)
|
||||
assert.match(profileSource, /已登记的统一社会信用代码不可直接修改/)
|
||||
assert.match(profileSource, /companyCreditCodeValid\.value/)
|
||||
assert.doesNotMatch(profileSource, /<el-select v-model="qualificationDraft\.type"/)
|
||||
@@ -68,13 +68,14 @@ test('implements company details, contact information and qualification workflow
|
||||
assert.match(profileSource, /const valid = await profileFormRef\.value\?\.validate\(\)/)
|
||||
assert.match(profileSource, /const requiredQualificationTypes = qualificationTypes\.filter\(\(type\) => type\.required\)/)
|
||||
assert.match(profileSource, /货代资质证书'.*required: false/)
|
||||
assert.match(profileSource, /法人身份证'.*required: false/)
|
||||
assert.match(profileSource, /银行开户许可证'.*required: false/)
|
||||
assert.match(profileSource, /requiredQualificationTypes\.every/)
|
||||
assert.doesNotMatch(profileSource, /四类必填资质/)
|
||||
assert.match(profileSource, /qualificationUploadErrorMessage\(error\)/)
|
||||
assert.match(profileSource, /submitTenantQualificationReview/)
|
||||
assert.match(profileSource, /提交企业审核/)
|
||||
assert.match(profileSource, /营业执照、统一社会信用代码和法人身份证已完整保存,尚未提交审核/)
|
||||
assert.match(profileSource, /营业执照和统一社会信用代码已保存,尚未提交审核/)
|
||||
assert.match(profileSource, /资质文件已保存,但最新状态刷新失败/)
|
||||
assert.match(profileSource, /企业资料已提交审核,但最新状态刷新失败/)
|
||||
assert.match(profileSource, /qualificationStatusMeta/)
|
||||
|
||||
Reference in New Issue
Block a user