feat: add planning layout and product limit fields
This commit is contained in:
parent
4415ef8b33
commit
cf4b370f4a
@ -11,6 +11,8 @@ const ALPHABET_LIBRARY_IMAGE_MAX_SIZE_BYTES = 1.5 * 1024 * 1024
|
||||
const ALPHABET_LIBRARY_LETTERS = Array.from({ length: 26 }, (_, index) =>
|
||||
String.fromCharCode('A'.charCodeAt(0) + index),
|
||||
)
|
||||
const LIBRARY_PRIMARY_BUTTON_CLASS =
|
||||
'!border-zinc-950 !bg-zinc-950 !bg-none !text-white !shadow-none hover:!border-zinc-800 hover:!bg-zinc-800 focus:!border-zinc-600 disabled:!border-zinc-200 disabled:!bg-zinc-200 disabled:!text-zinc-400'
|
||||
|
||||
type AlphabetLibraryLetter = (typeof ALPHABET_LIBRARY_LETTERS)[number]
|
||||
|
||||
@ -363,11 +365,22 @@ export default function AlphabetLibraryPanel({
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm font-apple-medium text-apple-label-secondary">查询动作</p>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<Button type="button" size="small" onClick={applySearch}>
|
||||
<Button
|
||||
type="button"
|
||||
size="small"
|
||||
className={LIBRARY_PRIMARY_BUTTON_CLASS}
|
||||
onClick={applySearch}
|
||||
>
|
||||
<Search className="h-4 w-4" />
|
||||
查询
|
||||
</Button>
|
||||
<Button type="button" variant="tinted" size="small" onClick={() => openCreateModal(activeLetter)}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="tinted"
|
||||
size="small"
|
||||
className={LIBRARY_PRIMARY_BUTTON_CLASS}
|
||||
onClick={() => openCreateModal(activeLetter)}
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
新增当前字母
|
||||
</Button>
|
||||
@ -421,7 +434,7 @@ export default function AlphabetLibraryPanel({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openCreateModal(letterKey)}
|
||||
className="rounded-full border border-apple-separator px-2 py-1 text-[11px] text-apple-blue transition hover:bg-apple-blue/10"
|
||||
className="rounded-full border border-zinc-950 bg-zinc-950 px-2 py-1 text-[11px] text-white transition hover:border-zinc-800 hover:bg-zinc-800"
|
||||
>
|
||||
新增
|
||||
</button>
|
||||
@ -442,7 +455,13 @@ export default function AlphabetLibraryPanel({
|
||||
当前显示 {currentLetterItems.length} 条数据,字段固定为编号、名称、图片、价格。
|
||||
</p>
|
||||
</div>
|
||||
<Button type="button" variant="gray" size="small" onClick={() => openCreateModal(activeLetter)}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="gray"
|
||||
size="small"
|
||||
className={LIBRARY_PRIMARY_BUTTON_CLASS}
|
||||
onClick={() => openCreateModal(activeLetter)}
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
新增 {activeLetter} 组
|
||||
</Button>
|
||||
@ -516,7 +535,7 @@ export default function AlphabetLibraryPanel({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openEditModal(item)}
|
||||
className="rounded-full border border-apple-separator px-2 py-1 text-apple-blue transition hover:bg-apple-blue/10"
|
||||
className="rounded-full border border-zinc-950 bg-zinc-950 px-2 py-1 text-white transition hover:border-zinc-800 hover:bg-zinc-800"
|
||||
>
|
||||
编辑
|
||||
</button>
|
||||
@ -569,7 +588,11 @@ export default function AlphabetLibraryPanel({
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
<Button type="button" onClick={saveDraft}>
|
||||
<Button
|
||||
type="button"
|
||||
className={LIBRARY_PRIMARY_BUTTON_CLASS}
|
||||
onClick={saveDraft}
|
||||
>
|
||||
保存
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@ -30,6 +30,8 @@ type EdgeBandingLibraryCategory = {
|
||||
|
||||
const EDGE_BANDING_CATEGORY_STORAGE_KEY =
|
||||
'formal-intake-edge-banding-library-categories'
|
||||
const LIBRARY_PRIMARY_BUTTON_CLASS =
|
||||
'!border-zinc-950 !bg-zinc-950 !bg-none !text-white !shadow-none hover:!border-zinc-800 hover:!bg-zinc-800 focus:!border-zinc-600 disabled:!border-zinc-200 disabled:!bg-zinc-200 disabled:!text-zinc-400'
|
||||
|
||||
function normalizeText(value: string) {
|
||||
return value.trim().toLowerCase()
|
||||
@ -576,11 +578,22 @@ export default function EdgeBandingLibraryPanel() {
|
||||
placeholder="例如:12MM"
|
||||
/>
|
||||
<div className="flex flex-wrap items-end justify-end gap-2">
|
||||
<Button type="button" size="small" onClick={applySearch}>
|
||||
<Button
|
||||
type="button"
|
||||
size="small"
|
||||
className={LIBRARY_PRIMARY_BUTTON_CLASS}
|
||||
onClick={applySearch}
|
||||
>
|
||||
<Search className="h-4 w-4" />
|
||||
查询
|
||||
</Button>
|
||||
<Button type="button" variant="tinted" size="small" onClick={startCreate}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="tinted"
|
||||
size="small"
|
||||
className={LIBRARY_PRIMARY_BUTTON_CLASS}
|
||||
onClick={startCreate}
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
新建封边
|
||||
</Button>
|
||||
@ -606,7 +619,7 @@ export default function EdgeBandingLibraryPanel() {
|
||||
type="button"
|
||||
variant="tinted"
|
||||
size="small"
|
||||
className="!min-h-8 !px-2 !py-1 text-xs"
|
||||
className={`!min-h-8 !px-2 !py-1 text-xs ${LIBRARY_PRIMARY_BUTTON_CLASS}`}
|
||||
onClick={startCreateCategory}
|
||||
>
|
||||
<Plus className="h-3.5 w-3.5" />
|
||||
@ -766,7 +779,7 @@ export default function EdgeBandingLibraryPanel() {
|
||||
<Button
|
||||
type="button"
|
||||
size="small"
|
||||
className="!min-h-8 !px-3 !py-1 text-xs"
|
||||
className={`!min-h-8 !px-3 !py-1 text-xs ${LIBRARY_PRIMARY_BUTTON_CLASS}`}
|
||||
onClick={isCreatingCategory ? saveCategoryDraft : saveDraft}
|
||||
disabled={isCreatingCategory ? !categoryCanSave : !draftCanSave}
|
||||
>
|
||||
|
||||
@ -394,6 +394,8 @@ const TABLE_ACTION_BUTTON_BASE =
|
||||
"!h-8 !min-h-8 !w-8 !rounded-md !px-0 !py-0 !shadow-none";
|
||||
const TABLE_ACTION_BUTTON_SECONDARY = `${TABLE_ACTION_BUTTON_BASE} !border-zinc-200 !bg-white !text-zinc-500 hover:!border-zinc-300 hover:!bg-zinc-50 hover:!text-zinc-950 focus:!border-zinc-400 disabled:!border-zinc-100 disabled:!bg-zinc-50 disabled:!text-zinc-300`;
|
||||
const TABLE_ACTION_BUTTON_PRIMARY = `${TABLE_ACTION_BUTTON_BASE} !border-zinc-950 !bg-zinc-950 !text-white hover:!border-zinc-800 hover:!bg-zinc-800 focus:!border-zinc-600 disabled:!border-zinc-200 disabled:!bg-zinc-200 disabled:!text-zinc-400`;
|
||||
const INTAKE_PRIMARY_BUTTON_CLASS =
|
||||
"!border-zinc-950 !bg-zinc-950 !bg-none !text-white !shadow-none hover:!border-zinc-800 hover:!bg-zinc-800 focus:!border-zinc-600 disabled:!border-zinc-200 disabled:!bg-zinc-200 disabled:!text-zinc-400";
|
||||
|
||||
const BOM_LIBRARY_OPTIONS: {
|
||||
key: BomLibraryKey;
|
||||
@ -1391,6 +1393,7 @@ function CopyProductDialog({
|
||||
<Button
|
||||
type="button"
|
||||
size="small"
|
||||
className={INTAKE_PRIMARY_BUTTON_CLASS}
|
||||
onClick={onConfirm}
|
||||
disabled={isSubmitting || brandOptions.length === 0}
|
||||
>
|
||||
@ -1553,7 +1556,7 @@ function IntegratedPendingDetail({
|
||||
<div className="flex items-end">
|
||||
<Button
|
||||
size="small"
|
||||
className="h-9 w-full text-xs"
|
||||
className={`h-9 w-full text-xs ${INTAKE_PRIMARY_BUTTON_CLASS}`}
|
||||
onClick={() => {
|
||||
setAppliedQuery(queryDraft);
|
||||
setCurrentPage(1);
|
||||
@ -1564,7 +1567,10 @@ function IntegratedPendingDetail({
|
||||
</div>
|
||||
<div className="flex items-end justify-end">
|
||||
<Link href="/intake/integrated/new" className="w-full">
|
||||
<Button size="small" className="h-9 w-full px-4 text-xs">
|
||||
<Button
|
||||
size="small"
|
||||
className={`h-9 w-full px-4 text-xs ${INTAKE_PRIMARY_BUTTON_CLASS}`}
|
||||
>
|
||||
新增产品
|
||||
</Button>
|
||||
</Link>
|
||||
@ -1644,7 +1650,12 @@ function IntegratedPendingDetail({
|
||||
</td>
|
||||
<td className="px-3 py-3">
|
||||
<div className="flex gap-2">
|
||||
<Button size="small">修改</Button>
|
||||
<Button
|
||||
size="small"
|
||||
className={INTAKE_PRIMARY_BUTTON_CLASS}
|
||||
>
|
||||
修改
|
||||
</Button>
|
||||
<Button variant="plain" size="small">
|
||||
暂缓
|
||||
</Button>
|
||||
@ -2005,7 +2016,7 @@ function NonIntegratedPendingDetail({
|
||||
<div className="flex items-end">
|
||||
<Button
|
||||
size="small"
|
||||
className="h-9 w-full text-xs"
|
||||
className={`h-9 w-full text-xs ${INTAKE_PRIMARY_BUTTON_CLASS}`}
|
||||
onClick={() => setAppliedQuery(queryDraft)}
|
||||
>
|
||||
查询
|
||||
@ -2014,7 +2025,7 @@ function NonIntegratedPendingDetail({
|
||||
<div className="flex items-end justify-end">
|
||||
<Button
|
||||
size="small"
|
||||
className="h-9 w-full px-4 text-xs"
|
||||
className={`h-9 w-full px-4 text-xs ${INTAKE_PRIMARY_BUTTON_CLASS}`}
|
||||
onClick={onOpenNewNonIntegratedProductWorkspace}
|
||||
>
|
||||
新增产品
|
||||
@ -3544,7 +3555,7 @@ function ProductDevelopmentProgressDashboard({
|
||||
<div className="flex items-end">
|
||||
<Button
|
||||
size="small"
|
||||
className="h-9 w-full text-xs"
|
||||
className={`h-9 w-full text-xs ${INTAKE_PRIMARY_BUTTON_CLASS}`}
|
||||
onClick={() => setAppliedQuery(queryDraft)}
|
||||
>
|
||||
查询
|
||||
@ -3553,7 +3564,7 @@ function ProductDevelopmentProgressDashboard({
|
||||
<div className="flex items-end">
|
||||
<Button
|
||||
size="small"
|
||||
className="h-9 w-full text-xs"
|
||||
className={`h-9 w-full text-xs ${INTAKE_PRIMARY_BUTTON_CLASS}`}
|
||||
onClick={() => setAppliedQuery(queryDraft)}
|
||||
>
|
||||
刷新
|
||||
@ -5160,7 +5171,7 @@ function ProductPatentStatusDashboard({
|
||||
<div className="flex items-end">
|
||||
<Button
|
||||
size="small"
|
||||
className="h-9 w-full text-xs"
|
||||
className={`h-9 w-full text-xs ${INTAKE_PRIMARY_BUTTON_CLASS}`}
|
||||
onClick={() => setAppliedQuery(queryDraft)}
|
||||
>
|
||||
查询
|
||||
@ -5169,7 +5180,7 @@ function ProductPatentStatusDashboard({
|
||||
<div className="flex items-end">
|
||||
<Button
|
||||
size="small"
|
||||
className="h-9 w-full text-xs"
|
||||
className={`h-9 w-full text-xs ${INTAKE_PRIMARY_BUTTON_CLASS}`}
|
||||
onClick={refreshRecords}
|
||||
>
|
||||
刷新
|
||||
|
||||
@ -32,6 +32,8 @@ const STEEL_FRAME_CREATE_PRESETS = [
|
||||
{ key: 'flat', label: '新增扁管', name: '扁管' },
|
||||
{ key: 'round', label: '新增圆管', name: '圆管' },
|
||||
] as const
|
||||
const LIBRARY_PRIMARY_BUTTON_CLASS =
|
||||
'!border-zinc-950 !bg-zinc-950 !bg-none !text-white !shadow-none hover:!border-zinc-800 hover:!bg-zinc-800 focus:!border-zinc-600 disabled:!border-zinc-200 disabled:!bg-zinc-200 disabled:!text-zinc-400'
|
||||
|
||||
export default function SteelFrameLibraryPanel() {
|
||||
const initialState = useMemo(() => readSteelFrameLibraryState(), [])
|
||||
@ -273,13 +275,19 @@ export default function SteelFrameLibraryPanel() {
|
||||
type="button"
|
||||
size="small"
|
||||
variant="gray"
|
||||
className={LIBRARY_PRIMARY_BUTTON_CLASS}
|
||||
onClick={() => handleStartCreate(preset.name)}
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
{preset.label}
|
||||
</Button>
|
||||
))}
|
||||
<Button type="button" size="small" onClick={applySearch}>
|
||||
<Button
|
||||
type="button"
|
||||
size="small"
|
||||
className={LIBRARY_PRIMARY_BUTTON_CLASS}
|
||||
onClick={applySearch}
|
||||
>
|
||||
<Search className="h-4 w-4" />
|
||||
查询
|
||||
</Button>
|
||||
@ -399,7 +407,13 @@ export default function SteelFrameLibraryPanel() {
|
||||
删除
|
||||
</Button>
|
||||
) : null}
|
||||
<Button type="button" size="small" onClick={handleSaveDraft} disabled={!draftCanSave}>
|
||||
<Button
|
||||
type="button"
|
||||
size="small"
|
||||
className={LIBRARY_PRIMARY_BUTTON_CLASS}
|
||||
onClick={handleSaveDraft}
|
||||
disabled={!draftCanSave}
|
||||
>
|
||||
<Save className="h-4 w-4" />
|
||||
保存
|
||||
</Button>
|
||||
|
||||
@ -274,6 +274,7 @@ function createProductRecord(
|
||||
skuItems: [],
|
||||
factoryRows: [],
|
||||
pricingRows: [],
|
||||
productLimitRows: [],
|
||||
sampleMeasurementRows: [],
|
||||
uploadFiles: [],
|
||||
documentWorkspaceNotes: {},
|
||||
@ -308,6 +309,7 @@ function createSkuItem(
|
||||
drawingId: "SZ0230K01AV0",
|
||||
installMethod: "拆装",
|
||||
executeStandard: "",
|
||||
nationalStandardCompliant: "",
|
||||
manufacturer: "",
|
||||
manufacturerCode: "",
|
||||
tagMaterial: "",
|
||||
@ -476,6 +478,7 @@ describe("NonIntegratedDetailClient", () => {
|
||||
expect(mockedUploadDetachedNonIntegratedFile).toHaveBeenCalledWith(
|
||||
undefined,
|
||||
expect.objectContaining({ name: "remote-preview.png" }),
|
||||
expect.any(Function),
|
||||
);
|
||||
expect(mockedSaveSharedNonIntegratedProduct.mock.calls[0]?.[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
@ -1127,6 +1130,7 @@ describe("NonIntegratedDetailClient", () => {
|
||||
"document-workspace-trigger-设计卖点",
|
||||
"document-workspace-trigger-产品模型",
|
||||
"document-workspace-trigger-评审资料",
|
||||
"document-workspace-trigger-企划布局",
|
||||
"document-workspace-trigger-专利申请",
|
||||
"document-workspace-trigger-产品实拍图",
|
||||
"document-workspace-trigger-安装方式",
|
||||
@ -1134,6 +1138,7 @@ describe("NonIntegratedDetailClient", () => {
|
||||
"document-workspace-trigger-产品定厂",
|
||||
"document-workspace-trigger-样包测量",
|
||||
"document-workspace-trigger-产品核价",
|
||||
"document-workspace-trigger-产品限价",
|
||||
]);
|
||||
expect(
|
||||
screen.queryByTestId("document-workspace-trigger-立项评审"),
|
||||
@ -1205,6 +1210,7 @@ describe("NonIntegratedDetailClient", () => {
|
||||
"document-workspace-trigger-设计卖点",
|
||||
"document-workspace-trigger-产品模型",
|
||||
"document-workspace-trigger-评审资料",
|
||||
"document-workspace-trigger-企划布局",
|
||||
"document-workspace-trigger-专利申请",
|
||||
"document-workspace-trigger-产品实拍图",
|
||||
"document-workspace-trigger-安装方式",
|
||||
@ -1212,6 +1218,7 @@ describe("NonIntegratedDetailClient", () => {
|
||||
"document-workspace-trigger-产品定厂",
|
||||
"document-workspace-trigger-样包测量",
|
||||
"document-workspace-trigger-产品核价",
|
||||
"document-workspace-trigger-产品限价",
|
||||
]);
|
||||
expect(
|
||||
sidebar.querySelector<HTMLButtonElement>(
|
||||
@ -1747,11 +1754,7 @@ describe("NonIntegratedDetailClient", () => {
|
||||
screen.queryByRole("button", { name: "删除当前" }),
|
||||
).not.toBeInTheDocument();
|
||||
|
||||
expect(
|
||||
screen.queryByTestId("document-workspace-confirm-专利申请"),
|
||||
).not.toBeInTheDocument();
|
||||
|
||||
await actor.click(screen.getByTestId("workflow-approve-current-node"));
|
||||
await actor.click(screen.getByTestId("document-workspace-confirm-专利申请"));
|
||||
|
||||
await waitFor(() => {
|
||||
const savedProduct = getNonIntegratedProductById(
|
||||
@ -1833,7 +1836,7 @@ describe("NonIntegratedDetailClient", () => {
|
||||
expect(screen.queryByLabelText("申请号")).not.toBeInTheDocument();
|
||||
expect(screen.queryByLabelText("申请缴费日")).not.toBeInTheDocument();
|
||||
|
||||
await actor.click(screen.getByTestId("workflow-approve-current-node"));
|
||||
await actor.click(screen.getByTestId("document-workspace-confirm-专利申请"));
|
||||
|
||||
await waitFor(() => {
|
||||
const savedProduct = getNonIntegratedProductById(
|
||||
@ -1918,6 +1921,55 @@ describe("NonIntegratedDetailClient", () => {
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("persists the SKU national standard compliance selection", async () => {
|
||||
const actor = userEvent.setup();
|
||||
|
||||
saveNonIntegratedProduct(
|
||||
createProductRecord({
|
||||
id: "SZ0230-national-standard",
|
||||
spu: "SZ0230",
|
||||
skuItems: [
|
||||
createSkuItem({
|
||||
id: "sku-national-standard-1",
|
||||
model: "SZ0230K01",
|
||||
}),
|
||||
createSkuItem({
|
||||
id: "sku-national-standard-2",
|
||||
model: "SZ0230K01",
|
||||
}),
|
||||
createSkuItem({
|
||||
id: "sku-national-standard-3",
|
||||
model: "SZ0230K02",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
render(
|
||||
<NonIntegratedDetailClient
|
||||
mode="edit"
|
||||
productId="SZ0230-national-standard"
|
||||
/>,
|
||||
);
|
||||
|
||||
const skuRow = screen.getAllByDisplayValue("SZ0230K01")[0]?.closest("tr");
|
||||
expect(skuRow).not.toBeNull();
|
||||
|
||||
await actor.selectOptions(
|
||||
within(skuRow!).getByRole("combobox", { name: "符合国标" }),
|
||||
"是",
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
const savedProduct = getNonIntegratedProductById(
|
||||
"SZ0230-national-standard",
|
||||
);
|
||||
expect(savedProduct?.skuItems[0]?.nationalStandardCompliant).toBe("是");
|
||||
expect(savedProduct?.skuItems[1]?.nationalStandardCompliant).toBe("是");
|
||||
expect(savedProduct?.skuItems[2]?.nationalStandardCompliant).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
it("hides redundant fields from the product detail summary", () => {
|
||||
saveNonIntegratedProduct(
|
||||
createProductRecord({
|
||||
@ -1981,6 +2033,7 @@ describe("NonIntegratedDetailClient", () => {
|
||||
id: "sku-sample-1",
|
||||
model: "SZ0230K01",
|
||||
skuCode: "SZ023001",
|
||||
barcode: "SZ023001N1280V01",
|
||||
setCount: "1",
|
||||
specName: "款式一 140/60/75",
|
||||
barcode: "SZ023001N1280V01",
|
||||
@ -2055,8 +2108,9 @@ describe("NonIntegratedDetailClient", () => {
|
||||
|
||||
it("lets purchasing users edit only purchasing document nodes", async () => {
|
||||
const actor = userEvent.setup();
|
||||
const openingReferenceCategory = NON_INTEGRATED_DOCUMENT_WORKSPACE_CATEGORIES[0];
|
||||
const sampleMeasurementCategory = NON_INTEGRATED_DOCUMENT_WORKSPACE_CATEGORIES[10];
|
||||
const openingReferenceCategory = "开品参考";
|
||||
const sampleMeasurementCategory = "样包测量";
|
||||
const productLimitCategory = "产品限价";
|
||||
|
||||
saveNonIntegratedProduct(
|
||||
createProductRecord({
|
||||
@ -2106,12 +2160,24 @@ describe("NonIntegratedDetailClient", () => {
|
||||
);
|
||||
expect(savedProduct?.sampleMeasurementRows?.[0]?.lengthCm).toBe("120");
|
||||
});
|
||||
|
||||
await actor.click(
|
||||
screen.getByTestId(`document-workspace-trigger-${productLimitCategory}`),
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByTestId(
|
||||
"product-limit-expectedDailyLimitPrice-sku-purchasing-permission-1",
|
||||
),
|
||||
).toHaveAttribute("readonly");
|
||||
});
|
||||
|
||||
it("lets planning users edit only the opening reference node", async () => {
|
||||
it("lets planning users edit opening reference, planning layout, and product limit nodes", async () => {
|
||||
const actor = userEvent.setup();
|
||||
const openingReferenceCategory = NON_INTEGRATED_DOCUMENT_WORKSPACE_CATEGORIES[0];
|
||||
const sampleMeasurementCategory = NON_INTEGRATED_DOCUMENT_WORKSPACE_CATEGORIES[10];
|
||||
const openingReferenceCategory = "开品参考";
|
||||
const planningLayoutCategory = "企划布局";
|
||||
const productLimitCategory = "产品限价";
|
||||
const sampleMeasurementCategory = "样包测量";
|
||||
|
||||
saveNonIntegratedProduct(
|
||||
createProductRecord({
|
||||
@ -2137,7 +2203,7 @@ describe("NonIntegratedDetailClient", () => {
|
||||
initialDocumentWorkspaceCategory={openingReferenceCategory}
|
||||
currentUserRole="USER"
|
||||
currentUserName="企划同事"
|
||||
currentUserBusinessRoles={["PLANNING"]}
|
||||
currentUserDepartmentName="品牌中心"
|
||||
/>,
|
||||
);
|
||||
|
||||
@ -2153,6 +2219,32 @@ describe("NonIntegratedDetailClient", () => {
|
||||
expect(savedProduct?.openingReference.reason).toBe("企划补充开品理由");
|
||||
});
|
||||
|
||||
await actor.click(
|
||||
screen.getByTestId(`document-workspace-trigger-${planningLayoutCategory}`),
|
||||
);
|
||||
expect(
|
||||
screen.getByTestId(`document-workspace-confirm-${planningLayoutCategory}`),
|
||||
).not.toBeDisabled();
|
||||
|
||||
await actor.click(
|
||||
screen.getByTestId(`document-workspace-trigger-${productLimitCategory}`),
|
||||
);
|
||||
await actor.type(
|
||||
screen.getByTestId(
|
||||
"product-limit-expectedDailyLimitPrice-sku-planning-permission-1",
|
||||
),
|
||||
"1999",
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
const savedProduct = getNonIntegratedProductById(
|
||||
"SZ0230-planning-permission",
|
||||
);
|
||||
expect(savedProduct?.productLimitRows?.[0]?.expectedDailyLimitPrice).toBe(
|
||||
"1999",
|
||||
);
|
||||
});
|
||||
|
||||
await actor.click(
|
||||
screen.getByTestId(`document-workspace-trigger-${sampleMeasurementCategory}`),
|
||||
);
|
||||
@ -2191,7 +2283,7 @@ describe("NonIntegratedDetailClient", () => {
|
||||
});
|
||||
|
||||
it("keeps regular users read-only in purchasing document nodes", async () => {
|
||||
const sampleMeasurementCategory = NON_INTEGRATED_DOCUMENT_WORKSPACE_CATEGORIES[10];
|
||||
const sampleMeasurementCategory = "样包测量";
|
||||
|
||||
saveNonIntegratedProduct(
|
||||
createProductRecord({
|
||||
@ -2267,11 +2359,9 @@ describe("NonIntegratedDetailClient", () => {
|
||||
);
|
||||
|
||||
await actor.click(screen.getByTestId("document-workspace-trigger-包装细节"));
|
||||
await actor.click(screen.getByTestId("packaging-workspace-panel-factoryQuality"));
|
||||
|
||||
const panel = screen.getByTestId("document-workspace-panel");
|
||||
expect(
|
||||
screen.getByTestId("packaging-factory-quality-section"),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByTestId("factory-quality-workspace")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("factory-quality-style-K01")).toHaveTextContent(
|
||||
"SZ0230K01",
|
||||
@ -2349,6 +2439,7 @@ describe("NonIntegratedDetailClient", () => {
|
||||
id: "sku-pricing-1",
|
||||
model: "SZ0230K01",
|
||||
skuCode: "SZ023001",
|
||||
barcode: "SZ023001N1280V01",
|
||||
setCount: "1",
|
||||
specName: "款式一 140/60/75",
|
||||
manufacturer: "兰考华兰家具",
|
||||
@ -2414,6 +2505,119 @@ describe("NonIntegratedDetailClient", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("renders planning layout rows from SKU data without SKU edit actions", async () => {
|
||||
const actor = userEvent.setup();
|
||||
|
||||
saveNonIntegratedProduct(
|
||||
createProductRecord({
|
||||
id: "DNZ0001-planning-layout",
|
||||
skuItems: [
|
||||
createSkuItem({
|
||||
id: "sku-planning-layout-1",
|
||||
model: "SZ0230K01",
|
||||
skuCode: "SZ023001",
|
||||
barcode: "SZ023001N1280V01",
|
||||
setCount: "1",
|
||||
specName: "款式一 140/60/75",
|
||||
size: "140/60/75",
|
||||
color: "胡桃色",
|
||||
material: "橡胶木",
|
||||
installMethod: "整装",
|
||||
manufacturer: "兰考华兰家具",
|
||||
executeStandard: "GB/T3324-2024",
|
||||
nationalStandardCompliant: "是",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
render(
|
||||
<NonIntegratedDetailClient
|
||||
mode="edit"
|
||||
productId="DNZ0001-planning-layout"
|
||||
/>,
|
||||
);
|
||||
|
||||
await actor.click(screen.getByTestId("document-workspace-trigger-企划布局"));
|
||||
|
||||
const planningWorkspace = screen.getByTestId("planning-layout-workspace");
|
||||
expect(planningWorkspace).toHaveTextContent("SZ0230K01");
|
||||
expect(planningWorkspace).toHaveTextContent("SZ023001N1280V01");
|
||||
expect(planningWorkspace).toHaveTextContent("GB/T3324-2024");
|
||||
expect(planningWorkspace).toHaveTextContent("是");
|
||||
expect(
|
||||
screen.getByTestId("document-workspace-confirm-企划布局"),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
within(planningWorkspace).queryByRole("button", { name: "新增SKU" }),
|
||||
).not.toBeInTheDocument();
|
||||
expect(
|
||||
within(planningWorkspace).queryByText("上传图片"),
|
||||
).not.toBeInTheDocument();
|
||||
expect(
|
||||
within(planningWorkspace).queryByText("删除"),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders product limit rows and persists editable limit fields", async () => {
|
||||
const actor = userEvent.setup();
|
||||
|
||||
saveNonIntegratedProduct(
|
||||
createProductRecord({
|
||||
id: "DNZ0001-product-limit",
|
||||
spu: "SZ0230",
|
||||
previewImage: "data:image/png;base64,product-limit-image",
|
||||
skuItems: [
|
||||
createSkuItem({
|
||||
id: "sku-product-limit-1",
|
||||
model: "SZ0230K01",
|
||||
skuCode: "SZ023001",
|
||||
barcode: "SZ023001N1280V01",
|
||||
setCount: "1",
|
||||
specName: "款式一 140/60/75",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
render(
|
||||
<NonIntegratedDetailClient mode="edit" productId="DNZ0001-product-limit" />,
|
||||
);
|
||||
|
||||
await actor.click(screen.getByTestId("document-workspace-trigger-产品限价"));
|
||||
|
||||
const productLimitWorkspace = screen.getByTestId("product-limit-workspace");
|
||||
expect(productLimitWorkspace).toHaveTextContent("SPU");
|
||||
expect(productLimitWorkspace).toHaveTextContent("SZ0230");
|
||||
expect(productLimitWorkspace).toHaveTextContent("期望日常限价");
|
||||
expect(productLimitWorkspace).toHaveTextContent("期望限价毛利率(%)");
|
||||
expect(productLimitWorkspace).toHaveTextContent("非SPU负责人限价");
|
||||
|
||||
await actor.type(screen.getByTestId("product-limit-expectedDailyLimitPrice-sku-product-limit-1"), "1999");
|
||||
await actor.type(screen.getByTestId("product-limit-expectedLimitGrossMarginRate-sku-product-limit-1"), "45");
|
||||
await actor.type(screen.getByTestId("product-limit-nonSpuOwnerLimitPrice-sku-product-limit-1"), "1899");
|
||||
|
||||
await waitFor(() => {
|
||||
const savedProduct = getNonIntegratedProductById("DNZ0001-product-limit");
|
||||
expect(savedProduct?.productLimitRows).toEqual([
|
||||
{
|
||||
skuId: "sku-product-limit-1",
|
||||
expectedDailyLimitPrice: "1999",
|
||||
expectedLimitGrossMarginRate: "45",
|
||||
nonSpuOwnerLimitPrice: "1899",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
await actor.click(screen.getByRole("button", { name: "确认限价" }));
|
||||
|
||||
await waitFor(() => {
|
||||
const savedProduct = getNonIntegratedProductById("DNZ0001-product-limit");
|
||||
expect(savedProduct?.documentWorkspaceConfirmations["产品限价"]).toBe(true);
|
||||
expect(savedProduct?.documentWorkspaceApprovedAt?.["产品限价"]).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it.skip("persists distinct board material rows after submit", async () => {
|
||||
const actor = userEvent.setup();
|
||||
|
||||
@ -2817,7 +3021,7 @@ describe("NonIntegratedDetailClient", () => {
|
||||
expect(
|
||||
screen.getByTestId("document-workspace-basic-info-trigger"),
|
||||
).toHaveAttribute("data-pending-confirmation", "true");
|
||||
expect(screen.getByTestId("workflow-approve-current-node")).toBeEnabled();
|
||||
expect(screen.getByTestId("basic-info-confirm")).toBeEnabled();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -37,6 +37,7 @@ export const NON_INTEGRATED_DOCUMENT_WORKSPACE_CATEGORIES = [
|
||||
"立项评审",
|
||||
"产品模型",
|
||||
"评审资料",
|
||||
"企划布局",
|
||||
"专利申请",
|
||||
"产品实拍图",
|
||||
"安装方式",
|
||||
@ -44,11 +45,14 @@ export const NON_INTEGRATED_DOCUMENT_WORKSPACE_CATEGORIES = [
|
||||
"产品定厂",
|
||||
"样包测量",
|
||||
"产品核价",
|
||||
"产品限价",
|
||||
"工厂质检",
|
||||
"检测报告",
|
||||
] as const;
|
||||
|
||||
export const NON_INTEGRATED_BASIC_INFO_WORKSPACE_CATEGORY = "基础信息" as const;
|
||||
export const NON_INTEGRATED_PLANNING_LAYOUT_WORKSPACE_CATEGORY =
|
||||
"企划布局" as const;
|
||||
|
||||
export const NON_INTEGRATED_WORKFLOW_NODE_CATEGORIES = [
|
||||
"开品参考",
|
||||
@ -57,6 +61,7 @@ export const NON_INTEGRATED_WORKFLOW_NODE_CATEGORIES = [
|
||||
"产品模型",
|
||||
"评审资料",
|
||||
NON_INTEGRATED_BASIC_INFO_WORKSPACE_CATEGORY,
|
||||
NON_INTEGRATED_PLANNING_LAYOUT_WORKSPACE_CATEGORY,
|
||||
"专利申请",
|
||||
"产品实拍图",
|
||||
"安装方式",
|
||||
@ -64,6 +69,7 @@ export const NON_INTEGRATED_WORKFLOW_NODE_CATEGORIES = [
|
||||
"产品定厂",
|
||||
"样包测量",
|
||||
"产品核价",
|
||||
"产品限价",
|
||||
"工厂质检",
|
||||
"检测报告",
|
||||
] as const;
|
||||
@ -250,6 +256,7 @@ const NON_INTEGRATED_UPLOAD_CATEGORY_MAP: Record<string, string> = {
|
||||
立项评审: "立项评审",
|
||||
评审资料: "评审资料",
|
||||
样品评审: "评审资料",
|
||||
企划布局: "企划布局",
|
||||
专利申请: "专利申请",
|
||||
资料信息: "评审资料",
|
||||
产品模型: "产品模型",
|
||||
@ -261,6 +268,7 @@ const NON_INTEGRATED_UPLOAD_CATEGORY_MAP: Record<string, string> = {
|
||||
产品定厂: "产品定厂",
|
||||
样包测量: "样包测量",
|
||||
产品核价: "产品核价",
|
||||
产品限价: "产品限价",
|
||||
工厂质检: "工厂质检",
|
||||
仓库质检: "工厂质检",
|
||||
仓库抽检: "检测报告",
|
||||
@ -291,6 +299,7 @@ export type NonIntegratedSkuItem = {
|
||||
drawingId: string;
|
||||
installMethod: string;
|
||||
executeStandard: string;
|
||||
nationalStandardCompliant: string;
|
||||
manufacturer: string;
|
||||
manufacturerCode: string;
|
||||
tagMaterial: string;
|
||||
@ -331,6 +340,13 @@ export type NonIntegratedPricingRow = {
|
||||
productTag: string;
|
||||
};
|
||||
|
||||
export type NonIntegratedProductLimitRow = {
|
||||
skuId: string;
|
||||
expectedDailyLimitPrice: string;
|
||||
expectedLimitGrossMarginRate: string;
|
||||
nonSpuOwnerLimitPrice: string;
|
||||
};
|
||||
|
||||
export type NonIntegratedSampleMeasurementRow = {
|
||||
skuId: string;
|
||||
lengthCm: string;
|
||||
@ -528,6 +544,7 @@ export type NonIntegratedProductRecord = {
|
||||
skuBatchChangeTypes?: Record<string, string>;
|
||||
factoryRows: NonIntegratedFactoryRow[];
|
||||
pricingRows: NonIntegratedPricingRow[];
|
||||
productLimitRows: NonIntegratedProductLimitRow[];
|
||||
sampleMeasurementRows: NonIntegratedSampleMeasurementRow[];
|
||||
uploadFiles: NonIntegratedUploadFile[];
|
||||
documentWorkspaceNotes?: Record<string, string>;
|
||||
@ -1039,6 +1056,7 @@ function normalizeSkuItem(
|
||||
drawingId: normalizeString(item.drawingId),
|
||||
installMethod: normalizeString(item.installMethod),
|
||||
executeStandard: normalizeString(item.executeStandard),
|
||||
nationalStandardCompliant: normalizeString(item.nationalStandardCompliant),
|
||||
manufacturer: normalizeString(item.manufacturer),
|
||||
manufacturerCode: normalizeString(item.manufacturerCode),
|
||||
tagMaterial: normalizeString(item.tagMaterial),
|
||||
@ -1104,6 +1122,37 @@ function normalizePricingRow(
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeProductLimitRow(
|
||||
item: Partial<NonIntegratedProductLimitRow>,
|
||||
): NonIntegratedProductLimitRow | null {
|
||||
if (typeof item.skuId !== "string") return null;
|
||||
|
||||
return {
|
||||
skuId: item.skuId,
|
||||
expectedDailyLimitPrice:
|
||||
typeof item.expectedDailyLimitPrice === "string"
|
||||
? item.expectedDailyLimitPrice
|
||||
: typeof item.expectedDailyLimitPrice === "number" &&
|
||||
Number.isFinite(item.expectedDailyLimitPrice)
|
||||
? String(item.expectedDailyLimitPrice)
|
||||
: "",
|
||||
expectedLimitGrossMarginRate:
|
||||
typeof item.expectedLimitGrossMarginRate === "string"
|
||||
? item.expectedLimitGrossMarginRate
|
||||
: typeof item.expectedLimitGrossMarginRate === "number" &&
|
||||
Number.isFinite(item.expectedLimitGrossMarginRate)
|
||||
? String(item.expectedLimitGrossMarginRate)
|
||||
: "",
|
||||
nonSpuOwnerLimitPrice:
|
||||
typeof item.nonSpuOwnerLimitPrice === "string"
|
||||
? item.nonSpuOwnerLimitPrice
|
||||
: typeof item.nonSpuOwnerLimitPrice === "number" &&
|
||||
Number.isFinite(item.nonSpuOwnerLimitPrice)
|
||||
? String(item.nonSpuOwnerLimitPrice)
|
||||
: "",
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeSampleMeasurementRow(
|
||||
item: Partial<NonIntegratedSampleMeasurementRow>,
|
||||
): NonIntegratedSampleMeasurementRow | null {
|
||||
@ -1650,6 +1699,13 @@ function toProductRecord(
|
||||
.map((entry) => normalizePricingRow(entry))
|
||||
.filter((entry): entry is NonIntegratedPricingRow => entry !== null)
|
||||
: [],
|
||||
productLimitRows: Array.isArray(item.productLimitRows)
|
||||
? item.productLimitRows
|
||||
.map((entry) => normalizeProductLimitRow(entry))
|
||||
.filter(
|
||||
(entry): entry is NonIntegratedProductLimitRow => entry !== null,
|
||||
)
|
||||
: [],
|
||||
sampleMeasurementRows: Array.isArray(item.sampleMeasurementRows)
|
||||
? item.sampleMeasurementRows
|
||||
.map((entry) => normalizeSampleMeasurementRow(entry))
|
||||
@ -1976,6 +2032,7 @@ export function createCopiedNonIntegratedProduct(
|
||||
})),
|
||||
factoryRows: [],
|
||||
pricingRows: [],
|
||||
productLimitRows: [],
|
||||
sampleMeasurementRows: [],
|
||||
uploadFiles: sourceProduct.uploadFiles.map((file) => ({ ...file })),
|
||||
documentWorkspaceNotes: {
|
||||
|
||||
@ -15,6 +15,7 @@ export type NonIntegratedSkuExportRow = {
|
||||
drawingId: string
|
||||
installMethod: string
|
||||
executeStandard: string
|
||||
nationalStandardCompliant: string
|
||||
manufacturer: string
|
||||
manufacturerCode: string
|
||||
batchNo: string
|
||||
@ -47,6 +48,7 @@ export const NON_INTEGRATED_SKU_TEMPLATE_COLUMNS: {
|
||||
{ key: 'drawingId', label: '图纸ID', width: 22 },
|
||||
{ key: 'installMethod', label: '安装方式', width: 14 },
|
||||
{ key: 'executeStandard', label: '执行标准', width: 18 },
|
||||
{ key: 'nationalStandardCompliant', label: '符合国标', width: 12 },
|
||||
{ key: 'manufacturer', label: '供应商名称', width: 18 },
|
||||
{ key: 'manufacturerCode', label: '供应商代码', width: 14 },
|
||||
{ key: 'batchNo', label: '创建批次', width: 12 },
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user