Browse Source

fix(core): Fix error when creating Product in sub-channel

Relates to #556, relates to #613
Michael Bromley 5 years ago
parent
commit
96c5103e0a

+ 78 - 1
packages/core/e2e/product-channel.e2e-spec.ts

@@ -10,11 +10,14 @@ import {
     AssignProductVariantsToChannel,
     CreateAdministrator,
     CreateChannel,
+    CreateProduct,
+    CreateProductVariants,
     CreateRole,
     CurrencyCode,
     GetProductWithVariants,
     LanguageCode,
     Permission,
+    ProductVariantFragment,
     RemoveProductsFromChannel,
     RemoveProductVariantsFromChannel,
 } from './graphql/generated-e2e-admin-types';
@@ -23,6 +26,8 @@ import {
     ASSIGN_PRODUCT_TO_CHANNEL,
     CREATE_ADMINISTRATOR,
     CREATE_CHANNEL,
+    CREATE_PRODUCT,
+    CREATE_PRODUCT_VARIANTS,
     CREATE_ROLE,
     GET_PRODUCT_WITH_VARIANTS,
     REMOVE_PRODUCTVARIANT_FROM_CHANNEL,
@@ -195,6 +200,7 @@ describe('ChannelAware Products and ProductVariants', () => {
         });
 
         it('does not assign Product to same channel twice', async () => {
+            adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
             const { assignProductsToChannel } = await adminClient.query<
                 AssignProductsToChannel.Mutation,
                 AssignProductsToChannel.Variables
@@ -295,7 +301,7 @@ describe('ChannelAware Products and ProductVariants', () => {
             >(GET_PRODUCT_WITH_VARIANTS, {
                 id: product1.id,
             });
-            expect(product!.channels.map(c => c.id).sort()).toEqual(['T_1', 'T_3']);
+            expect(product!.channels.map(c => c.id).sort()).toEqual(['T_3']);
             expect(product!.variants.map(v => v.price)).toEqual([
                 Math.round((product1.variants[0].price * PRICE_FACTOR) / 1.2),
             ]);
@@ -303,6 +309,19 @@ describe('ChannelAware Products and ProductVariants', () => {
             expect(product!.variants.map(v => v.priceWithTax)).toEqual([
                 product1.variants[0].price * PRICE_FACTOR,
             ]);
+
+            await adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
+            const { product: check } = await adminClient.query<
+                GetProductWithVariants.Query,
+                GetProductWithVariants.Variables
+            >(GET_PRODUCT_WITH_VARIANTS, {
+                id: product1.id,
+            });
+
+            // from the default channel, all channels are visible
+            expect(check?.channels.map(c => c.id).sort()).toEqual(['T_1', 'T_3']);
+            expect(check?.variants[0].channels.map(c => c.id).sort()).toEqual(['T_1', 'T_3']);
+            expect(check?.variants[1].channels.map(c => c.id).sort()).toEqual(['T_1']);
         });
 
         it('does not assign ProductVariant to same channel twice', async () => {
@@ -393,4 +412,62 @@ describe('ChannelAware Products and ProductVariants', () => {
             expect(product!.channels.map(c => c.id).sort()).toEqual(['T_1']);
         });
     });
+
+    describe('creating Product in sub-channel', () => {
+        let createdProduct: CreateProduct.CreateProduct;
+        let createdVariant: ProductVariantFragment;
+
+        it('creates a Product in sub-channel', async () => {
+            adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
+
+            const { createProduct } = await adminClient.query<
+                CreateProduct.Mutation,
+                CreateProduct.Variables
+            >(CREATE_PRODUCT, {
+                input: {
+                    translations: [
+                        {
+                            languageCode: LanguageCode.en,
+                            name: 'Channel Product',
+                            slug: 'channel-product',
+                            description: 'Channel product',
+                        },
+                    ],
+                },
+            });
+            const { createProductVariants } = await adminClient.query<
+                CreateProductVariants.Mutation,
+                CreateProductVariants.Variables
+            >(CREATE_PRODUCT_VARIANTS, {
+                input: [
+                    {
+                        productId: createProduct.id,
+                        sku: 'PV1',
+                        optionIds: [],
+                        translations: [{ languageCode: LanguageCode.en, name: 'Variant 1' }],
+                    },
+                ],
+            });
+
+            createdProduct = createProduct;
+            createdVariant = createProductVariants[0]!;
+
+            // from sub-channel, only that channel is visible
+            expect(createdProduct.channels.map(c => c.id).sort()).toEqual(['T_2']);
+            expect(createdVariant.channels.map(c => c.id).sort()).toEqual(['T_2']);
+
+            adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
+
+            const { product } = await adminClient.query<
+                GetProductWithVariants.Query,
+                GetProductWithVariants.Variables
+            >(GET_PRODUCT_WITH_VARIANTS, {
+                id: createProduct.id,
+            });
+
+            // from the default channel, all channels are visible
+            expect(product?.channels.map(c => c.id).sort()).toEqual(['T_1', 'T_2']);
+            expect(product?.variants[0].channels.map(c => c.id).sort()).toEqual(['T_1', 'T_2']);
+        });
+    });
 });

+ 5 - 5
packages/core/src/api/resolvers/entity/product-entity.resolver.ts

@@ -1,6 +1,8 @@
 import { Info, Parent, ResolveField, Resolver } from '@nestjs/graphql';
+import { DEFAULT_CHANNEL_CODE } from '@vendure/common/lib/shared-constants';
 
 import { Translated } from '../../../common/types/locale-types';
+import { idsAreEqual } from '../../../common/utils';
 import { Asset } from '../../../entity/asset/asset.entity';
 import { Channel } from '../../../entity/channel/channel.entity';
 import { Collection } from '../../../entity/collection/collection.entity';
@@ -88,10 +90,8 @@ export class ProductAdminEntityResolver {
 
     @ResolveField()
     async channels(@Ctx() ctx: RequestContext, @Parent() product: Product): Promise<Channel[]> {
-        if (product.channels) {
-            return product.channels;
-        } else {
-            return this.productService.getProductChannels(ctx, product.id);
-        }
+        const isDefaultChannel = ctx.channel.code === DEFAULT_CHANNEL_CODE;
+        const channels = product.channels || (await this.productService.getProductChannels(ctx, product.id));
+        return channels.filter(channel => (isDefaultChannel ? true : idsAreEqual(channel.id, ctx.channelId)));
     }
 }

+ 2 - 11
packages/core/src/api/resolvers/entity/product-variant-entity.resolver.ts

@@ -103,16 +103,7 @@ export class ProductVariantAdminEntityResolver {
     @ResolveField()
     async channels(@Ctx() ctx: RequestContext, @Parent() productVariant: ProductVariant): Promise<Channel[]> {
         const isDefaultChannel = ctx.channel.code === DEFAULT_CHANNEL_CODE;
-        if (!isDefaultChannel && productVariant.channels) {
-            return productVariant.channels;
-        } else {
-            const channels = await this.productVariantService.getProductVariantChannels(
-                ctx,
-                productVariant.id,
-            );
-            return channels.filter(channel =>
-                isDefaultChannel ? true : idsAreEqual(channel.id, ctx.channelId),
-            );
-        }
+        const channels = await this.productVariantService.getProductVariantChannels(ctx, productVariant.id);
+        return channels.filter(channel => (isDefaultChannel ? true : idsAreEqual(channel.id, ctx.channelId)));
     }
 }

+ 7 - 0
packages/core/src/service/services/product-variant.service.ts

@@ -324,7 +324,14 @@ export class ProductVariantService {
             );
         }
 
+        const defaultChannelId = this.channelService.getDefaultChannel().id;
         await this.createProductVariantPrice(ctx, createdVariant.id, input.price, ctx.channelId);
+        if (!idsAreEqual(ctx.channelId, defaultChannelId)) {
+            // When creating a ProductVariant _not_ in the default Channel, we still need to
+            // create a ProductVariantPrice for it in the default Channel, otherwise errors will
+            // result when trying to query it there.
+            await this.createProductVariantPrice(ctx, createdVariant.id, input.price, defaultChannelId);
+        }
         return createdVariant.id;
     }