fix(asset-center): scope hardware families by owning series
This commit is contained in:
parent
b6c2a8ec62
commit
252c744ddd
@ -26,6 +26,10 @@ jest.mock('next/link', () => ({
|
||||
),
|
||||
}))
|
||||
|
||||
jest.mock('next/navigation', () => ({
|
||||
useRouter: () => ({ push: jest.fn() }),
|
||||
}))
|
||||
|
||||
jest.mock('@/components/ui/Modal', () => ({
|
||||
__esModule: true,
|
||||
default: () => null,
|
||||
@ -265,6 +269,7 @@ function createFamily(overrides?: Partial<AssetFamilyDTO>): AssetFamilyDTO {
|
||||
description: null,
|
||||
productCategoryId: null,
|
||||
productCategoryName: null,
|
||||
assetSeriesId: 'template-1',
|
||||
compatibleSeries: [{ id: 'template-1', code: 'single-board-desk', name: '单板桌' }],
|
||||
connectionRoles: [],
|
||||
metadata: null,
|
||||
|
||||
@ -247,6 +247,7 @@ function createFamily(overrides?: Partial<AssetFamilyDTO>): AssetFamilyDTO {
|
||||
description: null,
|
||||
productCategoryId: null,
|
||||
productCategoryName: null,
|
||||
assetSeriesId: 'template-1',
|
||||
compatibleSeries: [{ id: 'template-1', code: 'single-board-desk', name: '单板桌' }],
|
||||
connectionRoles: [],
|
||||
metadata: null,
|
||||
|
||||
@ -1258,16 +1258,11 @@ function getVisibleFamiliesForTab(
|
||||
|
||||
function familyBelongsToSeries(family: AssetFamilyDTO, series: AssetSeriesDTO | null): boolean {
|
||||
if (!series) return false
|
||||
if (family.assetSeriesId) {
|
||||
return family.assetSeriesId === series.id
|
||||
}
|
||||
const compatibleSeries = family.compatibleSeries ?? []
|
||||
return compatibleSeries.some((item) => item.id === series.id || item.code === series.code)
|
||||
return family.assetSeriesId === series.id
|
||||
}
|
||||
|
||||
function familyVisibleInSeriesContext(family: AssetFamilyDTO, series: AssetSeriesDTO | null): boolean {
|
||||
if (isPackagingMaterialLibraryFamily(family)) return false
|
||||
if (family.type === 'HARDWARE') return true
|
||||
return familyBelongsToSeries(family, series)
|
||||
}
|
||||
|
||||
@ -2250,7 +2245,6 @@ export default function AssetCenterClient({
|
||||
const count = families.filter(
|
||||
(family) =>
|
||||
familyBelongsToSeries(family, series) &&
|
||||
family.type !== 'HARDWARE' &&
|
||||
!isPackagingMaterialLibraryFamily(family),
|
||||
).length
|
||||
map.set(series.id, count)
|
||||
@ -2824,7 +2818,7 @@ export default function AssetCenterClient({
|
||||
setFamilyRoleDefinitions(
|
||||
mergeConnectionRoleDefinitions({
|
||||
assetType: defaultAssetType,
|
||||
seriesCode: defaultAssetType === 'HARDWARE' ? null : defaultSeriesCode,
|
||||
seriesCode: defaultSeriesCode,
|
||||
}),
|
||||
)
|
||||
setFamilyModalOpen(true)
|
||||
@ -2969,9 +2963,7 @@ export default function AssetCenterClient({
|
||||
const submittedAssetSeriesId = familyForm.assetSeriesId || null
|
||||
const submittedCompatibleSeriesIds = submittedAssetSeriesId ? [submittedAssetSeriesId] : []
|
||||
const primarySeriesCode =
|
||||
familyForm.type === 'HARDWARE'
|
||||
? null
|
||||
: initialAssetSeries.find((series) => series.id === submittedAssetSeriesId)?.code ?? null
|
||||
initialAssetSeries.find((series) => series.id === submittedAssetSeriesId)?.code ?? null
|
||||
const hiddenRecommendedRoleValues = getStandardConnectionRoleDefinitions({
|
||||
assetType: familyForm.type,
|
||||
seriesCode: primarySeriesCode,
|
||||
@ -4485,9 +4477,7 @@ export default function AssetCenterClient({
|
||||
|
||||
setFamilyRoleDefinitions((current) => {
|
||||
const primarySeriesCode =
|
||||
familyForm.type === 'HARDWARE'
|
||||
? null
|
||||
: initialAssetSeries.find((series) => series.id === familyForm.assetSeriesId)?.code ?? null
|
||||
initialAssetSeries.find((series) => series.id === familyForm.assetSeriesId)?.code ?? null
|
||||
const merged = mergeConnectionRoleDefinitions({
|
||||
assetType: familyForm.type,
|
||||
seriesCode: primarySeriesCode,
|
||||
@ -6531,8 +6521,7 @@ export default function AssetCenterClient({
|
||||
placeholder="例如 单板桌桌面板"
|
||||
/>
|
||||
</div>
|
||||
{familyForm.type !== 'HARDWARE' ? (
|
||||
<Select
|
||||
<Select
|
||||
label="归属系列"
|
||||
required
|
||||
searchable
|
||||
@ -6562,8 +6551,7 @@ export default function AssetCenterClient({
|
||||
label: `${series.name} · ${series.code}`,
|
||||
}))}
|
||||
placeholder="请选择归属系列"
|
||||
/>
|
||||
) : null}
|
||||
/>
|
||||
<details className="group rounded-apple-lg border border-apple-separator bg-apple-bg-secondary/30">
|
||||
<summary className="flex cursor-pointer list-none items-center justify-between gap-3 px-4 py-3">
|
||||
<div className="flex items-center gap-2">
|
||||
|
||||
@ -21,6 +21,10 @@ jest.mock('next/link', () => ({
|
||||
),
|
||||
}))
|
||||
|
||||
jest.mock('next/navigation', () => ({
|
||||
useRouter: () => ({ push: jest.fn() }),
|
||||
}))
|
||||
|
||||
jest.mock('@/components/ui/Modal', () => ({
|
||||
__esModule: true,
|
||||
default: ({
|
||||
@ -227,6 +231,7 @@ describe('AssetCenterClient workflow', () => {
|
||||
description: null,
|
||||
productCategoryId: null,
|
||||
productCategoryName: null,
|
||||
assetSeriesId: 'template-1',
|
||||
compatibleSeries: [{ id: 'template-1', code: 'single-board-desk', name: '单板桌' }],
|
||||
connectionRoles: ['desktop_surface'],
|
||||
metadata: null,
|
||||
@ -278,6 +283,7 @@ describe('AssetCenterClient workflow', () => {
|
||||
description: null,
|
||||
productCategoryId: null,
|
||||
productCategoryName: null,
|
||||
assetSeriesId: 'template-1',
|
||||
compatibleSeries: [{ id: 'template-1', code: 'single-board-desk', name: '单板桌' }],
|
||||
connectionRoles: ['desktop_surface'],
|
||||
metadata: {
|
||||
@ -350,7 +356,7 @@ describe('AssetCenterClient workflow', () => {
|
||||
await actor.click(screen.getByRole('button', { name: 'CAD 导入到该资产族' }))
|
||||
expect(screen.getByText('导入 CAD 规格款 · 板材')).toBeInTheDocument()
|
||||
expect(screen.getByText('系统自动生成')).toBeInTheDocument()
|
||||
expect(screen.getByPlaceholderText('例如 桌面板 1200 / 方管桌腿 / 十字连接件')).toHaveValue('BOARD-001-')
|
||||
expect(screen.getByPlaceholderText('例如 桌面板 1200 / 方管桌腿 / 十字连接件')).toHaveValue('')
|
||||
expect(screen.getByTestId('cad-import-target-family')).toHaveStyle({
|
||||
backgroundColor: 'var(--obs-shell-surface-bg)',
|
||||
borderColor: 'var(--obs-shell-border)',
|
||||
@ -378,6 +384,7 @@ describe('AssetCenterClient workflow', () => {
|
||||
description: null,
|
||||
productCategoryId: null,
|
||||
productCategoryName: null,
|
||||
assetSeriesId: 'template-1',
|
||||
compatibleSeries: [{ id: 'template-1', code: 'single-board-desk', name: '单板桌' }],
|
||||
connectionRoles: [],
|
||||
metadata: null,
|
||||
@ -435,6 +442,7 @@ describe('AssetCenterClient workflow', () => {
|
||||
description: null,
|
||||
productCategoryId: null,
|
||||
productCategoryName: null,
|
||||
assetSeriesId: 'template-1',
|
||||
compatibleSeries: [{ id: 'template-1', code: 'single-board-desk', name: '单板桌' }],
|
||||
connectionRoles: ['cross_support'],
|
||||
metadata: {
|
||||
@ -467,6 +475,7 @@ describe('AssetCenterClient workflow', () => {
|
||||
description: null,
|
||||
productCategoryId: null,
|
||||
productCategoryName: null,
|
||||
assetSeriesId: 'template-1',
|
||||
compatibleSeries: [{ id: 'template-1', code: 'single-board-desk', name: '单板桌' }],
|
||||
connectionRoles: ['cross_support'],
|
||||
metadata: {
|
||||
@ -752,6 +761,7 @@ describe('AssetCenterClient workflow', () => {
|
||||
description: null,
|
||||
productCategoryId: null,
|
||||
productCategoryName: null,
|
||||
assetSeriesId: 'template-1',
|
||||
compatibleSeries: [{ id: 'template-1', code: 'single-board-desk', name: '单板桌' }],
|
||||
connectionRoles: ['desktop_surface'],
|
||||
metadata: null,
|
||||
@ -822,6 +832,7 @@ describe('AssetCenterClient workflow', () => {
|
||||
description: null,
|
||||
productCategoryId: null,
|
||||
productCategoryName: null,
|
||||
assetSeriesId: 'template-1',
|
||||
compatibleSeries: [{ id: 'template-1', code: 'single-board-desk', name: '单板桌' }],
|
||||
connectionRoles: ['desktop_surface'],
|
||||
metadata: null,
|
||||
|
||||
@ -137,8 +137,7 @@ export function FamilyEditModal({
|
||||
placeholder="例如 单板桌桌面板"
|
||||
/>
|
||||
</div>
|
||||
{form.type !== 'HARDWARE' ? (
|
||||
<div className="space-y-2">
|
||||
<div className="space-y-2">
|
||||
<p className="text-xs font-bold text-[#2d3335]">归属系列</p>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{assetSeries.map((series) => {
|
||||
@ -173,8 +172,7 @@ export function FamilyEditModal({
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<details className="group rounded-apple-lg border border-apple-separator bg-apple-bg-secondary/30">
|
||||
<summary className="flex cursor-pointer list-none items-center justify-between gap-3 px-4 py-3">
|
||||
<div className="flex items-center gap-2">
|
||||
|
||||
@ -0,0 +1,53 @@
|
||||
import type { AssetFamilyDTO, AssetSeriesDTO } from '@/server/services'
|
||||
import { familyVisibleInSeriesContext } from './asset-center-utils'
|
||||
|
||||
const dbzSeries = {
|
||||
id: 'series-dbz',
|
||||
code: 'DBZ',
|
||||
name: 'single board desk',
|
||||
} as AssetSeriesDTO
|
||||
|
||||
const cbzSeries = {
|
||||
id: 'series-cbz',
|
||||
code: 'CBZ',
|
||||
name: 'bedside table',
|
||||
} as AssetSeriesDTO
|
||||
|
||||
function createFamily(overrides?: Partial<AssetFamilyDTO>): AssetFamilyDTO {
|
||||
return {
|
||||
id: 'family-hardware-dbz',
|
||||
type: 'HARDWARE',
|
||||
code: 'DBZ-SPJK05',
|
||||
name: 'hardware',
|
||||
description: null,
|
||||
productCategoryId: null,
|
||||
productCategoryName: null,
|
||||
assetSeriesId: dbzSeries.id,
|
||||
compatibleSeries: [dbzSeries, cbzSeries],
|
||||
connectionRoles: [],
|
||||
metadata: null,
|
||||
status: 'AVAILABLE',
|
||||
createdBy: 'user-1',
|
||||
creatorName: 'Test User',
|
||||
workflow: {} as AssetFamilyDTO['workflow'],
|
||||
createdAt: new Date('2026-06-03T00:00:00.000Z'),
|
||||
updatedAt: new Date('2026-06-03T00:00:00.000Z'),
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
describe('asset center series visibility', () => {
|
||||
it('keeps hardware visible only in its owning series', () => {
|
||||
const dbzHardware = createFamily()
|
||||
|
||||
expect(familyVisibleInSeriesContext(dbzHardware, dbzSeries)).toBe(true)
|
||||
expect(familyVisibleInSeriesContext(dbzHardware, cbzSeries)).toBe(false)
|
||||
})
|
||||
|
||||
it('does not fall back to compatible series without an owning series', () => {
|
||||
const legacyFamily = createFamily({ assetSeriesId: null })
|
||||
|
||||
expect(familyVisibleInSeriesContext(legacyFamily, dbzSeries)).toBe(false)
|
||||
expect(familyVisibleInSeriesContext(legacyFamily, cbzSeries)).toBe(false)
|
||||
})
|
||||
})
|
||||
@ -517,16 +517,11 @@ export function getVisibleFamiliesForTab(
|
||||
|
||||
export function familyBelongsToSeries(family: AssetFamilyDTO, series: AssetSeriesDTO | null): boolean {
|
||||
if (!series) return false
|
||||
if (family.assetSeriesId) {
|
||||
return family.assetSeriesId === series.id
|
||||
}
|
||||
const compatibleSeries = family.compatibleSeries ?? []
|
||||
return compatibleSeries.some((item) => item.id === series.id || item.code === series.code)
|
||||
return family.assetSeriesId === series.id
|
||||
}
|
||||
|
||||
export function familyVisibleInSeriesContext(family: AssetFamilyDTO, series: AssetSeriesDTO | null): boolean {
|
||||
if (isPackagingMaterialLibraryFamily(family)) return false
|
||||
if (family.type === 'HARDWARE') return true
|
||||
return familyBelongsToSeries(family, series)
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user