Browse Source

feat(core): Publish StockMovementEvent

Closes #902
Michael Bromley 4 years ago
parent
commit
0a71723eda

+ 8 - 0
packages/core/src/entity/stock-movement/allocation.entity.ts

@@ -6,6 +6,14 @@ import { OrderLine } from '../order-line/order-line.entity';
 
 import { StockMovement } from './stock-movement.entity';
 
+/**
+ * @description
+ * An Allocation is created for each ProductVariant in an Order when the checkout is completed
+ * (as configured by the {@link StockAllocationStrategy}. This prevents stock being sold twice.
+ *
+ * @docsCategory entities
+ * @docsPage StockMovement
+ */
 @ChildEntity()
 export class Allocation extends StockMovement {
     readonly type = StockMovementType.ALLOCATION;

+ 7 - 0
packages/core/src/entity/stock-movement/cancellation.entity.ts

@@ -6,6 +6,13 @@ import { OrderItem } from '../order-item/order-item.entity';
 
 import { StockMovement } from './stock-movement.entity';
 
+/**
+ * @description
+ * A Cancellation is created when OrderItems from a fulfilled Order are cancelled.
+ *
+ * @docsCategory entities
+ * @docsPage StockMovement
+ */
 @ChildEntity()
 export class Cancellation extends StockMovement {
     readonly type = StockMovementType.CANCELLATION;

+ 8 - 0
packages/core/src/entity/stock-movement/release.entity.ts

@@ -6,6 +6,14 @@ import { OrderItem } from '../order-item/order-item.entity';
 
 import { StockMovement } from './stock-movement.entity';
 
+/**
+ * @description
+ * A Release is created when OrderItems which have been allocated (but not yet fulfilled)
+ * are cancelled.
+ *
+ * @docsCategory entities
+ * @docsPage StockMovement
+ */
 @ChildEntity()
 export class Release extends StockMovement {
     readonly type = StockMovementType.RELEASE;

+ 7 - 0
packages/core/src/entity/stock-movement/sale.entity.ts

@@ -6,6 +6,13 @@ import { OrderLine } from '../order-line/order-line.entity';
 
 import { StockMovement } from './stock-movement.entity';
 
+/**
+ * @description
+ * A Sale is created when OrderItems are fulfilled.
+ *
+ * @docsCategory entities
+ * @docsPage StockMovement
+ */
 @ChildEntity()
 export class Sale extends StockMovement {
     readonly type = StockMovementType.SALE;

+ 7 - 0
packages/core/src/entity/stock-movement/stock-adjustment.entity.ts

@@ -4,6 +4,13 @@ import { ChildEntity } from 'typeorm';
 
 import { StockMovement } from './stock-movement.entity';
 
+/**
+ * @description
+ * A StockAdjustment is created when the `stockOnHand` level of a ProductVariant is manually adjusted.
+ *
+ * @docsCategory entities
+ * @docsPage StockMovement
+ */
 @ChildEntity()
 export class StockAdjustment extends StockMovement {
     readonly type = StockMovementType.ADJUSTMENT;

+ 2 - 0
packages/core/src/entity/stock-movement/stock-movement.entity.ts

@@ -10,6 +10,8 @@ import { ProductVariant } from '../product-variant/product-variant.entity';
  * or out.
  *
  * @docsCategory entities
+ * @docsPage StockMovement
+ * @docsWeight 0
  */
 @Entity()
 @TableInheritance({ column: { type: 'varchar', name: 'discriminator' } })

+ 22 - 0
packages/core/src/event-bus/events/stock-movement-event.ts

@@ -0,0 +1,22 @@
+import { StockMovementType } from '@vendure/common/lib/generated-types';
+
+import { RequestContext } from '../../api/common/request-context';
+import { StockMovement } from '../../entity/stock-movement/stock-movement.entity';
+import { VendureEvent } from '../vendure-event';
+
+/**
+ * @description
+ * This event is fired whenever a {@link StockMovement} entity is created, which occurs when the saleable
+ * stock level of a ProductVariant is altered due to things like sales, manual adjustments, and cancellations.
+ *
+ * @docsCategory events
+ * @docsPage Event Types
+ */
+export class StockMovementEvent extends VendureEvent {
+    public readonly type: StockMovementType;
+
+    constructor(public ctx: RequestContext, public stockMovements: StockMovement[]) {
+        super();
+        this.type = stockMovements[0]?.type;
+    }
+}

+ 1 - 0
packages/core/src/event-bus/index.ts

@@ -22,4 +22,5 @@ export * from './events/product-channel-event';
 export * from './events/product-variant-event';
 export * from './events/product-variant-channel-event';
 export * from './events/refund-state-transition-event';
+export * from './events/stock-movement-event';
 export * from './events/tax-rate-modification-event';

+ 31 - 9
packages/core/src/service/services/stock-movement.service.ts

@@ -17,6 +17,8 @@ import { Release } from '../../entity/stock-movement/release.entity';
 import { Sale } from '../../entity/stock-movement/sale.entity';
 import { StockAdjustment } from '../../entity/stock-movement/stock-adjustment.entity';
 import { StockMovement } from '../../entity/stock-movement/stock-movement.entity';
+import { EventBus } from '../../event-bus/event-bus';
+import { StockMovementEvent } from '../../event-bus/events/stock-movement-event';
 import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-builder';
 import { TransactionalConnection } from '../transaction/transactional-connection';
 
@@ -32,6 +34,7 @@ export class StockMovementService {
         private connection: TransactionalConnection,
         private listQueryBuilder: ListQueryBuilder,
         private globalSettingsService: GlobalSettingsService,
+        private eventBus: EventBus,
     ) {}
 
     getStockMovementsByProductVariantId(
@@ -63,11 +66,14 @@ export class StockMovementService {
         }
         const delta = newStockLevel - oldStockLevel;
 
-        const adjustment = new StockAdjustment({
-            quantity: delta,
-            productVariant: { id: productVariantId },
-        });
-        return this.connection.getRepository(ctx, StockAdjustment).save(adjustment);
+        const adjustment = await this.connection.getRepository(ctx, StockAdjustment).save(
+            new StockAdjustment({
+                quantity: delta,
+                productVariant: { id: productVariantId },
+            }),
+        );
+        this.eventBus.publish(new StockMovementEvent(ctx, [adjustment]));
+        return adjustment;
     }
 
     async createAllocationsForOrder(ctx: RequestContext, order: Order): Promise<Allocation[]> {
@@ -92,7 +98,11 @@ export class StockMovementService {
                     .save(productVariant, { reload: false });
             }
         }
-        return this.connection.getRepository(ctx, Allocation).save(allocations);
+        const savedAllocations = await this.connection.getRepository(ctx, Allocation).save(allocations);
+        if (savedAllocations.length) {
+            this.eventBus.publish(new StockMovementEvent(ctx, savedAllocations));
+        }
+        return savedAllocations;
     }
 
     async createSalesForOrder(ctx: RequestContext, orderItems: OrderItem[]): Promise<Sale[]> {
@@ -131,7 +141,11 @@ export class StockMovementService {
                     .save(productVariant, { reload: false });
             }
         }
-        return this.connection.getRepository(ctx, Sale).save(sales);
+        const savedSales = await this.connection.getRepository(ctx, Sale).save(sales);
+        if (savedSales.length) {
+            this.eventBus.publish(new StockMovementEvent(ctx, savedSales));
+        }
+        return savedSales;
     }
 
     async createCancellationsForOrderItems(ctx: RequestContext, items: OrderItem[]): Promise<Cancellation[]> {
@@ -168,7 +182,11 @@ export class StockMovementService {
                     .save(productVariant, { reload: false });
             }
         }
-        return this.connection.getRepository(ctx, Cancellation).save(cancellations);
+        const savedCancellations = await this.connection.getRepository(ctx, Cancellation).save(cancellations);
+        if (savedCancellations.length) {
+            this.eventBus.publish(new StockMovementEvent(ctx, savedCancellations));
+        }
+        return savedCancellations;
     }
 
     async createReleasesForOrderItems(ctx: RequestContext, items: OrderItem[]): Promise<Release[]> {
@@ -205,7 +223,11 @@ export class StockMovementService {
                     .save(productVariant, { reload: false });
             }
         }
-        return this.connection.getRepository(ctx, Release).save(releases);
+        const savedReleases = await this.connection.getRepository(ctx, Release).save(releases);
+        if (savedReleases.length) {
+            this.eventBus.publish(new StockMovementEvent(ctx, savedReleases));
+        }
+        return savedReleases;
     }
 
     private trackInventoryForVariant(variant: ProductVariant, globalTrackInventory: boolean): boolean {