Ver Fonte

feat(server): Initial impl of tax-adjusted ProductVariant pricing

Relates to #31
Michael Bromley há 7 anos atrás
pai
commit
73ec9c731d

+ 1 - 0
package.json

@@ -14,6 +14,7 @@
     "prepush": "yarn test && cd admin-ui && yarn build --prod"
   },
   "devDependencies": {
+    "@types/node": "^10.11.5",
     "graphql": "^14.0.2",
     "graphql-code-generator": "^0.12.6",
     "graphql-codegen-typescript-template": "^0.12.6",

Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
schema.json


+ 3 - 2
server/e2e/adjustment-source.e2e-spec.ts

@@ -194,8 +194,9 @@ describe('AdjustmentSource resolver', () => {
             },
         );
 
-        expect(result.adjustmentSources.totalItems).toBe(1);
-        expect(result.adjustmentSources.items[0].name).toBe('tax adjustment source');
+        // 4 = 3 generated by the populate script + 1 created in this test suite.
+        expect(result.adjustmentSources.totalItems).toBe(4);
+        expect(result.adjustmentSources.items[3].name).toBe('tax adjustment source');
     });
 
     it('adjustmentSources, type = shipping', async () => {

+ 60 - 3
server/mock-data/mock-data.service.ts

@@ -4,8 +4,11 @@ import gql from 'graphql-tag';
 import * as path from 'path';
 import {
     AddOptionGroupToProduct,
+    AdjustmentSource,
+    AdjustmentType,
     Asset,
     CreateAddressInput,
+    CreateAdjustmentSource,
     CreateCustomerInput,
     CreateFacet,
     CreateFacetValueWithFacetInput,
@@ -14,9 +17,11 @@ import {
     GenerateProductVariants,
     LanguageCode,
     ProductTranslationInput,
+    ProductVariant,
     UpdateProductVariants,
 } from 'shared/generated-types';
 
+import { CREATE_ADJUSTMENT_SOURCE } from '../../admin-ui/src/app/data/definitions/adjustment-source-definitions';
 import { CREATE_FACET } from '../../admin-ui/src/app/data/definitions/facet-definitions';
 import {
     ADD_OPTION_GROUP_TO_PRODUCT,
@@ -25,6 +30,8 @@ import {
     GENERATE_PRODUCT_VARIANTS,
     UPDATE_PRODUCT_VARIANTS,
 } from '../../admin-ui/src/app/data/definitions/product-definitions';
+import { taxAction } from '../src/config/adjustment/required-adjustment-actions';
+import { taxCondition } from '../src/config/adjustment/required-adjustment-conditions';
 import { Channel } from '../src/entity/channel/channel.entity';
 import { Customer } from '../src/entity/customer/customer.entity';
 
@@ -96,6 +103,44 @@ export class MockDataService {
             });
     }
 
+    async populateTaxCategories() {
+        const taxCategories = [
+            { name: 'Standard Tax', rate: 20 },
+            { name: 'Reduced Tax', rate: 5 },
+            { name: 'Zero Tax', rate: 0 },
+        ];
+
+        const results: AdjustmentSource.Fragment[] = [];
+
+        for (const category of taxCategories) {
+            const result = await this.client.query<
+                CreateAdjustmentSource.Mutation,
+                CreateAdjustmentSource.Variables
+            >(CREATE_ADJUSTMENT_SOURCE, {
+                input: {
+                    name: category.name,
+                    type: AdjustmentType.TAX,
+                    enabled: true,
+                    conditions: [
+                        {
+                            code: taxCondition.code,
+                            arguments: [],
+                        },
+                    ],
+                    actions: [
+                        {
+                            code: taxAction.code,
+                            arguments: [category.rate.toString()],
+                        },
+                    ],
+                },
+            });
+            results.push(result.createAdjustmentSource);
+        }
+        this.log(`Created ${results.length} tax categories`);
+        return results;
+    }
+
     async populateCustomers(count: number = 5): Promise<any> {
         for (let i = 0; i < count; i++) {
             const firstName = faker.name.firstName();
@@ -166,7 +211,12 @@ export class MockDataService {
         });
     }
 
-    async populateProducts(count: number = 5, optionGroupId: string, assets: Asset[]): Promise<any> {
+    async populateProducts(
+        count: number = 5,
+        optionGroupId: string,
+        assets: Asset[],
+        taxCategories: AdjustmentSource.Fragment[],
+    ): Promise<any> {
         for (let i = 0; i < count; i++) {
             const query = CREATE_PRODUCT;
 
@@ -206,7 +256,10 @@ export class MockDataService {
                         optionGroupId,
                     },
                 );
-                const prodWithVariants = await this.makeProductVariant(product.createProduct.id);
+                const prodWithVariants = await this.makeProductVariant(
+                    product.createProduct.id,
+                    taxCategories[0],
+                );
                 const variants = prodWithVariants.generateVariantsForProduct.variants;
                 for (const variant of variants) {
                     const variantEN = variant.translations[0];
@@ -284,10 +337,14 @@ export class MockDataService {
         };
     }
 
-    private async makeProductVariant(productId: string): Promise<GenerateProductVariants.Mutation> {
+    private async makeProductVariant(
+        productId: string,
+        taxCategory: AdjustmentSource.Fragment,
+    ): Promise<GenerateProductVariants.Mutation> {
         const query = GENERATE_PRODUCT_VARIANTS;
         return this.client.query<GenerateProductVariants.Mutation, GenerateProductVariants.Variables>(query, {
             productId,
+            defaultTaxCategoryId: taxCategory.id,
             defaultSku: faker.random.alphaNumeric(5),
             defaultPrice: faker.random.number({
                 min: 100,

+ 2 - 1
server/mock-data/populate.ts

@@ -41,7 +41,8 @@ export async function populate(
     }
     const assets = await mockDataService.populateAssets();
     const optionGroupId = await mockDataService.populateOptions();
-    await mockDataService.populateProducts(options.productCount, optionGroupId, assets);
+    const taxCategories = await mockDataService.populateTaxCategories();
+    await mockDataService.populateProducts(options.productCount, optionGroupId, assets, taxCategories);
     await mockDataService.populateCustomers(options.customerCount);
     await mockDataService.populateFacets();
     return app;

+ 11 - 5
server/src/api/resolvers/product.resolver.ts

@@ -99,13 +99,19 @@ export class ProductResolver {
 
     @Mutation()
     @Allow(Permission.CreateCatalog)
-    @Decode('productId')
+    @Decode('productId', 'defaultTaxCategoryId')
     async generateVariantsForProduct(
         @Ctx() ctx: RequestContext,
         @Args() args: GenerateVariantsForProductMutationArgs,
     ): Promise<Translated<Product>> {
-        const { productId, defaultPrice, defaultSku } = args;
-        await this.productVariantService.generateVariantsForProduct(ctx, productId, defaultPrice, defaultSku);
+        const { productId, defaultTaxCategoryId, defaultPrice, defaultSku } = args;
+        await this.productVariantService.generateVariantsForProduct(
+            ctx,
+            productId,
+            defaultTaxCategoryId,
+            defaultPrice,
+            defaultSku,
+        );
         return assertFound(this.productService.findOne(ctx, productId));
     }
 
@@ -116,7 +122,7 @@ export class ProductResolver {
         @Args() args: UpdateProductVariantsMutationArgs,
     ): Promise<Array<Translated<ProductVariant>>> {
         const { input } = args;
-        return Promise.all(input.map(variant => this.productVariantService.update(variant)));
+        return Promise.all(input.map(variant => this.productVariantService.update(ctx, variant)));
     }
 
     @Mutation()
@@ -140,6 +146,6 @@ export class ProductResolver {
             }),
         );
 
-        return this.productVariantService.addFacetValues(productVariantIds, facetValues);
+        return this.productVariantService.addFacetValues(ctx, productVariantIds, facetValues);
     }
 }

+ 1 - 1
server/src/api/types/product.api.graphql

@@ -17,7 +17,7 @@ type Mutation {
     removeOptionGroupFromProduct(productId: ID!, optionGroupId: ID!): Product!
 
     "Create a set of ProductVariants based on the OptionGroups assigned to the given Product"
-    generateVariantsForProduct(productId: ID!, defaultPrice: Int, defaultSku: String): Product!
+    generateVariantsForProduct(productId: ID!, defaultTaxCategoryId: ID, defaultPrice: Int, defaultSku: String): Product!
 
     "Update existing ProductVariants"
     updateProductVariants(input: [UpdateProductVariantInput!]!): [ProductVariant]!

+ 11 - 0
server/src/entity/adjustment-source/adjustment-source.entity.ts

@@ -3,6 +3,7 @@ import { DeepPartial, ID } from 'shared/shared-types';
 import { Column, Entity, JoinTable, ManyToMany } from 'typeorm';
 
 import { ChannelAware } from '../../common/types/common-types';
+import { I18nError } from '../../i18n/i18n-error';
 import { VendureEntity } from '../base/base.entity';
 import { Channel } from '../channel/channel.entity';
 
@@ -25,6 +26,16 @@ export class AdjustmentSource extends VendureEntity implements ChannelAware {
     @Column('simple-json') conditions: AdjustmentOperation[];
 
     @Column('simple-json') actions: AdjustmentOperation[];
+
+    /**
+     * A shorthand method for getting the tax rate on a TAX type adjustment source.
+     */
+    getTaxCategoryRate(): number {
+        if (this.type !== AdjustmentType.TAX) {
+            throw new I18nError(`error.getTaxCategoryRate-only-valid-for-tax-adjustment-sources`);
+        }
+        return Number(this.actions[0].args[0].value);
+    }
 }
 
 /**

+ 6 - 0
server/src/entity/product-variant/product-variant-price.entity.ts

@@ -1,6 +1,7 @@
 import { DeepPartial } from 'shared/shared-types';
 import { Column, Entity, ManyToOne } from 'typeorm';
 
+import { AdjustmentSource } from '../adjustment-source/adjustment-source.entity';
 import { VendureEntity } from '../base/base.entity';
 import { Channel } from '../channel/channel.entity';
 
@@ -14,6 +15,11 @@ export class ProductVariantPrice extends VendureEntity {
 
     @Column() price: number;
 
+    @Column() priceBeforeTax: number;
+
+    @ManyToOne(type => AdjustmentSource, { eager: true })
+    taxCategory: AdjustmentSource;
+
     @Column() channelId: number;
 
     @ManyToOne(type => ProductVariant, variant => variant.productVariantPrices)

+ 19 - 1
server/src/entity/product-variant/product-variant.entity.ts

@@ -1,4 +1,4 @@
-import { DeepPartial, HasCustomFields } from 'shared/shared-types';
+import { DeepPartial, HasCustomFields, ID } from 'shared/shared-types';
 import { Column, Entity, JoinTable, ManyToMany, ManyToOne, OneToMany } from 'typeorm';
 
 import { LocaleString, Translatable, Translation } from '../../common/types/locale-types';
@@ -21,12 +21,30 @@ export class ProductVariant extends VendureEntity implements Translatable, HasCu
 
     @Column() sku: string;
 
+    /**
+     * A synthetic property which is populated with data from a ProductVariantPrice entity.
+     * It is marked as a @Column() so that changes to it will trigger the afterUpdate subscriber.
+     */
     @Column({
         name: 'lastPriceValue',
         comment: 'Not used - actual price is stored in product_variant_price table',
     })
     price: number;
 
+    /**
+     * A synthetic property which is populated with data from a ProductVariantPrice entity.
+     */
+    priceBeforeTax: number;
+
+    /**
+     * A synthetic property which is populated with data from a ProductVariantPrice entity.
+     */
+    taxCategory: {
+        id: ID;
+        name: string;
+        taxRate: number;
+    };
+
     @OneToMany(type => ProductVariantPrice, price => price.variant, { eager: true })
     productVariantPrices: ProductVariantPrice[];
 

+ 13 - 1
server/src/entity/product-variant/product-variant.graphql

@@ -5,7 +5,9 @@ type ProductVariant implements Node {
     languageCode: LanguageCode!
     sku: String!
     name: String!
+    priceBeforeTax: Int!
     price: Int!
+    taxCategory: ProductTaxCategory!
     options: [ProductOption!]!
     facetValues: [FacetValue!]!
     translations: [ProductVariantTranslation!]!
@@ -19,6 +21,12 @@ type ProductVariantTranslation {
     name: String!
 }
 
+type ProductTaxCategory {
+    id: ID!
+    name: String!
+    taxRate: Float!
+}
+
 input ProductVariantTranslationInput {
     id: ID
     languageCode: LanguageCode!
@@ -28,7 +36,9 @@ input ProductVariantTranslationInput {
 input CreateProductVariantInput {
     translations: [ProductVariantTranslationInput!]!
     sku: String!
-    price: Int!
+    priceBeforeTax: Int
+    price: Int
+    taxCategoryId: ID!
     optionCodes: [String!]
 }
 
@@ -36,5 +46,7 @@ input UpdateProductVariantInput {
     id: ID!
     translations: [ProductVariantTranslationInput!]
     sku: String
+    taxCategoryId: ID
+    priceBeforeTax: Int
     price: Int
 }

+ 24 - 4
server/src/entity/product-variant/product-variant.subscriber.ts

@@ -1,6 +1,7 @@
 import { EntitySubscriberInterface, EventSubscriber, InsertEvent } from 'typeorm';
 
 import { I18nError } from '../../i18n/i18n-error';
+import { AdjustmentSource } from '../adjustment-source/adjustment-source.entity';
 
 import { ProductVariantPrice } from './product-variant-price.entity';
 import { ProductVariant } from './product-variant.entity';
@@ -15,23 +16,42 @@ export class ProductVariantSubscriber implements EntitySubscriberInterface<Produ
     }
 
     async afterInsert(event: InsertEvent<ProductVariant>) {
-        const channelId = event.queryRunner.data.channelId;
+        const { channelId, taxCategoryId } = event.queryRunner.data;
         const price = event.entity.price || 0;
         if (channelId === undefined) {
             throw new I18nError(`error.channel-id-not-set`);
         }
+        const taxCategory = await event.connection.getRepository(AdjustmentSource).findOne(taxCategoryId);
+        if (!taxCategory) {
+            throw new I18nError(`error.tax-category-not-found`, { id: taxCategoryId });
+        }
         const variantPrice = new ProductVariantPrice({ price, channelId });
         variantPrice.variant = event.entity;
+        variantPrice.priceBeforeTax = this.getPriceBeforeTax(price, taxCategory.getTaxCategoryRate());
+        variantPrice.taxCategory = taxCategory;
         await event.manager.save(variantPrice);
     }
 
     async afterUpdate(event: InsertEvent<ProductVariant>) {
-        const prices = await event.connection.getRepository(ProductVariantPrice).find({
+        const variantPrice = await event.connection.getRepository(ProductVariantPrice).findOne({
             where: {
                 variant: event.entity.id,
+                channelId: event.queryRunner.data.channelId,
             },
         });
-        prices[0].price = event.entity.price || 0;
-        await event.manager.save(prices[0]);
+        if (!variantPrice) {
+            throw new I18nError(`error.could-not-find-product-variant-price`);
+        }
+
+        variantPrice.price = event.entity.price || 0;
+        variantPrice.priceBeforeTax = this.getPriceBeforeTax(
+            variantPrice.price,
+            variantPrice.taxCategory.getTaxCategoryRate(),
+        );
+        await event.manager.save(variantPrice);
+    }
+
+    private getPriceBeforeTax(priceAfterTax: number, taxRatePercentage: number): number {
+        return Math.round(priceAfterTax / (1 + taxRatePercentage / 100));
     }
 }

+ 2 - 1
server/src/service/helpers/update-translatable.ts

@@ -19,6 +19,7 @@ export function updateTranslatable<T extends Translatable>(
     return async function saveTranslatable(
         connection: Connection,
         input: TranslatedInput<T> & { id: ID },
+        data?: any,
     ): Promise<T> {
         const existingTranslations = await connection.getRepository(translationType).find({
             where: { base: input.id },
@@ -33,6 +34,6 @@ export function updateTranslatable<T extends Translatable>(
             diff,
         );
         const updatedEntity = patchEntity(entity as any, omit(input, ['translations']));
-        return connection.manager.save(entity);
+        return connection.manager.save(entity, { data });
     };
 }

+ 10 - 0
server/src/service/providers/adjustment-source.service.ts

@@ -86,6 +86,16 @@ export class AdjustmentSourceService {
         return this.activeSources;
     }
 
+    /**
+     * Returns the default tax category.
+     * TODO: currently just returns the first one. There should be a "default" flag.
+     */
+    async getDefaultTaxCategory(): Promise<AdjustmentSource> {
+        const sources = await this.getActiveAdjustmentSources();
+        const taxCategories = sources.filter(s => s.type === AdjustmentType.TAX);
+        return taxCategories[0];
+    }
+
     async createAdjustmentSource(
         ctx: RequestContext,
         input: CreateAdjustmentSourceInput,

+ 32 - 8
server/src/service/providers/product-variant.service.ts

@@ -11,22 +11,23 @@ import { Translated } from '../../common/types/locale-types';
 import { assertFound, idsAreEqual } from '../../common/utils';
 import { FacetValue } from '../../entity/facet-value/facet-value.entity';
 import { ProductOption } from '../../entity/product-option/product-option.entity';
-import { ProductVariantPrice } from '../../entity/product-variant/product-variant-price.entity';
 import { ProductVariantTranslation } from '../../entity/product-variant/product-variant-translation.entity';
 import { ProductVariant } from '../../entity/product-variant/product-variant.entity';
 import { Product } from '../../entity/product/product.entity';
 import { I18nError } from '../../i18n/i18n-error';
-
 import { createTranslatable } from '../helpers/create-translatable';
 import { translateDeep } from '../helpers/translate-entity';
 import { TranslationUpdaterService } from '../helpers/translation-updater.service';
 import { updateTranslatable } from '../helpers/update-translatable';
 
+import { AdjustmentSourceService } from './adjustment-source.service';
+
 @Injectable()
 export class ProductVariantService {
     constructor(
         @InjectConnection() private connection: Connection,
         private translationUpdaterService: TranslationUpdaterService,
+        private adjustmentSourceService: AdjustmentSourceService,
     ) {}
 
     findOne(ctx: RequestContext, productVariantId: ID): Promise<Translated<ProductVariant> | undefined> {
@@ -54,29 +55,35 @@ export class ProductVariantService {
                 variant.options = selectedOptions;
             }
             variant.product = product;
-            const variantPrice = new ProductVariantPrice();
         });
-        return await save(this.connection, input, { channelId: ctx.channelId });
+        return await save(this.connection, input, {
+            channelId: ctx.channelId,
+            taxCategoryId: input.taxCategoryId,
+        });
     }
 
-    async update(input: UpdateProductVariantInput): Promise<Translated<ProductVariant>> {
+    async update(ctx: RequestContext, input: UpdateProductVariantInput): Promise<Translated<ProductVariant>> {
         const save = updateTranslatable(
             ProductVariant,
             ProductVariantTranslation,
             this.translationUpdaterService,
         );
-        await save(this.connection, input);
+        await save(this.connection, input, { channelId: ctx.channelId, taxCategoryId: input.taxCategoryId });
         const variant = await assertFound(
             this.connection.manager.getRepository(ProductVariant).findOne(input.id, {
                 relations: ['options', 'facetValues'],
             }),
         );
-        return translateDeep(variant, DEFAULT_LANGUAGE_CODE, ['options', 'facetValues']);
+        return translateDeep(this.applyChannelPrice(variant, ctx.channelId), DEFAULT_LANGUAGE_CODE, [
+            'options',
+            'facetValues',
+        ]);
     }
 
     async generateVariantsForProduct(
         ctx: RequestContext,
         productId: ID,
+        defaultTaxCategoryId?: string | null,
         defaultPrice?: number | null,
         defaultSku?: string | null,
     ): Promise<Array<Translated<ProductVariant>>> {
@@ -94,6 +101,10 @@ export class ProductVariantService {
             ? generateAllCombinations(product.optionGroups.map(g => g.options))
             : [[]];
 
+        const taxCategoryId =
+            defaultTaxCategoryId ||
+            (await this.adjustmentSourceService.getDefaultTaxCategory()).id.toString();
+
         const variants: ProductVariant[] = [];
         for (const options of optionCombinations) {
             const name = this.createVariantName(productName, options);
@@ -101,6 +112,7 @@ export class ProductVariantService {
                 sku: defaultSku || 'sku-not-set',
                 price: defaultPrice || 0,
                 optionCodes: options.map(o => o.code),
+                taxCategoryId,
                 translations: [
                     {
                         languageCode: ctx.languageCode,
@@ -115,6 +127,7 @@ export class ProductVariantService {
     }
 
     async addFacetValues(
+        ctx: RequestContext,
         productVariantIds: ID[],
         facetValues: FacetValue[],
     ): Promise<Array<Translated<ProductVariant>>> {
@@ -138,7 +151,12 @@ export class ProductVariantService {
             await this.connection.manager.save(variant);
         }
 
-        return variants.map(v => translateDeep(v, DEFAULT_LANGUAGE_CODE, ['options', 'facetValues']));
+        return variants.map(v =>
+            translateDeep(this.applyChannelPrice(v, ctx.channelId), DEFAULT_LANGUAGE_CODE, [
+                'options',
+                'facetValues',
+            ]),
+        );
     }
 
     /**
@@ -150,6 +168,12 @@ export class ProductVariantService {
             throw new I18nError(`error.no-price-found-for-channel`);
         }
         variant.price = channelPrice.price;
+        variant.priceBeforeTax = channelPrice.priceBeforeTax;
+        variant.taxCategory = {
+            id: channelPrice.taxCategory.id,
+            name: channelPrice.taxCategory.name,
+            taxRate: channelPrice.taxCategory.getTaxCategoryRate() || 0,
+        };
         return variant;
     }
 

+ 73 - 5
shared/generated-types.ts

@@ -53,6 +53,7 @@ export interface Query {
     facets: FacetList;
     facet?: Facet | null;
     order?: Order | null;
+    activeOrder?: Order | null;
     orders: OrderList;
     productOptionGroups: ProductOptionGroup[];
     productOptionGroup?: ProductOptionGroup | null;
@@ -263,6 +264,7 @@ export interface Order extends Node {
     customer?: Customer | null;
     items: OrderItem[];
     adjustments: Adjustment[];
+    totalPrice: number;
 }
 
 export interface OrderItem extends Node {
@@ -270,9 +272,11 @@ export interface OrderItem extends Node {
     createdAt: DateTime;
     updatedAt: DateTime;
     productVariant: ProductVariant;
+    adjustments: Adjustment[];
+    featuredAsset?: Asset | null;
     unitPrice: number;
     quantity: number;
-    adjustments: Adjustment[];
+    totalPrice: number;
     order: Order;
 }
 
@@ -283,13 +287,21 @@ export interface ProductVariant extends Node {
     languageCode: LanguageCode;
     sku: string;
     name: string;
+    priceBeforeTax: number;
     price: number;
+    taxCategory: ProductTaxCategory;
     options: ProductOption[];
     facetValues: FacetValue[];
     translations: ProductVariantTranslation[];
     customFields?: Json | null;
 }
 
+export interface ProductTaxCategory {
+    id: string;
+    name: string;
+    taxRate: number;
+}
+
 export interface ProductOption extends Node {
     id: string;
     createdAt: DateTime;
@@ -861,6 +873,8 @@ export interface UpdateProductVariantInput {
     id: string;
     translations?: ProductVariantTranslationInput[] | null;
     sku?: string | null;
+    taxCategoryId?: string | null;
+    priceBeforeTax?: number | null;
     price?: number | null;
     customFields?: Json | null;
 }
@@ -888,7 +902,9 @@ export interface UpdateRoleInput {
 export interface CreateProductVariantInput {
     translations: ProductVariantTranslationInput[];
     sku: string;
-    price: number;
+    priceBeforeTax?: number | null;
+    price?: number | null;
+    taxCategoryId: string;
     optionCodes?: string[] | null;
     customFields?: Json | null;
 }
@@ -1057,6 +1073,7 @@ export interface RemoveOptionGroupFromProductMutationArgs {
 }
 export interface GenerateVariantsForProductMutationArgs {
     productId: string;
+    defaultTaxCategoryId?: string | null;
     defaultPrice?: number | null;
     defaultSku?: string | null;
 }
@@ -1328,6 +1345,7 @@ export namespace QueryResolvers {
         facets?: FacetsResolver<FacetList, any, Context>;
         facet?: FacetResolver<Facet | null, any, Context>;
         order?: OrderResolver<Order | null, any, Context>;
+        activeOrder?: ActiveOrderResolver<Order | null, any, Context>;
         orders?: OrdersResolver<OrderList, any, Context>;
         productOptionGroups?: ProductOptionGroupsResolver<ProductOptionGroup[], any, Context>;
         productOptionGroup?: ProductOptionGroupResolver<ProductOptionGroup | null, any, Context>;
@@ -1468,6 +1486,11 @@ export namespace QueryResolvers {
         id: string;
     }
 
+    export type ActiveOrderResolver<R = Order | null, Parent = any, Context = any> = Resolver<
+        R,
+        Parent,
+        Context
+    >;
     export type OrdersResolver<R = OrderList, Parent = any, Context = any> = Resolver<
         R,
         Parent,
@@ -2083,6 +2106,7 @@ export namespace OrderResolvers {
         customer?: CustomerResolver<Customer | null, any, Context>;
         items?: ItemsResolver<OrderItem[], any, Context>;
         adjustments?: AdjustmentsResolver<Adjustment[], any, Context>;
+        totalPrice?: TotalPriceResolver<number, any, Context>;
     }
 
     export type IdResolver<R = string, Parent = any, Context = any> = Resolver<R, Parent, Context>;
@@ -2100,6 +2124,7 @@ export namespace OrderResolvers {
         Parent,
         Context
     >;
+    export type TotalPriceResolver<R = number, Parent = any, Context = any> = Resolver<R, Parent, Context>;
 }
 
 export namespace OrderItemResolvers {
@@ -2108,9 +2133,11 @@ export namespace OrderItemResolvers {
         createdAt?: CreatedAtResolver<DateTime, any, Context>;
         updatedAt?: UpdatedAtResolver<DateTime, any, Context>;
         productVariant?: ProductVariantResolver<ProductVariant, any, Context>;
+        adjustments?: AdjustmentsResolver<Adjustment[], any, Context>;
+        featuredAsset?: FeaturedAssetResolver<Asset | null, any, Context>;
         unitPrice?: UnitPriceResolver<number, any, Context>;
         quantity?: QuantityResolver<number, any, Context>;
-        adjustments?: AdjustmentsResolver<Adjustment[], any, Context>;
+        totalPrice?: TotalPriceResolver<number, any, Context>;
         order?: OrderResolver<Order, any, Context>;
     }
 
@@ -2122,13 +2149,19 @@ export namespace OrderItemResolvers {
         Parent,
         Context
     >;
-    export type UnitPriceResolver<R = number, Parent = any, Context = any> = Resolver<R, Parent, Context>;
-    export type QuantityResolver<R = number, Parent = any, Context = any> = Resolver<R, Parent, Context>;
     export type AdjustmentsResolver<R = Adjustment[], Parent = any, Context = any> = Resolver<
         R,
         Parent,
         Context
     >;
+    export type FeaturedAssetResolver<R = Asset | null, Parent = any, Context = any> = Resolver<
+        R,
+        Parent,
+        Context
+    >;
+    export type UnitPriceResolver<R = number, Parent = any, Context = any> = Resolver<R, Parent, Context>;
+    export type QuantityResolver<R = number, Parent = any, Context = any> = Resolver<R, Parent, Context>;
+    export type TotalPriceResolver<R = number, Parent = any, Context = any> = Resolver<R, Parent, Context>;
     export type OrderResolver<R = Order, Parent = any, Context = any> = Resolver<R, Parent, Context>;
 }
 
@@ -2140,7 +2173,9 @@ export namespace ProductVariantResolvers {
         languageCode?: LanguageCodeResolver<LanguageCode, any, Context>;
         sku?: SkuResolver<string, any, Context>;
         name?: NameResolver<string, any, Context>;
+        priceBeforeTax?: PriceBeforeTaxResolver<number, any, Context>;
         price?: PriceResolver<number, any, Context>;
+        taxCategory?: TaxCategoryResolver<ProductTaxCategory, any, Context>;
         options?: OptionsResolver<ProductOption[], any, Context>;
         facetValues?: FacetValuesResolver<FacetValue[], any, Context>;
         translations?: TranslationsResolver<ProductVariantTranslation[], any, Context>;
@@ -2157,7 +2192,17 @@ export namespace ProductVariantResolvers {
     >;
     export type SkuResolver<R = string, Parent = any, Context = any> = Resolver<R, Parent, Context>;
     export type NameResolver<R = string, Parent = any, Context = any> = Resolver<R, Parent, Context>;
+    export type PriceBeforeTaxResolver<R = number, Parent = any, Context = any> = Resolver<
+        R,
+        Parent,
+        Context
+    >;
     export type PriceResolver<R = number, Parent = any, Context = any> = Resolver<R, Parent, Context>;
+    export type TaxCategoryResolver<R = ProductTaxCategory, Parent = any, Context = any> = Resolver<
+        R,
+        Parent,
+        Context
+    >;
     export type OptionsResolver<R = ProductOption[], Parent = any, Context = any> = Resolver<
         R,
         Parent,
@@ -2180,6 +2225,18 @@ export namespace ProductVariantResolvers {
     >;
 }
 
+export namespace ProductTaxCategoryResolvers {
+    export interface Resolvers<Context = any> {
+        id?: IdResolver<string, any, Context>;
+        name?: NameResolver<string, any, Context>;
+        taxRate?: TaxRateResolver<number, any, Context>;
+    }
+
+    export type IdResolver<R = string, Parent = any, Context = any> = Resolver<R, Parent, Context>;
+    export type NameResolver<R = string, Parent = any, Context = any> = Resolver<R, Parent, Context>;
+    export type TaxRateResolver<R = number, Parent = any, Context = any> = Resolver<R, Parent, Context>;
+}
+
 export namespace ProductOptionResolvers {
     export interface Resolvers<Context = any> {
         id?: IdResolver<string, any, Context>;
@@ -2806,6 +2863,7 @@ export namespace MutationResolvers {
     >;
     export interface GenerateVariantsForProductArgs {
         productId: string;
+        defaultTaxCategoryId?: string | null;
         defaultPrice?: number | null;
         defaultSku?: string | null;
     }
@@ -3400,6 +3458,7 @@ export namespace CreateProduct {
 export namespace GenerateProductVariants {
     export type Variables = {
         productId: string;
+        defaultTaxCategoryId?: string | null;
         defaultPrice?: number | null;
         defaultSku?: string | null;
     };
@@ -3781,12 +3840,21 @@ export namespace ProductVariant {
         languageCode: LanguageCode;
         name: string;
         price: number;
+        priceBeforeTax: number;
+        taxCategory: TaxCategory;
         sku: string;
         options: Options[];
         facetValues: FacetValues[];
         translations: Translations[];
     };
 
+    export type TaxCategory = {
+        __typename?: 'ProductTaxCategory';
+        id: string;
+        name: string;
+        taxRate: number;
+    };
+
     export type Options = {
         __typename?: 'ProductOption';
         id: string;

+ 4 - 0
yarn.lock

@@ -50,6 +50,10 @@
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/@types/is-glob/-/is-glob-4.0.0.tgz#fb8a2bff539025d4dcd6d5efe7689e03341b876d"
 
+"@types/node@^10.11.5":
+  version "10.11.5"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-10.11.5.tgz#6489ebda4452592d3fd37aefa57eedc01ed13378"
+
 "@types/prettier@1.13.2":
   version "1.13.2"
   resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-1.13.2.tgz#ffe96278e712a8d4e467e367a338b05e22872646"

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff