فهرست منبع

feat(core): Add tax summary data to Order type

Relates to #467
Michael Bromley 5 سال پیش
والد
کامیت
a666fab524

+ 15 - 2
packages/admin-ui/src/lib/core/src/common/generated-types.ts

@@ -1348,6 +1348,7 @@ export type Order = Node & {
   shippingMethod?: Maybe<ShippingMethod>;
   totalBeforeTax: Scalars['Int'];
   total: Scalars['Int'];
+  taxSummary: Array<OrderTaxSummary>;
   history: HistoryEntryList;
   customFields?: Maybe<Scalars['JSON']>;
 };
@@ -2035,8 +2036,6 @@ 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 */
@@ -3301,6 +3300,20 @@ export type ImportInfo = {
   imported: Scalars['Int'];
 };
 
+/**
+ * A summary of the taxes being applied to this order, grouped
+ * by taxRate.
+ */
+export type OrderTaxSummary = {
+  __typename?: 'OrderTaxSummary';
+  /** The taxRate as a percentage */
+  taxRate: Scalars['Float'];
+  /** The total net price or OrderItems to which this taxRate applies */
+  taxBase: Scalars['Int'];
+  /** The total tax being applied to the Order at this taxRate */
+  taxTotal: Scalars['Int'];
+};
+
 export type OrderAddress = {
   __typename?: 'OrderAddress';
   fullName?: Maybe<Scalars['String']>;

+ 14 - 2
packages/asset-server-plugin/e2e/graphql/generated-e2e-asset-server-plugin-types.ts

@@ -1171,6 +1171,7 @@ export type Order = Node & {
     shippingMethod?: Maybe<ShippingMethod>;
     totalBeforeTax: Scalars['Int'];
     total: Scalars['Int'];
+    taxSummary: Array<OrderTaxSummary>;
     history: HistoryEntryList;
     customFields?: Maybe<Scalars['JSON']>;
 };
@@ -1860,8 +1861,6 @@ export enum Permission {
     UpdateSettings = 'UpdateSettings',
     /** Grants permission to delete Settings */
     DeleteSettings = 'DeleteSettings',
-    /** Allows external tools to sync stock levels */
-    SyncInventory = 'SyncInventory',
 }
 
 export type DeletionResponse = {
@@ -3076,6 +3075,19 @@ export type ImportInfo = {
     imported: Scalars['Int'];
 };
 
+/**
+ * A summary of the taxes being applied to this order, grouped
+ * by taxRate.
+ */
+export type OrderTaxSummary = {
+    /** The taxRate as a percentage */
+    taxRate: Scalars['Float'];
+    /** The total net price or OrderItems to which this taxRate applies */
+    taxBase: Scalars['Int'];
+    /** The total tax being applied to the Order at this taxRate */
+    taxTotal: Scalars['Int'];
+};
+
 export type OrderAddress = {
     fullName?: Maybe<Scalars['String']>;
     company?: Maybe<Scalars['String']>;

+ 15 - 2
packages/common/src/generated-shop-types.ts

@@ -415,8 +415,6 @@ export enum Permission {
     UpdateSettings = 'UpdateSettings',
     /** Grants permission to delete Settings */
     DeleteSettings = 'DeleteSettings',
-    /** Allows external tools to sync stock levels */
-    SyncInventory = 'SyncInventory',
 }
 
 export type DeletionResponse = {
@@ -2033,6 +2031,7 @@ export type Order = Node & {
     shippingMethod?: Maybe<ShippingMethod>;
     totalBeforeTax: Scalars['Int'];
     total: Scalars['Int'];
+    taxSummary: Array<OrderTaxSummary>;
     history: HistoryEntryList;
     customFields?: Maybe<Scalars['JSON']>;
 };
@@ -2041,6 +2040,20 @@ export type OrderHistoryArgs = {
     options?: Maybe<HistoryEntryListOptions>;
 };
 
+/**
+ * A summary of the taxes being applied to this order, grouped
+ * by taxRate.
+ */
+export type OrderTaxSummary = {
+    __typename?: 'OrderTaxSummary';
+    /** The taxRate as a percentage */
+    taxRate: Scalars['Float'];
+    /** The total net price or OrderItems to which this taxRate applies */
+    taxBase: Scalars['Int'];
+    /** The total tax being applied to the Order at this taxRate */
+    taxTotal: Scalars['Int'];
+};
+
 export type OrderAddress = {
     __typename?: 'OrderAddress';
     fullName?: Maybe<Scalars['String']>;

+ 16 - 3
packages/common/src/generated-types.ts

@@ -1317,6 +1317,7 @@ export type Order = Node & {
   shippingMethod?: Maybe<ShippingMethod>;
   totalBeforeTax: Scalars['Int'];
   total: Scalars['Int'];
+  taxSummary: Array<OrderTaxSummary>;
   history: HistoryEntryList;
   customFields?: Maybe<Scalars['JSON']>;
 };
@@ -2014,9 +2015,7 @@ export enum Permission {
   /** Grants permission to update Settings */
   UpdateSettings = 'UpdateSettings',
   /** Grants permission to delete Settings */
-  DeleteSettings = 'DeleteSettings',
-  /** Allows external tools to sync stock levels */
-  SyncInventory = 'SyncInventory'
+  DeleteSettings = 'DeleteSettings'
 }
 
 export type DeletionResponse = {
@@ -3269,6 +3268,20 @@ export type ImportInfo = {
   imported: Scalars['Int'];
 };
 
+/**
+ * A summary of the taxes being applied to this order, grouped
+ * by taxRate.
+ */
+export type OrderTaxSummary = {
+  __typename?: 'OrderTaxSummary';
+  /** The taxRate as a percentage */
+  taxRate: Scalars['Float'];
+  /** The total net price or OrderItems to which this taxRate applies */
+  taxBase: Scalars['Int'];
+  /** The total tax being applied to the Order at this taxRate */
+  taxTotal: Scalars['Int'];
+};
+
 export type OrderAddress = {
   __typename?: 'OrderAddress';
   fullName?: Maybe<Scalars['String']>;

+ 4 - 0
packages/core/e2e/fixtures/e2e-products-order-taxes.csv

@@ -0,0 +1,4 @@
+name  ,slug  ,description,assets,facets,optionGroups,optionValues,sku,price,taxCategory ,stockOnHand,trackInventory,variantAssets,variantFacets
+item-1,item-1,           ,      ,      ,            ,            ,I1 ,1.00 ,standard tax,100        ,false         ,             ,
+item-2,item-2,           ,      ,      ,            ,            ,I12,1.00 ,reduced tax ,100        ,false         ,             ,
+item-3,item-3,           ,      ,      ,            ,            ,I12,1.00 ,zero tax    ,100        ,false         ,             ,

+ 14 - 2
packages/core/e2e/graphql/generated-e2e-admin-types.ts

@@ -1171,6 +1171,7 @@ export type Order = Node & {
     shippingMethod?: Maybe<ShippingMethod>;
     totalBeforeTax: Scalars['Int'];
     total: Scalars['Int'];
+    taxSummary: Array<OrderTaxSummary>;
     history: HistoryEntryList;
     customFields?: Maybe<Scalars['JSON']>;
 };
@@ -1860,8 +1861,6 @@ export enum Permission {
     UpdateSettings = 'UpdateSettings',
     /** Grants permission to delete Settings */
     DeleteSettings = 'DeleteSettings',
-    /** Allows external tools to sync stock levels */
-    SyncInventory = 'SyncInventory',
 }
 
 export type DeletionResponse = {
@@ -3076,6 +3075,19 @@ export type ImportInfo = {
     imported: Scalars['Int'];
 };
 
+/**
+ * A summary of the taxes being applied to this order, grouped
+ * by taxRate.
+ */
+export type OrderTaxSummary = {
+    /** The taxRate as a percentage */
+    taxRate: Scalars['Float'];
+    /** The total net price or OrderItems to which this taxRate applies */
+    taxBase: Scalars['Int'];
+    /** The total tax being applied to the Order at this taxRate */
+    taxTotal: Scalars['Int'];
+};
+
 export type OrderAddress = {
     fullName?: Maybe<Scalars['String']>;
     company?: Maybe<Scalars['String']>;

+ 18 - 2
packages/core/e2e/graphql/generated-e2e-shop-types.ts

@@ -408,8 +408,6 @@ export enum Permission {
     UpdateSettings = 'UpdateSettings',
     /** Grants permission to delete Settings */
     DeleteSettings = 'DeleteSettings',
-    /** Allows external tools to sync stock levels */
-    SyncInventory = 'SyncInventory',
 }
 
 export type DeletionResponse = {
@@ -1959,6 +1957,7 @@ export type Order = Node & {
     shippingMethod?: Maybe<ShippingMethod>;
     totalBeforeTax: Scalars['Int'];
     total: Scalars['Int'];
+    taxSummary: Array<OrderTaxSummary>;
     history: HistoryEntryList;
     customFields?: Maybe<Scalars['JSON']>;
 };
@@ -1967,6 +1966,19 @@ export type OrderHistoryArgs = {
     options?: Maybe<HistoryEntryListOptions>;
 };
 
+/**
+ * A summary of the taxes being applied to this order, grouped
+ * by taxRate.
+ */
+export type OrderTaxSummary = {
+    /** The taxRate as a percentage */
+    taxRate: Scalars['Float'];
+    /** The total net price or OrderItems to which this taxRate applies */
+    taxBase: Scalars['Int'];
+    /** The total tax being applied to the Order at this taxRate */
+    taxTotal: Scalars['Int'];
+};
+
 export type OrderAddress = {
     fullName?: Maybe<Scalars['String']>;
     company?: Maybe<Scalars['String']>;
@@ -2864,6 +2876,7 @@ export type GetActiveOrderWithPriceDataQuery = {
                     adjustments: Array<Pick<Adjustment, 'amount' | 'type'>>;
                 }
             >;
+            taxSummary: Array<Pick<OrderTaxSummary, 'taxRate' | 'taxBase' | 'taxTotal'>>;
         }
     >;
 };
@@ -3345,6 +3358,9 @@ export namespace GetActiveOrderWithPriceData {
             >['adjustments']
         >[number]
     >;
+    export type TaxSummary = NonNullable<
+        NonNullable<NonNullable<GetActiveOrderWithPriceDataQuery['activeOrder']>['taxSummary']>[number]
+    >;
 }
 
 export namespace AdjustItemQuantity {

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

@@ -323,6 +323,11 @@ export const GET_ACTIVE_ORDER_WITH_PRICE_DATA = gql`
                     type
                 }
             }
+            taxSummary {
+                taxRate
+                taxBase
+                taxTotal
+            }
         }
     }
 `;

+ 53 - 1
packages/core/e2e/order-taxes.e2e-spec.ts

@@ -1,3 +1,4 @@
+/* tslint:disable:no-non-null-assertion */
 import { createErrorResultGuard, createTestEnvironment, ErrorResultGuard } from '@vendure/testing';
 import gql from 'graphql-tag';
 import path from 'path';
@@ -34,7 +35,7 @@ describe('Order taxes', () => {
     beforeAll(async () => {
         await server.init({
             initialData,
-            productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-promotions.csv'),
+            productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-order-taxes.csv'),
             customerCount: 2,
         });
         await adminClient.asSuperAdmin();
@@ -137,4 +138,55 @@ describe('Order taxes', () => {
             ]);
         });
     });
+
+    it('taxSummary works', async () => {
+        await adminClient.query<UpdateChannel.Mutation, UpdateChannel.Variables>(UPDATE_CHANNEL, {
+            input: {
+                id: 'T_1',
+                pricesIncludeTax: false,
+            },
+        });
+        await shopClient.asAnonymousUser();
+        await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(ADD_ITEM_TO_ORDER, {
+            productVariantId: products[0].variants[0].id,
+            quantity: 2,
+        });
+        await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(ADD_ITEM_TO_ORDER, {
+            productVariantId: products[1].variants[0].id,
+            quantity: 2,
+        });
+        await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(ADD_ITEM_TO_ORDER, {
+            productVariantId: products[2].variants[0].id,
+            quantity: 2,
+        });
+
+        const { activeOrder } = await shopClient.query<GetActiveOrderWithPriceData.Query>(
+            GET_ACTIVE_ORDER_WITH_PRICE_DATA,
+        );
+
+        expect(activeOrder?.taxSummary).toEqual([
+            {
+                taxRate: 20,
+                taxBase: 200,
+                taxTotal: 40,
+            },
+            {
+                taxRate: 10,
+                taxBase: 200,
+                taxTotal: 20,
+            },
+            {
+                taxRate: 0,
+                taxBase: 200,
+                taxTotal: 0,
+            },
+        ]);
+
+        // ensure that the summary total add up to the overall totals
+        const taxSummaryBaseTotal = activeOrder!.taxSummary.reduce((total, row) => total + row.taxBase, 0);
+        const taxSummaryTaxTotal = activeOrder!.taxSummary.reduce((total, row) => total + row.taxTotal, 0);
+
+        expect(taxSummaryBaseTotal).toBe(activeOrder?.totalBeforeTax);
+        expect(taxSummaryBaseTotal + taxSummaryTaxTotal).toBe(activeOrder?.total);
+    });
 });

+ 14 - 0
packages/core/src/api/schema/type/order.type.graphql

@@ -28,9 +28,23 @@ type Order implements Node {
     shippingMethod: ShippingMethod
     totalBeforeTax: Int!
     total: Int!
+    taxSummary: [OrderTaxSummary!]!
     history(options: HistoryEntryListOptions): HistoryEntryList!
 }
 
+"""
+A summary of the taxes being applied to this order, grouped
+by taxRate.
+"""
+type OrderTaxSummary {
+    "The taxRate as a percentage"
+    taxRate: Float!
+    "The total net price or OrderItems to which this taxRate applies"
+    taxBase: Int!
+    "The total tax being applied to the Order at this taxRate"
+    taxTotal: Int!
+}
+
 type OrderAddress {
     fullName: String
     company: String

+ 29 - 1
packages/core/src/entity/order/order.entity.ts

@@ -1,4 +1,10 @@
-import { Adjustment, AdjustmentType, CurrencyCode, OrderAddress } from '@vendure/common/lib/generated-types';
+import {
+    Adjustment,
+    AdjustmentType,
+    CurrencyCode,
+    OrderAddress,
+    OrderTaxSummary,
+} from '@vendure/common/lib/generated-types';
 import { DeepPartial, ID } from '@vendure/common/lib/shared-types';
 import { Column, Entity, JoinTable, ManyToMany, ManyToOne, OneToMany } from 'typeorm';
 
@@ -120,6 +126,28 @@ export class Order extends VendureEntity implements ChannelAware, HasCustomField
         return (this.lines || []).reduce((total, line) => total + line.quantity, 0);
     }
 
+    @Calculated()
+    get taxSummary(): OrderTaxSummary[] {
+        const taxRateMap = new Map<number, { base: number; tax: number }>();
+        for (const line of this.lines) {
+            const row = taxRateMap.get(line.taxRate);
+            if (row) {
+                row.tax += line.lineTax;
+                row.base += line.linePrice;
+            } else {
+                taxRateMap.set(line.taxRate, {
+                    tax: line.lineTax,
+                    base: line.linePrice,
+                });
+            }
+        }
+        return Array.from(taxRateMap.entries()).map(([taxRate, row]) => ({
+            taxRate,
+            taxBase: row.base,
+            taxTotal: row.tax,
+        }));
+    }
+
     get promotionAdjustmentsTotal(): number {
         return this.adjustments
             .filter(a => a.type === AdjustmentType.PROMOTION)

+ 1 - 6
packages/dev-server/dev-config.ts

@@ -41,12 +41,7 @@ export const devConfig: VendureConfig = {
         tokenMethod: 'cookie',
         sessionSecret: 'some-secret',
         requireVerification: true,
-        customPermissions: [
-            new PermissionDefinition({
-                name: 'SyncInventory',
-                description: 'Allows external tools to sync stock levels',
-            }),
-        ],
+        customPermissions: [],
     },
     dbConnectionOptions: {
         synchronize: false,

+ 14 - 2
packages/elasticsearch-plugin/e2e/graphql/generated-e2e-elasticsearch-plugin-types.ts

@@ -1171,6 +1171,7 @@ export type Order = Node & {
     shippingMethod?: Maybe<ShippingMethod>;
     totalBeforeTax: Scalars['Int'];
     total: Scalars['Int'];
+    taxSummary: Array<OrderTaxSummary>;
     history: HistoryEntryList;
     customFields?: Maybe<Scalars['JSON']>;
 };
@@ -1860,8 +1861,6 @@ export enum Permission {
     UpdateSettings = 'UpdateSettings',
     /** Grants permission to delete Settings */
     DeleteSettings = 'DeleteSettings',
-    /** Allows external tools to sync stock levels */
-    SyncInventory = 'SyncInventory',
 }
 
 export type DeletionResponse = {
@@ -3076,6 +3075,19 @@ export type ImportInfo = {
     imported: Scalars['Int'];
 };
 
+/**
+ * A summary of the taxes being applied to this order, grouped
+ * by taxRate.
+ */
+export type OrderTaxSummary = {
+    /** The taxRate as a percentage */
+    taxRate: Scalars['Float'];
+    /** The total net price or OrderItems to which this taxRate applies */
+    taxBase: Scalars['Int'];
+    /** The total tax being applied to the Order at this taxRate */
+    taxTotal: Scalars['Int'];
+};
+
 export type OrderAddress = {
     fullName?: Maybe<Scalars['String']>;
     company?: Maybe<Scalars['String']>;

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
schema-admin.json


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
schema-shop.json


برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است