瀏覽代碼

refactor(core): Replace Array.reduce summations with dedicated util fn

Michael Bromley 5 年之前
父節點
當前提交
4ceac0fdae

+ 10 - 3
packages/common/src/shared-utils.ts

@@ -29,11 +29,18 @@ type NumericPropsOf<T> = {
     [K in keyof T]: T[K] extends number ? K : never;
 }[keyof T];
 
+type OnlyNumerics<T> = {
+    [K in NumericPropsOf<T>]: T[K];
+};
+
 /**
- * Adds up all the values of a given property of a list of objects.
+ * Adds up all the values of a given numeric property of a list of objects.
  */
-function summate<T, K extends NumericPropsOf<T>>(items: T[], prop: K): number {
-    return items.reduce((sum, i) => sum + (i[prop] as any), 0);
+export function summate<T extends OnlyNumerics<T>>(
+    items: T[] | undefined | null,
+    prop: keyof OnlyNumerics<T>,
+): number {
+    return (items || []).reduce((sum, i) => sum + i[prop], 0);
 }
 
 /**

+ 3 - 2
packages/core/e2e/order-taxes.e2e-spec.ts

@@ -1,4 +1,5 @@
 /* tslint:disable:no-non-null-assertion */
+import { summate } from '@vendure/common/lib/shared-utils';
 import { createErrorResultGuard, createTestEnvironment, ErrorResultGuard } from '@vendure/testing';
 import gql from 'graphql-tag';
 import path from 'path';
@@ -175,8 +176,8 @@ describe('Order taxes', () => {
         ]);
 
         // 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);
+        const taxSummaryBaseTotal = summate(activeOrder!.taxSummary, 'taxBase');
+        const taxSummaryTaxTotal = summate(activeOrder!.taxSummary, 'taxTotal');
 
         expect(taxSummaryBaseTotal).toBe(activeOrder?.total);
         expect(taxSummaryBaseTotal + taxSummaryTaxTotal).toBe(activeOrder?.totalWithTax);

+ 2 - 1
packages/core/src/entity/order-item/order-item.entity.ts

@@ -1,5 +1,6 @@
 import { Adjustment, AdjustmentType, TaxLine } from '@vendure/common/lib/generated-types';
 import { DeepPartial, ID } from '@vendure/common/lib/shared-types';
+import { summate } from '@vendure/common/lib/shared-utils';
 import { Column, Entity, JoinTable, ManyToMany, ManyToOne, OneToOne } from 'typeorm';
 
 import { Calculated } from '../../common/calculated-decorator';
@@ -77,7 +78,7 @@ export class OrderItem extends VendureEntity {
      */
     @Calculated()
     get taxRate(): number {
-        return (this.taxLines || []).reduce((total, l) => total + l.taxRate, 0);
+        return summate(this.taxLines || [], 'taxRate');
     }
 
     @Calculated()

+ 20 - 12
packages/core/src/entity/order-line/order-line.entity.ts

@@ -1,5 +1,6 @@
 import { Adjustment, AdjustmentType, TaxLine } from '@vendure/common/lib/generated-types';
 import { DeepPartial } from '@vendure/common/lib/shared-types';
+import { summate } from '@vendure/common/lib/shared-utils';
 import { Column, Entity, ManyToOne, OneToMany } from 'typeorm';
 
 import { Calculated } from '../../common/calculated-decorator';
@@ -45,12 +46,12 @@ export class OrderLine extends VendureEntity implements HasCustomFields {
 
     @Calculated()
     get unitPrice(): number {
-        return this.activeItems.length ? this.activeItems[0].unitPrice : 0;
+        return this.firstActiveItemPropOr('unitPrice', 0);
     }
 
     @Calculated()
     get unitPriceWithTax(): number {
-        return this.activeItems.length ? this.activeItems[0].unitPriceWithTax : 0;
+        return this.firstActiveItemPropOr('unitPriceWithTax', 0);
     }
 
     @Calculated()
@@ -68,32 +69,32 @@ export class OrderLine extends VendureEntity implements HasCustomFields {
 
     @Calculated()
     get taxLines(): TaxLine[] {
-        return this.activeItems.length ? this.activeItems[0].taxLines : [];
+        return this.firstActiveItemPropOr('taxLines', []);
     }
 
     @Calculated()
     get taxRate(): number {
-        return this.activeItems.length ? this.activeItems[0].taxRate : 0;
+        return this.firstActiveItemPropOr('taxRate', 0);
     }
 
     @Calculated()
     get linePrice(): number {
-        return this.activeItems.reduce((total, item) => total + item.unitPrice, 0);
+        return summate(this.activeItems, 'unitPrice');
     }
 
     @Calculated()
     get linePriceWithTax(): number {
-        return this.activeItems.reduce((total, item) => total + item.unitPriceWithTax, 0);
+        return summate(this.activeItems, 'unitPriceWithTax');
     }
 
     @Calculated()
     get discountedLinePrice(): number {
-        return this.activeItems.reduce((total, item) => total + item.discountedUnitPrice, 0);
+        return summate(this.activeItems, 'discountedUnitPrice');
     }
 
     @Calculated()
     get discountedLinePriceWithTax(): number {
-        return this.activeItems.reduce((total, item) => total + item.discountedUnitPriceWithTax, 0);
+        return summate(this.activeItems, 'discountedUnitPriceWithTax');
     }
 
     @Calculated()
@@ -106,22 +107,22 @@ export class OrderLine extends VendureEntity implements HasCustomFields {
 
     @Calculated()
     get lineTax(): number {
-        return this.activeItems.reduce((total, item) => total + item.unitTax, 0);
+        return summate(this.activeItems, 'unitTax');
     }
 
     @Calculated()
     get proratedLinePrice(): number {
-        return this.activeItems.reduce((total, item) => total + item.proratedUnitPrice, 0);
+        return summate(this.activeItems, 'proratedUnitPrice');
     }
 
     @Calculated()
     get proratedLinePriceWithTax(): number {
-        return this.activeItems.reduce((total, item) => total + item.proratedUnitPriceWithTax, 0);
+        return summate(this.activeItems, 'proratedUnitPriceWithTax');
     }
 
     @Calculated()
     get proratedLineTax(): number {
-        return this.activeItems.reduce((total, item) => total + item.proratedUnitTax, 0);
+        return summate(this.activeItems, 'proratedUnitTax');
     }
 
     /**
@@ -138,4 +139,11 @@ export class OrderLine extends VendureEntity implements HasCustomFields {
     clearAdjustments(type?: AdjustmentType) {
         this.activeItems.forEach(item => item.clearAdjustments(type));
     }
+
+    private firstActiveItemPropOr<K extends keyof OrderItem>(
+        prop: K,
+        defaultVal: OrderItem[K],
+    ): OrderItem[K] {
+        return this.activeItems.length ? this.activeItems[0][prop] : defaultVal;
+    }
 }

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

@@ -1,5 +1,6 @@
 import { Adjustment, CurrencyCode, OrderAddress, OrderTaxSummary } from '@vendure/common/lib/generated-types';
 import { DeepPartial, ID } from '@vendure/common/lib/shared-types';
+import { summate } from '@vendure/common/lib/shared-utils';
 import { Column, Entity, JoinTable, ManyToMany, ManyToOne, OneToMany } from 'typeorm';
 
 import { Calculated } from '../../common/calculated-decorator';
@@ -126,7 +127,7 @@ export class Order extends VendureEntity implements ChannelAware, HasCustomField
 
     @Calculated()
     get totalQuantity(): number {
-        return (this.lines || []).reduce((total, line) => total + line.quantity, 0);
+        return summate(this.lines, 'quantity');
     }
 
     @Calculated()

+ 5 - 4
packages/core/src/service/helpers/order-calculator/order-calculator.spec.ts

@@ -1,6 +1,7 @@
 import { Test } from '@nestjs/testing';
 import { AdjustmentType, LanguageCode } from '@vendure/common/lib/generated-types';
 import { Omit } from '@vendure/common/lib/omit';
+import { summate } from '@vendure/common/lib/shared-utils';
 
 import { RequestContext } from '../../../api/common/request-context';
 import { PromotionItemAction, PromotionOrderAction } from '../../../config';
@@ -648,16 +649,16 @@ describe('OrderCalculator', () => {
      */
     function assertOrderTotalsAddUp(order: Order) {
         for (const line of order.lines) {
-            const itemUnitPriceSum = line.items.reduce((sum, i) => sum + i.unitPrice, 0);
+            const itemUnitPriceSum = summate(line.items, 'unitPrice');
             expect(line.linePrice).toBe(itemUnitPriceSum);
-            const itemUnitPriceWithTaxSum = line.items.reduce((sum, i) => sum + i.unitPriceWithTax, 0);
+            const itemUnitPriceWithTaxSum = summate(line.items, 'unitPriceWithTax');
             expect(line.linePriceWithTax).toBe(itemUnitPriceWithTaxSum);
         }
-        const taxableLinePriceSum = order.lines.reduce((sum, l) => sum + l.proratedLinePrice, 0);
+        const taxableLinePriceSum = summate(order.lines, 'proratedLinePrice');
         expect(order.subTotal).toBe(taxableLinePriceSum);
 
         // Make sure the customer-facing totals also add up
-        const displayPriceWithTaxSum = order.lines.reduce((sum, l) => sum + l.discountedLinePriceWithTax, 0);
+        const displayPriceWithTaxSum = summate(order.lines, 'discountedLinePriceWithTax');
         const orderDiscountsSum = order.discounts
             .filter(d => d.type === AdjustmentType.DISTRIBUTED_ORDER_PROMOTION)
             .reduce((sum, d) => sum + d.amount, 0);

+ 4 - 6
packages/core/src/service/services/order.service.ts

@@ -27,7 +27,7 @@ import {
     UpdateOrderNoteInput,
 } from '@vendure/common/lib/generated-types';
 import { ID, PaginatedList } from '@vendure/common/lib/shared-types';
-import { notNullOrUndefined } from '@vendure/common/lib/shared-utils';
+import { notNullOrUndefined, summate } from '@vendure/common/lib/shared-utils';
 import { unique } from '@vendure/common/lib/unique';
 import { doc } from 'prettier';
 
@@ -709,7 +709,7 @@ export class OrderService {
             !input.lines ||
             input.lines.length === 0 ||
             input.lines.length === 0 ||
-            input.lines.reduce((total, line) => total + line.quantity, 0) === 0
+            summate(input.lines, 'quantity') === 0
         ) {
             return new EmptyOrderLineSelectionError();
         }
@@ -842,7 +842,7 @@ export class OrderService {
         input: CancelOrderInput,
         lines: OrderLineInput[],
     ) {
-        if (lines.length === 0 || lines.reduce((total, line) => total + line.quantity, 0) === 0) {
+        if (lines.length === 0 || summate(lines, 'quantity') === 0) {
             return new EmptyOrderLineSelectionError();
         }
         const ordersAndItems = await this.getOrdersAndItemsFromLines(ctx, lines, i => !i.cancelled);
@@ -889,9 +889,7 @@ export class OrderService {
         input: RefundOrderInput,
     ): Promise<ErrorResultUnion<RefundOrderResult, Refund>> {
         if (
-            (!input.lines ||
-                input.lines.length === 0 ||
-                input.lines.reduce((total, line) => total + line.quantity, 0) === 0) &&
+            (!input.lines || input.lines.length === 0 || summate(input.lines, 'quantity') === 0) &&
             input.shipping === 0
         ) {
             return new NothingToRefundError();

+ 2 - 2
packages/core/src/service/services/payment-method.service.ts

@@ -7,7 +7,7 @@ import {
 } from '@vendure/common/lib/generated-types';
 import { omit } from '@vendure/common/lib/omit';
 import { ConfigArgType, ID, PaginatedList } from '@vendure/common/lib/shared-types';
-import { assertNever } from '@vendure/common/lib/shared-utils';
+import { assertNever, summate } from '@vendure/common/lib/shared-utils';
 
 import { RequestContext } from '../../api/common/request-context';
 import { UserInputError } from '../../common/error/errors';
@@ -106,7 +106,7 @@ export class PaymentMethodService {
         payment: Payment,
     ): Promise<Refund | RefundStateTransitionError> {
         const { paymentMethod, handler } = await this.getMethodAndHandler(ctx, payment.method);
-        const itemAmount = items.reduce((sum, item) => sum + item.unitPriceWithTax, 0);
+        const itemAmount = summate(items, 'unitPriceWithTax');
         const refundAmount = itemAmount + input.shipping + input.adjustment;
         let refund = new Refund({
             payment,