Explorar o código

fix(core): Fix importing products when 2 options have same name

Fixes #1445
Michael Bromley %!s(int64=3) %!d(string=hai) anos
pai
achega
316f5e967c

+ 68 - 0
packages/core/e2e/populate.e2e-spec.ts

@@ -16,6 +16,9 @@ import {
     GetAssetListQuery,
     GetCollectionsQuery,
     GetProductListQuery,
+    GetProductListQueryVariables,
+    GetProductWithVariantsQuery,
+    GetProductWithVariantsQueryVariables,
     LanguageCode,
 } from './graphql/generated-e2e-admin-types';
 import {
@@ -23,6 +26,7 @@ import {
     GET_ASSET_LIST,
     GET_COLLECTIONS,
     GET_PRODUCT_LIST,
+    GET_PRODUCT_WITH_VARIANTS,
 } from './graphql/shared-definitions';
 
 describe('populate() function', () => {
@@ -191,4 +195,68 @@ describe('populate() function', () => {
             expect(products.items.map(i => i.name).includes('Model Hand')).toBe(true);
         });
     });
+
+    // https://github.com/vendure-ecommerce/vendure/issues/1445
+    describe('clashing option names', () => {
+        let app: INestApplication;
+
+        beforeAll(async () => {
+            const initialDataForPopulate: InitialData = {
+                defaultLanguage: initialData.defaultLanguage,
+                defaultZone: initialData.defaultZone,
+                taxRates: [],
+                shippingMethods: [],
+                paymentMethods: [],
+                countries: [],
+                collections: [{ name: 'Collection 1', filters: [] }],
+            };
+            const csvFile = path.join(__dirname, 'fixtures', 'product-import-option-values.csv');
+            app = await populate(
+                async () => {
+                    await server.bootstrap();
+                    return server.app;
+                },
+                initialDataForPopulate,
+                csvFile,
+            );
+        }, TEST_SETUP_TIMEOUT_MS);
+
+        afterAll(async () => {
+            await app.close();
+        });
+
+        it('populates variants & options', async () => {
+            await adminClient.asSuperAdmin();
+            await adminClient.setChannelToken(channel2.token);
+            const { products } = await adminClient.query<GetProductListQuery, GetProductListQueryVariables>(
+                GET_PRODUCT_LIST,
+                {
+                    options: {
+                        filter: {
+                            slug: { eq: 'foo' },
+                        },
+                    },
+                },
+            );
+            expect(products.totalItems).toBe(1);
+            const fooProduct = products.items[0];
+            expect(fooProduct.name).toBe('Foo');
+
+            const { product } = await adminClient.query<
+                GetProductWithVariantsQuery,
+                GetProductWithVariantsQueryVariables
+            >(GET_PRODUCT_WITH_VARIANTS, {
+                id: fooProduct.id,
+            });
+
+            expect(product?.variants.length).toBe(4);
+            expect(product?.optionGroups.map(og => og.name).sort()).toEqual(['Bar', 'Foo']);
+            expect(
+                product?.variants
+                    .find(v => v.sku === 'foo-fiz-buz')
+                    ?.options.map(o => o.name)
+                    .sort(),
+            ).toEqual(['buz', 'fiz']);
+        });
+    });
 });

+ 12 - 5
packages/core/src/data-import/providers/importer/importer.ts

@@ -193,7 +193,9 @@ export class Importer {
             });
 
             const optionsMap: { [optionName: string]: ID } = {};
-            for (const optionGroup of product.optionGroups) {
+            for (const [optionGroup, optionGroupIndex] of product.optionGroups.map(
+                (group, i) => [group, i] as const,
+            )) {
                 const optionGroupMainTranslation = this.getTranslationByCodeOrFirst(
                     optionGroup.translations,
                     ctx.languageCode,
@@ -212,10 +214,12 @@ export class Importer {
                         };
                     }),
                 });
-                for (const optionIndex of optionGroupMainTranslation.values.map((value, index) => index)) {
+                for (const [optionIndex, value] of optionGroupMainTranslation.values.map(
+                    (val, index) => [index, val] as const,
+                )) {
                     const createdOptionId = await this.fastImporter.createProductOption({
                         productOptionGroupId: groupId,
-                        code: normalizeString(optionGroupMainTranslation.values[optionIndex], '-'),
+                        code: normalizeString(value, '-'),
                         translations: optionGroup.translations.map(translation => {
                             return {
                                 languageCode: translation.languageCode,
@@ -223,7 +227,7 @@ export class Importer {
                             };
                         }),
                     });
-                    optionsMap[optionGroupMainTranslation.values[optionIndex]] = createdOptionId;
+                    optionsMap[`${optionGroupIndex}_${value}`] = createdOptionId;
                 }
                 await this.fastImporter.addOptionGroupToProduct(createdProductId, groupId);
             }
@@ -246,6 +250,9 @@ export class Importer {
                     variantMainTranslation.customFields,
                     this.configService.customFields.ProductVariant,
                 );
+                const optionIds = variantMainTranslation.optionValues.map(
+                    (v, index) => optionsMap[`${index}_${v}`],
+                );
                 const createdVariant = await this.fastImporter.createProductVariant({
                     productId: createdProductId,
                     facetValueIds,
@@ -255,7 +262,7 @@ export class Importer {
                     taxCategoryId: this.getMatchingTaxCategoryId(variant.taxCategory, taxCategories),
                     stockOnHand: variant.stockOnHand,
                     trackInventory: variant.trackInventory,
-                    optionIds: variantMainTranslation.optionValues.map(v => optionsMap[v]),
+                    optionIds,
                     translations: variant.translations.map(translation => {
                         const productTranslation = product.translations.find(
                             t => t.languageCode === translation.languageCode,