Kaynağa Gözat

feat(server): PromotionAction execute fn can be async

Michael Bromley 7 yıl önce
ebeveyn
işleme
7a86defddc

+ 19 - 2
server/src/config/promotion/default-promotion-actions.ts

@@ -30,7 +30,24 @@ export const buy1Get1Free = new PromotionItemAction({
         }
         return 0;
     },
-    description: 'Discount every item by { discount }%',
+    description: 'Buy 1 get 1 free',
+});
+
+export const discountOnItemWithFacets = new PromotionItemAction({
+    code: 'facet_based_discount',
+    args: { discount: 'percentage', facets: 'facetValueIds' },
+    async execute(orderItem, orderLine, args, { hasFacetValues }) {
+        if (await hasFacetValues(orderLine, args.facets)) {
+            return -orderLine.unitPrice * (args.discount / 100);
+        }
+        return 0;
+    },
+    description: 'Discount products with these facets by { discount }%',
 });
 
-export const defaultPromotionActions = [orderPercentageDiscount, itemPercentageDiscount, buy1Get1Free];
+export const defaultPromotionActions = [
+    orderPercentageDiscount,
+    itemPercentageDiscount,
+    buy1Get1Free,
+    discountOnItemWithFacets,
+];

+ 1 - 1
server/src/config/promotion/default-promotion-conditions.ts

@@ -42,7 +42,7 @@ export const atLeastNOfProduct = new PromotionCondition({
 
 export const atLeastNWithFacets = new PromotionCondition({
     code: 'at_least_n_with_facets',
-    description: 'Buy at least { minimum } with the given facets',
+    description: 'Buy at least { minimum } products with the given facets',
     args: { minimum: 'int', facets: 'facetValueIds' },
     async check(order: Order, args, { hasFacetValues }) {
         let matches = 0;

+ 11 - 8
server/src/config/promotion/promotion-action.ts

@@ -1,22 +1,25 @@
 import { ConfigArg } from '../../../../shared/generated-types';
-
 import { OrderItem } from '../../entity/order-item/order-item.entity';
 import { OrderLine } from '../../entity/order-line/order-line.entity';
 import { Order } from '../../entity/order/order.entity';
 import { argsArrayToHash, ConfigArgs, ConfigArgValues } from '../common/config-args';
 
-export type PromotionActionArgType = 'percentage' | 'money';
+import { PromotionUtils } from './promotion-condition';
+
+export type PromotionActionArgType = 'percentage' | 'money' | 'int' | 'facetValueIds';
 export type PromotionActionArgs = ConfigArgs<PromotionActionArgType>;
 
 export type ExecutePromotionItemActionFn<T extends PromotionActionArgs> = (
     orderItem: OrderItem,
     orderLine: OrderLine,
     args: ConfigArgValues<T>,
-) => number;
+    utils: PromotionUtils,
+) => number | Promise<number>;
 export type ExecutePromotionOrderActionFn<T extends PromotionActionArgs> = (
     order: Order,
     args: ConfigArgValues<T>,
-) => number;
+    utils: PromotionUtils,
+) => number | Promise<number>;
 
 export interface PromotionActionConfig<T extends PromotionActionArgs> {
     args: T;
@@ -55,8 +58,8 @@ export class PromotionItemAction<T extends PromotionActionArgs = {}> extends Pro
         this.executeFn = config.execute;
     }
 
-    execute(orderItem: OrderItem, orderLine: OrderLine, args: ConfigArg[]) {
-        return this.executeFn(orderItem, orderLine, argsArrayToHash(args));
+    execute(orderItem: OrderItem, orderLine: OrderLine, args: ConfigArg[], utils: PromotionUtils) {
+        return this.executeFn(orderItem, orderLine, argsArrayToHash(args), utils);
     }
 }
 
@@ -70,7 +73,7 @@ export class PromotionOrderAction<T extends PromotionActionArgs = {}> extends Pr
         this.executeFn = config.execute;
     }
 
-    execute(order: Order, args: ConfigArg[]) {
-        return this.executeFn(order, argsArrayToHash(args));
+    execute(order: Order, args: ConfigArg[], utils: PromotionUtils) {
+        return this.executeFn(order, argsArrayToHash(args), utils);
     }
 }

+ 26 - 7
server/src/entity/promotion/promotion.entity.ts

@@ -17,6 +17,17 @@ import { OrderItem } from '../order-item/order-item.entity';
 import { OrderLine } from '../order-line/order-line.entity';
 import { Order } from '../order/order.entity';
 
+export interface ApplyOrderItemActionArgs {
+    orderItem: OrderItem;
+    orderLine: OrderLine;
+    utils: PromotionUtils;
+}
+
+export interface ApplyOrderActionArgs {
+    order: Order;
+    utils: PromotionUtils;
+}
+
 @Entity()
 export class Promotion extends AdjustmentSource implements ChannelAware {
     type = AdjustmentType.PROMOTION;
@@ -65,20 +76,22 @@ export class Promotion extends AdjustmentSource implements ChannelAware {
      */
     @Column() priorityScore: number;
 
-    apply(order: Order): Adjustment | undefined;
-    apply(orderItem: OrderItem, orderLine: OrderLine): Adjustment | undefined;
-    apply(orderItemOrOrder: OrderItem | Order, orderLine?: OrderLine): Adjustment | undefined {
+    async apply(args: ApplyOrderActionArgs | ApplyOrderItemActionArgs): Promise<Adjustment | undefined> {
         let amount = 0;
 
         for (const action of this.actions) {
             const promotionAction = this.allActions[action.code];
             if (this.isItemAction(promotionAction)) {
-                if (orderItemOrOrder instanceof OrderItem && orderLine) {
-                    amount += Math.round(promotionAction.execute(orderItemOrOrder, orderLine, action.args));
+                if (this.isOrderItemArg(args)) {
+                    const { orderItem, orderLine, utils } = args;
+                    amount += Math.round(
+                        await promotionAction.execute(orderItem, orderLine, action.args, utils),
+                    );
                 }
             } else {
-                if (orderItemOrOrder instanceof Order) {
-                    amount += Math.round(promotionAction.execute(orderItemOrOrder, action.args));
+                if (!this.isOrderItemArg(args)) {
+                    const { order, utils } = args;
+                    amount += Math.round(await promotionAction.execute(order, action.args, utils));
                 }
             }
         }
@@ -105,4 +118,10 @@ export class Promotion extends AdjustmentSource implements ChannelAware {
     private isItemAction(value: PromotionItemAction | PromotionOrderAction): value is PromotionItemAction {
         return value instanceof PromotionItemAction;
     }
+
+    private isOrderItemArg(
+        value: ApplyOrderItemActionArgs | ApplyOrderActionArgs,
+    ): value is ApplyOrderItemActionArgs {
+        return value.hasOwnProperty('orderItem');
+    }
 }

+ 6 - 2
server/src/service/helpers/order-calculator/order-calculator.ts

@@ -99,7 +99,11 @@ export class OrderCalculator {
                 if (await promotion.test(order, utils)) {
                     for (const item of line.items) {
                         if (applicablePromotions) {
-                            const adjustment = promotion.apply(item, line);
+                            const adjustment = await promotion.apply({
+                                orderItem: item,
+                                orderLine: line,
+                                utils,
+                            });
                             if (adjustment) {
                                 item.pendingAdjustments = item.pendingAdjustments.concat(adjustment);
                             }
@@ -116,7 +120,7 @@ export class OrderCalculator {
                 // re-test the promotion on each iteration, since the order total
                 // may be modified by a previously-applied promotion
                 if (await promotion.test(order, utils)) {
-                    const adjustment = promotion.apply(order);
+                    const adjustment = await promotion.apply({ order, utils });
                     if (adjustment) {
                         order.pendingAdjustments = order.pendingAdjustments.concat(adjustment);
                     }