Browse Source

fix(core): Allow stockOnHand adjustments to match outOfStockThreshold

Fixes #1483
Michael Bromley 3 years ago
parent
commit
77239b2fbe

+ 53 - 1
packages/core/e2e/stock-control.e2e-spec.ts

@@ -209,7 +209,7 @@ describe('Stock control', () => {
         });
 
         it(
-            'attempting to set a negative stockOnHand throws',
+            'attempting to set stockOnHand below saleable stock level throws',
             assertThrowsWithMessage(async () => {
                 const result = await adminClient.query<UpdateStock.Mutation, UpdateStock.Variables>(
                     UPDATE_STOCK_ON_HAND,
@@ -942,6 +942,58 @@ describe('Stock control', () => {
             expect(variant.stockAllocated).toBe(0);
         });
 
+        describe('adjusting stockOnHand with negative outOfStockThreshold', () => {
+            const variant1Id = 'T_1';
+            beforeAll(async () => {
+                await adminClient.query<UpdateProductVariants.Mutation, UpdateProductVariants.Variables>(
+                    UPDATE_PRODUCT_VARIANTS,
+                    {
+                        input: [
+                            {
+                                id: variant1Id,
+                                stockOnHand: 0,
+                                outOfStockThreshold: -20,
+                                trackInventory: GlobalFlag.TRUE,
+                                useGlobalOutOfStockThreshold: false,
+                            },
+                        ],
+                    },
+                );
+            });
+
+            it(
+                'attempting to set stockOnHand below outOfStockThreshold throws',
+                assertThrowsWithMessage(async () => {
+                    const result = await adminClient.query<UpdateStock.Mutation, UpdateStock.Variables>(
+                        UPDATE_STOCK_ON_HAND,
+                        {
+                            input: [
+                                {
+                                    id: variant1Id,
+                                    stockOnHand: -21,
+                                },
+                            ] as UpdateProductVariantInput[],
+                        },
+                    );
+                }, 'stockOnHand cannot be a negative value'),
+            );
+
+            it('can set negative stockOnHand that is not less than outOfStockThreshold', async () => {
+                const result = await adminClient.query<UpdateStock.Mutation, UpdateStock.Variables>(
+                    UPDATE_STOCK_ON_HAND,
+                    {
+                        input: [
+                            {
+                                id: variant1Id,
+                                stockOnHand: -10,
+                            },
+                        ] as UpdateProductVariantInput[],
+                    },
+                );
+                expect(result.updateProductVariants[0]!.stockOnHand).toBe(-10);
+            });
+        });
+
         describe('edge cases', () => {
             const variant5Id = 'T_5';
             const variant6Id = 'T_6';

+ 19 - 1
packages/core/src/service/services/product-variant.service.ts

@@ -318,6 +318,23 @@ export class ProductVariantService {
         return variant.stockOnHand - variant.stockAllocated - effectiveOutOfStockThreshold;
     }
 
+    private async getOutOfStockThreshold(ctx: RequestContext, variant: ProductVariant): Promise<number> {
+        const { outOfStockThreshold, trackInventory } = await this.requestCache.get(
+            ctx,
+            'globalSettings',
+            () => this.globalSettingsService.getSettings(ctx),
+        );
+
+        const inventoryNotTracked =
+            variant.trackInventory === GlobalFlag.FALSE ||
+            (variant.trackInventory === GlobalFlag.INHERIT && trackInventory === false);
+        if (inventoryNotTracked) {
+            return 0;
+        } else {
+            return variant.useGlobalOutOfStockThreshold ? outOfStockThreshold : variant.outOfStockThreshold;
+        }
+    }
+
     /**
      * @description
      * Returns the stockLevel to display to the customer, as specified by the configured
@@ -446,7 +463,8 @@ export class ProductVariantService {
             channelId: ctx.channelId,
             relations: ['facetValues', 'facetValues.channels'],
         });
-        if (input.stockOnHand && input.stockOnHand < 0) {
+        const outOfStockThreshold = await this.getOutOfStockThreshold(ctx, existingVariant);
+        if (input.stockOnHand && input.stockOnHand < outOfStockThreshold) {
             throw new UserInputError('error.stockonhand-cannot-be-negative');
         }
         const inputWithoutPrice = {