Browse Source

refactor: Create simple TaxCategory entity

Michael Bromley 7 years ago
parent
commit
8b51f1feeb
39 changed files with 511 additions and 276 deletions
  1. 5 4
      admin-ui/src/app/catalog/components/product-detail/product-detail.component.ts
  2. 2 12
      admin-ui/src/app/catalog/components/product-variants-list/product-variants-list.component.html
  3. 2 42
      admin-ui/src/app/catalog/components/product-variants-list/product-variants-list.component.ts
  4. 0 2
      admin-ui/src/app/data/definitions/product-definitions.ts
  5. 43 0
      admin-ui/src/app/data/definitions/settings-definitions.ts
  6. 0 16
      admin-ui/src/app/data/providers/adjustment-source-data.service.ts
  7. 4 4
      admin-ui/src/app/data/providers/data.service.mock.ts
  8. 1 3
      admin-ui/src/app/data/providers/product-data.service.ts
  9. 38 0
      admin-ui/src/app/data/providers/settings-data.service.ts
  10. 8 42
      admin-ui/src/app/settings/components/tax-category-detail/tax-category-detail.component.ts
  11. 1 6
      admin-ui/src/app/settings/components/tax-category-list/tax-category-list.component.html
  12. 9 13
      admin-ui/src/app/settings/components/tax-category-list/tax-category-list.component.ts
  13. 0 0
      schema.json
  14. 0 2
      server/e2e/product.e2e-spec.ts
  15. 19 30
      server/mock-data/mock-data.service.ts
  16. 2 0
      server/src/api/api.module.ts
  17. 45 0
      server/src/api/resolvers/tax-category.resolver.ts
  18. 12 0
      server/src/api/types/tax-category.api.graphql
  19. 16 0
      server/src/common/types/adjustment-source.ts
  20. 0 10
      server/src/entity/adjustment-source/adjustment-source.entity.ts
  21. 6 1
      server/src/entity/entities.ts
  22. 1 1
      server/src/entity/order-item/order-item.entity.ts
  23. 1 1
      server/src/entity/order/order.entity.ts
  24. 0 5
      server/src/entity/product-variant/product-variant-price.entity.ts
  25. 3 13
      server/src/entity/product-variant/product-variant.entity.ts
  26. 1 10
      server/src/entity/product-variant/product-variant.graphql
  27. 1 27
      server/src/entity/product-variant/product-variant.subscriber.ts
  28. 34 0
      server/src/entity/promotion/promotion.entity.ts
  29. 13 0
      server/src/entity/tax-category/tax-category.entity.ts
  30. 14 0
      server/src/entity/tax-category/tax-category.graphql
  31. 38 0
      server/src/entity/tax-rate/tax-rate.entity.ts
  32. 2 1
      server/src/service/helpers/apply-adjustments.ts
  33. 5 1
      server/src/service/helpers/update-translatable.ts
  34. 3 3
      server/src/service/providers/order.service.ts
  35. 12 7
      server/src/service/providers/product-variant.service.ts
  36. 1 0
      server/src/service/providers/product.service.ts
  37. 42 0
      server/src/service/providers/tax-category.service.ts
  38. 2 0
      server/src/service/service.module.ts
  39. 125 20
      shared/generated-types.ts

+ 5 - 4
admin-ui/src/app/catalog/components/product-detail/product-detail.component.ts

@@ -7,7 +7,9 @@ import {
     AdjustmentSource,
     CreateProductInput,
     LanguageCode,
+    ProductVariant,
     ProductWithVariants,
+    TaxCategory,
     UpdateProductInput,
     UpdateProductVariantInput,
 } from 'shared/generated-types';
@@ -34,7 +36,7 @@ export class ProductDetailComponent extends BaseDetailComponent<ProductWithVaria
     implements OnInit, OnDestroy {
     product$: Observable<ProductWithVariants.Fragment>;
     variants$: Observable<ProductWithVariants.Variants[]>;
-    taxCategories$: Observable<AdjustmentSource.Fragment[]>;
+    taxCategories$: Observable<TaxCategory[]>;
     customFields: CustomFieldConfig[];
     customVariantFields: CustomFieldConfig[];
     productForm: FormGroup;
@@ -69,9 +71,9 @@ export class ProductDetailComponent extends BaseDetailComponent<ProductWithVaria
         this.init();
         this.product$ = this.entity$;
         this.variants$ = this.product$.pipe(map(product => product.variants));
-        this.taxCategories$ = this.dataService.adjustmentSource
+        this.taxCategories$ = this.dataService.settings
             .getTaxCategories()
-            .mapSingle(data => data.adjustmentSources.items);
+            .mapSingle(data => data.taxCategories);
     }
 
     ngOnDestroy() {
@@ -245,7 +247,6 @@ export class ProductDetailComponent extends BaseDetailComponent<ProductWithVaria
                     sku: variant.sku,
                     name: variantTranslation ? variantTranslation.name : '',
                     price: variant.price,
-                    priceBeforeTax: variant.priceBeforeTax,
                     taxCategoryId: variant.taxCategory.id,
                 };
 

+ 2 - 12
admin-ui/src/app/catalog/components/product-variants-list/product-variants-list.component.html

@@ -40,20 +40,11 @@
                 </div>
             </div>
             <div class="pricing">
-                <div class="price-before-tax">
-                    <clr-input-container>
-                        <label>{{ 'catalog.price-before-tax' | translate }}</label>
-                        <vdr-currency-input clrInput
-                                            [formControl]="formArray.get([i, 'priceBeforeTax'])"
-                                            (input)="setPrice(i)"></vdr-currency-input>
-                    </clr-input-container>
-                </div>
                 <div class="tax-category">
                     <clr-select-container>
                         <label>{{ 'catalog.tax-category' | translate }}</label>
                         <select clrSelect name="options"
-                                [formControl]="formArray.get([i, 'taxCategoryId'])"
-                                (change)="setPrice(i)">
+                                [formControl]="formArray.get([i, 'taxCategoryId'])">
                             <option *ngFor="let taxCategory of taxCategories"
                                     [value]="taxCategory.id">{{ taxCategory.name }}</option>
                         </select>
@@ -63,8 +54,7 @@
                     <clr-input-container>
                         <label>{{ 'catalog.price' | translate }}</label>
                         <vdr-currency-input clrInput
-                                            [formControl]="formArray.get([i, 'price'])"
-                                            (input)="setPreTaxPrice(i)"></vdr-currency-input>
+                                            [formControl]="formArray.get([i, 'price'])"></vdr-currency-input>
                     </clr-input-container>
                 </div>
             </div>

+ 2 - 42
admin-ui/src/app/catalog/components/product-variants-list/product-variants-list.component.ts

@@ -1,6 +1,6 @@
 import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
 import { FormArray, FormControl } from '@angular/forms';
-import { AdjustmentSource, ProductWithVariants } from 'shared/generated-types';
+import { AdjustmentSource, ProductWithVariants, TaxCategory } from 'shared/generated-types';
 
 @Component({
     selector: 'vdr-product-variants-list',
@@ -11,7 +11,7 @@ import { AdjustmentSource, ProductWithVariants } from 'shared/generated-types';
 export class ProductVariantsListComponent {
     @Input('productVariantsFormArray') formArray: FormArray;
     @Input() variants: ProductWithVariants.Variants[];
-    @Input() taxCategories: AdjustmentSource.Fragment[];
+    @Input() taxCategories: TaxCategory[];
     selectedVariantIds: string[] = [];
 
     areAllSelected(): boolean {
@@ -38,44 +38,4 @@ export class ProductVariantsListComponent {
     isVariantSelected(variantId: string): boolean {
         return -1 < this.selectedVariantIds.indexOf(variantId);
     }
-
-    /**
-     * Set the priceBeforeTax value whenever the price is changed based on the current taxRate.
-     */
-    setPreTaxPrice(index: number) {
-        const { preTaxPriceControl, postTaxPriceControl, taxRate } = this.getPriceControlsAndTaxRate(index);
-        preTaxPriceControl.setValue(Math.round(postTaxPriceControl.value / (1 + taxRate / 100)));
-    }
-
-    /**
-     * Set the price (including tax) value whenever the priceBeforeTax or the taxRate is changed.
-     */
-    setPrice(index: number) {
-        const { preTaxPriceControl, postTaxPriceControl, taxRate } = this.getPriceControlsAndTaxRate(index);
-        postTaxPriceControl.setValue(Math.round(preTaxPriceControl.value * (1 + taxRate / 100)));
-    }
-
-    private getPriceControlsAndTaxRate(
-        index: number,
-    ): {
-        preTaxPriceControl: FormControl;
-        postTaxPriceControl: FormControl;
-        taxRate: number;
-    } {
-        const preTaxPriceControl = this.formArray.get([index, 'priceBeforeTax']);
-        const postTaxPriceControl = this.formArray.get([index, 'price']);
-        const taxCategoryIdControl = this.formArray.get([index, 'taxCategoryId']);
-        if (preTaxPriceControl && postTaxPriceControl && taxCategoryIdControl) {
-            const taxCategory = this.taxCategories.find(tc => tc.id === taxCategoryIdControl.value);
-            if (taxCategory) {
-                const taxRate = Number(taxCategory.actions[0].args[0].value);
-                return {
-                    preTaxPriceControl: preTaxPriceControl as FormControl,
-                    postTaxPriceControl: postTaxPriceControl as FormControl,
-                    taxRate,
-                };
-            }
-        }
-        throw new Error(`Could not find the corresponding form controls.`);
-    }
 }

+ 0 - 2
admin-ui/src/app/data/definitions/product-definitions.ts

@@ -18,11 +18,9 @@ export const PRODUCT_VARIANT_FRAGMENT = gql`
         languageCode
         name
         price
-        priceBeforeTax
         taxCategory {
             id
             name
-            taxRate
         }
         sku
         options {

+ 43 - 0
admin-ui/src/app/data/definitions/settings-definitions.ts

@@ -112,3 +112,46 @@ export const REMOVE_MEMBERS_FROM_ZONE = gql`
     }
     ${ZONE_FRAGMENT}
 `;
+
+export const TAX_CATEGORY_FRAGMENT = gql`
+    fragment TaxCategory on TaxCategory {
+        id
+        name
+    }
+`;
+
+export const GET_TAX_CATEGORIES = gql`
+    query GetTaxCategories {
+        taxCategories {
+            ...TaxCategory
+        }
+    }
+    ${TAX_CATEGORY_FRAGMENT}
+`;
+
+export const GET_TAX_CATEGORY = gql`
+    query GetTaxCategory($id: ID!) {
+        taxCategory(id: $id) {
+            ...TaxCategory
+        }
+    }
+    ${TAX_CATEGORY_FRAGMENT}
+`;
+
+export const CREATE_TAX_CATEGORY = gql`
+    mutation CreateTaxCategory($input: CreateTaxCategoryInput!) {
+        createTaxCategory(input: $input) {
+            ...TaxCategory
+        }
+    }
+    ${TAX_CATEGORY_FRAGMENT}
+`;
+
+export const UPDATE_TAX_CATEGORY = gql`
+    mutation UpdateTaxCategory($input: UpdateTaxCategoryInput!) {
+        updateTaxCategory(input: $input) {
+            ...TaxCategory
+        }
+    }
+    ${TAX_CATEGORY_FRAGMENT}
+`;

+ 0 - 16
admin-ui/src/app/data/providers/adjustment-source-data.service.ts

@@ -30,14 +30,6 @@ export class AdjustmentSourceDataService {
         return this.getAdjustmentSource(AdjustmentType.PROMOTION, id);
     }
 
-    getTaxCategories(take: number = 10, skip: number = 0) {
-        return this.getAdjustmentSourceList(AdjustmentType.TAX, take, skip);
-    }
-
-    getTaxCategory(id: string) {
-        return this.getAdjustmentSource(AdjustmentType.TAX, id);
-    }
-
     getAdjustmentOperations(type: AdjustmentType) {
         return this.baseDataService.query<GetAdjustmentOperations.Query, GetAdjustmentOperations.Variables>(
             GET_ADJUSTMENT_OPERATIONS,
@@ -55,14 +47,6 @@ export class AdjustmentSourceDataService {
         return this.updateAdjustmentSource(input);
     }
 
-    createTaxCategory(input: CreateAdjustmentSourceInput) {
-        return this.createAdjustmentSource(input);
-    }
-
-    updateTaxCategory(input: UpdateAdjustmentSourceInput) {
-        return this.updateAdjustmentSource(input);
-    }
-
     private getAdjustmentSourceList(type: AdjustmentType, take: number, skip: number) {
         return this.baseDataService.query<GetAdjustmentSourceList.Query, GetAdjustmentSourceList.Variables>(
             GET_ADJUSTMENT_SOURCE_LIST,

+ 4 - 4
admin-ui/src/app/data/providers/data.service.mock.ts

@@ -33,13 +33,9 @@ export class MockDataService implements DataServiceMock {
     adjustmentSource = {
         getPromotions: spyQueryResult('getPromotions'),
         getPromotion: spyQueryResult('getPromotion'),
-        getTaxCategories: spyQueryResult('getTaxCategories'),
-        getTaxCategory: spyQueryResult('getTaxCategory'),
         getAdjustmentOperations: spyQueryResult('getAdjustmentOperations'),
         createPromotion: spyObservable('createPromotion'),
         updatePromotion: spyObservable('updatePromotion'),
-        createTaxCategory: spyObservable('createTaxCategory'),
-        updateTaxCategory: spyObservable('updateTaxCategory'),
     };
     administrator = {
         getAdministrators: spyQueryResult('getAdministrators'),
@@ -103,5 +99,9 @@ export class MockDataService implements DataServiceMock {
         updateZone: spyObservable('updateZone'),
         addMembersToZone: spyObservable('addMembersToZone'),
         removeMembersFromZone: spyObservable('removeMembersFromZone'),
+        getTaxCategories: spyQueryResult('getTaxCategories'),
+        getTaxCategory: spyQueryResult('getTaxCategory'),
+        createTaxCategory: spyObservable('createTaxCategory'),
+        updateTaxCategory: spyObservable('updateTaxCategory'),
     };
 }

+ 1 - 3
admin-ui/src/app/data/providers/product-data.service.ts

@@ -90,9 +90,7 @@ export class ProductDataService {
 
     updateProductVariants(variants: UpdateProductVariantInput[]) {
         const input: UpdateProductVariants.Variables = {
-            input: variants.map(
-                pick(['id', 'translations', 'sku', 'price', 'priceBeforeTax', 'taxCategoryId']),
-            ),
+            input: variants.map(pick(['id', 'translations', 'sku', 'price', 'taxCategoryId'])),
         };
         return this.baseDataService.mutate<UpdateProductVariants.Mutation, UpdateProductVariants.Variables>(
             UPDATE_PRODUCT_VARIANTS,

+ 38 - 0
admin-ui/src/app/data/providers/settings-data.service.ts

@@ -2,15 +2,21 @@ import {
     AddMembersToZone,
     CreateCountry,
     CreateCountryInput,
+    CreateTaxCategory,
+    CreateTaxCategoryInput,
     CreateZone,
     CreateZoneInput,
     GetCountry,
     GetCountryList,
+    GetTaxCategories,
+    GetTaxCategory,
     GetZone,
     GetZones,
     RemoveMembersFromZone,
     UpdateCountry,
     UpdateCountryInput,
+    UpdateTaxCategory,
+    UpdateTaxCategoryInput,
     UpdateZone,
     UpdateZoneInput,
 } from 'shared/generated-types';
@@ -18,12 +24,16 @@ import {
 import {
     ADD_MEMBERS_TO_ZONE,
     CREATE_COUNTRY,
+    CREATE_TAX_CATEGORY,
     CREATE_ZONE,
     GET_COUNTRY,
     GET_COUNTRY_LIST,
+    GET_TAX_CATEGORIES,
+    GET_TAX_CATEGORY,
     GET_ZONES,
     REMOVE_MEMBERS_FROM_ZONE,
     UPDATE_COUNTRY,
+    UPDATE_TAX_CATEGORY,
     UPDATE_ZONE,
 } from '../definitions/settings-definitions';
 
@@ -96,4 +106,32 @@ export class SettingsDataService {
             },
         );
     }
+
+    getTaxCategories() {
+        return this.baseDataService.query<GetTaxCategories.Query>(GET_TAX_CATEGORIES);
+    }
+
+    getTaxCategory(id: string) {
+        return this.baseDataService.query<GetTaxCategory.Query, GetTaxCategory.Variables>(GET_TAX_CATEGORY, {
+            id,
+        });
+    }
+
+    createTaxCategory(input: CreateTaxCategoryInput) {
+        return this.baseDataService.mutate<CreateTaxCategory.Mutation, CreateTaxCategory.Variables>(
+            CREATE_TAX_CATEGORY,
+            {
+                input,
+            },
+        );
+    }
+
+    updateTaxCategory(input: UpdateTaxCategoryInput) {
+        return this.baseDataService.mutate<UpdateTaxCategory.Mutation, UpdateTaxCategory.Variables>(
+            UPDATE_TAX_CATEGORY,
+            {
+                input,
+            },
+        );
+    }
 }

+ 8 - 42
admin-ui/src/app/settings/components/tax-category-detail/tax-category-detail.component.ts

@@ -72,15 +72,15 @@ export class TaxCategoryDetailComponent extends BaseDetailComponent<AdjustmentSo
             return;
         }
         const formValue = this.taxCategoryForm.value;
-        const input = this.createAdjustmentSourceInput(formValue.name, formValue.taxRate);
-        this.dataService.adjustmentSource.createTaxCategory(input).subscribe(
+        const input = { name: formValue.name };
+        this.dataService.settings.createTaxCategory(input).subscribe(
             data => {
                 this.notificationService.success(_('common.notify-create-success'), {
                     entity: 'TaxCategory',
                 });
                 this.taxCategoryForm.markAsPristine();
                 this.changeDetector.markForCheck();
-                this.router.navigate(['../', data.createAdjustmentSource.id], { relativeTo: this.route });
+                this.router.navigate(['../', data.createTaxCategory.id], { relativeTo: this.route });
             },
             err => {
                 this.notificationService.error(_('common.notify-create-error'), {
@@ -99,12 +99,11 @@ export class TaxCategoryDetailComponent extends BaseDetailComponent<AdjustmentSo
             .pipe(
                 take(1),
                 mergeMap(taxCategory => {
-                    const input = this.createAdjustmentSourceInput(
-                        formValue.name,
-                        formValue.taxRate,
-                        taxCategory.id,
-                    );
-                    return this.dataService.adjustmentSource.updatePromotion(input);
+                    const input = {
+                        id: taxCategory.id,
+                        nadme: formValue.name,
+                    };
+                    return this.dataService.settings.updateTaxCategory(input);
                 }),
             )
             .subscribe(
@@ -123,39 +122,6 @@ export class TaxCategoryDetailComponent extends BaseDetailComponent<AdjustmentSo
             );
     }
 
-    private createAdjustmentSourceInput(name: string, taxRate: number): CreateAdjustmentSourceInput;
-    private createAdjustmentSourceInput(
-        name: string,
-        taxRate: number,
-        id: string,
-    ): UpdateAdjustmentSourceInput;
-    private createAdjustmentSourceInput(
-        name: string,
-        taxRate: number,
-        id?: string,
-    ): CreateAdjustmentSourceInput | UpdateAdjustmentSourceInput {
-        const input = {
-            name,
-            conditions: [
-                {
-                    code: this.taxCondition.code,
-                    arguments: [],
-                },
-            ],
-            actions: [
-                {
-                    code: this.taxAction.code,
-                    arguments: [taxRate.toString()],
-                },
-            ],
-        };
-        if (id !== undefined) {
-            return { ...input, id };
-        } else {
-            return { ...input, type: AdjustmentType.TAX, enabled: true } as CreateAdjustmentSourceInput;
-        }
-    }
-
     /**
      * Update the form values when the entity changes.
      */

+ 1 - 6
admin-ui/src/app/settings/components/tax-category-list/tax-category-list.component.html

@@ -7,12 +7,7 @@
     </vdr-ab-right>
 </vdr-action-bar>
 
-<vdr-data-table [items]="items$ | async"
-                [itemsPerPage]="itemsPerPage$ | async"
-                [totalItems]="totalItems$ | async"
-                [currentPage]="currentPage$ | async"
-                (pageChange)="setPageNumber($event)"
-                (itemsPerPageChange)="setItemsPerPage($event)">
+<vdr-data-table [items]="taxCategories$ | async">
     <vdr-dt-column>{{ 'common.ID' | translate }}</vdr-dt-column>
     <vdr-dt-column>{{ 'common.name' | translate }}</vdr-dt-column>
     <vdr-dt-column>{{ 'settings.tax-rate' | translate }}</vdr-dt-column>

+ 9 - 13
admin-ui/src/app/settings/components/tax-category-list/tax-category-list.component.ts

@@ -1,8 +1,7 @@
 import { ChangeDetectionStrategy, Component } from '@angular/core';
-import { ActivatedRoute, Router } from '@angular/router';
-import { GetAdjustmentSourceList } from 'shared/generated-types';
+import { Observable } from 'rxjs';
+import { TaxCategory } from 'shared/generated-types';
 
-import { BaseListComponent } from '../../../common/base-list.component';
 import { DataService } from '../../../data/providers/data.service';
 
 @Component({
@@ -11,15 +10,12 @@ import { DataService } from '../../../data/providers/data.service';
     styleUrls: ['./tax-category-list.component.scss'],
     changeDetection: ChangeDetectionStrategy.OnPush,
 })
-export class TaxCategoryListComponent extends BaseListComponent<
-    GetAdjustmentSourceList.Query,
-    GetAdjustmentSourceList.Items
-> {
-    constructor(private dataService: DataService, router: Router, route: ActivatedRoute) {
-        super(router, route);
-        super.setQueryFn(
-            (...args: any[]) => this.dataService.adjustmentSource.getTaxCategories(...args),
-            data => data.adjustmentSources,
-        );
+export class TaxCategoryListComponent {
+    taxCategories$: Observable<TaxCategory.Fragment[]>;
+
+    constructor(private dataService: DataService) {
+        this.taxCategories$ = this.dataService.settings
+            .getTaxCategories()
+            .mapStream(data => data.taxCategories);
     }
 }

File diff suppressed because it is too large
+ 0 - 0
schema.json


+ 0 - 2
server/e2e/product.e2e-spec.ts

@@ -145,7 +145,6 @@ describe('Product resolver', () => {
                 fail('Product not found');
                 return;
             }
-            expect(result.product.variants[0].priceBeforeTax).toBe(621);
             expect(result.product.variants[0].price).toBe(745);
             expect(result.product.variants[0].taxCategory).toEqual({
                 id: 'T_1',
@@ -496,7 +495,6 @@ describe('Product resolver', () => {
                 }
                 expect(updatedVariant.price).toBe(105);
                 expect(updatedVariant.taxCategory.id).toBe('T_2');
-                expect(updatedVariant.priceBeforeTax).toBe(100);
             });
 
             it('updateProductVariants throws with an invalid variant id', async () => {

+ 19 - 30
server/mock-data/mock-data.service.ts

@@ -40,6 +40,7 @@ import { Channel } from '../src/entity/channel/channel.entity';
 import { Customer } from '../src/entity/customer/customer.entity';
 
 import { SimpleGraphQLClient } from './simple-graphql-client';
+import TaxCategory = ProductVariant.TaxCategory;
 
 // tslint:disable:no-console
 /**
@@ -142,38 +143,26 @@ export class MockDataService {
     }
 
     async populateTaxCategories() {
-        const taxCategories = [
-            { name: 'Standard Tax', rate: 20 },
-            { name: 'Reduced Tax', rate: 5 },
-            { name: 'Zero Tax', rate: 0 },
-        ];
+        const taxCategories = [{ name: 'Standard Tax' }, { name: 'Reduced Tax' }, { name: 'Zero Tax' }];
 
-        const results: AdjustmentSource.Fragment[] = [];
+        const results: TaxCategory[] = [];
 
         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()],
-                        },
-                    ],
+            const result = await this.client.query(
+                gql`
+                    mutation($input: CreateTaxCategoryInput!) {
+                        createTaxCategory(input: $input) {
+                            id
+                        }
+                    }
+                `,
+                {
+                    input: {
+                        name: category.name,
+                    },
                 },
-            });
-            results.push(result.createAdjustmentSource);
+            );
+            results.push(result.createTaxCategory);
         }
         this.log(`Created ${results.length} tax categories`);
         return results;
@@ -253,7 +242,7 @@ export class MockDataService {
         count: number = 5,
         optionGroupId: string,
         assets: Asset[],
-        taxCategories: AdjustmentSource.Fragment[],
+        taxCategories: TaxCategory[],
     ): Promise<any> {
         for (let i = 0; i < count; i++) {
             const query = CREATE_PRODUCT;
@@ -377,7 +366,7 @@ export class MockDataService {
 
     private async makeProductVariant(
         productId: string,
-        taxCategory: AdjustmentSource.Fragment,
+        taxCategory: TaxCategory,
     ): Promise<GenerateProductVariants.Mutation> {
         const query = GENERATE_PRODUCT_VARIANTS;
         return this.client.query<GenerateProductVariants.Mutation, GenerateProductVariants.Variables>(query, {

+ 2 - 0
server/src/api/api.module.ts

@@ -26,6 +26,7 @@ import { OrderResolver } from './resolvers/order.resolver';
 import { ProductOptionResolver } from './resolvers/product-option.resolver';
 import { ProductResolver } from './resolvers/product.resolver';
 import { RoleResolver } from './resolvers/role.resolver';
+import { TaxCategoryResolver } from './resolvers/tax-category.resolver';
 import { ZoneResolver } from './resolvers/zone.resolver';
 
 const exportedProviders = [
@@ -43,6 +44,7 @@ const exportedProviders = [
     ProductOptionResolver,
     ProductResolver,
     RoleResolver,
+    TaxCategoryResolver,
     ZoneResolver,
 ];
 

+ 45 - 0
server/src/api/resolvers/tax-category.resolver.ts

@@ -0,0 +1,45 @@
+import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
+import {
+    CreateTaxCategoryMutationArgs,
+    Permission,
+    TaxCategoryQueryArgs,
+    UpdateTaxCategoryMutationArgs,
+} from 'shared/generated-types';
+
+import { TaxCategory } from '../../entity/tax-category/tax-category.entity';
+import { TaxCategoryService } from '../../service/providers/tax-category.service';
+import { Allow } from '../common/auth-guard';
+import { RequestContext } from '../common/request-context';
+import { Ctx } from '../common/request-context.decorator';
+
+@Resolver('TaxCategory')
+export class TaxCategoryResolver {
+    constructor(private taxCategoryService: TaxCategoryService) {}
+
+    @Query()
+    @Allow(Permission.ReadSettings)
+    taxCategories(@Ctx() ctx: RequestContext): Promise<TaxCategory[]> {
+        return this.taxCategoryService.findAll();
+    }
+
+    @Query()
+    @Allow(Permission.ReadSettings)
+    async taxCategory(
+        @Ctx() ctx: RequestContext,
+        @Args() args: TaxCategoryQueryArgs,
+    ): Promise<TaxCategory | undefined> {
+        return this.taxCategoryService.findOne(args.id);
+    }
+
+    @Mutation()
+    @Allow(Permission.CreateSettings)
+    async createTaxCategory(@Args() args: CreateTaxCategoryMutationArgs): Promise<TaxCategory> {
+        return this.taxCategoryService.create(args.input);
+    }
+
+    @Mutation()
+    @Allow(Permission.UpdateSettings)
+    async updateTaxCategory(@Args() args: UpdateTaxCategoryMutationArgs): Promise<TaxCategory> {
+        return this.taxCategoryService.update(args.input);
+    }
+}

+ 12 - 0
server/src/api/types/tax-category.api.graphql

@@ -0,0 +1,12 @@
+type Query {
+    taxCategories: [TaxCategory!]!
+    taxCategory(id: ID!): TaxCategory
+}
+
+type Mutation {
+    "Create a new TaxCategory"
+    createTaxCategory(input: CreateTaxCategoryInput!): TaxCategory!
+
+    "Update an existing TaxCategory"
+    updateTaxCategory(input: UpdateTaxCategoryInput!): TaxCategory!
+}

+ 16 - 0
server/src/common/types/adjustment-source.ts

@@ -0,0 +1,16 @@
+import { ID } from 'shared/shared-types';
+
+export interface AdjustmentSource {
+    test(): boolean;
+    apply(): Adjustment[];
+}
+
+/**
+ * When an AdjustmentSource is applied to an OrderItem or Order, an Adjustment is
+ * generated based on the actions assigned to the AdjustmentSource.
+ */
+export interface Adjustment {
+    adjustmentSourceId: ID;
+    description: string;
+    amount: number;
+}

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

@@ -68,13 +68,3 @@ export class AdjustmentSource extends VendureEntity implements ChannelAware {
         });
     }
 }
-
-/**
- * When an AdjustmentSource is applied to an OrderItem or Order, an Adjustment is
- * generated based on the actions assigned to the AdjustmentSource.
- */
-export interface Adjustment {
-    adjustmentSourceId: ID;
-    description: string;
-    amount: number;
-}

+ 6 - 1
server/src/entity/entities.ts

@@ -21,10 +21,13 @@ import { ProductVariantTranslation } from './product-variant/product-variant-tra
 import { ProductVariant } from './product-variant/product-variant.entity';
 import { ProductTranslation } from './product/product-translation.entity';
 import { Product } from './product/product.entity';
+import { Promotion } from './promotion/promotion.entity';
 import { Role } from './role/role.entity';
 import { AnonymousSession } from './session/anonymous-session.entity';
 import { AuthenticatedSession } from './session/authenticated-session.entity';
 import { Session } from './session/session.entity';
+import { TaxCategory } from './tax-category/tax-category.entity';
+import { TaxRate } from './tax-rate/tax-rate.entity';
 import { User } from './user/user.entity';
 import { Zone } from './zone/zone.entity';
 
@@ -33,7 +36,6 @@ import { Zone } from './zone/zone.entity';
  */
 export const coreEntitiesMap = {
     Address,
-    AdjustmentSource,
     Administrator,
     AnonymousSession,
     Asset,
@@ -57,8 +59,11 @@ export const coreEntitiesMap = {
     ProductVariant,
     ProductVariantPrice,
     ProductVariantTranslation,
+    Promotion,
     Role,
     Session,
+    TaxCategory,
+    TaxRate,
     User,
     Zone,
 };

+ 1 - 1
server/src/entity/order-item/order-item.entity.ts

@@ -1,7 +1,7 @@
 import { DeepPartial, ID } from 'shared/shared-types';
 import { Column, Entity, ManyToOne } from 'typeorm';
 
-import { Adjustment } from '../adjustment-source/adjustment-source.entity';
+import { Adjustment } from '../../common/types/adjustment-source';
 import { Asset } from '../asset/asset.entity';
 import { VendureEntity } from '../base/base.entity';
 import { Order } from '../order/order.entity';

+ 1 - 1
server/src/entity/order/order.entity.ts

@@ -1,7 +1,7 @@
 import { DeepPartial } from 'shared/shared-types';
 import { Column, Entity, ManyToOne, OneToMany } from 'typeorm';
 
-import { Adjustment } from '../adjustment-source/adjustment-source.entity';
+import { Adjustment } from '../../common/types/adjustment-source';
 import { VendureEntity } from '../base/base.entity';
 import { Customer } from '../customer/customer.entity';
 import { OrderItem } from '../order-item/order-item.entity';

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

@@ -15,11 +15,6 @@ 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)

+ 3 - 13
server/src/entity/product-variant/product-variant.entity.ts

@@ -7,6 +7,7 @@ import { CustomProductVariantFields } from '../custom-entity-fields';
 import { FacetValue } from '../facet-value/facet-value.entity';
 import { ProductOption } from '../product-option/product-option.entity';
 import { Product } from '../product/product.entity';
+import { TaxCategory } from '../tax-category/tax-category.entity';
 
 import { ProductVariantPrice } from './product-variant-price.entity';
 import { ProductVariantTranslation } from './product-variant-translation.entity';
@@ -31,19 +32,8 @@ export class ProductVariant extends VendureEntity implements Translatable, HasCu
     })
     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;
-    };
+    @ManyToOne(type => TaxCategory)
+    taxCategory: TaxCategory;
 
     @OneToMany(type => ProductVariantPrice, price => price.variant, { eager: true })
     productVariantPrices: ProductVariantPrice[];

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

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

+ 1 - 27
server/src/entity/product-variant/product-variant.subscriber.ts

@@ -1,8 +1,6 @@
-import { ID } from 'shared/shared-types';
-import { Connection, EntitySubscriberInterface, EventSubscriber, InsertEvent } from 'typeorm';
+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';
@@ -22,11 +20,8 @@ export class ProductVariantSubscriber implements EntitySubscriberInterface<Produ
         if (channelId === undefined) {
             throw new I18nError(`error.channel-id-not-set`);
         }
-        const taxCategory = await this.getTaxCategory(event.connection, 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);
     }
 
@@ -40,29 +35,8 @@ export class ProductVariantSubscriber implements EntitySubscriberInterface<Produ
         if (!variantPrice) {
             throw new I18nError(`error.could-not-find-product-variant-price`);
         }
-        let taxCategory = variantPrice.taxCategory;
-        if (event.queryRunner.data.taxCategoryId !== undefined) {
-            taxCategory = await this.getTaxCategory(event.connection, event.queryRunner.data.taxCategoryId);
-            variantPrice.taxCategory = taxCategory;
-        }
 
         variantPrice.price = event.entity.price || 0;
-        variantPrice.priceBeforeTax = this.getPriceBeforeTax(
-            variantPrice.price,
-            taxCategory.getTaxCategoryRate(),
-        );
         await event.manager.save(variantPrice);
     }
-
-    private getPriceBeforeTax(priceAfterTax: number, taxRatePercentage: number): number {
-        return Math.round(priceAfterTax / (1 + taxRatePercentage / 100));
-    }
-
-    private async getTaxCategory(connection: Connection, id: ID): Promise<AdjustmentSource> {
-        const taxCategory = await connection.getRepository(AdjustmentSource).findOne(id);
-        if (!taxCategory) {
-            throw new I18nError(`error.tax-category-not-found`, { id });
-        }
-        return taxCategory;
-    }
 }

+ 34 - 0
server/src/entity/promotion/promotion.entity.ts

@@ -0,0 +1,34 @@
+import { AdjustmentOperation, AdjustmentType } from 'shared/generated-types';
+import { DeepPartial } from 'shared/shared-types';
+import { Column, Entity, JoinTable, ManyToMany } from 'typeorm';
+
+import { Adjustment, AdjustmentSource } from '../../common/types/adjustment-source';
+import { VendureEntity } from '../base/base.entity';
+import { Channel } from '../channel/channel.entity';
+
+@Entity()
+export class Promotion extends VendureEntity implements AdjustmentSource {
+    constructor(input?: DeepPartial<Promotion>) {
+        super(input);
+    }
+
+    @Column() name: string;
+
+    @Column() enabled: boolean;
+
+    @ManyToMany(type => Channel)
+    @JoinTable()
+    channels: Channel[];
+
+    @Column('simple-json') conditions: AdjustmentOperation[];
+
+    @Column('simple-json') actions: AdjustmentOperation[];
+
+    apply(): Adjustment[] {
+        return [];
+    }
+
+    test(): boolean {
+        return false;
+    }
+}

+ 13 - 0
server/src/entity/tax-category/tax-category.entity.ts

@@ -0,0 +1,13 @@
+import { DeepPartial } from 'shared/shared-types';
+import { Column, Entity } from 'typeorm';
+
+import { VendureEntity } from '../base/base.entity';
+
+@Entity()
+export class TaxCategory extends VendureEntity {
+    constructor(input?: DeepPartial<TaxCategory>) {
+        super(input);
+    }
+
+    @Column() name: string;
+}

+ 14 - 0
server/src/entity/tax-category/tax-category.graphql

@@ -0,0 +1,14 @@
+type TaxCategory implements Node {
+    id: ID!
+    name: String!
+}
+
+input CreateTaxCategoryInput {
+    name: String!
+}
+
+
+input UpdateTaxCategoryInput {
+    id: ID!
+    name: String
+}

+ 38 - 0
server/src/entity/tax-rate/tax-rate.entity.ts

@@ -0,0 +1,38 @@
+import { AdjustmentOperation, AdjustmentType } from 'shared/generated-types';
+import { DeepPartial } from 'shared/shared-types';
+import { Column, Entity, JoinTable, ManyToMany, ManyToOne } from 'typeorm';
+
+import { Adjustment, AdjustmentSource } from '../../common/types/adjustment-source';
+import { VendureEntity } from '../base/base.entity';
+import { Channel } from '../channel/channel.entity';
+import { CustomerGroup } from '../customer-group/customer-group.entity';
+import { TaxCategory } from '../tax-category/tax-category.entity';
+import { Zone } from '../zone/zone.entity';
+
+@Entity()
+export class TaxRate extends VendureEntity implements AdjustmentSource {
+    constructor(input?: DeepPartial<TaxRate>) {
+        super(input);
+    }
+
+    @Column() name: string;
+
+    @Column() enabled: boolean;
+
+    @ManyToOne(type => TaxCategory)
+    category: TaxCategory;
+
+    @ManyToOne(type => Zone)
+    zone: Zone;
+
+    @ManyToOne(type => CustomerGroup, { nullable: true })
+    customerGroup?: CustomerGroup;
+
+    apply(): Adjustment[] {
+        return [];
+    }
+
+    test(): boolean {
+        return false;
+    }
+}

+ 2 - 1
server/src/service/helpers/apply-adjustments.ts

@@ -1,12 +1,13 @@
 import { AdjustmentArg, AdjustmentType } from 'shared/generated-types';
 
+import { Adjustment } from '../../common/types/adjustment-source';
 import { idsAreEqual } from '../../common/utils';
 import {
     AdjustmentActionDefinition,
     AdjustmentActionResult,
     AdjustmentConditionDefinition,
 } from '../../config/adjustment/adjustment-types';
-import { Adjustment, AdjustmentSource } from '../../entity/adjustment-source/adjustment-source.entity';
+import { AdjustmentSource } from '../../entity/adjustment-source/adjustment-source.entity';
 import { Order } from '../../entity/order/order.entity';
 
 /**

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

@@ -15,6 +15,7 @@ export function updateTranslatable<T extends Translatable>(
     entityType: Type<T>,
     translationType: Type<Translation<T>>,
     translationUpdaterService: TranslationUpdaterService,
+    beforeSave?: (newEntity: T) => void,
 ) {
     return async function saveTranslatable(
         connection: Connection,
@@ -34,6 +35,9 @@ export function updateTranslatable<T extends Translatable>(
             diff,
         );
         const updatedEntity = patchEntity(entity as any, omit(input, ['translations']));
-        return connection.manager.save(entity, { data });
+        if (typeof beforeSave === 'function') {
+            await beforeSave(entity);
+        }
+        return connection.manager.save(updatedEntity, { data });
     };
 }

+ 3 - 3
server/src/service/providers/order.service.ts

@@ -77,9 +77,9 @@ export class OrderService {
             taxCategoryId: productVariant.taxCategory.id,
             featuredAsset: productVariant.product.featuredAsset,
             unitPrice: productVariant.price,
-            unitPriceBeforeTax: productVariant.priceBeforeTax,
-            totalPriceBeforeAdjustment: productVariant.priceBeforeTax * quantity,
-            totalPrice: productVariant.priceBeforeTax * quantity,
+            unitPriceBeforeTax: 0,
+            totalPriceBeforeAdjustment: 0 * quantity,
+            totalPrice: 0 * quantity,
             adjustments: [],
         });
         const newOrderItem = await this.connection.getRepository(OrderItem).save(orderItem);

+ 12 - 7
server/src/service/providers/product-variant.service.ts

@@ -21,11 +21,13 @@ import { TranslationUpdaterService } from '../helpers/translation-updater.servic
 import { updateTranslatable } from '../helpers/update-translatable';
 
 import { AdjustmentSourceService } from './adjustment-source.service';
+import { TaxCategoryService } from './tax-category.service';
 
 @Injectable()
 export class ProductVariantService {
     constructor(
         @InjectConnection() private connection: Connection,
+        private taxCategoryService: TaxCategoryService,
         private translationUpdaterService: TranslationUpdaterService,
         private adjustmentSourceService: AdjustmentSourceService,
     ) {}
@@ -55,6 +57,7 @@ export class ProductVariantService {
                 variant.options = selectedOptions;
             }
             variant.product = product;
+            variant.taxCategory = { id: input.taxCategoryId } as any;
         });
         return await save(this.connection, input, {
             channelId: ctx.channelId,
@@ -67,11 +70,19 @@ export class ProductVariantService {
             ProductVariant,
             ProductVariantTranslation,
             this.translationUpdaterService,
+            async updatedVariant => {
+                if (input.taxCategoryId) {
+                    const taxCategory = await this.taxCategoryService.findOne(input.taxCategoryId);
+                    if (taxCategory) {
+                        updatedVariant.taxCategory = taxCategory;
+                    }
+                }
+            },
         );
         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'],
+                relations: ['options', 'facetValues', 'taxCategory'],
             }),
         );
         return translateDeep(this.applyChannelPrice(variant, ctx.channelId), DEFAULT_LANGUAGE_CODE, [
@@ -168,12 +179,6 @@ 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;
     }
 

+ 1 - 0
server/src/service/providers/product.service.ts

@@ -74,6 +74,7 @@ export class ProductService {
             'optionGroups',
             'variants.options',
             'variants.facetValues',
+            'variants.taxCategory',
         ];
         const product = await this.connection.manager.findOne(Product, productId, { relations });
         if (!product) {

+ 42 - 0
server/src/service/providers/tax-category.service.ts

@@ -0,0 +1,42 @@
+import { Injectable } from '@nestjs/common';
+import { InjectConnection } from '@nestjs/typeorm';
+import { CreateTaxCategoryInput, UpdateTaxCategoryInput } from 'shared/generated-types';
+import { ID } from 'shared/shared-types';
+import { Connection } from 'typeorm';
+
+import { assertFound } from '../../common/utils';
+import { TaxCategory } from '../../entity/tax-category/tax-category.entity';
+import { I18nError } from '../../i18n/i18n-error';
+import { patchEntity } from '../helpers/patch-entity';
+
+@Injectable()
+export class TaxCategoryService {
+    constructor(@InjectConnection() private connection: Connection) {}
+
+    findAll(): Promise<TaxCategory[]> {
+        return this.connection.getRepository(TaxCategory).find();
+    }
+
+    findOne(taxCategoryId: ID): Promise<TaxCategory | undefined> {
+        return this.connection.getRepository(TaxCategory).findOne(taxCategoryId);
+    }
+
+    async create(input: CreateTaxCategoryInput): Promise<TaxCategory> {
+        const taxCategory = new TaxCategory(input);
+        const newTaxCategory = await this.connection.getRepository(TaxCategory).save(taxCategory);
+        return assertFound(this.findOne(newTaxCategory.id));
+    }
+
+    async update(input: UpdateTaxCategoryInput): Promise<TaxCategory> {
+        const taxCategory = await this.findOne(input.id);
+        if (!taxCategory) {
+            throw new I18nError(`error.entity-with-id-not-found`, {
+                entityName: 'TaxCategory',
+                id: input.id,
+            });
+        }
+        const updatedTaxCategory = patchEntity(taxCategory, input);
+        await this.connection.getRepository(TaxCategory).save(updatedTaxCategory);
+        return assertFound(this.findOne(taxCategory.id));
+    }
+}

+ 2 - 0
server/src/service/service.module.ts

@@ -23,6 +23,7 @@ import { ProductOptionService } from './providers/product-option.service';
 import { ProductVariantService } from './providers/product-variant.service';
 import { ProductService } from './providers/product.service';
 import { RoleService } from './providers/role.service';
+import { TaxCategoryService } from './providers/tax-category.service';
 import { ZoneService } from './providers/zone.service';
 
 const exportedProviders = [
@@ -42,6 +43,7 @@ const exportedProviders = [
     ProductService,
     ProductVariantService,
     RoleService,
+    TaxCategoryService,
     ZoneService,
 ];
 

+ 125 - 20
shared/generated-types.ts

@@ -65,6 +65,8 @@ export interface Query {
     product?: Product | null;
     roles: RoleList;
     role?: Role | null;
+    taxCategories: TaxCategory[];
+    taxCategory?: TaxCategory | null;
     zones: Zone[];
     zone?: Zone | null;
     networkStatus: NetworkStatus;
@@ -312,19 +314,17 @@ export interface ProductVariant extends Node {
     languageCode: LanguageCode;
     sku: string;
     name: string;
-    priceBeforeTax: number;
     price: number;
-    taxCategory: ProductTaxCategory;
+    taxCategory: TaxCategory;
     options: ProductOption[];
     facetValues: FacetValue[];
     translations: ProductVariantTranslation[];
     customFields?: Json | null;
 }
 
-export interface ProductTaxCategory {
+export interface TaxCategory extends Node {
     id: string;
     name: string;
-    taxRate: number;
 }
 
 export interface ProductOption extends Node {
@@ -490,6 +490,8 @@ export interface Mutation {
     applyFacetValuesToProductVariants: ProductVariant[];
     createRole: Role;
     updateRole: Role;
+    createTaxCategory: TaxCategory;
+    updateTaxCategory: TaxCategory;
     createZone: Zone;
     updateZone: Zone;
     addMembersToZone: Zone;
@@ -964,7 +966,6 @@ export interface UpdateProductVariantInput {
     translations?: ProductVariantTranslationInput[] | null;
     sku?: string | null;
     taxCategoryId?: string | null;
-    priceBeforeTax?: number | null;
     price?: number | null;
     customFields?: Json | null;
 }
@@ -989,6 +990,15 @@ export interface UpdateRoleInput {
     permissions?: Permission[] | null;
 }
 
+export interface CreateTaxCategoryInput {
+    name: string;
+}
+
+export interface UpdateTaxCategoryInput {
+    id: string;
+    name?: string | null;
+}
+
 export interface CreateZoneInput {
     name: string;
     memberIds?: string[] | null;
@@ -1002,7 +1012,6 @@ export interface UpdateZoneInput {
 export interface CreateProductVariantInput {
     translations: ProductVariantTranslationInput[];
     sku: string;
-    priceBeforeTax?: number | null;
     price?: number | null;
     taxCategoryId: string;
     optionCodes?: string[] | null;
@@ -1102,6 +1111,9 @@ export interface RolesQueryArgs {
 export interface RoleQueryArgs {
     id: string;
 }
+export interface TaxCategoryQueryArgs {
+    id: string;
+}
 export interface ZoneQueryArgs {
     id: string;
 }
@@ -1222,6 +1234,12 @@ export interface CreateRoleMutationArgs {
 export interface UpdateRoleMutationArgs {
     input: UpdateRoleInput;
 }
+export interface CreateTaxCategoryMutationArgs {
+    input: CreateTaxCategoryInput;
+}
+export interface UpdateTaxCategoryMutationArgs {
+    input: UpdateTaxCategoryInput;
+}
 export interface CreateZoneMutationArgs {
     input: CreateZoneInput;
 }
@@ -1507,6 +1525,8 @@ export namespace QueryResolvers {
         product?: ProductResolver<Product | null, any, Context>;
         roles?: RolesResolver<RoleList, any, Context>;
         role?: RoleResolver<Role | null, any, Context>;
+        taxCategories?: TaxCategoriesResolver<TaxCategory[], any, Context>;
+        taxCategory?: TaxCategoryResolver<TaxCategory | null, any, Context>;
         zones?: ZonesResolver<Zone[], any, Context>;
         zone?: ZoneResolver<Zone | null, any, Context>;
         networkStatus?: NetworkStatusResolver<NetworkStatus, any, Context>;
@@ -1755,6 +1775,21 @@ export namespace QueryResolvers {
         id: string;
     }
 
+    export type TaxCategoriesResolver<R = TaxCategory[], Parent = any, Context = any> = Resolver<
+        R,
+        Parent,
+        Context
+    >;
+    export type TaxCategoryResolver<R = TaxCategory | null, Parent = any, Context = any> = Resolver<
+        R,
+        Parent,
+        Context,
+        TaxCategoryArgs
+    >;
+    export interface TaxCategoryArgs {
+        id: string;
+    }
+
     export type ZonesResolver<R = Zone[], Parent = any, Context = any> = Resolver<R, Parent, Context>;
     export type ZoneResolver<R = Zone | null, Parent = any, Context = any> = Resolver<
         R,
@@ -2413,9 +2448,8 @@ 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>;
+        taxCategory?: TaxCategoryResolver<TaxCategory, any, Context>;
         options?: OptionsResolver<ProductOption[], any, Context>;
         facetValues?: FacetValuesResolver<FacetValue[], any, Context>;
         translations?: TranslationsResolver<ProductVariantTranslation[], any, Context>;
@@ -2432,13 +2466,8 @@ 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<
+    export type TaxCategoryResolver<R = TaxCategory, Parent = any, Context = any> = Resolver<
         R,
         Parent,
         Context
@@ -2465,16 +2494,14 @@ export namespace ProductVariantResolvers {
     >;
 }
 
-export namespace ProductTaxCategoryResolvers {
+export namespace TaxCategoryResolvers {
     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 {
@@ -2872,6 +2899,8 @@ export namespace MutationResolvers {
         >;
         createRole?: CreateRoleResolver<Role, any, Context>;
         updateRole?: UpdateRoleResolver<Role, any, Context>;
+        createTaxCategory?: CreateTaxCategoryResolver<TaxCategory, any, Context>;
+        updateTaxCategory?: UpdateTaxCategoryResolver<TaxCategory, any, Context>;
         createZone?: CreateZoneResolver<Zone, any, Context>;
         updateZone?: UpdateZoneResolver<Zone, any, Context>;
         addMembersToZone?: AddMembersToZoneResolver<Zone, any, Context>;
@@ -3235,6 +3264,26 @@ export namespace MutationResolvers {
         input: UpdateRoleInput;
     }
 
+    export type CreateTaxCategoryResolver<R = TaxCategory, Parent = any, Context = any> = Resolver<
+        R,
+        Parent,
+        Context,
+        CreateTaxCategoryArgs
+    >;
+    export interface CreateTaxCategoryArgs {
+        input: CreateTaxCategoryInput;
+    }
+
+    export type UpdateTaxCategoryResolver<R = TaxCategory, Parent = any, Context = any> = Resolver<
+        R,
+        Parent,
+        Context,
+        UpdateTaxCategoryArgs
+    >;
+    export interface UpdateTaxCategoryArgs {
+        input: UpdateTaxCategoryInput;
+    }
+
     export type CreateZoneResolver<R = Zone, Parent = any, Context = any> = Resolver<
         R,
         Parent,
@@ -4181,6 +4230,56 @@ export namespace RemoveMembersFromZone {
     export type RemoveMembersFromZone = Zone.Fragment;
 }
 
+export namespace GetTaxCategories {
+    export type Variables = {};
+
+    export type Query = {
+        __typename?: 'Query';
+        taxCategories: TaxCategories[];
+    };
+
+    export type TaxCategories = TaxCategory.Fragment;
+}
+
+export namespace GetTaxCategory {
+    export type Variables = {
+        id: string;
+    };
+
+    export type Query = {
+        __typename?: 'Query';
+        taxCategory?: TaxCategory | null;
+    };
+
+    export type TaxCategory = TaxCategory.Fragment;
+}
+
+export namespace CreateTaxCategory {
+    export type Variables = {
+        input: CreateTaxCategoryInput;
+    };
+
+    export type Mutation = {
+        __typename?: 'Mutation';
+        createTaxCategory: CreateTaxCategory;
+    };
+
+    export type CreateTaxCategory = TaxCategory.Fragment;
+}
+
+export namespace UpdateTaxCategory {
+    export type Variables = {
+        input: UpdateTaxCategoryInput;
+    };
+
+    export type Mutation = {
+        __typename?: 'Mutation';
+        updateTaxCategory: UpdateTaxCategory;
+    };
+
+    export type UpdateTaxCategory = TaxCategory.Fragment;
+}
+
 export namespace AdjustmentOperation {
     export type Fragment = {
         __typename?: 'AdjustmentOperation';
@@ -4346,7 +4445,6 @@ export namespace ProductVariant {
         languageCode: LanguageCode;
         name: string;
         price: number;
-        priceBeforeTax: number;
         taxCategory: TaxCategory;
         sku: string;
         options: Options[];
@@ -4355,10 +4453,9 @@ export namespace ProductVariant {
     };
 
     export type TaxCategory = {
-        __typename?: 'ProductTaxCategory';
+        __typename?: 'TaxCategory';
         id: string;
         name: string;
-        taxRate: number;
     };
 
     export type Options = {
@@ -4473,3 +4570,11 @@ export namespace Zone {
 
     export type Members = Country.Fragment;
 }
+
+export namespace TaxCategory {
+    export type Fragment = {
+        __typename?: 'TaxCategory';
+        id: string;
+        name: string;
+    };
+}

Some files were not shown because too many files changed in this diff