diff --git a/furniture-3d-system/src/app/intake/non-integrated/workspaces/basic-info/oaBoardOpeningCodeImport.test.ts b/furniture-3d-system/src/app/intake/non-integrated/workspaces/basic-info/oaBoardOpeningCodeImport.test.ts new file mode 100644 index 00000000..b2074dd4 --- /dev/null +++ b/furniture-3d-system/src/app/intake/non-integrated/workspaces/basic-info/oaBoardOpeningCodeImport.test.ts @@ -0,0 +1,94 @@ +import { buildOaBoardOpeningCodeImportPayload } from "./oaBoardOpeningCodeImport"; +import type { OaBoardProjectLookupItem } from "../../core/nonIntegratedApi"; +import type { FormValues } from "./productFormValueHelpers"; +import type { NonIntegratedOpeningReference } from "../../nonIntegratedStorage"; + +function createOaItem(overrides: Partial = {}) { + return { + title: "OA 开款", + productName: "", + brand: "", + productCategory: "", + categoryName: "", + openingCode: "XP2026071700000329", + currentStageName: "", + currentApprovalStageName: "", + styleImages: [], + formSummary: { + openingCode: "XP2026071700000329", + demandType: "", + category: "", + categoryName: "", + productPlanner: "", + operationBrandOwner: "", + spuOwner: "", + appearanceDesigner: "", + structureDesigner: "", + purchaseOwner: "", + merchandiserOwner: "", + competitorLink: "", + openingReason: "", + salesPriceBand: "", + platformPriceBand: "", + openingBrand: "", + }, + ...overrides, + } as OaBoardProjectLookupItem; +} + +function createCurrent() { + return { + formValues: { + brand: "", + adaptableBrands: [], + salesCategory: "", + productCategory: "", + } as FormValues, + openingReference: { + adaptableBrands: [], + salesCategory: "", + productCategory: "", + } as NonIntegratedOpeningReference, + }; +} + +describe("buildOaBoardOpeningCodeImportPayload", () => { + it.each([ + ["住宅家具 - 鞋柜", "柜类", "鞋柜"], + ["住宅家具 - 书架", "架类", "书架"], + ])("maps %s to %s / %s", (sourceCategory, salesCategory, productCategory) => { + const item = createOaItem({ + formSummary: { + ...createOaItem().formSummary, + category: sourceCategory, + categoryName: sourceCategory, + }, + }); + + const result = buildOaBoardOpeningCodeImportPayload(item, createCurrent()); + + expect(result.formValues).toMatchObject({ salesCategory, productCategory }); + expect(result.openingReference).toMatchObject({ salesCategory, productCategory }); + }); + + it("does not retain an unmapped residential path as a product category", () => { + const item = createOaItem({ + formSummary: { + ...createOaItem().formSummary, + category: "住宅家具 - 未定义柜体", + categoryName: "住宅家具 - 未定义柜体", + }, + }); + + const result = buildOaBoardOpeningCodeImportPayload(item, createCurrent()); + + expect(result.formValues).toMatchObject({ + salesCategory: "", + productCategory: "未定义柜体", + }); + expect(result.openingReference).toMatchObject({ + salesCategory: "", + productCategory: "未定义柜体", + }); + }); +}); diff --git a/furniture-3d-system/src/app/intake/non-integrated/workspaces/basic-info/oaBoardOpeningCodeImport.ts b/furniture-3d-system/src/app/intake/non-integrated/workspaces/basic-info/oaBoardOpeningCodeImport.ts index e4211932..8b121359 100644 --- a/furniture-3d-system/src/app/intake/non-integrated/workspaces/basic-info/oaBoardOpeningCodeImport.ts +++ b/furniture-3d-system/src/app/intake/non-integrated/workspaces/basic-info/oaBoardOpeningCodeImport.ts @@ -7,12 +7,22 @@ import { import type { OaBoardProjectLookupItem } from "../../core/nonIntegratedApi"; import type { FormValues } from "./productFormValueHelpers"; import { BRAND_OPTIONS } from "./basicInfoConfig"; +import { getNonIntegratedSecondaryCategoryOptionsForProductCategory } from "@/lib/non-integrated-category-options"; type OaBoardImportPayload = { formValues: FormValues; openingReference: NonIntegratedOpeningReference; }; +type ImportedCategoryValues = { + salesCategory: string; + productCategory: string; + hasSalesCategory: boolean; + hasProductCategory: boolean; +}; + +const RESIDENTIAL_FURNITURE_PREFIX = "住宅家具"; + function pickFirstNonEmpty(...values: Array) { for (const value of values) { const normalized = String(value ?? "").trim(); @@ -44,6 +54,59 @@ function resolveImportedBrand( return currentBrand || NON_INTEGRATED_DEFAULT_BRAND; } +function isResidentialFurnitureCategoryPath(value: string) { + return new RegExp(`^${RESIDENTIAL_FURNITURE_PREFIX}\\s*-\\s*`).test(value); +} + +function readResidentialFurnitureLeafCategory(value: string) { + return value + .replace(new RegExp(`^${RESIDENTIAL_FURNITURE_PREFIX}\\s*-\\s*`), "") + .trim(); +} + +function resolveImportedCategories( + item: OaBoardProjectLookupItem, +): ImportedCategoryValues { + const rawSalesCategory = pickFirstNonEmpty( + item.formSummary.category, + item.productCategory, + ); + const rawProductCategory = pickFirstNonEmpty( + item.formSummary.categoryName, + item.categoryName, + ); + const categoryPath = [rawProductCategory, rawSalesCategory].find( + isResidentialFurnitureCategoryPath, + ); + + if (!categoryPath) { + return { + salesCategory: rawSalesCategory, + productCategory: rawProductCategory, + hasSalesCategory: Boolean(rawSalesCategory), + hasProductCategory: Boolean(rawProductCategory), + }; + } + + const leafCategory = readResidentialFurnitureLeafCategory(categoryPath); + const secondaryCategoryOptions = + getNonIntegratedSecondaryCategoryOptionsForProductCategory(leafCategory); + const mappedSecondaryCategory = + secondaryCategoryOptions.length === 1 ? secondaryCategoryOptions[0] : ""; + + return { + salesCategory: mappedSecondaryCategory || + (isResidentialFurnitureCategoryPath(rawSalesCategory) + ? "" + : rawSalesCategory), + productCategory: isResidentialFurnitureCategoryPath(rawProductCategory) + ? leafCategory + : rawProductCategory || leafCategory, + hasSalesCategory: Boolean(rawSalesCategory), + hasProductCategory: Boolean(rawProductCategory) || Boolean(leafCategory), + }; +} + export function buildOaBoardOpeningCodeImportPayload( item: OaBoardProjectLookupItem, current: OaBoardImportPayload, @@ -59,14 +122,7 @@ export function buildOaBoardOpeningCodeImportPayload( ...current.formValues.adaptableBrands, ...current.openingReference.adaptableBrands, ]).filter((brand) => BRAND_OPTIONS.includes(brand)); - const nextSalesCategory = pickFirstNonEmpty( - item.formSummary.category, - item.productCategory, - ); - const nextProductCategory = pickFirstNonEmpty( - item.formSummary.categoryName, - item.categoryName, - ); + const importedCategories = resolveImportedCategories(item); const nextProductName = pickFirstNonEmpty(item.productName, item.title); const nextTargetPriceBand = pickFirstNonEmpty( item.formSummary.salesPriceBand, @@ -93,9 +149,12 @@ export function buildOaBoardOpeningCodeImportPayload( productName: nextProductName || current.openingReference.productName, brand: normalizedBrand, adaptableBrands: nextAdaptableBrands, - salesCategory: nextSalesCategory || current.openingReference.salesCategory, - productCategory: - nextProductCategory || current.openingReference.productCategory, + salesCategory: importedCategories.hasSalesCategory + ? importedCategories.salesCategory + : current.openingReference.salesCategory, + productCategory: importedCategories.hasProductCategory + ? importedCategories.productCategory + : current.openingReference.productCategory, designer: current.openingReference.designer || pickFirstNonEmpty(item.formSummary.appearanceDesigner), @@ -125,9 +184,12 @@ export function buildOaBoardOpeningCodeImportPayload( pickFirstNonEmpty(styleImage?.alt) || current.formValues.previewImageName, brand: normalizedBrand, adaptableBrands: nextAdaptableBrands, - salesCategory: nextSalesCategory || current.formValues.salesCategory, - productCategory: - nextProductCategory || current.formValues.productCategory, + salesCategory: importedCategories.hasSalesCategory + ? importedCategories.salesCategory + : current.formValues.salesCategory, + productCategory: importedCategories.hasProductCategory + ? importedCategories.productCategory + : current.formValues.productCategory, designer: current.formValues.designer || pickFirstNonEmpty(item.formSummary.appearanceDesigner), diff --git a/furniture-3d-system/src/lib/non-integrated-category-options.ts b/furniture-3d-system/src/lib/non-integrated-category-options.ts index 5e6081ce..9349ab63 100644 --- a/furniture-3d-system/src/lib/non-integrated-category-options.ts +++ b/furniture-3d-system/src/lib/non-integrated-category-options.ts @@ -161,6 +161,19 @@ export function getNonIntegratedProductCategoryOptions( ); } +export function getNonIntegratedSecondaryCategoryOptionsForProductCategory( + productCategory: string, +) { + const normalizedProductCategory = productCategory.trim(); + if (!normalizedProductCategory) return []; + + return getUniqueNonEmptyValues( + NON_INTEGRATED_CATEGORY_OPTIONS.filter( + (option) => option.productCategory === normalizedProductCategory, + ).map((option) => option.secondaryCategory), + ); +} + export function normalizeNonIntegratedProductCategoryForSecondaryCategory( secondaryCategory: string, productCategory: string,