Skip to content

Commit

Permalink
fix(core): Fix error when creating Product in sub-channel
Browse files Browse the repository at this point in the history
Relates to #556, relates to #613
  • Loading branch information
michaelbromley committed Jan 8, 2021
1 parent c5e3c6d commit 96c5103
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 17 deletions.
79 changes: 78 additions & 1 deletion packages/core/e2e/product-channel.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@ import {
AssignProductVariantsToChannel,
CreateAdministrator,
CreateChannel,
CreateProduct,
CreateProductVariants,
CreateRole,
CurrencyCode,
GetProductWithVariants,
LanguageCode,
Permission,
ProductVariantFragment,
RemoveProductsFromChannel,
RemoveProductVariantsFromChannel,
} from './graphql/generated-e2e-admin-types';
Expand All @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -295,14 +301,27 @@ 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),
]);
// Third Channel is configured to include taxes in price, so they should be the same.
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 () => {
Expand Down Expand Up @@ -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']);
});
});
});
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 7 additions & 0 deletions packages/core/src/service/services/product-variant.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down

0 comments on commit 96c5103

Please sign in to comment.