Update accessory library and intake permissions
This commit is contained in:
parent
9217b8db16
commit
e07c12a34f
@ -77,6 +77,7 @@ const LETTER_CATEGORY_LABELS: Partial<Record<AccessoryLibraryLetter, string>> =
|
||||
B: '平头螺丝',
|
||||
C: '连接螺杆',
|
||||
D: '偏心连接件',
|
||||
E: '预埋螺母',
|
||||
}
|
||||
|
||||
const A_HEAD_TYPES: HeadType[] = [
|
||||
@ -123,6 +124,11 @@ const B_HEAD_TYPES: HeadType[] = [
|
||||
aliases: '沉头、平杯',
|
||||
scenario: '表面齐平、拉手安装、预埋件连接',
|
||||
},
|
||||
{
|
||||
name: '子母',
|
||||
aliases: '对锁螺丝、公母螺丝、扣管螺丝',
|
||||
scenario: '板件对锁连接、成套公母连接件',
|
||||
},
|
||||
{
|
||||
name: '杯头',
|
||||
aliases: '圆柱头、内六角杯头',
|
||||
@ -136,7 +142,13 @@ const HEAD_TYPES_BY_LETTER: Partial<Record<AccessoryLibraryLetter, HeadType[]>>
|
||||
}
|
||||
|
||||
const A_SCREW_CATEGORIES = ['自攻螺丝', '过山丝']
|
||||
const B_SCREW_CATEGORIES = ['平头螺丝', '内六角螺丝', '拉手螺丝', '杯头螺丝']
|
||||
const B_SCREW_CATEGORIES = [
|
||||
'平头螺丝',
|
||||
'子母螺丝',
|
||||
'内六角螺丝',
|
||||
'拉手螺丝',
|
||||
'杯头螺丝',
|
||||
]
|
||||
const C_SCREW_CATEGORIES = ['全牙螺杆', '双头螺杆', '双头牙']
|
||||
const D_SCREW_CATEGORIES = [
|
||||
'三合一连接件',
|
||||
@ -145,12 +157,19 @@ const D_SCREW_CATEGORIES = [
|
||||
'隐形连接件',
|
||||
'双头偏心连接杆',
|
||||
]
|
||||
const E_SCREW_CATEGORIES = [
|
||||
'内外牙螺母',
|
||||
'胶粒螺母',
|
||||
'横孔螺母',
|
||||
'圆柱螺母',
|
||||
]
|
||||
|
||||
const CATEGORY_SHORTCUTS_BY_LETTER: Partial<
|
||||
Record<AccessoryLibraryLetter, string[]>
|
||||
> = {
|
||||
C: C_SCREW_CATEGORIES,
|
||||
D: D_SCREW_CATEGORIES,
|
||||
E: E_SCREW_CATEGORIES,
|
||||
}
|
||||
|
||||
const SELECT_FIELDS = [
|
||||
@ -455,11 +474,69 @@ const D_SELECT_FIELD_OVERRIDES: Partial<
|
||||
},
|
||||
}
|
||||
|
||||
const E_SELECT_FIELD_OVERRIDES: Partial<
|
||||
Record<ScrewFilterKey, Partial<Pick<SelectFieldDefinition, 'label' | 'options'>>>
|
||||
> = {
|
||||
screwCategory: {
|
||||
options: E_SCREW_CATEGORIES,
|
||||
},
|
||||
drive: {
|
||||
options: ['内六角', '一字', '无槽'],
|
||||
},
|
||||
tail: {
|
||||
label: '介型',
|
||||
options: ['带介', '无介', '无'],
|
||||
},
|
||||
material: {
|
||||
label: '材质/表面处理/颜色',
|
||||
options: [
|
||||
'锌合金',
|
||||
'塑料',
|
||||
'白色 塑料',
|
||||
'尼龙',
|
||||
'白色 尼龙',
|
||||
'碳钢',
|
||||
'镀锌',
|
||||
'镀镍',
|
||||
'铜',
|
||||
'不锈钢',
|
||||
'黑色',
|
||||
'本色',
|
||||
],
|
||||
},
|
||||
tooth: {
|
||||
options: ['木牙', '机牙', '粗牙', '细牙'],
|
||||
},
|
||||
diameter: {
|
||||
label: '规格',
|
||||
options: ['M4', 'M5', 'M6', 'M8', 'M10', 'M12'],
|
||||
},
|
||||
length: {
|
||||
label: '尺寸',
|
||||
options: [
|
||||
'8mm',
|
||||
'10mm',
|
||||
'12mm',
|
||||
'13mm',
|
||||
'15mm',
|
||||
'16mm',
|
||||
'18mm',
|
||||
'20mm',
|
||||
'25mm',
|
||||
'30mm',
|
||||
],
|
||||
},
|
||||
preDrill: {
|
||||
options: ['需预开孔', '按配套孔位安装'],
|
||||
},
|
||||
}
|
||||
|
||||
const HIDDEN_SELECT_FIELD_KEYS_BY_LETTER: Partial<
|
||||
Record<AccessoryLibraryLetter, ScrewFilterKey[]>
|
||||
> = {
|
||||
C: ['drive'],
|
||||
D: ['drive', 'tail', 'finish', 'tooth', 'color', 'preDrill'],
|
||||
E: ['finish', 'color'],
|
||||
}
|
||||
|
||||
const EMPTY_FILTERS: ScrewFilterState = {
|
||||
@ -567,6 +644,9 @@ function getTableColumns(letter: AccessoryLibraryLetter, shouldShowHeadType: boo
|
||||
}).map((column) => {
|
||||
if (letter === 'C' && column === '长度') return '尺寸'
|
||||
if (letter === 'D' && column === '材质') return '材质/表面处理'
|
||||
if (letter === 'E' && column === '材质') return '材质/表面处理/颜色'
|
||||
if (letter === 'E' && column === '尾型') return '介型'
|
||||
if (letter === 'E' && column === '长度') return '尺寸'
|
||||
return column
|
||||
})
|
||||
}
|
||||
@ -664,6 +744,21 @@ const DEFAULT_SCREW_FIELDS_BY_LETTER: Partial<
|
||||
preDrill: '按配套孔位安装',
|
||||
environments: ['室内'],
|
||||
},
|
||||
E: {
|
||||
screwCategory: '内外牙螺母',
|
||||
headType: '',
|
||||
drive: '内六角',
|
||||
tail: '无介',
|
||||
material: '锌合金',
|
||||
finish: '',
|
||||
tooth: '木牙',
|
||||
diameter: 'M6',
|
||||
length: '13mm',
|
||||
usages: ['板式家具', '柜体', '床架'],
|
||||
color: '',
|
||||
preDrill: '需预开孔',
|
||||
environments: ['室内'],
|
||||
},
|
||||
}
|
||||
|
||||
const PRIMARY_BUTTON_CLASS =
|
||||
@ -719,7 +814,9 @@ function getSelectFields(letter: AccessoryLibraryLetter) {
|
||||
? C_SELECT_FIELD_OVERRIDES
|
||||
: letter === 'D'
|
||||
? D_SELECT_FIELD_OVERRIDES
|
||||
: {}
|
||||
: letter === 'E'
|
||||
? E_SELECT_FIELD_OVERRIDES
|
||||
: {}
|
||||
const hiddenKeys = HIDDEN_SELECT_FIELD_KEYS_BY_LETTER[letter] ?? []
|
||||
|
||||
return SELECT_FIELDS.filter((field) => !hiddenKeys.includes(field.key)).map(
|
||||
@ -730,6 +827,27 @@ function getSelectFields(letter: AccessoryLibraryLetter) {
|
||||
)
|
||||
}
|
||||
|
||||
function shouldShowSelectFieldForItem(
|
||||
item: Pick<ScrewItem, 'letter' | 'screwCategory'>,
|
||||
fieldKey: ScrewFilterKey,
|
||||
) {
|
||||
if (
|
||||
item.letter === 'E' &&
|
||||
item.screwCategory !== '内外牙螺母' &&
|
||||
(fieldKey === 'tail' || fieldKey === 'tooth')
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function getSelectFieldsForItem(item: Pick<ScrewItem, 'letter' | 'screwCategory'>) {
|
||||
return getSelectFields(item.letter).filter((field) =>
|
||||
shouldShowSelectFieldForItem(item, field.key),
|
||||
)
|
||||
}
|
||||
|
||||
function normalizeScrewItem(item: AccessoryLibraryItem): ScrewItem {
|
||||
const letter = ACCESSORY_LIBRARY_LETTERS.includes(item.letter as AccessoryLibraryLetter)
|
||||
? (item.letter as AccessoryLibraryLetter)
|
||||
@ -867,9 +985,32 @@ function renderEccentricConnectorArtwork(accent: string, category: string) {
|
||||
`
|
||||
}
|
||||
|
||||
function renderEmbeddedNutArtwork(accent: string, category: string) {
|
||||
const shell =
|
||||
category.includes('胶粒')
|
||||
? `<path d="M92 82 L196 82 L214 104 L214 150 L196 172 L92 172 L74 150 L74 104 Z" fill="#ffffff" stroke="${accent}" stroke-width="5" />
|
||||
<circle cx="144" cy="127" r="33" fill="${accent}" opacity="0.12" />
|
||||
<circle cx="144" cy="127" r="18" fill="#ffffff" stroke="${accent}" stroke-width="4" />`
|
||||
: `<rect x="72" y="76" width="144" height="104" rx="18" fill="#ffffff" stroke="${accent}" stroke-width="5" />
|
||||
<circle cx="144" cy="128" r="34" fill="${accent}" opacity="0.12" />
|
||||
<circle cx="144" cy="128" r="18" fill="#ffffff" stroke="${accent}" stroke-width="4" />`
|
||||
const sideHole =
|
||||
category.includes('横孔') || category.includes('圆柱')
|
||||
? `<rect x="74" y="116" width="140" height="24" rx="12" fill="${accent}" opacity="0.16" />
|
||||
<circle cx="92" cy="128" r="9" fill="#ffffff" stroke="${accent}" stroke-width="3" />
|
||||
<circle cx="196" cy="128" r="9" fill="#ffffff" stroke="${accent}" stroke-width="3" />`
|
||||
: `<line x1="94" y1="92" x2="194" y2="164" stroke="${accent}" stroke-width="3" opacity="0.45" />
|
||||
<line x1="194" y1="92" x2="94" y2="164" stroke="${accent}" stroke-width="3" opacity="0.28" />`
|
||||
|
||||
return `
|
||||
${shell}
|
||||
${sideHole}
|
||||
`
|
||||
}
|
||||
|
||||
function getTemporaryReferenceImage(item: ScrewItem) {
|
||||
if (item.imageUrl.trim()) return ''
|
||||
if (item.letter !== 'C' && item.letter !== 'D') return ''
|
||||
if (item.letter !== 'C' && item.letter !== 'D' && item.letter !== 'E') return ''
|
||||
|
||||
const category = item.screwCategory.trim() || '配件'
|
||||
const palette = getTemporaryReferencePalette(category)
|
||||
@ -881,7 +1022,9 @@ function getTemporaryReferenceImage(item: ScrewItem) {
|
||||
const artwork =
|
||||
item.letter === 'C'
|
||||
? renderThreadedRodArtwork(palette.accent, category)
|
||||
: renderEccentricConnectorArtwork(palette.accent, category)
|
||||
: item.letter === 'D'
|
||||
? renderEccentricConnectorArtwork(palette.accent, category)
|
||||
: renderEmbeddedNutArtwork(palette.accent, category)
|
||||
const title = escapeSvgText(category)
|
||||
const subtitle = escapeSvgText(spec || item.name || '临时参考图')
|
||||
const svg = `
|
||||
@ -923,6 +1066,7 @@ function buildScrewName(
|
||||
| 'screwCategory'
|
||||
| 'material'
|
||||
| 'finish'
|
||||
| 'tooth'
|
||||
>,
|
||||
) {
|
||||
const diameter = item.diameter.trim()
|
||||
@ -947,6 +1091,22 @@ function buildScrewName(
|
||||
.join(' ')
|
||||
}
|
||||
|
||||
if (item.letter === 'E') {
|
||||
const materialOrFinish = item.material.trim() || item.finish.trim()
|
||||
const isThreadedInsertNut = item.screwCategory === '内外牙螺母'
|
||||
return [
|
||||
specSize,
|
||||
isThreadedInsertNut ? item.tail : '',
|
||||
item.drive,
|
||||
isThreadedInsertNut ? item.tooth : '',
|
||||
materialOrFinish,
|
||||
item.screwCategory,
|
||||
]
|
||||
.map((value) => value.trim())
|
||||
.filter(Boolean)
|
||||
.join(' ')
|
||||
}
|
||||
|
||||
return [specSize, item.drive, item.headType, item.tail, item.screwCategory]
|
||||
.map((value) => value.trim())
|
||||
.filter(Boolean)
|
||||
@ -991,6 +1151,18 @@ function createScrewDraft(
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeScrewDraftForCategory(draft: ScrewItem): ScrewItem {
|
||||
if (draft.letter !== 'E' || draft.screwCategory === '内外牙螺母') {
|
||||
return draft
|
||||
}
|
||||
|
||||
return {
|
||||
...draft,
|
||||
tail: '',
|
||||
tooth: '',
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeDuplicateValue(value: string | string[] | undefined) {
|
||||
return Array.isArray(value) ? value.join(',').trim() : (value ?? '').trim()
|
||||
}
|
||||
@ -1164,6 +1336,22 @@ export default function AccessoryLibraryPanel({
|
||||
() => getSelectFields(selectedLetter),
|
||||
[selectedLetter],
|
||||
)
|
||||
const visibleSelectedSelectFields = useMemo(
|
||||
() =>
|
||||
selectedSelectFields.filter((field) => {
|
||||
if (
|
||||
selectedLetter === 'E' &&
|
||||
appliedFilters.screwCategory &&
|
||||
appliedFilters.screwCategory !== '内外牙螺母' &&
|
||||
(field.key === 'tail' || field.key === 'tooth')
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}),
|
||||
[appliedFilters.screwCategory, selectedLetter, selectedSelectFields],
|
||||
)
|
||||
const selectedHiddenFieldKeys = useMemo(
|
||||
() => getHiddenFieldKeys(selectedLetter, shouldShowHeadType),
|
||||
[selectedLetter, shouldShowHeadType],
|
||||
@ -1220,7 +1408,7 @@ export default function AccessoryLibraryPanel({
|
||||
return false
|
||||
}
|
||||
|
||||
return selectedSelectFields.every((field) => {
|
||||
return visibleSelectedSelectFields.every((field) => {
|
||||
const filterValue = appliedFilters[field.key]
|
||||
if (field.key === 'screwCategory') return true
|
||||
return !filterValue || item[field.key] === filterValue
|
||||
@ -1232,9 +1420,9 @@ export default function AccessoryLibraryPanel({
|
||||
appliedFilters,
|
||||
isSelectedLetterConfigured,
|
||||
selectedLetterItems,
|
||||
selectedSelectFields,
|
||||
shouldShowCategoryShortcuts,
|
||||
shouldShowHeadType,
|
||||
visibleSelectedSelectFields,
|
||||
])
|
||||
|
||||
function updateFilterDraft(key: keyof ScrewFilterState, value: string) {
|
||||
@ -1328,7 +1516,7 @@ export default function AccessoryLibraryPanel({
|
||||
setDraft((current) => {
|
||||
if (!current) return current
|
||||
|
||||
const nextDraft = { ...current, [key]: value }
|
||||
const nextDraft = normalizeScrewDraftForCategory({ ...current, [key]: value })
|
||||
if (
|
||||
[
|
||||
'diameter',
|
||||
@ -1339,6 +1527,7 @@ export default function AccessoryLibraryPanel({
|
||||
'screwCategory',
|
||||
'material',
|
||||
'finish',
|
||||
'tooth',
|
||||
].includes(key)
|
||||
) {
|
||||
return {
|
||||
@ -1425,7 +1614,8 @@ export default function AccessoryLibraryPanel({
|
||||
|
||||
if (!draft) return
|
||||
|
||||
const duplicateItem = findDuplicateScrewItem(items, draft, editingId)
|
||||
const normalizedDraft = normalizeScrewDraftForCategory(draft)
|
||||
const duplicateItem = findDuplicateScrewItem(items, normalizedDraft, editingId)
|
||||
if (duplicateItem) {
|
||||
setEditingError(
|
||||
`已存在相同配置:${duplicateItem.code} ${duplicateItem.name}。请至少修改分类、头型、槽型、尾型、材质、牙型、规格、长度、颜色中的任意一项。`,
|
||||
@ -1434,15 +1624,15 @@ export default function AccessoryLibraryPanel({
|
||||
}
|
||||
|
||||
const nextItem: ScrewItem = {
|
||||
...draft,
|
||||
letter: draft.letter,
|
||||
...normalizedDraft,
|
||||
letter: normalizedDraft.letter,
|
||||
code: editingId
|
||||
? draft.code.trim().toUpperCase()
|
||||
: buildNextScrewCode(items, draft.letter),
|
||||
name: buildScrewName(draft),
|
||||
price: draft.price.trim(),
|
||||
imageUrl: draft.imageUrl.trim(),
|
||||
imageName: draft.imageName.trim(),
|
||||
? normalizedDraft.code.trim().toUpperCase()
|
||||
: buildNextScrewCode(items, normalizedDraft.letter),
|
||||
name: buildScrewName(normalizedDraft),
|
||||
price: normalizedDraft.price.trim(),
|
||||
imageUrl: normalizedDraft.imageUrl.trim(),
|
||||
imageName: normalizedDraft.imageName.trim(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
}
|
||||
|
||||
@ -1514,7 +1704,7 @@ export default function AccessoryLibraryPanel({
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{selectedSelectFields.map((field) => (
|
||||
{visibleSelectedSelectFields.map((field) => (
|
||||
<div key={field.label} className="w-[112px]">
|
||||
<SelectField
|
||||
compact
|
||||
@ -1905,6 +2095,10 @@ export default function AccessoryLibraryPanel({
|
||||
>
|
||||
{draft ? (
|
||||
<div className="space-y-4">
|
||||
{(() => {
|
||||
const draftSelectFields = getSelectFieldsForItem(draft)
|
||||
return (
|
||||
<>
|
||||
{editingError ? (
|
||||
<div className="rounded-apple-lg border border-rose-200 bg-rose-50 px-4 py-3 text-xs leading-5 text-rose-700">
|
||||
{editingError}
|
||||
@ -1935,7 +2129,7 @@ export default function AccessoryLibraryPanel({
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-4">
|
||||
{getSelectFields(draft.letter).slice(0, 1).map((field) => (
|
||||
{draftSelectFields.slice(0, 1).map((field) => (
|
||||
<SelectField
|
||||
key={field.label}
|
||||
label={field.label}
|
||||
@ -1952,7 +2146,7 @@ export default function AccessoryLibraryPanel({
|
||||
onChange={(value) => updateDraftField('headType', value)}
|
||||
/>
|
||||
) : null}
|
||||
{getSelectFields(draft.letter).slice(1, 7).map((field) => (
|
||||
{draftSelectFields.slice(1, 7).map((field) => (
|
||||
<SelectField
|
||||
key={field.label}
|
||||
label={field.label}
|
||||
@ -1964,7 +2158,7 @@ export default function AccessoryLibraryPanel({
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-4">
|
||||
{getSelectFields(draft.letter).slice(7).map((field) => (
|
||||
{draftSelectFields.slice(7).map((field) => (
|
||||
<SelectField
|
||||
key={field.label}
|
||||
label={field.label}
|
||||
@ -1983,6 +2177,9 @@ export default function AccessoryLibraryPanel({
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
})()}
|
||||
|
||||
<div className="grid gap-4 xl:grid-cols-2">
|
||||
<MultiTagField
|
||||
|
||||
@ -61,15 +61,15 @@ describe('alphabet library panels', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('lets every accessory letter group be selected even before it is configured', async () => {
|
||||
it('lets unconfigured accessory letter groups be selected', async () => {
|
||||
const actor = userEvent.setup()
|
||||
|
||||
render(<AccessoryLibraryPanel />)
|
||||
|
||||
await actor.click(screen.getByRole('button', { name: 'E 0 条' }))
|
||||
await actor.click(screen.getByRole('button', { name: 'F 0 条' }))
|
||||
|
||||
expect(screen.getByText('E 组配件')).toBeInTheDocument()
|
||||
expect(screen.getByText('E 组暂未配置配件分类')).toBeInTheDocument()
|
||||
expect(screen.getByText('F 组配件')).toBeInTheDocument()
|
||||
expect(screen.getByText('F 组暂未配置配件分类')).toBeInTheDocument()
|
||||
expect(screen.queryByRole('button', { name: '新增 A 组' })).not.toBeInTheDocument()
|
||||
|
||||
await actor.click(screen.getByRole('button', { name: 'A 自攻螺丝 0 条' }))
|
||||
@ -139,6 +139,97 @@ describe('alphabet library panels', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('includes child-mother screws in the B accessory group', async () => {
|
||||
const actor = userEvent.setup()
|
||||
|
||||
render(<AccessoryLibraryPanel />)
|
||||
|
||||
await actor.click(screen.getByRole('button', { name: 'B 平头螺丝 0 条' }))
|
||||
|
||||
expect(screen.getByRole('button', { name: '子母螺丝' })).toBeInTheDocument()
|
||||
|
||||
await actor.click(screen.getByRole('button', { name: '新增 B 组' }))
|
||||
|
||||
const dialog = await screen.findByRole('dialog', { name: '新增B组配件' })
|
||||
fireEvent.change(within(dialog).getByLabelText('分类'), {
|
||||
target: { value: '子母螺丝' },
|
||||
})
|
||||
|
||||
expect(within(dialog).getByLabelText('配件名称')).toHaveValue(
|
||||
'M4*20 内六角 平头 平尾 子母螺丝',
|
||||
)
|
||||
})
|
||||
|
||||
it('uses embedded nut rules for E accessories', async () => {
|
||||
const actor = userEvent.setup()
|
||||
|
||||
render(<AccessoryLibraryPanel />)
|
||||
|
||||
await actor.click(screen.getByRole('button', { name: 'E 预埋螺母 0 条' }))
|
||||
|
||||
expect(screen.getByText('E 组配件 · 预埋螺母')).toBeInTheDocument()
|
||||
expect(screen.queryByRole('button', { name: '全部头型' })).not.toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: '全部分类' })).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: '内外牙螺母' })).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: '胶粒螺母' })).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: '横孔螺母' })).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: '圆柱螺母' })).toBeInTheDocument()
|
||||
|
||||
await actor.click(screen.getByRole('button', { name: '新增 E 组' }))
|
||||
|
||||
const dialog = await screen.findByRole('dialog', { name: '新增E组配件' })
|
||||
expect(within(dialog).getByLabelText('配件名称')).toHaveValue(
|
||||
'M6*13 无介 内六角 木牙 锌合金 内外牙螺母',
|
||||
)
|
||||
expect(within(dialog).queryByLabelText('头型')).not.toBeInTheDocument()
|
||||
expect(within(dialog).getByLabelText('槽型')).toBeInTheDocument()
|
||||
expect(within(dialog).getByLabelText('介型')).toBeInTheDocument()
|
||||
expect(within(dialog).getByLabelText('牙型')).toBeInTheDocument()
|
||||
expect(within(dialog).getByLabelText('规格')).toBeInTheDocument()
|
||||
expect(within(dialog).getByLabelText('尺寸')).toBeInTheDocument()
|
||||
expect(within(dialog).getByLabelText('材质/表面处理/颜色')).toBeInTheDocument()
|
||||
|
||||
fireEvent.change(within(dialog).getByLabelText('分类'), {
|
||||
target: { value: '胶粒螺母' },
|
||||
})
|
||||
expect(within(dialog).queryByLabelText('介型')).not.toBeInTheDocument()
|
||||
expect(within(dialog).queryByLabelText('牙型')).not.toBeInTheDocument()
|
||||
fireEvent.change(within(dialog).getByLabelText('槽型'), {
|
||||
target: { value: '无槽' },
|
||||
})
|
||||
fireEvent.change(within(dialog).getByLabelText('材质/表面处理/颜色'), {
|
||||
target: { value: '白色 塑料' },
|
||||
})
|
||||
|
||||
expect(within(dialog).getByLabelText('配件名称')).toHaveValue(
|
||||
'M6*13 无槽 白色 塑料 胶粒螺母',
|
||||
)
|
||||
|
||||
await actor.click(within(dialog).getByRole('button', { name: '保存' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('M6*13 无槽 白色 塑料 胶粒螺母')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
const savedItems = JSON.parse(
|
||||
window.localStorage.getItem(ACCESSORY_LIBRARY_STORAGE_KEY) ?? '[]',
|
||||
)
|
||||
expect(savedItems[0]).toMatchObject({
|
||||
letter: 'E',
|
||||
code: 'E0',
|
||||
name: 'M6*13 无槽 白色 塑料 胶粒螺母',
|
||||
screwCategory: '胶粒螺母',
|
||||
headType: '',
|
||||
drive: '无槽',
|
||||
tail: '',
|
||||
material: '白色 塑料',
|
||||
finish: '',
|
||||
tooth: '',
|
||||
diameter: 'M6',
|
||||
length: '13mm',
|
||||
})
|
||||
})
|
||||
|
||||
it('filters C and D accessory groups by category shortcuts', async () => {
|
||||
const actor = userEvent.setup()
|
||||
|
||||
|
||||
@ -15,7 +15,7 @@ describe('intake node permission settings', () => {
|
||||
'江苏简尚品牌管理有限公司 > 品牌中心 > 空间视觉部',
|
||||
],
|
||||
}),
|
||||
).toEqual(['brandCenter', 'space'])
|
||||
).toEqual(['space'])
|
||||
|
||||
expect(
|
||||
resolveIntakeNodePermissionSubjectKeys({
|
||||
@ -29,6 +29,16 @@ describe('intake node permission settings', () => {
|
||||
}),
|
||||
).toEqual(['space'])
|
||||
|
||||
expect(
|
||||
resolveIntakeNodePermissionSubjectKeys({
|
||||
role: 'USER',
|
||||
businessRoles: [],
|
||||
departmentPaths: [
|
||||
'江苏简尚品牌管理有限公司 > 品牌中心 > 商品企划部 > 桌类',
|
||||
],
|
||||
}),
|
||||
).toEqual(['planning'])
|
||||
|
||||
expect(
|
||||
resolveIntakeNodePermissionSubjectKeys({
|
||||
role: 'USER',
|
||||
@ -41,7 +51,7 @@ describe('intake node permission settings', () => {
|
||||
})
|
||||
|
||||
it('uses the same matrix for default view, download, and admin-opened overrides', () => {
|
||||
const spaceKeys = ['brandCenter', 'space'] as const
|
||||
const spaceKeys = ['space'] as const
|
||||
expect(
|
||||
resolveIntakeNodePermissionAccess({}, 'planningLayout', spaceKeys),
|
||||
).toMatchObject({
|
||||
@ -98,12 +108,12 @@ describe('intake node permission settings', () => {
|
||||
const overrides = {
|
||||
[buildIntakeNodePermissionOverrideKey(
|
||||
'productData',
|
||||
'space',
|
||||
'brandCenter',
|
||||
'view',
|
||||
)]: true,
|
||||
[buildIntakeNodePermissionOverrideKey(
|
||||
'productData',
|
||||
'space',
|
||||
'brandCenter',
|
||||
'download',
|
||||
)]: true,
|
||||
}
|
||||
@ -111,8 +121,8 @@ describe('intake node permission settings', () => {
|
||||
resolveIntakeNodePermissionAccess(overrides, 'productData', spaceKeys),
|
||||
).toMatchObject({
|
||||
defaultState: 'hidden',
|
||||
view: true,
|
||||
download: true,
|
||||
view: false,
|
||||
download: false,
|
||||
edit: false,
|
||||
})
|
||||
})
|
||||
|
||||
@ -248,7 +248,7 @@ export const CORE_NODE_PERMISSION_SUBJECTS: IntakeNodePermissionSubject[] = [
|
||||
{
|
||||
key: "brandCenter",
|
||||
name: "品牌中心",
|
||||
department: "品牌中心-事业部 / 策划 / 企划",
|
||||
department: "品牌中心-事业部 / 策划(不含商品企划、空间视觉)",
|
||||
},
|
||||
{
|
||||
key: "planning",
|
||||
@ -804,20 +804,26 @@ export function resolveIntakeNodePermissionSubjectKeys(
|
||||
user: IntakeNonIntegratedPermissionUser,
|
||||
) {
|
||||
const keys: IntakeNodePermissionSubjectKey[] = [];
|
||||
const isBrandPlanningUser = hasDepartmentPath(user, isBrandPlanningDepartmentPath);
|
||||
const isSpaceVisualUser = hasDepartmentPath(user, isSpaceVisualDepartmentPath);
|
||||
|
||||
if (hasDepartmentPath(user, isProductResearchDepartmentPath)) {
|
||||
pushUniqueSubject(keys, "design");
|
||||
}
|
||||
|
||||
if (hasDepartmentPath(user, isBrandCenterDepartmentPath)) {
|
||||
if (
|
||||
hasDepartmentPath(user, isBrandCenterDepartmentPath) &&
|
||||
!isBrandPlanningUser &&
|
||||
!isSpaceVisualUser
|
||||
) {
|
||||
pushUniqueSubject(keys, "brandCenter");
|
||||
}
|
||||
|
||||
if (hasDepartmentPath(user, isBrandPlanningDepartmentPath)) {
|
||||
if (isBrandPlanningUser) {
|
||||
pushUniqueSubject(keys, "planning");
|
||||
}
|
||||
|
||||
if (hasDepartmentPath(user, isSpaceVisualDepartmentPath)) {
|
||||
if (isSpaceVisualUser) {
|
||||
pushUniqueSubject(keys, "space");
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user