Forráskód Böngészése

feat(core): Expose additional price & tax data on OrderLine

Relates to #467. The OrderLine type now exposes `taxRate`, `linePrice`, `lineTax` &
`linePriceWithTax`.

BREAKING CHANGE: The `OrderLine.totalPrice` field has been deprecated and will be removed in a
future release. Use the new `OrderLine.linePriceWithTax` field instead.
Michael Bromley 5 éve
szülő
commit
c8706845a2

+ 13 - 0
packages/admin-ui/src/lib/core/src/common/generated-types.ts

@@ -2035,6 +2035,8 @@ export enum Permission {
   ReadSettings = 'ReadSettings',
   /** SuperAdmin has unrestricted access to all operations */
   SuperAdmin = 'SuperAdmin',
+  /** Allows external tools to sync stock levels */
+  SyncInventory = 'SyncInventory',
   /** Grants permission to update Administrator */
   UpdateAdministrator = 'UpdateAdministrator',
   /** Grants permission to update Catalog */
@@ -3334,8 +3336,11 @@ export type OrderItem = Node & {
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
   cancelled: Scalars['Boolean'];
+  /** The price of a single unit, excluding tax */
   unitPrice: Scalars['Int'];
+  /** The price of a single unit, including tax */
   unitPriceWithTax: Scalars['Int'];
+  /** @deprecated `unitPrice` is now always without tax */
   unitPriceIncludesTax: Scalars['Boolean'];
   taxRate: Scalars['Float'];
   adjustments: Array<Adjustment>;
@@ -3354,7 +3359,15 @@ export type OrderLine = Node & {
   unitPriceWithTax: Scalars['Int'];
   quantity: Scalars['Int'];
   items: Array<OrderItem>;
+  /** @deprecated Use `linePriceWithTax` instead */
   totalPrice: Scalars['Int'];
+  taxRate: Scalars['Float'];
+  /** The total price of the line excluding tax */
+  linePrice: Scalars['Int'];
+  /** The total tax on this line */
+  lineTax: Scalars['Int'];
+  /** The total price of the line including tax */
+  linePriceWithTax: Scalars['Int'];
   adjustments: Array<Adjustment>;
   order: Order;
   customFields?: Maybe<Scalars['JSON']>;

+ 13 - 0
packages/asset-server-plugin/e2e/graphql/generated-e2e-asset-server-plugin-types.ts

@@ -1860,6 +1860,8 @@ export enum Permission {
     UpdateSettings = 'UpdateSettings',
     /** Grants permission to delete Settings */
     DeleteSettings = 'DeleteSettings',
+    /** Allows external tools to sync stock levels */
+    SyncInventory = 'SyncInventory',
 }
 
 export type DeletionResponse = {
@@ -3105,8 +3107,11 @@ export type OrderItem = Node & {
     createdAt: Scalars['DateTime'];
     updatedAt: Scalars['DateTime'];
     cancelled: Scalars['Boolean'];
+    /** The price of a single unit, excluding tax */
     unitPrice: Scalars['Int'];
+    /** The price of a single unit, including tax */
     unitPriceWithTax: Scalars['Int'];
+    /** @deprecated `unitPrice` is now always without tax */
     unitPriceIncludesTax: Scalars['Boolean'];
     taxRate: Scalars['Float'];
     adjustments: Array<Adjustment>;
@@ -3124,7 +3129,15 @@ export type OrderLine = Node & {
     unitPriceWithTax: Scalars['Int'];
     quantity: Scalars['Int'];
     items: Array<OrderItem>;
+    /** @deprecated Use `linePriceWithTax` instead */
     totalPrice: Scalars['Int'];
+    taxRate: Scalars['Float'];
+    /** The total price of the line excluding tax */
+    linePrice: Scalars['Int'];
+    /** The total tax on this line */
+    lineTax: Scalars['Int'];
+    /** The total price of the line including tax */
+    linePriceWithTax: Scalars['Int'];
     adjustments: Array<Adjustment>;
     order: Order;
     customFields?: Maybe<Scalars['JSON']>;

+ 13 - 0
packages/common/src/generated-shop-types.ts

@@ -415,6 +415,8 @@ export enum Permission {
     UpdateSettings = 'UpdateSettings',
     /** Grants permission to delete Settings */
     DeleteSettings = 'DeleteSettings',
+    /** Allows external tools to sync stock levels */
+    SyncInventory = 'SyncInventory',
 }
 
 export type DeletionResponse = {
@@ -2074,8 +2076,11 @@ export type OrderItem = Node & {
     createdAt: Scalars['DateTime'];
     updatedAt: Scalars['DateTime'];
     cancelled: Scalars['Boolean'];
+    /** The price of a single unit, excluding tax */
     unitPrice: Scalars['Int'];
+    /** The price of a single unit, including tax */
     unitPriceWithTax: Scalars['Int'];
+    /** @deprecated `unitPrice` is now always without tax */
     unitPriceIncludesTax: Scalars['Boolean'];
     taxRate: Scalars['Float'];
     adjustments: Array<Adjustment>;
@@ -2094,7 +2099,15 @@ export type OrderLine = Node & {
     unitPriceWithTax: Scalars['Int'];
     quantity: Scalars['Int'];
     items: Array<OrderItem>;
+    /** @deprecated Use `linePriceWithTax` instead */
     totalPrice: Scalars['Int'];
+    taxRate: Scalars['Float'];
+    /** The total price of the line excluding tax */
+    linePrice: Scalars['Int'];
+    /** The total tax on this line */
+    lineTax: Scalars['Int'];
+    /** The total price of the line including tax */
+    linePriceWithTax: Scalars['Int'];
     adjustments: Array<Adjustment>;
     order: Order;
     customFields?: Maybe<Scalars['JSON']>;

+ 14 - 1
packages/common/src/generated-types.ts

@@ -2014,7 +2014,9 @@ export enum Permission {
   /** Grants permission to update Settings */
   UpdateSettings = 'UpdateSettings',
   /** Grants permission to delete Settings */
-  DeleteSettings = 'DeleteSettings'
+  DeleteSettings = 'DeleteSettings',
+  /** Allows external tools to sync stock levels */
+  SyncInventory = 'SyncInventory'
 }
 
 export type DeletionResponse = {
@@ -3302,8 +3304,11 @@ export type OrderItem = Node & {
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
   cancelled: Scalars['Boolean'];
+  /** The price of a single unit, excluding tax */
   unitPrice: Scalars['Int'];
+  /** The price of a single unit, including tax */
   unitPriceWithTax: Scalars['Int'];
+  /** @deprecated `unitPrice` is now always without tax */
   unitPriceIncludesTax: Scalars['Boolean'];
   taxRate: Scalars['Float'];
   adjustments: Array<Adjustment>;
@@ -3322,7 +3327,15 @@ export type OrderLine = Node & {
   unitPriceWithTax: Scalars['Int'];
   quantity: Scalars['Int'];
   items: Array<OrderItem>;
+  /** @deprecated Use `linePriceWithTax` instead */
   totalPrice: Scalars['Int'];
+  taxRate: Scalars['Float'];
+  /** The total price of the line excluding tax */
+  linePrice: Scalars['Int'];
+  /** The total tax on this line */
+  lineTax: Scalars['Int'];
+  /** The total price of the line including tax */
+  linePriceWithTax: Scalars['Int'];
   adjustments: Array<Adjustment>;
   order: Order;
   customFields?: Maybe<Scalars['JSON']>;

+ 46 - 31
packages/core/e2e/graphql/generated-e2e-admin-types.ts

@@ -1860,6 +1860,8 @@ export enum Permission {
     UpdateSettings = 'UpdateSettings',
     /** Grants permission to delete Settings */
     DeleteSettings = 'DeleteSettings',
+    /** Allows external tools to sync stock levels */
+    SyncInventory = 'SyncInventory',
 }
 
 export type DeletionResponse = {
@@ -3105,8 +3107,11 @@ export type OrderItem = Node & {
     createdAt: Scalars['DateTime'];
     updatedAt: Scalars['DateTime'];
     cancelled: Scalars['Boolean'];
+    /** The price of a single unit, excluding tax */
     unitPrice: Scalars['Int'];
+    /** The price of a single unit, including tax */
     unitPriceWithTax: Scalars['Int'];
+    /** @deprecated `unitPrice` is now always without tax */
     unitPriceIncludesTax: Scalars['Boolean'];
     taxRate: Scalars['Float'];
     adjustments: Array<Adjustment>;
@@ -3124,7 +3129,15 @@ export type OrderLine = Node & {
     unitPriceWithTax: Scalars['Int'];
     quantity: Scalars['Int'];
     items: Array<OrderItem>;
+    /** @deprecated Use `linePriceWithTax` instead */
     totalPrice: Scalars['Int'];
+    taxRate: Scalars['Float'];
+    /** The total price of the line excluding tax */
+    linePrice: Scalars['Int'];
+    /** The total tax on this line */
+    lineTax: Scalars['Int'];
+    /** The total price of the line including tax */
+    linePriceWithTax: Scalars['Int'];
     adjustments: Array<Adjustment>;
     order: Order;
     customFields?: Maybe<Scalars['JSON']>;
@@ -5150,19 +5163,9 @@ export type UpdateRoleMutationVariables = Exact<{
 
 export type UpdateRoleMutation = { updateRole: RoleFragment };
 
-export type UpdateOptionGroupMutationVariables = Exact<{
-    input: UpdateProductOptionGroupInput;
-}>;
-
-export type UpdateOptionGroupMutation = { updateProductOptionGroup: Pick<ProductOptionGroup, 'id'> };
-
-export type DeletePromotionAdHoc1MutationVariables = Exact<{ [key: string]: never }>;
-
-export type DeletePromotionAdHoc1Mutation = { deletePromotion: Pick<DeletionResponse, 'result'> };
-
-export type GetPromoProductsQueryVariables = Exact<{ [key: string]: never }>;
+export type GetProductsWithVariantPricesQueryVariables = Exact<{ [key: string]: never }>;
 
-export type GetPromoProductsQuery = {
+export type GetProductsWithVariantPricesQuery = {
     products: {
         items: Array<
             Pick<Product, 'id' | 'slug'> & {
@@ -5176,6 +5179,16 @@ export type GetPromoProductsQuery = {
     };
 };
 
+export type UpdateOptionGroupMutationVariables = Exact<{
+    input: UpdateProductOptionGroupInput;
+}>;
+
+export type UpdateOptionGroupMutation = { updateProductOptionGroup: Pick<ProductOptionGroup, 'id'> };
+
+export type DeletePromotionAdHoc1MutationVariables = Exact<{ [key: string]: never }>;
+
+export type DeletePromotionAdHoc1Mutation = { deletePromotion: Pick<DeletionResponse, 'result'> };
+
 export type SettlePaymentMutationVariables = Exact<{
     id: Scalars['ID'];
 }>;
@@ -6991,29 +7004,17 @@ export namespace UpdateRole {
     export type UpdateRole = NonNullable<UpdateRoleMutation['updateRole']>;
 }
 
-export namespace UpdateOptionGroup {
-    export type Variables = UpdateOptionGroupMutationVariables;
-    export type Mutation = UpdateOptionGroupMutation;
-    export type UpdateProductOptionGroup = NonNullable<UpdateOptionGroupMutation['updateProductOptionGroup']>;
-}
-
-export namespace DeletePromotionAdHoc1 {
-    export type Variables = DeletePromotionAdHoc1MutationVariables;
-    export type Mutation = DeletePromotionAdHoc1Mutation;
-    export type DeletePromotion = NonNullable<DeletePromotionAdHoc1Mutation['deletePromotion']>;
-}
-
-export namespace GetPromoProducts {
-    export type Variables = GetPromoProductsQueryVariables;
-    export type Query = GetPromoProductsQuery;
-    export type Products = NonNullable<GetPromoProductsQuery['products']>;
+export namespace GetProductsWithVariantPrices {
+    export type Variables = GetProductsWithVariantPricesQueryVariables;
+    export type Query = GetProductsWithVariantPricesQuery;
+    export type Products = NonNullable<GetProductsWithVariantPricesQuery['products']>;
     export type Items = NonNullable<
-        NonNullable<NonNullable<GetPromoProductsQuery['products']>['items']>[number]
+        NonNullable<NonNullable<GetProductsWithVariantPricesQuery['products']>['items']>[number]
     >;
     export type Variants = NonNullable<
         NonNullable<
             NonNullable<
-                NonNullable<NonNullable<GetPromoProductsQuery['products']>['items']>[number]
+                NonNullable<NonNullable<GetProductsWithVariantPricesQuery['products']>['items']>[number]
             >['variants']
         >[number]
     >;
@@ -7022,7 +7023,9 @@ export namespace GetPromoProducts {
             NonNullable<
                 NonNullable<
                     NonNullable<
-                        NonNullable<NonNullable<GetPromoProductsQuery['products']>['items']>[number]
+                        NonNullable<
+                            NonNullable<GetProductsWithVariantPricesQuery['products']>['items']
+                        >[number]
                     >['variants']
                 >[number]
             >['facetValues']
@@ -7030,6 +7033,18 @@ export namespace GetPromoProducts {
     >;
 }
 
+export namespace UpdateOptionGroup {
+    export type Variables = UpdateOptionGroupMutationVariables;
+    export type Mutation = UpdateOptionGroupMutation;
+    export type UpdateProductOptionGroup = NonNullable<UpdateOptionGroupMutation['updateProductOptionGroup']>;
+}
+
+export namespace DeletePromotionAdHoc1 {
+    export type Variables = DeletePromotionAdHoc1MutationVariables;
+    export type Mutation = DeletePromotionAdHoc1Mutation;
+    export type DeletePromotion = NonNullable<DeletePromotionAdHoc1Mutation['deletePromotion']>;
+}
+
 export namespace SettlePayment {
     export type Variables = SettlePaymentMutationVariables;
     export type Mutation = SettlePaymentMutation;

+ 60 - 0
packages/core/e2e/graphql/generated-e2e-shop-types.ts

@@ -408,6 +408,8 @@ export enum Permission {
     UpdateSettings = 'UpdateSettings',
     /** Grants permission to delete Settings */
     DeleteSettings = 'DeleteSettings',
+    /** Allows external tools to sync stock levels */
+    SyncInventory = 'SyncInventory',
 }
 
 export type DeletionResponse = {
@@ -1996,8 +1998,11 @@ export type OrderItem = Node & {
     createdAt: Scalars['DateTime'];
     updatedAt: Scalars['DateTime'];
     cancelled: Scalars['Boolean'];
+    /** The price of a single unit, excluding tax */
     unitPrice: Scalars['Int'];
+    /** The price of a single unit, including tax */
     unitPriceWithTax: Scalars['Int'];
+    /** @deprecated `unitPrice` is now always without tax */
     unitPriceIncludesTax: Scalars['Boolean'];
     taxRate: Scalars['Float'];
     adjustments: Array<Adjustment>;
@@ -2015,7 +2020,15 @@ export type OrderLine = Node & {
     unitPriceWithTax: Scalars['Int'];
     quantity: Scalars['Int'];
     items: Array<OrderItem>;
+    /** @deprecated Use `linePriceWithTax` instead */
     totalPrice: Scalars['Int'];
+    taxRate: Scalars['Float'];
+    /** The total price of the line excluding tax */
+    linePrice: Scalars['Int'];
+    /** The total tax on this line */
+    lineTax: Scalars['Int'];
+    /** The total price of the line including tax */
+    linePriceWithTax: Scalars['Int'];
     adjustments: Array<Adjustment>;
     order: Order;
     customFields?: Maybe<Scalars['JSON']>;
@@ -2831,6 +2844,30 @@ export type GetActiveOrderQueryVariables = Exact<{ [key: string]: never }>;
 
 export type GetActiveOrderQuery = { activeOrder?: Maybe<TestOrderFragmentFragment> };
 
+export type GetActiveOrderWithPriceDataQueryVariables = Exact<{ [key: string]: never }>;
+
+export type GetActiveOrderWithPriceDataQuery = {
+    activeOrder?: Maybe<
+        Pick<Order, 'id' | 'subTotalBeforeTax' | 'subTotal' | 'totalBeforeTax' | 'total'> & {
+            lines: Array<
+                Pick<
+                    OrderLine,
+                    | 'id'
+                    | 'unitPrice'
+                    | 'unitPriceWithTax'
+                    | 'taxRate'
+                    | 'linePrice'
+                    | 'lineTax'
+                    | 'linePriceWithTax'
+                > & {
+                    items: Array<Pick<OrderItem, 'id' | 'unitPrice' | 'unitPriceWithTax' | 'taxRate'>>;
+                    adjustments: Array<Pick<Adjustment, 'amount' | 'type'>>;
+                }
+            >;
+        }
+    >;
+};
+
 export type AdjustItemQuantityMutationVariables = Exact<{
     orderLineId: Scalars['ID'];
     quantity: Scalars['Int'];
@@ -3287,6 +3324,29 @@ export namespace GetActiveOrder {
     export type ActiveOrder = NonNullable<GetActiveOrderQuery['activeOrder']>;
 }
 
+export namespace GetActiveOrderWithPriceData {
+    export type Variables = GetActiveOrderWithPriceDataQueryVariables;
+    export type Query = GetActiveOrderWithPriceDataQuery;
+    export type ActiveOrder = NonNullable<GetActiveOrderWithPriceDataQuery['activeOrder']>;
+    export type Lines = NonNullable<
+        NonNullable<NonNullable<GetActiveOrderWithPriceDataQuery['activeOrder']>['lines']>[number]
+    >;
+    export type Items = NonNullable<
+        NonNullable<
+            NonNullable<
+                NonNullable<NonNullable<GetActiveOrderWithPriceDataQuery['activeOrder']>['lines']>[number]
+            >['items']
+        >[number]
+    >;
+    export type Adjustments = NonNullable<
+        NonNullable<
+            NonNullable<
+                NonNullable<NonNullable<GetActiveOrderWithPriceDataQuery['activeOrder']>['lines']>[number]
+            >['adjustments']
+        >[number]
+    >;
+}
+
 export namespace AdjustItemQuantity {
     export type Variables = AdjustItemQuantityMutationVariables;
     export type Mutation = AdjustItemQuantityMutation;

+ 21 - 0
packages/core/e2e/graphql/shared-definitions.ts

@@ -714,3 +714,24 @@ export const UPDATE_ROLE = gql`
     }
     ${ROLE_FRAGMENT}
 `;
+
+export const GET_PRODUCTS_WITH_VARIANT_PRICES = gql`
+    query GetProductsWithVariantPrices {
+        products {
+            items {
+                id
+                slug
+                variants {
+                    id
+                    price
+                    priceWithTax
+                    sku
+                    facetValues {
+                        id
+                        code
+                    }
+                }
+            }
+        }
+    }
+`;

+ 31 - 0
packages/core/e2e/graphql/shop-definitions.ts

@@ -296,6 +296,37 @@ export const GET_ACTIVE_ORDER = gql`
     ${TEST_ORDER_FRAGMENT}
 `;
 
+export const GET_ACTIVE_ORDER_WITH_PRICE_DATA = gql`
+    query GetActiveOrderWithPriceData {
+        activeOrder {
+            id
+            subTotalBeforeTax
+            subTotal
+            totalBeforeTax
+            total
+            lines {
+                id
+                unitPrice
+                unitPriceWithTax
+                taxRate
+                linePrice
+                lineTax
+                linePriceWithTax
+                items {
+                    id
+                    unitPrice
+                    unitPriceWithTax
+                    taxRate
+                }
+                adjustments {
+                    amount
+                    type
+                }
+            }
+        }
+    }
+`;
+
 export const ADJUST_ITEM_QUANTITY = gql`
     mutation AdjustItemQuantity($orderLineId: ID!, $quantity: Int!) {
         adjustOrderLine(orderLineId: $orderLineId, quantity: $quantity) {

+ 11 - 29
packages/core/e2e/order-promotion.e2e-spec.ts

@@ -23,7 +23,7 @@ import {
     CreatePromotion,
     CreatePromotionInput,
     GetFacetList,
-    GetPromoProducts,
+    GetProductsWithVariantPrices,
     HistoryEntryType,
     PromotionFragment,
     RemoveCustomersFromGroup,
@@ -47,6 +47,7 @@ import {
     CREATE_CUSTOMER_GROUP,
     CREATE_PROMOTION,
     GET_FACET_LIST,
+    GET_PRODUCTS_WITH_VARIANT_PRICES,
     REMOVE_CUSTOMERS_FROM_GROUP,
 } from './graphql/shared-definitions';
 import {
@@ -58,7 +59,6 @@ import {
     REMOVE_COUPON_CODE,
     SET_CUSTOMER,
 } from './graphql/shop-definitions';
-import { assertThrowsWithMessage } from './utils/assert-throws-with-message';
 import { addPaymentToOrder, proceedToArrangingPayment } from './utils/test-order-utils';
 
 describe('Promotions applied to Orders', () => {
@@ -901,12 +901,15 @@ describe('Promotions applied to Orders', () => {
     });
 
     async function getProducts() {
-        const result = await adminClient.query<GetPromoProducts.Query>(GET_PROMO_PRODUCTS, {
-            options: {
-                take: 10,
-                skip: 0,
+        const result = await adminClient.query<GetProductsWithVariantPrices.Query>(
+            GET_PRODUCTS_WITH_VARIANT_PRICES,
+            {
+                options: {
+                    take: 10,
+                    skip: 0,
+                },
             },
-        });
+        );
         products = result.products.items;
     }
     async function createGlobalPromotions() {
@@ -941,7 +944,7 @@ describe('Promotions applied to Orders', () => {
 
     function getVariantBySlug(
         slug: 'item-1' | 'item-12' | 'item-60' | 'item-sale-1' | 'item-sale-12',
-    ): GetPromoProducts.Variants {
+    ): GetProductsWithVariantPrices.Variants {
         return products.find(p => p.slug === slug)!.variants[0];
     }
 
@@ -955,24 +958,3 @@ describe('Promotions applied to Orders', () => {
         `);
     }
 });
-
-export const GET_PROMO_PRODUCTS = gql`
-    query GetPromoProducts {
-        products {
-            items {
-                id
-                slug
-                variants {
-                    id
-                    price
-                    priceWithTax
-                    sku
-                    facetValues {
-                        id
-                        code
-                    }
-                }
-            }
-        }
-    }
-`;

+ 140 - 0
packages/core/e2e/order-taxes.e2e-spec.ts

@@ -0,0 +1,140 @@
+import { createErrorResultGuard, createTestEnvironment, ErrorResultGuard } from '@vendure/testing';
+import gql from 'graphql-tag';
+import path from 'path';
+
+import { initialData } from '../../../e2e-common/e2e-initial-data';
+import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
+
+import { testSuccessfulPaymentMethod } from './fixtures/test-payment-methods';
+import { GetProductsWithVariantPrices, UpdateChannel } from './graphql/generated-e2e-admin-types';
+import {
+    AddItemToOrder,
+    AdjustmentType,
+    GetActiveOrderWithPriceData,
+    TestOrderFragmentFragment,
+    UpdatedOrderFragment,
+} from './graphql/generated-e2e-shop-types';
+import { GET_PRODUCTS_WITH_VARIANT_PRICES, UPDATE_CHANNEL } from './graphql/shared-definitions';
+import { ADD_ITEM_TO_ORDER, GET_ACTIVE_ORDER_WITH_PRICE_DATA } from './graphql/shop-definitions';
+
+describe('Order taxes', () => {
+    const { server, adminClient, shopClient } = createTestEnvironment({
+        ...testConfig,
+        paymentOptions: {
+            paymentMethodHandlers: [testSuccessfulPaymentMethod],
+        },
+    });
+
+    type OrderSuccessResult = UpdatedOrderFragment | TestOrderFragmentFragment;
+    const orderResultGuard: ErrorResultGuard<OrderSuccessResult> = createErrorResultGuard<OrderSuccessResult>(
+        input => !!input.lines,
+    );
+    let products: GetProductsWithVariantPrices.Items[];
+
+    beforeAll(async () => {
+        await server.init({
+            initialData,
+            productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-promotions.csv'),
+            customerCount: 2,
+        });
+        await adminClient.asSuperAdmin();
+        const result = await adminClient.query<GetProductsWithVariantPrices.Query>(
+            GET_PRODUCTS_WITH_VARIANT_PRICES,
+        );
+        products = result.products.items;
+    }, TEST_SETUP_TIMEOUT_MS);
+
+    afterAll(async () => {
+        await server.destroy();
+    });
+
+    describe('Channel.pricesIncludeTax = false', () => {
+        beforeAll(async () => {
+            await adminClient.query<UpdateChannel.Mutation, UpdateChannel.Variables>(UPDATE_CHANNEL, {
+                input: {
+                    id: 'T_1',
+                    pricesIncludeTax: false,
+                },
+            });
+            await shopClient.asAnonymousUser();
+        });
+
+        it('prices are correct', async () => {
+            const variant = products[0].variants[0];
+            await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(ADD_ITEM_TO_ORDER, {
+                productVariantId: variant.id,
+                quantity: 2,
+            });
+
+            const { activeOrder } = await shopClient.query<GetActiveOrderWithPriceData.Query>(
+                GET_ACTIVE_ORDER_WITH_PRICE_DATA,
+            );
+            expect(activeOrder?.total).toBe(240);
+            expect(activeOrder?.totalBeforeTax).toBe(200);
+            expect(activeOrder?.lines[0].taxRate).toBe(20);
+            expect(activeOrder?.lines[0].linePrice).toBe(200);
+            expect(activeOrder?.lines[0].lineTax).toBe(40);
+            expect(activeOrder?.lines[0].linePriceWithTax).toBe(240);
+            expect(activeOrder?.lines[0].unitPrice).toBe(100);
+            expect(activeOrder?.lines[0].unitPriceWithTax).toBe(120);
+            expect(activeOrder?.lines[0].items[0].unitPrice).toBe(100);
+            expect(activeOrder?.lines[0].items[0].unitPriceWithTax).toBe(120);
+            expect(activeOrder?.lines[0].items[0].taxRate).toBe(20);
+            expect(activeOrder?.lines[0].adjustments).toEqual([
+                {
+                    type: AdjustmentType.TAX,
+                    amount: 20,
+                },
+                {
+                    type: AdjustmentType.TAX,
+                    amount: 20,
+                },
+            ]);
+        });
+    });
+
+    describe('Channel.pricesIncludeTax = true', () => {
+        beforeAll(async () => {
+            await adminClient.query<UpdateChannel.Mutation, UpdateChannel.Variables>(UPDATE_CHANNEL, {
+                input: {
+                    id: 'T_1',
+                    pricesIncludeTax: true,
+                },
+            });
+            await shopClient.asAnonymousUser();
+        });
+
+        it('prices are correct', async () => {
+            const variant = products[0].variants[0];
+            await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(ADD_ITEM_TO_ORDER, {
+                productVariantId: variant.id,
+                quantity: 2,
+            });
+
+            const { activeOrder } = await shopClient.query<GetActiveOrderWithPriceData.Query>(
+                GET_ACTIVE_ORDER_WITH_PRICE_DATA,
+            );
+            expect(activeOrder?.total).toBe(200);
+            expect(activeOrder?.totalBeforeTax).toBe(166);
+            expect(activeOrder?.lines[0].taxRate).toBe(20);
+            expect(activeOrder?.lines[0].linePrice).toBe(166);
+            expect(activeOrder?.lines[0].lineTax).toBe(34);
+            expect(activeOrder?.lines[0].linePriceWithTax).toBe(200);
+            expect(activeOrder?.lines[0].unitPrice).toBe(83);
+            expect(activeOrder?.lines[0].unitPriceWithTax).toBe(100);
+            expect(activeOrder?.lines[0].items[0].unitPrice).toBe(83);
+            expect(activeOrder?.lines[0].items[0].unitPriceWithTax).toBe(100);
+            expect(activeOrder?.lines[0].items[0].taxRate).toBe(20);
+            expect(activeOrder?.lines[0].adjustments).toEqual([
+                {
+                    type: AdjustmentType.TAX,
+                    amount: 17,
+                },
+                {
+                    type: AdjustmentType.TAX,
+                    amount: 17,
+                },
+            ]);
+        });
+    });
+});

+ 10 - 1
packages/core/src/api/schema/type/order.type.graphql

@@ -62,7 +62,9 @@ type OrderItem implements Node {
     createdAt: DateTime!
     updatedAt: DateTime!
     cancelled: Boolean!
+    "The price of a single unit, excluding tax"
     unitPrice: Int!
+    "The price of a single unit, including tax"
     unitPriceWithTax: Int!
     unitPriceIncludesTax: Boolean! @deprecated(reason: "`unitPrice` is now always without tax")
     taxRate: Float!
@@ -81,7 +83,14 @@ type OrderLine implements Node {
     unitPriceWithTax: Int!
     quantity: Int!
     items: [OrderItem!]!
-    totalPrice: Int!
+    totalPrice: Int! @deprecated(reason: "Use `linePriceWithTax` instead")
+    taxRate: Float!
+    "The total price of the line excluding tax"
+    linePrice: Int!
+    "The total tax on this line"
+    lineTax: Int!
+    "The total price of the line including tax"
+    linePriceWithTax: Int!
     adjustments: [Adjustment!]!
     order: Order!
 }

+ 23 - 0
packages/core/src/entity/order-line/order-line.entity.ts

@@ -57,6 +57,10 @@ export class OrderLine extends VendureEntity implements HasCustomFields {
         return this.activeItems.length;
     }
 
+    /**
+     * @deprecated Use `linePriceWithTax`
+     * TODO: Remove this in a future release
+     */
     @Calculated()
     get totalPrice(): number {
         return this.activeItems.reduce((total, item) => total + item.unitPriceWithPromotionsAndTax, 0);
@@ -70,10 +74,29 @@ export class OrderLine extends VendureEntity implements HasCustomFields {
         );
     }
 
+    @Calculated()
+    get taxRate(): number {
+        return this.activeItems.length ? this.activeItems[0].taxRate : 0;
+    }
+
+    @Calculated()
+    get linePrice(): number {
+        return this.activeItems.reduce((total, item) => total + item.unitPrice, 0);
+    }
+
+    @Calculated()
     get lineTax(): number {
         return this.activeItems.reduce((total, item) => total + item.unitTax, 0);
     }
 
+    @Calculated()
+    get linePriceWithTax(): number {
+        return this.activeItems.reduce((total, item) => total + item.unitPriceWithPromotionsAndTax, 0);
+    }
+
+    /**
+     * Returns all non-cancelled OrderItems on this line.
+     */
     get activeItems(): OrderItem[] {
         return (this.items || []).filter(i => !i.cancelled);
     }

+ 13 - 0
packages/elasticsearch-plugin/e2e/graphql/generated-e2e-elasticsearch-plugin-types.ts

@@ -1860,6 +1860,8 @@ export enum Permission {
     UpdateSettings = 'UpdateSettings',
     /** Grants permission to delete Settings */
     DeleteSettings = 'DeleteSettings',
+    /** Allows external tools to sync stock levels */
+    SyncInventory = 'SyncInventory',
 }
 
 export type DeletionResponse = {
@@ -3105,8 +3107,11 @@ export type OrderItem = Node & {
     createdAt: Scalars['DateTime'];
     updatedAt: Scalars['DateTime'];
     cancelled: Scalars['Boolean'];
+    /** The price of a single unit, excluding tax */
     unitPrice: Scalars['Int'];
+    /** The price of a single unit, including tax */
     unitPriceWithTax: Scalars['Int'];
+    /** @deprecated `unitPrice` is now always without tax */
     unitPriceIncludesTax: Scalars['Boolean'];
     taxRate: Scalars['Float'];
     adjustments: Array<Adjustment>;
@@ -3124,7 +3129,15 @@ export type OrderLine = Node & {
     unitPriceWithTax: Scalars['Int'];
     quantity: Scalars['Int'];
     items: Array<OrderItem>;
+    /** @deprecated Use `linePriceWithTax` instead */
     totalPrice: Scalars['Int'];
+    taxRate: Scalars['Float'];
+    /** The total price of the line excluding tax */
+    linePrice: Scalars['Int'];
+    /** The total tax on this line */
+    lineTax: Scalars['Int'];
+    /** The total price of the line including tax */
+    linePriceWithTax: Scalars['Int'];
     adjustments: Array<Adjustment>;
     order: Order;
     customFields?: Maybe<Scalars['JSON']>;

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 0
schema-admin.json


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 0
schema-shop.json


Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott