优化
This commit is contained in:
@@ -14,7 +14,7 @@ server {
|
||||
|
||||
# 前端代码中的 API 前缀为 /api;保持原始路径转给 Gateway。
|
||||
location /api/ {
|
||||
client_max_body_size 6m;
|
||||
client_max_body_size 12m;
|
||||
proxy_pass ${API_UPSTREAM};
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
|
||||
@@ -6,7 +6,7 @@ const config = readFileSync(resolve(process.cwd(), 'nginx/default.conf.template'
|
||||
|
||||
describe('admin nginx deployment config', () => {
|
||||
it('allows qualification uploads with multipart overhead through the API proxy', () => {
|
||||
expect(config).toContain('client_max_body_size 6m;')
|
||||
expect(config).toContain('client_max_body_size 12m;')
|
||||
})
|
||||
|
||||
it('prevents the application shell from retaining stale chunk references', () => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
updatePlatformRole,
|
||||
updatePlatformTenant,
|
||||
uploadPlatformTenantQualification,
|
||||
uploadPlatformTenantRequiredQualificationPackage,
|
||||
type PlatformPermissionResource,
|
||||
type PlatformInternalUser,
|
||||
type PlatformManagedUser,
|
||||
@@ -271,7 +272,7 @@ describe('platform IAM roles', () => {
|
||||
it('creates, updates, and changes customer status with the platform tenant contract', async () => {
|
||||
const tenant = { id: '8', tenantCode: 'TEN8', tenantName: '柏悦物流', status: 'ACTIVE' }
|
||||
const create = {
|
||||
tenantName: '柏悦物流', legalName: '柏悦物流有限公司', legalRepresentative: '张三', unifiedSocialCreditCode: '91440300MA5ABCDE12',
|
||||
tenantName: '柏悦物流', legalName: '柏悦物流有限公司', legalRepresentative: '张三',
|
||||
contactName: '张三', contactPhone: '13800138000', contactEmail: 'ops@example.com', servicePackage: '标准版',
|
||||
contactWechat: 'baiyue-ops', salesOwnerName: '李四', paymentTermDays: 30, enterpriseType: '有限责任公司',
|
||||
establishedDate: '2020-01-01', registeredCapital: 1000, registeredCapitalCurrency: 'CNY',
|
||||
@@ -281,12 +282,11 @@ describe('platform IAM roles', () => {
|
||||
vi.mocked(http.put).mockResolvedValue({ code: '200', message: 'success', data: tenant })
|
||||
|
||||
await expect(createPlatformTenant(create)).resolves.toEqual(tenant)
|
||||
const { unifiedSocialCreditCode: _creditCode, ...update } = create
|
||||
await expect(updatePlatformTenant('8', update)).resolves.toEqual(tenant)
|
||||
await expect(updatePlatformTenant('8', create)).resolves.toEqual(tenant)
|
||||
await expect(changePlatformTenantStatus('8', 'DISABLED')).resolves.toEqual(tenant)
|
||||
|
||||
expect(http.post).toHaveBeenCalledWith('/platform/v1/iam/tenants', create)
|
||||
expect(http.put).toHaveBeenNthCalledWith(1, '/platform/v1/iam/tenants/8', update)
|
||||
expect(http.put).toHaveBeenNthCalledWith(1, '/platform/v1/iam/tenants/8', create)
|
||||
expect(http.put).toHaveBeenNthCalledWith(2, '/platform/v1/iam/tenants/8/status', { status: 'DISABLED' })
|
||||
})
|
||||
|
||||
@@ -297,26 +297,49 @@ describe('platform IAM roles', () => {
|
||||
expect(http.get).toHaveBeenCalledWith('/platform/v1/orders/first-leg/tenant-counts?tenantIds=8,9')
|
||||
})
|
||||
|
||||
it('uploads qualification metadata before explicitly submitting the customer review', async () => {
|
||||
it('uploads optional qualification metadata before explicitly submitting the customer review', async () => {
|
||||
const uploadResult = { qualificationId: '91', reviewId: null, tenantStatus: 'PROFILE_REQUIRED', documentUrl: '/file.pdf' }
|
||||
const tenant = { id: '8', tenantCode: 'TEN8', tenantName: '柏悦物流', status: 'PENDING' }
|
||||
vi.mocked(http.post).mockResolvedValueOnce({ code: '200', message: 'success', data: uploadResult })
|
||||
.mockResolvedValueOnce({ code: '200', message: 'success', data: tenant })
|
||||
const file = new File(['%PDF-1.4'], '营业执照.pdf', { type: 'application/pdf' })
|
||||
const file = new File(['%PDF-1.4'], '开户许可证.pdf', { type: 'application/pdf' })
|
||||
|
||||
await expect(uploadPlatformTenantQualification('8', 'BUSINESS_LICENSE', file, '2030-12-31', '91440300MA5ABCDE12', true)).resolves.toEqual(uploadResult)
|
||||
await expect(uploadPlatformTenantQualification('8', 'BANK_ACCOUNT_LICENSE', file, '2030-12-31', undefined, true)).resolves.toEqual(uploadResult)
|
||||
await expect(submitPlatformTenantReview('8')).resolves.toEqual(tenant)
|
||||
|
||||
const form = vi.mocked(http.post).mock.calls[0]?.[1] as FormData
|
||||
expect(http.post).toHaveBeenNthCalledWith(1, '/platform/v1/files/qualifications/8', expect.any(FormData))
|
||||
expect(form.get('file')).toBe(file)
|
||||
expect(form.get('qualificationType')).toBe('BUSINESS_LICENSE')
|
||||
expect(form.get('qualificationType')).toBe('BANK_ACCOUNT_LICENSE')
|
||||
expect(form.get('validTo')).toBe('2030-12-31')
|
||||
expect(form.get('certificateNo')).toBe('91440300MA5ABCDE12')
|
||||
expect(form.get('certificateNo')).toBeNull()
|
||||
expect(form.get('replace')).toBe('true')
|
||||
expect(http.post).toHaveBeenNthCalledWith(2, '/platform/v1/iam/tenants/8/submit-review', {})
|
||||
})
|
||||
|
||||
it('uploads both required files and the credit code in one package request', async () => {
|
||||
const result = {
|
||||
businessLicenseQualificationId: '91', legalPersonIdQualificationId: '92',
|
||||
tenantStatus: 'PROFILE_REQUIRED', businessLicenseDocumentUrl: '/license.pdf',
|
||||
legalPersonIdDocumentUrl: '/legal-person.png',
|
||||
}
|
||||
vi.mocked(http.post).mockResolvedValue({ code: '200', message: 'success', data: result })
|
||||
const businessLicense = new File(['%PDF-1.4'], '营业执照.pdf', { type: 'application/pdf' })
|
||||
const legalPersonId = new File(['image'], '法人身份证.png', { type: 'image/png' })
|
||||
|
||||
await expect(uploadPlatformTenantRequiredQualificationPackage(
|
||||
'8', businessLicense, legalPersonId, '91440300MA5ABCDE12', true, true,
|
||||
)).resolves.toEqual(result)
|
||||
|
||||
const form = vi.mocked(http.post).mock.calls[0]?.[1] as FormData
|
||||
expect(http.post).toHaveBeenCalledWith('/platform/v1/files/qualifications/8/required-package', form)
|
||||
expect(form.get('businessLicense')).toBe(businessLicense)
|
||||
expect(form.get('legalPersonId')).toBe(legalPersonId)
|
||||
expect(form.get('unifiedSocialCreditCode')).toBe('91440300MA5ABCDE12')
|
||||
expect(form.get('replaceBusinessLicense')).toBe('true')
|
||||
expect(form.get('replaceLegalPersonId')).toBe('true')
|
||||
})
|
||||
|
||||
it('preserves long review identifiers and returns one-time administrator credentials', async () => {
|
||||
const reviewId = '190000000000000001'
|
||||
const approval = {
|
||||
|
||||
@@ -53,7 +53,6 @@ export interface CreatePlatformTenantRequest {
|
||||
tenantName: string
|
||||
legalName: string
|
||||
legalRepresentative: string
|
||||
unifiedSocialCreditCode?: string
|
||||
contactName: string
|
||||
contactPhone: string
|
||||
contactEmail: string
|
||||
@@ -69,7 +68,7 @@ export interface CreatePlatformTenantRequest {
|
||||
internalRemark: string
|
||||
}
|
||||
|
||||
export type UpdatePlatformTenantRequest = Omit<CreatePlatformTenantRequest, 'unifiedSocialCreditCode'>
|
||||
export type UpdatePlatformTenantRequest = CreatePlatformTenantRequest
|
||||
|
||||
export interface PlatformTenantOrderCount {
|
||||
tenantId: number | string
|
||||
@@ -112,6 +111,14 @@ export interface PlatformQualificationUploadResponse {
|
||||
replacedDocumentUrl?: string | null
|
||||
}
|
||||
|
||||
export interface PlatformRequiredQualificationPackageUploadResponse {
|
||||
businessLicenseQualificationId: number | string
|
||||
legalPersonIdQualificationId: number | string
|
||||
tenantStatus: PlatformTenantStatus
|
||||
businessLicenseDocumentUrl: string
|
||||
legalPersonIdDocumentUrl: string
|
||||
}
|
||||
|
||||
export interface EnterpriseApprovalResponse {
|
||||
tenantId: number | string
|
||||
administratorCreated: boolean
|
||||
@@ -341,6 +348,27 @@ export async function uploadPlatformTenantQualification(
|
||||
return unwrapApiResponse(response)
|
||||
}
|
||||
|
||||
export async function uploadPlatformTenantRequiredQualificationPackage(
|
||||
tenantId: number | string,
|
||||
businessLicense: File,
|
||||
legalPersonId: File,
|
||||
unifiedSocialCreditCode: string,
|
||||
replaceBusinessLicense = false,
|
||||
replaceLegalPersonId = false,
|
||||
) {
|
||||
const form = new FormData()
|
||||
form.append('businessLicense', businessLicense)
|
||||
form.append('legalPersonId', legalPersonId)
|
||||
form.append('unifiedSocialCreditCode', unifiedSocialCreditCode)
|
||||
if (replaceBusinessLicense) form.append('replaceBusinessLicense', 'true')
|
||||
if (replaceLegalPersonId) form.append('replaceLegalPersonId', 'true')
|
||||
const response = await http.post<ApiEnvelope<PlatformRequiredQualificationPackageUploadResponse>, ApiEnvelope<PlatformRequiredQualificationPackageUploadResponse>>(
|
||||
`/platform/v1/files/qualifications/${tenantId}/required-package`,
|
||||
form,
|
||||
)
|
||||
return unwrapApiResponse(response)
|
||||
}
|
||||
|
||||
export function listPlatformTenantOrderCounts(tenantIds: Array<number | string>) {
|
||||
const ids = tenantIds.map((id) => encodeURIComponent(String(id))).join(',')
|
||||
return getData<PlatformTenantOrderCount[]>(`/platform/v1/orders/first-leg/tenant-counts?tenantIds=${ids}`)
|
||||
|
||||
@@ -23,7 +23,7 @@ describe('platform inbound query API', () => {
|
||||
it('uses the implemented non-v1 list path and normalizes page values and IDs', async () => {
|
||||
vi.mocked(http.get).mockResolvedValue({
|
||||
code: '200', message: 'success', data: {
|
||||
records: [{ id: 19001, orderId: 18001, customerId: 10001, totalPackageCount: '6', holdFlag: 'false' }],
|
||||
records: [{ id: 19001, tenantId: 10002, orderId: 18001, customerId: 10001, totalPackageCount: '6', holdFlag: 'false' }],
|
||||
page: '1', size: '20', total: '1', pages: '1',
|
||||
},
|
||||
})
|
||||
@@ -31,7 +31,7 @@ describe('platform inbound query API', () => {
|
||||
await expect(listPlatformInbounds('10001', {
|
||||
page: 1, size: 20, tab: 'WAIT_MEASURE', inboundCode: 'RK', holdFlag: false,
|
||||
})).resolves.toMatchObject({
|
||||
records: [{ id: '19001', orderId: '18001', customerId: '10001', totalPackageCount: 6, holdFlag: false }],
|
||||
records: [{ id: '19001', tenantId: '10002', orderId: '18001', customerId: '10001', totalPackageCount: 6, holdFlag: false }],
|
||||
total: 1, pages: 1,
|
||||
})
|
||||
expect(http.get).toHaveBeenCalledWith('/platform/inbounds', {
|
||||
@@ -42,14 +42,14 @@ describe('platform inbound query API', () => {
|
||||
it('loads a tenant-scoped aggregate detail and keeps nested IDs as strings', async () => {
|
||||
vi.mocked(http.get).mockResolvedValue({
|
||||
code: '200', message: 'success', data: {
|
||||
summary: { id: 19001, orderId: 18001 }, publicRemark: null,
|
||||
summary: { id: 19001, tenantId: 10002, orderId: 18001 }, publicRemark: null,
|
||||
palletLocations: [{ palletId: 21001, locationId: 22001 }], files: [{ fileId: 23001, uploadedBy: 2 }],
|
||||
internalNotes: [{ id: 24001, createdBy: 2 }], statusHistory: [{ operatorId: 2 }],
|
||||
},
|
||||
})
|
||||
|
||||
await expect(getPlatformInbound('10001', '19001')).resolves.toMatchObject({
|
||||
summary: { id: '19001', orderId: '18001' },
|
||||
summary: { id: '19001', tenantId: '10002', orderId: '18001' },
|
||||
palletLocations: [{ palletId: '21001', locationId: '22001' }],
|
||||
files: [{ fileId: '23001', uploadedBy: '2' }],
|
||||
internalNotes: [{ id: '24001', createdBy: '2' }],
|
||||
|
||||
@@ -39,6 +39,7 @@ export function normalizeInboundSummary(raw: InboundOrderSummary): InboundOrderS
|
||||
return {
|
||||
...raw,
|
||||
id: String(raw.id),
|
||||
tenantId: String(raw.tenantId),
|
||||
orderId: id(raw.orderId),
|
||||
customerId: id(raw.customerId),
|
||||
companyId: id(raw.companyId),
|
||||
|
||||
@@ -15,6 +15,7 @@ export type InboundFeeStatus = 'UNPRICED' | 'CALCULATED' | 'CONFIRMED' | 'SENT'
|
||||
|
||||
export interface InboundOrderSummary {
|
||||
id: string
|
||||
tenantId: string
|
||||
orderId: string | null
|
||||
orderNo: string | null
|
||||
inboundCode: string | null
|
||||
|
||||
@@ -31,7 +31,7 @@ const customers: PlatformTenantResource[] = [
|
||||
]
|
||||
|
||||
const summary: InboundOrderSummary = {
|
||||
id: '19001', orderId: '18001', orderNo: 'ORD-001', inboundCode: 'RK-001', processStatus: 'WAIT_MEASURE',
|
||||
id: '19001', tenantId: '10002', orderId: '18001', orderNo: 'ORD-001', inboundCode: 'RK-001', processStatus: 'WAIT_MEASURE',
|
||||
orderStatusSnapshot: 'APPROVED', measureReviewStatus: 'PENDING', feeStatus: 'UNPRICED', feeReviewStatus: null,
|
||||
notificationStatus: null, version: 1, expectedArrivalAt: '2026-08-04T10:00:00', customerId: '10001', customerName: '示例客户',
|
||||
companyId: '10001', companyName: '示例企业', warehouseId: '20001', warehouseName: '义乌收货仓', deliveryWarehouseId: null,
|
||||
@@ -165,7 +165,7 @@ describe('InboundOrdersView', () => {
|
||||
expect(listPlatformInbounds).toHaveBeenLastCalledWith('10001', expect.objectContaining({ tab: 'WAIT_REVIEW', page: 1 }))
|
||||
;[...document.querySelectorAll<HTMLButtonElement>('button')].filter((button) => button.textContent?.trim() === '查看').at(-1)?.click()
|
||||
await flushPromises()
|
||||
expect(getPlatformInbound).toHaveBeenCalledWith('10001', '19001')
|
||||
expect(getPlatformInbound).toHaveBeenCalledWith('10002', '19001')
|
||||
expect(document.body.textContent).toContain('托盘与库位')
|
||||
expect(document.body.textContent).toContain('请优先安排测量')
|
||||
wrapper.unmount()
|
||||
|
||||
@@ -281,7 +281,7 @@ async function openDetail(row: InboundOrderSummary) {
|
||||
detailError.value = null
|
||||
detail.value = null
|
||||
try {
|
||||
const response = await getPlatformInbound(recordCustomerId(row), row.id)
|
||||
const response = await getPlatformInbound(row.tenantId, row.id)
|
||||
if (disposed || requestId !== detailRequestId) return
|
||||
detail.value = response
|
||||
} catch (error) {
|
||||
@@ -293,10 +293,8 @@ async function openDetail(row: InboundOrderSummary) {
|
||||
}
|
||||
|
||||
function closeDetail() { resetDetail() }
|
||||
function recordCustomerId(row: InboundOrderSummary) { return row.customerId || row.companyId || selectedCustomerId.value }
|
||||
function openWorkspace(row: InboundOrderSummary) {
|
||||
const customerId = recordCustomerId(row)
|
||||
void router.push({ name: 'inbound-order-detail', params: { inboundId: row.id }, query: customerId ? { tenantId: customerId } : undefined })
|
||||
void router.push({ name: 'inbound-order-detail', params: { inboundId: row.id }, query: { tenantId: row.tenantId } })
|
||||
}
|
||||
function formatNumber(value: number | null, digits = 3) { return value === null ? '—' : value.toFixed(digits) }
|
||||
function formatDate(value: string | null) {
|
||||
|
||||
Reference in New Issue
Block a user