From ade68c56dea0eef43329753395ecfd1d0af16eab Mon Sep 17 00:00:00 2001 From: WuYongJie Date: Thu, 21 May 2026 16:18:18 +0800 Subject: [PATCH] =?UTF-8?q?fix(asset-center):=20=E7=94=B1=E5=90=8E?= =?UTF-8?q?=E7=AB=AF=E7=94=9F=E6=88=90=20CAD=20=E5=AF=BC=E5=85=A5=E8=A7=84?= =?UTF-8?q?=E6=A0=BC=E7=BC=96=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/asset-center/AssetCenterClient.tsx | 101 +++++++----------- .../AssetCenterClient.workflow.test.tsx | 37 ++++--- .../src/lib/open-product/platform-api.test.ts | 52 +++++++++ .../src/lib/open-product/platform-api.ts | 31 +++--- 4 files changed, 130 insertions(+), 91 deletions(-) create mode 100644 furniture-3d-system/src/lib/open-product/platform-api.test.ts diff --git a/furniture-3d-system/src/app/asset-center/AssetCenterClient.tsx b/furniture-3d-system/src/app/asset-center/AssetCenterClient.tsx index 690cab3d..82c72587 100644 --- a/furniture-3d-system/src/app/asset-center/AssetCenterClient.tsx +++ b/furniture-3d-system/src/app/asset-center/AssetCenterClient.tsx @@ -758,19 +758,6 @@ function buildVariantIdentityPrefix(familyCode: string): string { return normalizedCode.endsWith('-') ? normalizedCode : `${normalizedCode}-` } -function shouldReplaceIdentityPrefill(value: string, identityPrefix: string): boolean { - const normalizedValue = value.trim() - return normalizedValue.length === 0 || (identityPrefix.length > 0 && normalizedValue === identityPrefix) -} - -function mergeIdentityPrefix(identityPrefix: string, value: string): string { - const normalizedValue = value.trim() - if (!identityPrefix) return normalizedValue - if (!normalizedValue) return identityPrefix - if (normalizedValue.startsWith(identityPrefix)) return normalizedValue - return `${identityPrefix}${normalizedValue}` -} - function emptyVariantForm(assetFamilyId: string, identityPrefix = '') { return { assetFamilyId, @@ -831,7 +818,6 @@ type CadImportFieldKey = | 'familyCode' | 'familyName' | 'connectionRoles' - | 'variantCode' | 'variantName' | 'substrateId' | 'file' @@ -847,8 +833,9 @@ function splitConnectionRoles(value: string): string[] { function buildCadImportFieldErrors(params: { form: CadImportForm + assetSeriesId?: string | null }): CadImportFieldErrors { - const { form } = params + const { form, assetSeriesId } = params const errors: CadImportFieldErrors = {} if (form.familyMode === 'existing') { @@ -856,7 +843,7 @@ function buildCadImportFieldErrors(params: { errors.familyId = '请选择目标资产族' } } else { - if (!form.familyCode.trim()) { + if (!assetSeriesId && !form.familyCode.trim()) { errors.familyCode = '请填写资产族编码' } if (!form.familyName.trim()) { @@ -864,9 +851,6 @@ function buildCadImportFieldErrors(params: { } } - if (!form.variantCode.trim()) { - errors.variantCode = '请填写规格款编码' - } if (!form.variantName.trim()) { errors.variantName = '请填写规格款名称' } @@ -1502,8 +1486,9 @@ export default function AssetCenterClient({ () => buildCadImportFieldErrors({ form: cadImportForm, + assetSeriesId: selectedSeriesId, }), - [cadImportForm], + [cadImportForm, selectedSeriesId], ) const cadImportChecklist = useMemo( @@ -1541,13 +1526,10 @@ export default function AssetCenterClient({ } const nextForm = emptyCadImportForm(family.type, true) - const identityPrefix = buildVariantIdentityPrefix(family.code) setCadImportForm({ ...nextForm, familyMode: 'existing', familyId: family.id, - variantCode: identityPrefix, - variantName: identityPrefix, }) setCadImportSubmitAttempted(false) setCadImportResult(null) @@ -1564,14 +1546,10 @@ export default function AssetCenterClient({ } function prepareNextCadImport() { - const currentFamily = families.find((family) => family.id === cadImportForm.familyId) ?? null - const identityPrefix = currentFamily ? buildVariantIdentityPrefix(currentFamily.code) : '' setCadImportForm((current) => ({ ...emptyCadImportForm(current.assetType, true), familyMode: 'existing', familyId: current.familyId, - variantCode: identityPrefix, - variantName: identityPrefix, })) setCadImportSubmitAttempted(false) setCadImportResult(null) @@ -1624,31 +1602,18 @@ export default function AssetCenterClient({ zipFileName: file.name, cadExportData, }) - const currentFamily = - current.familyMode === 'existing' - ? families.find((family) => family.id === current.familyId) ?? null - : null - const nextFamilyCode = - current.familyMode === 'create' && !current.familyCode.trim() - ? defaults.familyCode - : current.familyCode - const identityPrefix = - current.familyMode === 'existing' - ? buildVariantIdentityPrefix(currentFamily?.code ?? '') - : buildVariantIdentityPrefix(nextFamilyCode) - const shouldInferVariantCode = shouldReplaceIdentityPrefill( - current.variantCode, - identityPrefix, - ) - const shouldInferVariantName = shouldReplaceIdentityPrefill( - current.variantName, - identityPrefix, - ) + const shouldUseGeneratedFamilyCode = Boolean(selectedSeriesId) + const shouldInferVariantName = current.variantName.trim().length === 0 const inferred = new Set() - if (current.familyMode === 'create' && !current.familyCode.trim()) inferred.add('familyCode') + if ( + current.familyMode === 'create' && + !current.familyCode.trim() && + !shouldUseGeneratedFamilyCode + ) { + inferred.add('familyCode') + } if (current.familyMode === 'create' && !current.familyName.trim()) inferred.add('familyName') - if (shouldInferVariantCode) inferred.add('variantCode') if (shouldInferVariantName) inferred.add('variantName') setAutoInferredFields(inferred) @@ -1656,18 +1621,17 @@ export default function AssetCenterClient({ ...current, file, familyCode: - current.familyMode === 'create' && !current.familyCode.trim() + current.familyMode === 'create' && + !current.familyCode.trim() && + !shouldUseGeneratedFamilyCode ? defaults.familyCode : current.familyCode, familyName: current.familyMode === 'create' && !current.familyName.trim() ? defaults.familyName : current.familyName, - variantCode: shouldInferVariantCode - ? mergeIdentityPrefix(identityPrefix, defaults.variantCode) - : current.variantCode, variantName: shouldInferVariantName - ? mergeIdentityPrefix(identityPrefix, defaults.variantName) + ? defaults.variantName : current.variantName, } }) @@ -2751,7 +2715,10 @@ export default function AssetCenterClient({ assetType: cadImportForm.assetType, familyMode: cadImportForm.familyMode, familyId: cadImportForm.familyMode === 'existing' ? cadImportForm.familyId || null : null, - familyCode: cadImportForm.familyMode === 'create' ? cadImportForm.familyCode : null, + familyCode: + cadImportForm.familyMode === 'create' && !selectedSeriesId + ? cadImportForm.familyCode + : null, familyName: cadImportForm.familyMode === 'create' ? cadImportForm.familyName : null, familyDescription: cadImportForm.familyMode === 'create' ? cadImportForm.familyDescription : null, @@ -2760,7 +2727,7 @@ export default function AssetCenterClient({ cadImportForm.familyMode === 'create' ? cadImportConnectionRoles : undefined, - variantCode: cadImportForm.variantCode, + variantCode: null, variantName: cadImportForm.variantName, sizeLabel: cadImportForm.sizeLabel || null, substrateId: @@ -4070,14 +4037,20 @@ export default function AssetCenterClient({ {!cadImportResult && (
- setCadImportForm((current) => ({ ...current, variantCode: e.target.value }))} - placeholder={`${cadImportForm.assetType}-VARIANT-001`} - errorText={cadImportSubmitAttempted ? cadImportFieldErrors.variantCode : undefined} - /> +
+

+ 规格款编码 +

+

+ 系统自动生成 +

+
{ family: { id: 'family-1', type: 'BOARD', - code: 'BOARD-140', + code: 'DBZ-SPJK001', name: '140台面', description: null, productCategoryId: null, productCategoryName: null, - compatibleSeries: [{ id: 'template-1', code: 'single-board-desk', name: '单板桌' }], + assetSeriesId: 'template-1', + assetSeriesCode: 'DBZ', + assetSeriesName: '单板桌', + compatibleSeries: [{ id: 'template-1', code: 'DBZ', name: '单板桌' }], connectionRoles: [], metadata: null, status: 'DRAFT', @@ -586,9 +589,9 @@ describe('AssetCenterClient workflow', () => { variant: { id: 'variant-1', assetFamilyId: 'family-1', - assetFamilyCode: 'BOARD-140', + assetFamilyCode: 'DBZ-SPJK001', assetFamilyName: '140台面', - code: 'TM-001', + code: 'DBZ-SPJ00001', name: '140台面-A', assetType: 'BOARD', sizeLabel: '1400x600x25', @@ -634,12 +637,15 @@ describe('AssetCenterClient workflow', () => { { id: 'family-1', type: 'BOARD', - code: 'BOARD-140', + code: 'DBZ-SPJK001', name: '140台面资产族', description: null, productCategoryId: null, productCategoryName: null, - compatibleSeries: [{ id: 'template-1', code: 'single-board-desk', name: '单板桌' }], + assetSeriesId: 'template-1', + assetSeriesCode: 'DBZ', + assetSeriesName: '单板桌', + compatibleSeries: [{ id: 'template-1', code: 'DBZ', name: '单板桌' }], connectionRoles: ['desktop_surface'], metadata: { connectionRoleDefinitions: [ @@ -647,7 +653,7 @@ describe('AssetCenterClient workflow', () => { value: 'desktop_surface', label: '桌面板', assetType: 'BOARD', - seriesCode: 'single-board-desk', + seriesCode: 'DBZ', }, ], }, @@ -671,13 +677,13 @@ describe('AssetCenterClient workflow', () => { initialAssetSeries={[ { id: 'template-1', - code: 'single-board-desk', + code: 'DBZ', name: '单板桌', description: null, - sortOrder: 0, - isActive: true, - createdAt: new Date('2026-04-01T00:00:00.000Z'), - updatedAt: new Date('2026-04-01T00:00:00.000Z'), + sortOrder: 0, + isActive: true, + createdAt: new Date('2026-04-01T00:00:00.000Z'), + updatedAt: new Date('2026-04-01T00:00:00.000Z'), }, ] as AssetSeriesDTO[]} finishes={[ @@ -716,7 +722,7 @@ describe('AssetCenterClient workflow', () => { await actor.click(screen.getByText('140台面资产族')) await actor.click(screen.getByRole('button', { name: 'CAD 导入到该资产族' })) - await actor.type(screen.getByPlaceholderText('BOARD-VARIANT-001'), 'TM-001') + expect(screen.getByText('系统自动生成')).toBeInTheDocument() await actor.type(screen.getByPlaceholderText('例如 桌面板 1200 / 方管桌腿 / 十字连接件'), '140台面-A') await actor.click(screen.getByRole('combobox', { name: '基材' })) @@ -736,8 +742,9 @@ describe('AssetCenterClient workflow', () => { await waitFor(() => { expect(importAssetCad).toHaveBeenCalledWith( expect.objectContaining({ - variantCode: 'BOARD-140-TM-001', - variantName: 'BOARD-140-140台面-A', + assetSeriesId: 'template-1', + variantCode: null, + variantName: '140台面-A', lossFactor: 1.2, substrateId: 'substrate-1', finishId: 'finish-1', diff --git a/furniture-3d-system/src/lib/open-product/platform-api.test.ts b/furniture-3d-system/src/lib/open-product/platform-api.test.ts new file mode 100644 index 00000000..71ee1a37 --- /dev/null +++ b/furniture-3d-system/src/lib/open-product/platform-api.test.ts @@ -0,0 +1,52 @@ +import { importAssetCad } from './platform-api' + +describe('platform-api importAssetCad', () => { + const originalFetch = global.fetch + + afterEach(() => { + global.fetch = originalFetch + }) + + it('does not submit blank CAD import codes so backend can generate series codes', async () => { + let submittedFormData: FormData | null = null + global.fetch = jest.fn(async (_input, init) => { + submittedFormData = init?.body as FormData + return { + ok: true, + json: async () => ({ + success: true, + data: { + result: { + family: { id: 'family-1' }, + variant: { id: 'variant-1' }, + summary: {}, + warnings: [], + similarCandidates: [], + similarityNotice: null, + }, + }, + }), + } as Response + }) + + await importAssetCad({ + assetType: 'BOARD', + familyMode: 'existing', + familyId: 'family-1', + assetSeriesId: 'series-dbz', + familyCode: '', + variantCode: '', + variantName: '桌面板 1200', + substrateId: 'substrate-1', + finishId: '', + file: new File(['zip-content'], 'asset.zip', { type: 'application/zip' }), + }) + + expect(submittedFormData).not.toBeNull() + expect(submittedFormData?.has('familyCode')).toBe(false) + expect(submittedFormData?.has('variantCode')).toBe(false) + expect(submittedFormData?.has('finishId')).toBe(false) + expect(submittedFormData?.get('variantName')).toBe('桌面板 1200') + expect(submittedFormData?.get('assetSeriesId')).toBe('series-dbz') + }) +}) diff --git a/furniture-3d-system/src/lib/open-product/platform-api.ts b/furniture-3d-system/src/lib/open-product/platform-api.ts index 3550337c..da4ac119 100644 --- a/furniture-3d-system/src/lib/open-product/platform-api.ts +++ b/furniture-3d-system/src/lib/open-product/platform-api.ts @@ -311,8 +311,8 @@ export interface ImportAssetCadPayload { familyDescription?: string | null assetSeriesId?: string | null connectionRoles?: string[] - variantCode: string - variantName: string + variantCode?: string | null + variantName?: string | null sizeLabel?: string | null substrateId?: string | null finishId?: string | null @@ -320,23 +320,30 @@ export interface ImportAssetCadPayload { file: File } +function appendNonBlankFormValue(formData: FormData, key: string, value?: string | null) { + const normalized = value?.trim() + if (normalized) { + formData.append(key, normalized) + } +} + export async function importAssetCad(input: ImportAssetCadPayload): Promise { const formData = new FormData() formData.append('assetType', input.assetType) formData.append('familyMode', input.familyMode) - if (input.familyId) formData.append('familyId', input.familyId) - if (input.familyCode) formData.append('familyCode', input.familyCode) - if (input.familyName) formData.append('familyName', input.familyName) - if (input.familyDescription) formData.append('familyDescription', input.familyDescription) - if (input.assetSeriesId) formData.append('assetSeriesId', input.assetSeriesId) + appendNonBlankFormValue(formData, 'familyId', input.familyId) + appendNonBlankFormValue(formData, 'familyCode', input.familyCode) + appendNonBlankFormValue(formData, 'familyName', input.familyName) + appendNonBlankFormValue(formData, 'familyDescription', input.familyDescription) + appendNonBlankFormValue(formData, 'assetSeriesId', input.assetSeriesId) if (input.connectionRoles && input.connectionRoles.length > 0) { formData.append('connectionRoles', JSON.stringify(input.connectionRoles)) } - formData.append('variantCode', input.variantCode) - formData.append('variantName', input.variantName) - if (input.sizeLabel) formData.append('sizeLabel', input.sizeLabel) - if (input.substrateId) formData.append('substrateId', input.substrateId) - if (input.finishId) formData.append('finishId', input.finishId) + appendNonBlankFormValue(formData, 'variantCode', input.variantCode) + appendNonBlankFormValue(formData, 'variantName', input.variantName) + appendNonBlankFormValue(formData, 'sizeLabel', input.sizeLabel) + appendNonBlankFormValue(formData, 'substrateId', input.substrateId) + appendNonBlankFormValue(formData, 'finishId', input.finishId) if (input.lossFactor != null) formData.append('lossFactor', String(input.lossFactor)) formData.append('file', input.file)