Browse Source

fix(core): Allow configurable stock allocation logic

Relates to #550. This commit introduces a StockAllocationStrategy to allow advanced configuration
of stock allocation.
Michael Bromley 5 years ago
parent
commit
782c0f4c3f

+ 2 - 0
packages/core/src/app.module.ts

@@ -127,6 +127,7 @@ export class AppModule implements NestModule, OnApplicationBootstrap, OnApplicat
             priceCalculationStrategy,
             process,
             orderCodeStrategy,
+            stockAllocationStrategy,
         } = this.configService.orderOptions;
         const { entityIdStrategy } = this.configService;
         return [
@@ -144,6 +145,7 @@ export class AppModule implements NestModule, OnApplicationBootstrap, OnApplicat
             entityIdStrategy,
             priceCalculationStrategy,
             ...process,
+            stockAllocationStrategy,
         ];
     }
 

+ 2 - 0
packages/core/src/config/default-config.ts

@@ -18,6 +18,7 @@ import { AutoIncrementIdStrategy } from './entity-id-strategy/auto-increment-id-
 import { DefaultLogger } from './logger/default-logger';
 import { TypeOrmLogger } from './logger/typeorm-logger';
 import { DefaultPriceCalculationStrategy } from './order/default-price-calculation-strategy';
+import { DefaultStockAllocationStrategy } from './order/default-stock-allocation-strategy';
 import { MergeOrdersStrategy } from './order/merge-orders-strategy';
 import { DefaultOrderCodeStrategy } from './order/order-code-strategy';
 import { UseGuestStrategy } from './order/use-guest-strategy';
@@ -107,6 +108,7 @@ export const defaultConfig: RuntimeVendureConfig = {
         mergeStrategy: new MergeOrdersStrategy(),
         checkoutMergeStrategy: new UseGuestStrategy(),
         process: [],
+        stockAllocationStrategy: new DefaultStockAllocationStrategy(),
         orderCodeStrategy: new DefaultOrderCodeStrategy(),
     },
     paymentOptions: {

+ 1 - 0
packages/core/src/config/index.ts

@@ -21,6 +21,7 @@ export * from './order/custom-order-process';
 export * from './order/order-code-strategy';
 export * from './order/order-merge-strategy';
 export * from './order/price-calculation-strategy';
+export * from './order/stock-allocation-strategy';
 export * from './payment-method/example-payment-method-handler';
 export * from './payment-method/payment-method-handler';
 export * from './promotion';

+ 26 - 0
packages/core/src/config/order/default-stock-allocation-strategy.ts

@@ -0,0 +1,26 @@
+import { RequestContext } from '../../api/common/request-context';
+import { Order } from '../../entity/order/order.entity';
+import { OrderState } from '../../service/helpers/order-state-machine/order-state';
+
+import { StockAllocationStrategy } from './stock-allocation-strategy';
+
+/**
+ * @description
+ * Allocates stock when the Order transitions from `ArrangingPayment` to either
+ * `PaymentAuthorized` or `PaymentSettled`.
+ *
+ * @docsCategory order
+ */
+export class DefaultStockAllocationStrategy implements StockAllocationStrategy {
+    shouldAllocateStock(
+        ctx: RequestContext,
+        fromState: OrderState,
+        toState: OrderState,
+        order: Order,
+    ): boolean | Promise<boolean> {
+        return (
+            fromState === 'ArrangingPayment' &&
+            (toState === 'PaymentAuthorized' || toState === 'PaymentSettled')
+        );
+    }
+}

+ 25 - 0
packages/core/src/config/order/stock-allocation-strategy.ts

@@ -0,0 +1,25 @@
+import { RequestContext } from '../../api/common/request-context';
+import { InjectableStrategy } from '../../common/types/injectable-strategy';
+import { Order } from '../../entity/order/order.entity';
+import { OrderState } from '../../service/helpers/order-state-machine/order-state';
+
+/**
+ * @description
+ * This strategy is responsible for deciding at which stage in the order process
+ * stock will be allocated.
+ *
+ * @docsCategory order
+ */
+export interface StockAllocationStrategy extends InjectableStrategy {
+    /**
+     * @description
+     * This method is called whenever and Order transitions from one state to another.
+     * If it resolves to `true`, then stock will be allocated for this order.
+     */
+    shouldAllocateStock(
+        ctx: RequestContext,
+        fromState: OrderState,
+        toState: OrderState,
+        order: Order,
+    ): boolean | Promise<boolean>;
+}

+ 8 - 0
packages/core/src/config/vendure-config.ts

@@ -27,6 +27,7 @@ import { CustomOrderProcess } from './order/custom-order-process';
 import { OrderCodeStrategy } from './order/order-code-strategy';
 import { OrderMergeStrategy } from './order/order-merge-strategy';
 import { PriceCalculationStrategy } from './order/price-calculation-strategy';
+import { StockAllocationStrategy } from './order/stock-allocation-strategy';
 import { PaymentMethodHandler } from './payment-method/payment-method-handler';
 import { PromotionAction } from './promotion/promotion-action';
 import { PromotionCondition } from './promotion/promotion-condition';
@@ -398,6 +399,13 @@ export interface OrderOptions {
      * @default []
      */
     process?: Array<CustomOrderProcess<any>>;
+    /**
+     * @description
+     * Determines the point of the order process at which stock gets allocated.
+     *
+     * @default DefaultStockAllocationStrategy
+     */
+    stockAllocationStrategy: StockAllocationStrategy;
     /**
      * @description
      * Defines the strategy used to merge a guest Order and an existing Order when

+ 17 - 7
packages/core/src/service/helpers/order-state-machine/order-state-machine.ts

@@ -119,22 +119,32 @@ export class OrderStateMachine {
      * Specific business logic to be executed after Order state transition completes.
      */
     private async onTransitionEnd(fromState: OrderState, toState: OrderState, data: OrderTransitionData) {
+        const { ctx, order } = data;
+        const { stockAllocationStrategy } = this.configService.orderOptions;
         if (
             fromState === 'ArrangingPayment' &&
             (toState === 'PaymentAuthorized' || toState === 'PaymentSettled')
         ) {
-            data.order.active = false;
-            data.order.orderPlacedAt = new Date();
-            await this.stockMovementService.createAllocationsForOrder(data.ctx, data.order);
-            await this.promotionService.addPromotionsToOrder(data.ctx, data.order);
+            order.active = false;
+            order.orderPlacedAt = new Date();
+            await this.promotionService.addPromotionsToOrder(ctx, order);
+        }
+        const shouldAllocateStock = await stockAllocationStrategy.shouldAllocateStock(
+            ctx,
+            fromState,
+            toState,
+            order,
+        );
+        if (shouldAllocateStock) {
+            await this.stockMovementService.createAllocationsForOrder(ctx, order);
         }
         if (toState === 'Cancelled') {
-            data.order.active = false;
+            order.active = false;
         }
         await this.historyService.createHistoryEntryForOrder({
-            orderId: data.order.id,
+            orderId: order.id,
             type: HistoryEntryType.ORDER_STATE_TRANSITION,
-            ctx: data.ctx,
+            ctx,
             data: {
                 from: fromState,
                 to: toState,