refactor(intake): split primary intake domains

This commit is contained in:
WuYongJie 2026-06-10 19:35:39 +08:00
parent 64cd9d67a5
commit aa2ce7910b
23 changed files with 8436 additions and 8145 deletions

View File

@ -1,7 +1,7 @@
import { fireEvent, render, screen, waitFor, within } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import AccessoryLibraryPanel from '@/app/intake/AccessoryLibraryPanel'
import PackagingMaterialLibraryPanel from '@/app/intake/PackagingMaterialLibraryPanel'
import AccessoryLibraryPanel from '@/app/intake/bom-library/AccessoryLibraryPanel'
import PackagingMaterialLibraryPanel from '@/app/intake/bom-library/PackagingMaterialLibraryPanel'
const LEGACY_PACKAGING_MATERIAL_LIBRARY_STORAGE_KEY =
'formal-intake-packaging-material-library-items'

View File

@ -1,6 +1,6 @@
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import EdgeBandingLibraryPanel from '@/app/intake/EdgeBandingLibraryPanel'
import EdgeBandingLibraryPanel from '@/app/intake/bom-library/EdgeBandingLibraryPanel'
import { createEdgeBandingLibraryItem } from '@/lib/edge-banding-library'
const LEGACY_EDGE_BANDING_LIBRARY_STORAGE_KEY =

View File

@ -820,6 +820,7 @@ describe('IntakeClient', () => {
'true',
)
} finally {
jest.runOnlyPendingTimers()
jest.useRealTimers()
}
})
@ -1961,10 +1962,25 @@ describe('IntakeClient', () => {
new File(['reference-image'], 'cabinet.png', { type: 'image/png' }),
)
await waitFor(
() =>
expect(fetchMock).toHaveBeenCalledWith(
'/api/intake/reference-image-estimate',
expect.objectContaining({
method: 'POST',
}),
),
{ timeout: 5000 },
)
expect(screen.getByRole('heading', { name: '样包模板明细' })).toBeInTheDocument()
expect(screen.getByText('分类')).toBeInTheDocument()
expect(screen.getByText('封边米数')).toBeInTheDocument()
expect(await screen.findByText('图片识别:柜体侧板')).toBeInTheDocument()
expect(
await screen.findByText('图片识别:柜体侧板', undefined, {
timeout: 5000,
}),
).toBeInTheDocument()
expect(screen.getByText('图片识别:层板/顶底板')).toBeInTheDocument()
expect(screen.getByText('图片识别:前后拉板')).toBeInTheDocument()
expect(screen.getByText('图片识别:三合板背板')).toBeInTheDocument()

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,202 @@
import { Layers3, Package, Palette } from "lucide-react";
import MaterialLibraryClient from "@/app/asset-center/material-library/MaterialLibraryClient";
import AccessoryLibraryPanel from "./AccessoryLibraryPanel";
import EdgeBandingLibraryPanel from "./EdgeBandingLibraryPanel";
import PackagingMaterialLibraryPanel from "./PackagingMaterialLibraryPanel";
import SteelFrameLibraryPanel from "./SteelFrameLibraryPanel";
import { isIntakeNonIntegratedDesignDepartmentUser } from "@/lib/non-integrated-permissions";
import type { MaterialCategoryDTO, MaterialDTO, SubstrateDictionaryBundleDTO, SubstrateDTO } from "@/server/services";
type IntakeDomainUser = {
role: string;
id: string;
name: string;
email: string;
businessRoles?: string[];
avatar: string | null;
department: { id: string; name: string; fullPath?: string } | null;
departmentPaths?: string[];
};
export type BomLibraryKey =
| "materialLibrary"
| "finishLibrary"
| "edgeBandingLibrary"
| "steelFrameLibrary"
| "packagingLibrary"
| "packagingMaterialLibrary";
export const BOM_LIBRARY_OPTIONS: {
key: BomLibraryKey;
label: string;
description: string;
helper: string;
}[] = [
{
key: "materialLibrary",
label: "板材资料库",
description:
"只展示板材基材,方便在产品资料 BOM 下维护板材本体资料。",
helper:
"数据仍复用资产中心基础资料库,系列和饰面花色入口在这里不显示。",
},
{
key: "finishLibrary",
label: "花色资料库",
description:
"只展示饰面花色,方便在产品资料 BOM 下维护花色、贴图和参数。",
helper:
"数据仍复用资产中心基础资料库,板材基材入口在这里不显示。",
},
{
key: "edgeBandingLibrary",
label: "封边资料库",
description:
"按板材厚度维护封边条,细节资料里的封边自动项会按对应厚度筛选并带出单价。",
helper:
"维护规格名称、规格、单位、单价和适配板材厚度;封边行仍可在细节资料里手动改单价。",
},
{
key: "steelFrameLibrary",
label: "钢架资料库",
description:
"不再预置系统钢架规格,只保留每吨钢价与人工新建规格,系统会自动重算重量、支数和每米单价。",
helper:
"当前按名称、长、宽、壁厚录入,保存后的规格会直接参与钢架 BOM 匹配,并随每吨钢价联动刷新。",
},
{
key: "packagingLibrary",
label: "配件资料库",
description:
"产品资料下独立维护的配件资料区,按新的业务口径重新配置,不复用原包材资料逻辑。",
helper: "适合维护配件名称、配件编码、分类规则和后续要补充的独立资料。",
},
{
key: "packagingMaterialLibrary",
label: "包材资料库",
description:
"产品资料下独立维护的包材资料区,沿用和配件资料库相同的字母分组逻辑,但单独保存数据。",
helper:
"适合维护纸箱、泡沫、护角等包材名称、编码、图片和价格,不会和配件资料串在一起。",
},
];
export function getBomLibraryLabel(library: BomLibraryKey) {
return (
BOM_LIBRARY_OPTIONS.find((option) => option.key === library)?.label ??
"BOM资料库"
);
}
export function BomDetail({
user,
canEditLibraries,
activeBomLibrary,
materialLibrarySubstrates,
materialLibraryFinishes,
materialLibraryCategories,
materialLibraryDictionaries,
onMaterialLibraryFinishesChange,
}: {
user: IntakeDomainUser;
canEditLibraries?: boolean;
activeBomLibrary: BomLibraryKey;
materialLibrarySubstrates: SubstrateDTO[];
materialLibraryFinishes: MaterialDTO[];
materialLibraryCategories: MaterialCategoryDTO[];
materialLibraryDictionaries: SubstrateDictionaryBundleDTO;
onMaterialLibraryFinishesChange?: (finishes: MaterialDTO[]) => void;
}) {
const canEditBomLibraries =
canEditLibraries ??
(user.role === "SUPER_ADMIN" ||
user.role === "ADMIN" ||
isIntakeNonIntegratedDesignDepartmentUser(user));
if (activeBomLibrary === "materialLibrary" || activeBomLibrary === "finishLibrary") {
const visibleTab = activeBomLibrary === "materialLibrary" ? "substrate" : "finish";
return (
<div className="flex h-full min-h-0 flex-col overflow-hidden rounded-apple-xl border border-apple-separator bg-apple-bg-primary shadow-apple-xs">
<MaterialLibraryClient
key={activeBomLibrary}
embedded
canEditOverride={canEditBomLibraries}
initialActiveTab={visibleTab}
visibleTabs={[visibleTab]}
initialSubstrates={materialLibrarySubstrates}
initialFinishes={materialLibraryFinishes}
initialCategories={materialLibraryCategories}
initialDictionaries={materialLibraryDictionaries}
onFinishesChange={onMaterialLibraryFinishesChange}
user={user}
metrics={[
...(visibleTab === "substrate"
? [
{
label: "板材基材",
value: materialLibrarySubstrates.length,
icon: <Package className="h-4 w-4" />,
},
]
: [
{
label: "饰面花色",
value: materialLibraryFinishes.length,
icon: <Palette className="h-4 w-4" />,
},
{
label: "花色分类",
value: materialLibraryCategories.length,
icon: <Layers3 className="h-4 w-4" />,
},
]),
]}
/>
</div>
);
}
if (activeBomLibrary === "steelFrameLibrary") {
return (
<div className="flex h-full flex-col rounded-apple-xl border border-apple-separator bg-apple-bg-primary p-4 shadow-apple-xs">
<div className="flex-1 overflow-y-auto">
<SteelFrameLibraryPanel canEditItems={canEditBomLibraries} />
</div>
</div>
);
}
if (activeBomLibrary === "edgeBandingLibrary") {
return (
<div className="flex h-full flex-col rounded-apple-xl border border-apple-separator bg-apple-bg-primary p-4 shadow-apple-xs">
<div className="flex-1 overflow-y-auto">
<EdgeBandingLibraryPanel canEditItems={canEditBomLibraries} />
</div>
</div>
);
}
if (activeBomLibrary === "packagingMaterialLibrary") {
return (
<div className="flex h-full flex-col rounded-apple-xl border border-apple-separator bg-apple-bg-primary p-4 shadow-apple-xs">
<div className="flex-1 overflow-y-auto">
<PackagingMaterialLibraryPanel canEditItems={canEditBomLibraries} />
</div>
</div>
);
}
return (
<div className="flex h-full flex-col rounded-apple-xl border border-apple-separator bg-apple-bg-primary p-4 shadow-apple-xs">
<div className="flex-1 overflow-y-auto">
<AccessoryLibraryPanel
canEditItems={canEditBomLibraries}
canDeleteItems={
canEditBomLibraries &&
(user.role === "SUPER_ADMIN" || user.role === "ADMIN")
}
/>
</div>
</div>
);
}

View File

@ -0,0 +1,7 @@
export function SpuLabel() {
return (
<span translate="no" className="notranslate">
SPU
</span>
);
}

View File

@ -46,7 +46,7 @@ import type { MaterialDTO } from "@/server/services";
import {
clearSpuRuleLibraryRuntimeCache,
writeSpuRuleLibraryRules,
} from "../spuRuleLibraryLogic";
} from "../product-development/spuRuleLibraryLogic";
import { parseZipFile } from "@/lib/zip-parser";
import { extractPreviewModelFromZip } from "../../../lib/model-preview-zip";
import {

View File

@ -245,12 +245,12 @@ import type {
AssetVariantDTO,
MaterialDTO,
} from "@/server/services";
import RawMaterialLibraryPanel from "@/app/intake/RawMaterialLibraryPanel";
import RawMaterialLibraryPanel from "@/app/intake/raw-material-library/RawMaterialLibraryPanel";
import {
SPU_RULE_LIBRARY_UPDATED_EVENT,
getEstimatedSpuAssignment,
syncSpuRuleLibraryRulesFromServer,
} from "../spuRuleLibraryLogic";
} from "../product-development/spuRuleLibraryLogic";
import {
readPreSkuPackageComponentDetails,
type SeriesDraftSkuFields,
@ -9078,7 +9078,7 @@ export default function NonIntegratedDetailClient({
}
let isMounted = true;
void import("@/app/intake/IntakeClient").then((module) => {
void import("@/app/intake/product-patent/ProductPatentDomain").then((module) => {
if (!isMounted) return;
setLinkEntryProductPatentDashboard(
() => module.ProductPatentDashboard,

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,46 @@
import RawMaterialLibraryPanel, { type RawMaterialLibraryKey } from "./RawMaterialLibraryPanel";
export type { RawMaterialLibraryKey };
export const RAW_MATERIAL_LIBRARY_OPTIONS: {
key: RawMaterialLibraryKey;
label: string;
description: string;
}[] = [
{
key: "processConfig",
label: "材质工艺配置",
description: "表格类信息和工艺规则占位",
},
{
key: "detailArchive",
label: "材质详情档案",
description: "材质百科式详情页占位",
},
];
export function getRawMaterialLibraryLabel(library: RawMaterialLibraryKey) {
return (
RAW_MATERIAL_LIBRARY_OPTIONS.find((option) => option.key === library)
?.label ?? "原材料资料库"
);
}
export function RawMaterialLibraryDetail({
activeRawMaterialLibrary,
selectedMaterialName,
}: {
activeRawMaterialLibrary: RawMaterialLibraryKey;
selectedMaterialName?: string;
}) {
return (
<div className="flex h-full flex-col rounded-apple-xl border border-apple-separator bg-apple-bg-primary p-4 shadow-apple-xs">
<div className="flex-1 overflow-y-auto">
<RawMaterialLibraryPanel
activeLibrary={activeRawMaterialLibrary}
selectedMaterialName={selectedMaterialName}
/>
</div>
</div>
);
}

View File

@ -0,0 +1,19 @@
import { ReferenceImageCostEstimator } from "../raw-material-library/RawMaterialLibraryPanel";
import type { SubstrateDTO } from "@/server/services";
export function ReferenceEstimatorDetail({
materialLibrarySubstrates,
}: {
materialLibrarySubstrates: SubstrateDTO[];
}) {
return (
<div className="flex h-full flex-col rounded-apple-xl border border-apple-separator bg-apple-bg-primary p-4 shadow-apple-xs">
<div className="flex-1 overflow-y-auto">
<ReferenceImageCostEstimator
materialLibrarySubstrates={materialLibrarySubstrates}
/>
</div>
</div>
);
}

View File

@ -8,7 +8,7 @@ import type {
NonIntegratedSampleMeasurementRow,
NonIntegratedSkuItem,
} from '@/app/intake/non-integrated/nonIntegratedStorage'
import { SPU_RULE_LIBRARY_SEED_RULES } from '@/app/intake/spuRuleLibrarySeed'
import { SPU_RULE_LIBRARY_SEED_RULES } from '@/app/intake/product-development/spuRuleLibrarySeed'
type JushuitanCredentials = {
appKey: string