Skip to content

Commit

Permalink
feat(core): Make Orders ChannelAware
Browse files Browse the repository at this point in the history
* feat(core): Make Order entity ChannelAware
* test(core): Add e2e tests for ChannelAware Orders
* fix(core): Fix orders not being added to default channel

Co-authored-by: Hendrik Depauw <[email protected]>

Closes #440 

BREAKING CHANGE: Orders are now channel-aware which requires a non-destructive DB migration to apply the schema changes required for this relation.
  • Loading branch information
hendrik-advantitge authored Aug 25, 2020
1 parent 7673353 commit 9bb5750
Show file tree
Hide file tree
Showing 4 changed files with 245 additions and 17 deletions.
193 changes: 193 additions & 0 deletions packages/core/e2e/order-channel.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
/* tslint:disable:no-non-null-assertion */
import { createTestEnvironment, E2E_DEFAULT_CHANNEL_TOKEN } from '@vendure/testing';
import path from 'path';

import { initialData } from '../../../e2e-common/e2e-initial-data';
import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config';

import {
AssignProductsToChannel,
CreateChannel,
CurrencyCode,
GetCustomerList,
GetOrder,
GetOrderList,
GetProductWithVariants,
LanguageCode,
} from './graphql/generated-e2e-admin-types';
import { AddItemToOrder, GetActiveOrder } from './graphql/generated-e2e-shop-types';
import {
ASSIGN_PRODUCT_TO_CHANNEL,
CREATE_CHANNEL,
GET_CUSTOMER_LIST,
GET_ORDER,
GET_PRODUCT_WITH_VARIANTS,
} from './graphql/shared-definitions';
import { ADD_ITEM_TO_ORDER, GET_ACTIVE_ORDER } from './graphql/shop-definitions';
import { GET_ORDERS_LIST } from './order.e2e-spec';

describe('Channelaware orders', () => {
const { server, adminClient, shopClient } = createTestEnvironment(testConfig);
const SECOND_CHANNEL_TOKEN = 'second_channel_token';
const THIRD_CHANNEL_TOKEN = 'third_channel_token';
let customerUser: GetCustomerList.Items;
let product1: GetProductWithVariants.Product;
let product2: GetProductWithVariants.Product;
let order1Id: string;
let order2Id: string;

beforeAll(async () => {
await server.init({
initialData,
productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
customerCount: 1,
});
await adminClient.asSuperAdmin();

const { customers } = await adminClient.query<GetCustomerList.Query, GetCustomerList.Variables>(
GET_CUSTOMER_LIST,
{
options: { take: 1 },
},
);
customerUser = customers.items[0];
await shopClient.asUserWithCredentials(customerUser.emailAddress, 'test');

await adminClient.query<CreateChannel.Mutation, CreateChannel.Variables>(CREATE_CHANNEL, {
input: {
code: 'second-channel',
token: SECOND_CHANNEL_TOKEN,
defaultLanguageCode: LanguageCode.en,
currencyCode: CurrencyCode.GBP,
pricesIncludeTax: true,
defaultShippingZoneId: 'T_1',
defaultTaxZoneId: 'T_1',
},
});

await adminClient.query<CreateChannel.Mutation, CreateChannel.Variables>(CREATE_CHANNEL, {
input: {
code: 'third-channel',
token: THIRD_CHANNEL_TOKEN,
defaultLanguageCode: LanguageCode.en,
currencyCode: CurrencyCode.GBP,
pricesIncludeTax: true,
defaultShippingZoneId: 'T_1',
defaultTaxZoneId: 'T_1',
},
});

product1 = (
await adminClient.query<GetProductWithVariants.Query, GetProductWithVariants.Variables>(
GET_PRODUCT_WITH_VARIANTS,
{
id: 'T_1',
},
)
).product!;

await adminClient.query<AssignProductsToChannel.Mutation, AssignProductsToChannel.Variables>(
ASSIGN_PRODUCT_TO_CHANNEL,
{
input: {
channelId: 'T_2',
productIds: [product1.id],
},
},
);

product2 = (
await adminClient.query<GetProductWithVariants.Query, GetProductWithVariants.Variables>(
GET_PRODUCT_WITH_VARIANTS,
{
id: 'T_2',
},
)
).product!;

await adminClient.query<AssignProductsToChannel.Mutation, AssignProductsToChannel.Variables>(
ASSIGN_PRODUCT_TO_CHANNEL,
{
input: {
channelId: 'T_3',
productIds: [product2.id],
},
},
);
}, TEST_SETUP_TIMEOUT_MS);

afterAll(async () => {
await server.destroy();
});

it('creates order on current channel', async () => {
shopClient.setChannelToken(SECOND_CHANNEL_TOKEN);
const { addItemToOrder } = await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(
ADD_ITEM_TO_ORDER,
{
productVariantId: product1.variants[0].id,
quantity: 1,
},
);

expect(addItemToOrder!.lines.length).toBe(1);
expect(addItemToOrder!.lines[0].quantity).toBe(1);
expect(addItemToOrder!.lines[0].productVariant.id).toBe(product1.variants[0].id);

order1Id = addItemToOrder!.id;
});

it('sets active order to null when switching channel', async () => {
shopClient.setChannelToken(THIRD_CHANNEL_TOKEN);
const result = await shopClient.query<GetActiveOrder.Query>(GET_ACTIVE_ORDER);
expect(result.activeOrder).toBeNull();
});

it('creates new order on current channel when already active order on other channel', async () => {
const { addItemToOrder } = await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(
ADD_ITEM_TO_ORDER,
{
productVariantId: product2.variants[0].id,
quantity: 1,
},
);

expect(addItemToOrder!.lines.length).toBe(1);
expect(addItemToOrder!.lines[0].quantity).toBe(1);
expect(addItemToOrder!.lines[0].productVariant.id).toBe(product2.variants[0].id);

order2Id = addItemToOrder!.id;
});

it('goes back to most recent active order when switching channel', async () => {
shopClient.setChannelToken(SECOND_CHANNEL_TOKEN);
const { activeOrder } = await shopClient.query<GetActiveOrder.Query>(GET_ACTIVE_ORDER);
expect(activeOrder!.id).toBe(order1Id);
});

it('returns null when requesting order from other channel', async () => {
const result = await shopClient.query<GetOrder.Query>(GET_ORDER, {
id: order2Id,
});
expect(result!.order).toBeNull();
});

it('returns order when requesting order from correct channel', async () => {
const result = await shopClient.query<GetOrder.Query>(GET_ORDER, {
id: order1Id,
});
expect(result.order!.id).toBe(order1Id);
});

it('returns all orders on default channel', async () => {
adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
const result = await adminClient.query<GetOrderList.Query>(GET_ORDERS_LIST);
expect(result.orders.items.map((o) => o.id)).toEqual([order1Id, order2Id]);
});

it('returns only channel specific orders when on other than default channel', async () => {
adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
const result = await adminClient.query<GetOrderList.Query>(GET_ORDERS_LIST);
expect(result.orders.items.map((o) => o.id)).toEqual([order1Id]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ export class OrderEntityResolver {
}

@ResolveField()
async promotions(@Parent() order: Order) {
async promotions(@Ctx() ctx: RequestContext, @Parent() order: Order) {
if (order.promotions) {
return order.promotions;
}
return this.orderService.getOrderPromotions(order.id);
return this.orderService.getOrderPromotions(ctx, order.id);
}
}

Expand Down
8 changes: 7 additions & 1 deletion packages/core/src/entity/order/order.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { OrderLine } from '../order-line/order-line.entity';
import { Payment } from '../payment/payment.entity';
import { Promotion } from '../promotion/promotion.entity';
import { ShippingMethod } from '../shipping-method/shipping-method.entity';
import { ChannelAware } from '../../common/types/common-types';
import { Channel } from '../channel/channel.entity';

/**
* @description
Expand All @@ -27,7 +29,7 @@ import { ShippingMethod } from '../shipping-method/shipping-method.entity';
* @docsCategory entities
*/
@Entity()
export class Order extends VendureEntity implements HasCustomFields {
export class Order extends VendureEntity implements ChannelAware, HasCustomFields {
constructor(input?: DeepPartial<Order>) {
super(input);
}
Expand Down Expand Up @@ -94,6 +96,10 @@ export class Order extends VendureEntity implements HasCustomFields {
@EntityId({ nullable: true })
taxZoneId?: ID;

@ManyToMany((type) => Channel)
@JoinTable()
channels: Channel[];

@Calculated()
get totalBeforeTax(): number {
return this.subTotalBeforeTax + this.promotionAdjustmentsTotal + (this.shipping || 0);
Expand Down
Loading

0 comments on commit 9bb5750

Please sign in to comment.