优化
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
||||
rejectEnterpriseReview,
|
||||
updatePlatformTenant,
|
||||
uploadPlatformTenantQualification,
|
||||
uploadPlatformTenantRequiredQualificationPackage,
|
||||
type EnterpriseReview,
|
||||
type PlatformTenantResource,
|
||||
} from '@/modules/iam'
|
||||
@@ -45,6 +46,7 @@ vi.mock('@/modules/iam', () => ({
|
||||
submitPlatformTenantReview: vi.fn(),
|
||||
updatePlatformTenant: vi.fn(),
|
||||
uploadPlatformTenantQualification: vi.fn(),
|
||||
uploadPlatformTenantRequiredQualificationPackage: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/modules/downloads', () => ({ createPlatformExportTask: vi.fn() }))
|
||||
@@ -107,6 +109,13 @@ describe('CustomersView', () => {
|
||||
vi.mocked(uploadPlatformTenantQualification).mockResolvedValue({
|
||||
qualificationId: 'qualification-1', reviewId: null, tenantStatus: 'PROFILE_REQUIRED', documentUrl: '/files/license.png',
|
||||
})
|
||||
vi.mocked(uploadPlatformTenantRequiredQualificationPackage).mockResolvedValue({
|
||||
businessLicenseQualificationId: 'qualification-business',
|
||||
legalPersonIdQualificationId: 'qualification-legal',
|
||||
tenantStatus: 'PROFILE_REQUIRED',
|
||||
businessLicenseDocumentUrl: '/files/license.png',
|
||||
legalPersonIdDocumentUrl: '/files/legal-person-new.png',
|
||||
})
|
||||
vi.mocked(createPlatformExportTask).mockResolvedValue({
|
||||
id: 'export-1', taskName: '客户列表', taskNo: 'EXP2084', status: 'PENDING',
|
||||
} as never)
|
||||
@@ -198,7 +207,7 @@ describe('CustomersView', () => {
|
||||
expect(getPlatformTenant).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('saves customer data and selected qualifications with one action', async () => {
|
||||
it('saves both required files and the credit code through one package action', async () => {
|
||||
vi.mocked(listTenantQualifications).mockResolvedValue([{
|
||||
id: 'qualification-legal', tenantId: fullTenant.id, qualificationType: 'LEGAL_PERSON_ID',
|
||||
certificateNo: null, documentUrl: '/files/legal-person.png', status: 'PENDING', reviewComment: null, validTo: null,
|
||||
@@ -209,9 +218,14 @@ describe('CustomersView', () => {
|
||||
await flushPromises()
|
||||
|
||||
const licenseFile = new File(['license'], 'license.png', { type: 'image/png' })
|
||||
const licenseInput = document.querySelector<HTMLInputElement>('.qualification-file input[type="file"]')
|
||||
const legalPersonFile = new File(['legal-person'], 'legal-person.png', { type: 'image/png' })
|
||||
const qualificationInputs = document.querySelectorAll<HTMLInputElement>('.qualification-file input[type="file"]')
|
||||
const licenseInput = qualificationInputs[0]
|
||||
const legalPersonInput = qualificationInputs[1]
|
||||
Object.defineProperty(licenseInput, 'files', { configurable: true, value: [licenseFile] })
|
||||
licenseInput?.dispatchEvent(new Event('change', { bubbles: true }))
|
||||
Object.defineProperty(legalPersonInput, 'files', { configurable: true, value: [legalPersonFile] })
|
||||
legalPersonInput?.dispatchEvent(new Event('change', { bubbles: true }))
|
||||
await flushPromises()
|
||||
|
||||
const buttons = [...document.querySelectorAll<HTMLButtonElement>('button')]
|
||||
@@ -220,14 +234,15 @@ describe('CustomersView', () => {
|
||||
await flushPromises()
|
||||
|
||||
expect(updatePlatformTenant).toHaveBeenCalledWith(fullTenant.id, expect.objectContaining({ tenantName: fullTenant.tenantName }))
|
||||
expect(uploadPlatformTenantQualification).toHaveBeenCalledWith(
|
||||
expect(uploadPlatformTenantRequiredQualificationPackage).toHaveBeenCalledWith(
|
||||
fullTenant.id,
|
||||
'BUSINESS_LICENSE',
|
||||
licenseFile,
|
||||
undefined,
|
||||
legalPersonFile,
|
||||
fullTenant.unifiedSocialCreditCode,
|
||||
false,
|
||||
true,
|
||||
)
|
||||
expect(uploadPlatformTenantQualification).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('submits the current filters to the backend export service without loading every customer page', async () => {
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
submitPlatformTenantReview,
|
||||
updatePlatformTenant,
|
||||
uploadPlatformTenantQualification,
|
||||
uploadPlatformTenantRequiredQualificationPackage,
|
||||
type CreatePlatformTenantRequest,
|
||||
type EnterpriseApprovalResponse,
|
||||
type EnterpriseReview,
|
||||
@@ -97,7 +98,8 @@ const queryFields: QueryField[] = [
|
||||
] },
|
||||
]
|
||||
|
||||
const emptyForm = (): CreatePlatformTenantRequest => ({
|
||||
type CustomerForm = CreatePlatformTenantRequest & { unifiedSocialCreditCode: string }
|
||||
const emptyForm = (): CustomerForm => ({
|
||||
tenantName: '', legalName: '', legalRepresentative: '', unifiedSocialCreditCode: '', contactName: '', contactPhone: '',
|
||||
contactEmail: '', contactWechat: '', servicePackage: '基础版', salesOwnerName: '', paymentTermDays: 30,
|
||||
enterpriseType: '', establishedDate: null, registeredCapital: 100000, registeredCapitalCurrency: 'CNY',
|
||||
@@ -174,17 +176,24 @@ const canSubmitReview = computed(() => Boolean(editingId.value) && ['PROFILE_REQ
|
||||
const uploadingQualifications = computed(() => qualifications.value.some((item) => item.uploading))
|
||||
const hasSelectedQualifications = computed(() => qualifications.value.some((item) => item.file))
|
||||
const selectedBusinessLicense = computed(() => qualifications.value.find((item) => item.type === 'BUSINESS_LICENSE')?.file)
|
||||
const creditCodeReadyForSave = computed(() => creditCodeEstablished.value
|
||||
|| (Boolean(selectedBusinessLicense.value) && unifiedSocialCreditCodePattern.test(form.value.unifiedSocialCreditCode || '')))
|
||||
const selectedLegalPersonId = computed(() => qualifications.value.find((item) => item.type === 'LEGAL_PERSON_ID')?.file)
|
||||
const requiredPackageChangeSelected = computed(() => Boolean(selectedBusinessLicense.value || selectedLegalPersonId.value))
|
||||
const requiredPackageFilesSelected = computed(() => Boolean(selectedBusinessLicense.value && selectedLegalPersonId.value))
|
||||
const creditCodeReadyForSave = computed(() => requiredPackageChangeSelected.value
|
||||
? requiredPackageFilesSelected.value && (creditCodeEstablished.value
|
||||
|| unifiedSocialCreditCodePattern.test(form.value.unifiedSocialCreditCode || ''))
|
||||
: creditCodeEstablished.value)
|
||||
const requiredQualificationPackageComplete = computed(() => creditCodeReadyForSave.value
|
||||
&& qualifications.value.filter((item) => item.required).every((item) => item.uploaded || Boolean(item.file)))
|
||||
&& (requiredPackageChangeSelected.value
|
||||
? requiredPackageFilesSelected.value
|
||||
: qualifications.value.filter((item) => item.required).every((item) => item.uploaded)))
|
||||
const missingQualificationPackageItems = computed(() => {
|
||||
const businessLicense = qualifications.value.find((item) => item.type === 'BUSINESS_LICENSE')
|
||||
const legalPersonId = qualifications.value.find((item) => item.type === 'LEGAL_PERSON_ID')
|
||||
const missing: string[] = []
|
||||
if (!businessLicense?.uploaded && !businessLicense?.file) missing.push('营业执照')
|
||||
if (!creditCodeReadyForSave.value) missing.push('18位统一社会信用代码(需随营业执照保存)')
|
||||
if (!legalPersonId?.uploaded && !legalPersonId?.file) missing.push('法人身份证')
|
||||
if (requiredPackageChangeSelected.value ? !businessLicense?.file : !businessLicense?.uploaded) missing.push('营业执照')
|
||||
if (!creditCodeReadyForSave.value) missing.push('18位统一社会信用代码(需随必填资质包保存)')
|
||||
if (requiredPackageChangeSelected.value ? !legalPersonId?.file : !legalPersonId?.uploaded) missing.push('法人身份证')
|
||||
return missing
|
||||
})
|
||||
const hasUnsavedChanges = computed(() => Boolean(editingId.value) && JSON.stringify(form.value) !== savedFormSnapshot.value)
|
||||
@@ -322,7 +331,7 @@ function openCreate() {
|
||||
editingId.value = null; editorStatus.value = null; resetEditor(); editorOpen.value = true
|
||||
nextTick(() => formRef.value?.clearValidate())
|
||||
}
|
||||
function mapTenantToForm(row: PlatformTenantResource): CreatePlatformTenantRequest {
|
||||
function mapTenantToForm(row: PlatformTenantResource): CustomerForm {
|
||||
return {
|
||||
tenantName: row.tenantName, legalName: row.legalName || '', legalRepresentative: row.legalRepresentative || '', unifiedSocialCreditCode: row.unifiedSocialCreditCode || '',
|
||||
contactName: row.contactName || '', contactPhone: row.contactPhone || '', contactEmail: row.contactEmail || '',
|
||||
@@ -435,16 +444,40 @@ function handleQualificationPreviewError(draft: QualificationDraft) {
|
||||
|
||||
async function uploadSelectedQualifications(tenantId: string) {
|
||||
const failures: string[] = []
|
||||
for (const draft of qualifications.value.filter((item) => item.file)) {
|
||||
const businessLicense = qualifications.value.find((item) => item.type === 'BUSINESS_LICENSE')
|
||||
const legalPersonId = qualifications.value.find((item) => item.type === 'LEGAL_PERSON_ID')
|
||||
if (requiredPackageChangeSelected.value && businessLicense?.file && legalPersonId?.file) {
|
||||
businessLicense.uploading = true; legalPersonId.uploading = true
|
||||
businessLicense.error = ''; legalPersonId.error = ''
|
||||
try {
|
||||
const uploaded = await uploadPlatformTenantRequiredQualificationPackage(
|
||||
tenantId, businessLicense.file, legalPersonId.file, form.value.unifiedSocialCreditCode,
|
||||
businessLicense.uploaded, legalPersonId.uploaded,
|
||||
)
|
||||
businessLicense.uploaded = true; legalPersonId.uploaded = true
|
||||
creditCodeEstablished.value = true
|
||||
businessLicense.uploadedName = businessLicense.file.name
|
||||
legalPersonId.uploadedName = legalPersonId.file.name
|
||||
businessLicense.documentUrl = uploaded.businessLicenseDocumentUrl
|
||||
legalPersonId.documentUrl = uploaded.legalPersonIdDocumentUrl
|
||||
businessLicense.file = null; legalPersonId.file = null
|
||||
} catch (error) {
|
||||
const message = getApiErrorMessage(error, '必填资质包上传失败,请重试')
|
||||
businessLicense.error = message; legalPersonId.error = message
|
||||
failures.push('必填资质包')
|
||||
} finally {
|
||||
businessLicense.uploading = false; legalPersonId.uploading = false
|
||||
}
|
||||
}
|
||||
for (const draft of failures.length ? [] : qualifications.value.filter((item) => !item.required && item.file)) {
|
||||
draft.uploading = true; draft.error = ''
|
||||
try {
|
||||
const uploaded = await uploadPlatformTenantQualification(
|
||||
tenantId, draft.type, draft.file as File, draft.validTo || undefined,
|
||||
draft.type === 'BUSINESS_LICENSE' ? form.value.unifiedSocialCreditCode : undefined,
|
||||
undefined,
|
||||
draft.uploaded,
|
||||
)
|
||||
draft.uploaded = true
|
||||
if (draft.type === 'BUSINESS_LICENSE') creditCodeEstablished.value = true
|
||||
draft.uploadedName = draft.file?.name || '已登记文件'
|
||||
draft.documentUrl = uploaded.documentUrl
|
||||
draft.file = null
|
||||
@@ -470,7 +503,8 @@ async function saveCustomer() {
|
||||
const { unifiedSocialCreditCode: _immutable, ...payload } = form.value
|
||||
saved = await updatePlatformTenant(editingId.value, payload)
|
||||
} else {
|
||||
saved = await createPlatformTenant({ ...form.value, unifiedSocialCreditCode: undefined })
|
||||
const { unifiedSocialCreditCode: _qualificationCreditCode, ...payload } = form.value
|
||||
saved = await createPlatformTenant(payload)
|
||||
editingId.value = String(saved.id)
|
||||
}
|
||||
editorStatus.value = saved.status
|
||||
@@ -667,7 +701,7 @@ onMounted(async () => {
|
||||
</div><el-form-item label="内部备注" prop="internalRemark"><el-input v-model="form.internalRemark" type="textarea" :rows="3" placeholder="请输入仅平台人员可见的客户备注" maxlength="1000" show-word-limit /></el-form-item></div>
|
||||
|
||||
<div class="form-section qualification-section"><div class="qualification-heading"><div><h3>企业认证必填业务项</h3><p>营业执照、18位统一社会信用代码、法人身份证需共同完整保存;货代资质证书和银行开户许可证选填。</p></div><el-tag v-if="editorStatus" :type="statusMeta(editorStatus).type" effect="light">{{ statusMeta(editorStatus).label }}</el-tag></div>
|
||||
<el-form-item label="统一社会信用代码" prop="unifiedSocialCreditCode" required class="qualification-credit-code"><el-input v-model="form.unifiedSocialCreditCode" :disabled="creditCodeEstablished" placeholder="请输入18位大写字母或数字" maxlength="18" @input="form.unifiedSocialCreditCode = String($event).toUpperCase()" /><small>{{ creditCodeEstablished ? '已登记的统一社会信用代码不可直接修改。' : '必须与营业执照一致,并随营业执照文件共同保存。' }}</small></el-form-item>
|
||||
<el-form-item label="统一社会信用代码" prop="unifiedSocialCreditCode" required class="qualification-credit-code"><el-input v-model="form.unifiedSocialCreditCode" :disabled="creditCodeEstablished" placeholder="请输入18位大写字母或数字" maxlength="18" @input="form.unifiedSocialCreditCode = String($event).toUpperCase()" /><small>{{ creditCodeEstablished ? '已登记的统一社会信用代码不可直接修改。' : '必须与营业执照一致,并随营业执照、法人身份证共同保存。' }}</small></el-form-item>
|
||||
<div v-if="qualificationLoadError" class="qualification-load-error" role="alert">{{ qualificationLoadError }}<el-button v-if="editingId" link type="primary" @click="loadEditorQualifications(editingId)">重试</el-button></div>
|
||||
<div class="qualification-table" role="table" aria-label="客户资质文件">
|
||||
<div class="qualification-row qualification-header" role="row"><span role="columnheader">文件类型</span><span role="columnheader">上传文件</span><span role="columnheader">有效期</span></div>
|
||||
|
||||
Reference in New Issue
Block a user