From ea2b6b05c437c7b61877c4d7cd02e09d8434f800 Mon Sep 17 00:00:00 2001 From: Michael Bromley Date: Tue, 29 Jun 2021 16:00:05 +0200 Subject: [PATCH] feat(core): Check availability of variants when adding to Order Closes #723 --- .../core/e2e/fixtures/e2e-products-full.csv | 70 +++++----- packages/core/e2e/shop-order.e2e-spec.ts | 124 ++++++++++++++++++ packages/core/src/i18n/messages/en.json | 1 + .../order-state-machine.ts | 16 +++ .../src/service/services/order.service.ts | 11 +- 5 files changed, 186 insertions(+), 36 deletions(-) diff --git a/packages/core/e2e/fixtures/e2e-products-full.csv b/packages/core/e2e/fixtures/e2e-products-full.csv index 5fe6beeafc..eadd5aa2dd 100644 --- a/packages/core/e2e/fixtures/e2e-products-full.csv +++ b/packages/core/e2e/fixtures/e2e-products-full.csv @@ -1,35 +1,35 @@ -name,slug,description,assets,facets,optionGroups,optionValues,sku,price,taxCategory,stockOnHand,trackInventory,variantAssets,variantFacets -Laptop,laptop,"Now equipped with seventh-generation Intel Core processors, Laptop is snappier than ever. From daily tasks like launching apps and opening files to more advanced computing, you can power through your day thanks to faster SSDs and Turbo Boost processing up to 3.6GHz.",derick-david-409858-unsplash.jpg,category:electronics|category:computers,screen size|RAM,13 inch|8GB,L2201308,1299.00,standard,100,false,, -,,,,,,15 inch|8GB,L2201508,1399.00,standard,100,false,, -,,,,,,13 inch|16GB,L2201316,2199.00,standard,100,false,, -,,,,,,15 inch|16GB,L2201516,2299.00,standard,100,false,, -Curvy Monitor,curvy-monitor,"Discover a truly immersive viewing experience with this monitor curved more deeply than any other. Wrapping around your field of vision the 1,800 R screencreates a wider field of view, enhances depth perception, and minimises peripheral distractions to draw you deeper in to your content.",alexandru-acea-686569-unsplash.jpg,category:electronics|category:computers,monitor size,24 inch,C24F390,143.74,standard,100,false,, -,,,,,,27 inch,C27F390,169.94,standard,100,false,, -Gaming PC,gaming-pc,"This pc is optimised for gaming, and is also VR ready. The Intel Core-i7 CPU and High Performance GPU give the computer the raw power it needs to function at a high level.",florian-olivo-1166419-unsplash.jpg,category:electronics|category:computers,cpu|HDD,i7-8700|240GB SSD,CGS480VR1063,1087.20,standard,100,false,, -,,,,,,R7-2700|240GB SSD,CGS480VR1064,1099.95,standard,100,false,, -,,,,,,i7-8700|120GB SSD,CGS480VR1065,931.20,standard,100,false,, -,,,,,,R7-2700|120GB SSD,CGS480VR1066,949.20,standard,100,false,, -Hard Drive,hard-drive,"Boost your PC storage with this internal hard drive, designed just for desktop and all-in-one PCs.",vincent-botta-736919-unsplash.jpg,category:electronics|category:computers,HDD capacity,1TB,IHD455T1,37.99,standard,100,false,, -,,,,,,2TB,IHD455T2,53.74,standard,100,false,, -,,,,,,3TB,IHD455T3,78.96,standard,100,false,, -,,,,,,4TB,IHD455T4,92.99,standard,100,false,, -,,,,,,6TB,IHD455T6,134.35,standard,100,false,, -Clacky Keyboard,clacky-keyboard,"Let all your colleagues know that you are typing on this exclusive, colorful klicky-klacky keyboard. Huge travel on each keypress ensures maximum klack on each and every keystroke.",,category:electronics|category:computers,,,A4TKLA45535,74.89,standard,100,false,, -USB Cable,usb-cable,"Solid conductors eliminate strand-interaction distortion and reduce jitter. As the surface is made of high-purity silver, the performance is very close to that of a solid silver cable, but priced much closer to solid copper cable.",,category:electronics|category:computers,,,USBCIN01.5MI,69.00,standard,100,false,, -Instant Camera,instant-camera,"With its nostalgic design and simple point-and-shoot functionality, the Instant Camera is the perfect pick to get started with instant photography.",,category:electronics|category:photo,,,IC22MWDD,174.99,standard,100,false,, -Camera Lens,camera-lens,This lens is a Di type lens using an optical system with improved multi-coating designed to function with digital SLR cameras as well as film cameras.,,category:electronics|category:photo,,,B0012UUP02,104.00,standard,100,false,, -Tripod,tripod,"Capture vivid, professional-style photographs with help from this lightweight tripod. The adjustable-height tripod makes it easy to achieve reliable stability and score just the right angle when going after that award-winning shot.",,category:electronics|category:photo,,,B00XI87KV8,14.98,standard,100,false,, -Slr Camera,slr-camera,"Retro styled, portable in size and built around a powerful 24-megapixel APS-C CMOS sensor, this digital camera is the ideal companion for creative everyday photography. Packed full of high spec features such as an advanced hybrid autofocus system able to keep pace with even the most active subjects, a speedy 6fps continuous-shooting mode, high-resolution electronic viewfinder and intuitive swivelling touchscreen, it brings professional image making into everyone’s grasp.",,category:electronics|category:photo,,,B07D75V44S,521.00,standard,100,false,, -Road Bike,road-bike,"Featuring a full carbon chassis - complete with cyclocross-specific carbon fork - and a component setup geared for hard use on the race circuit, it's got the low weight, exceptional efficiency and brilliant handling you'll need to stay at the front of the pack.",,category:sports equipment,,,RB000844334,2499.00,standard,100,false,, -Skipping Rope,skipping-rope,When you're working out you need a quality rope that doesn't tangle at every couple of jumps and with this sipping rope you won't have this problem.,,category:sports equipment,,,B07CNGXVXT,7.99,standard,100,false,, -Boxing Gloves,boxing-gloves,"Training gloves designed for optimum training. Our gloves promote proper punching technique because they are conformed to the natural shape of your fist. Dense, innovative two-layer foam provides better shock absorbency and full padding on the front, back and wrist to promote proper punching technique.",,category:sports equipment,,,B000ZYLPPU,33.04,standard,100,false,, -Tent,tent,"With tons of space inside (for max. 4 persons), full head height throughout the entire tent and an unusual and striking shape, this tent offers you everything you need.",,category:sports equipment,,,2000023510,214.93,standard,100,false,, -Cruiser Skateboard,cruiser-skateboard,"Based on the 1970s iconic shape, but made to a larger 69cm size, with updated, quality component, these skateboards are great for beginners to learn the foot spacing required, and are perfect for all-day cruising.",,category:sports equipment,,,799872520,24.99,standard,100,false,, -Football,football,"This football features high-contrast graphics for high-visibility during play, while its machine-stitched tpu casing offers consistent performance.",,category:sports equipment,,,SC3137-056,57.07,standard,100,false,, -Running Shoe,running-shoe,"With its ultra-light, uber-responsive magic foam and a carbon fiber plate that feels like it’s propelling you forward, the Running Shoe is ready to push you to victories both large and small",,category:sports equipment,shoe size,Size 40,RS0040,99.99,standard,100,false,, -,,,,,,Size 42,RS0042,99.99,standard,100,false,, -,,,,,,Size 44,RS0044,99.99,standard,100,false,, -,,,,,,Size 46,RS0046,99.99,standard,100,false,, -Spiky Cactus,spiky-cactus,A spiky yet elegant house cactus - perfect for the home or office. Origin and habitat: Probably native only to the Andes of Peru,,category:home & garden|category:plants,,,SC011001,15.50,standard,100,false,, -Orchid,orchid,Gloriously elegant. It can go along with any interior as it is a neutral color and the most popular Phalaenopsis overall. 2 to 3 foot stems host large white flowers that can last for over 2 months.,,category:home & garden|category:plants,,,ROR00221,65.00,standard,100,false,, -Bonsai Tree,bonsai-tree,Excellent semi-evergreen bonsai. Indoors or out but needs some winter protection. All trees sent will leave the nursery in excellent condition and will be of equal quality or better than the photograph shown.,,category:home & garden|category:plants,,,B01MXFLUSV,19.99,standard,100,false,, +name ,slug ,description ,assets ,facets ,optionGroups ,optionValues ,sku ,price ,taxCategory,stockOnHand,trackInventory,variantAssets,variantFacets +Laptop ,laptop ,"Now equipped with seventh-generation Intel Core processors, Laptop is snappier than ever. From daily tasks like launching apps and opening files to more advanced computing, you can power through your day thanks to faster SSDs and Turbo Boost processing up to 3.6GHz." ,derick-david-409858-unsplash.jpg ,category:electronics|category:computers,screen size|RAM,13 inch|8GB ,L2201308 ,1299.00,standard ,100 ,false , , + , , , , , ,15 inch|8GB ,L2201508 ,1399.00,standard ,100 ,false , , + , , , , , ,13 inch|16GB ,L2201316 ,2199.00,standard ,100 ,false , , + , , , , , ,15 inch|16GB ,L2201516 ,2299.00,standard ,100 ,false , , +Curvy Monitor ,curvy-monitor ,"Discover a truly immersive viewing experience with this monitor curved more deeply than any other. Wrapping around your field of vision the 1,800 R screencreates a wider field of view, enhances depth perception, and minimises peripheral distractions to draw you deeper in to your content." ,alexandru-acea-686569-unsplash.jpg,category:electronics|category:computers,monitor size ,24 inch ,C24F390 ,143.74 ,standard ,100 ,false , , + , , , , , ,27 inch ,C27F390 ,169.94 ,standard ,100 ,false , , +Gaming PC ,gaming-pc ,"This pc is optimised for gaming, and is also VR ready. The Intel Core-i7 CPU and High Performance GPU give the computer the raw power it needs to function at a high level." ,florian-olivo-1166419-unsplash.jpg,category:electronics|category:computers,cpu|HDD ,i7-8700|240GB SSD,CGS480VR1063,1087.20,standard ,100 ,false , , + , , , , , ,R7-2700|240GB SSD,CGS480VR1064,1099.95,standard ,100 ,false , , + , , , , , ,i7-8700|120GB SSD,CGS480VR1065,931.20 ,standard ,100 ,false , , + , , , , , ,R7-2700|120GB SSD,CGS480VR1066,949.20 ,standard ,100 ,false , , +Hard Drive ,hard-drive ,"Boost your PC storage with this internal hard drive, designed just for desktop and all-in-one PCs." ,vincent-botta-736919-unsplash.jpg ,category:electronics|category:computers,HDD capacity ,1TB ,IHD455T1 ,37.99 ,standard ,100 ,false , , + , , , , , ,2TB ,IHD455T2 ,53.74 ,standard ,100 ,false , , + , , , , , ,3TB ,IHD455T3 ,78.96 ,standard ,100 ,false , , + , , , , , ,4TB ,IHD455T4 ,92.99 ,standard ,100 ,false , , + , , , , , ,6TB ,IHD455T6 ,134.35 ,standard ,100 ,false , , +Clacky Keyboard ,clacky-keyboard ,"Let all your colleagues know that you are typing on this exclusive, colorful klicky-klacky keyboard. Huge travel on each keypress ensures maximum klack on each and every keystroke." , ,category:electronics|category:computers, , ,A4TKLA45535 ,74.89 ,standard ,100 ,false , , +USB Cable ,usb-cable ,"Solid conductors eliminate strand-interaction distortion and reduce jitter. As the surface is made of high-purity silver, the performance is very close to that of a solid silver cable, but priced much closer to solid copper cable." , ,category:electronics|category:computers, , ,USBCIN01.5MI,69.00 ,standard ,100 ,false , , +Instant Camera ,instant-camera ,"With its nostalgic design and simple point-and-shoot functionality, the Instant Camera is the perfect pick to get started with instant photography." , ,category:electronics|category:photo , , ,IC22MWDD ,174.99 ,standard ,100 ,false , , +Camera Lens ,camera-lens ,This lens is a Di type lens using an optical system with improved multi-coating designed to function with digital SLR cameras as well as film cameras. , ,category:electronics|category:photo , , ,B0012UUP02 ,104.00 ,standard ,100 ,false , , +Tripod ,tripod ,"Capture vivid, professional-style photographs with help from this lightweight tripod. The adjustable-height tripod makes it easy to achieve reliable stability and score just the right angle when going after that award-winning shot." , ,category:electronics|category:photo , , ,B00XI87KV8 ,14.98 ,standard ,100 ,false , , +Slr Camera ,slr-camera ,"Retro styled, portable in size and built around a powerful 24-megapixel APS-C CMOS sensor, this digital camera is the ideal companion for creative everyday photography. Packed full of high spec features such as an advanced hybrid autofocus system able to keep pace with even the most active subjects, a speedy 6fps continuous-shooting mode, high-resolution electronic viewfinder and intuitive swivelling touchscreen, it brings professional image making into everyone’s grasp.", ,category:electronics|category:photo , , ,B07D75V44S ,521.00 ,standard ,100 ,false , , +Road Bike ,road-bike ,"Featuring a full carbon chassis - complete with cyclocross-specific carbon fork - and a component setup geared for hard use on the race circuit, it's got the low weight, exceptional efficiency and brilliant handling you'll need to stay at the front of the pack." , ,category:sports equipment , , ,RB000844334 ,2499.00,standard ,100 ,false , , +Skipping Rope ,skipping-rope ,When you're working out you need a quality rope that doesn't tangle at every couple of jumps and with this sipping rope you won't have this problem. , ,category:sports equipment , , ,B07CNGXVXT ,7.99 ,standard ,100 ,false , , +Boxing Gloves ,boxing-gloves ,"Training gloves designed for optimum training. Our gloves promote proper punching technique because they are conformed to the natural shape of your fist. Dense, innovative two-layer foam provides better shock absorbency and full padding on the front, back and wrist to promote proper punching technique." , ,category:sports equipment , , ,B000ZYLPPU ,33.04 ,standard ,100 ,false , , +Tent ,tent ,"With tons of space inside (for max. 4 persons), full head height throughout the entire tent and an unusual and striking shape, this tent offers you everything you need." , ,category:sports equipment , , ,2000023510 ,214.93 ,standard ,100 ,false , , +Cruiser Skateboard,cruiser-skateboard,"Based on the 1970s iconic shape, but made to a larger 69cm size, with updated, quality component, these skateboards are great for beginners to learn the foot spacing required, and are perfect for all-day cruising." , ,category:sports equipment , , ,799872520 ,24.99 ,standard ,100 ,false , , +Football ,football ,"This football features high-contrast graphics for high-visibility during play, while its machine-stitched tpu casing offers consistent performance." , ,category:sports equipment , , ,SC3137-056 ,57.07 ,standard ,100 ,false , , +Running Shoe ,running-shoe ,"With its ultra-light, uber-responsive magic foam and a carbon fiber plate that feels like it’s propelling you forward, the Running Shoe is ready to push you to victories both large and small" , ,category:sports equipment ,shoe size ,Size 40 ,RS0040 ,99.99 ,standard ,100 ,false , , + , , , , , ,Size 42 ,RS0042 ,99.99 ,standard ,100 ,false , , + , , , , , ,Size 44 ,RS0044 ,99.99 ,standard ,100 ,false , , + , , , , , ,Size 46 ,RS0046 ,99.99 ,standard ,100 ,false , , +Spiky Cactus ,spiky-cactus ,A spiky yet elegant house cactus - perfect for the home or office. Origin and habitat: Probably native only to the Andes of Peru , ,category:home & garden|category:plants , , ,SC011001 ,15.50 ,standard ,100 ,false , , +Orchid ,orchid ,Gloriously elegant. It can go along with any interior as it is a neutral color and the most popular Phalaenopsis overall. 2 to 3 foot stems host large white flowers that can last for over 2 months. , ,category:home & garden|category:plants , , ,ROR00221 ,65.00 ,standard ,100 ,false , , +Bonsai Tree ,bonsai-tree ,Excellent semi-evergreen bonsai. Indoors or out but needs some winter protection. All trees sent will leave the nursery in excellent condition and will be of equal quality or better than the photograph shown. , ,category:home & garden|category:plants , , ,B01MXFLUSV ,19.99 ,standard ,100 ,false , , diff --git a/packages/core/e2e/shop-order.e2e-spec.ts b/packages/core/e2e/shop-order.e2e-spec.ts index 8163ea3a5b..a98f1d8a6a 100644 --- a/packages/core/e2e/shop-order.e2e-spec.ts +++ b/packages/core/e2e/shop-order.e2e-spec.ts @@ -16,10 +16,14 @@ import { import { AttemptLogin, CreateAddressInput, + DeleteProduct, + DeleteProductVariant, GetCountryList, GetCustomer, GetCustomerList, UpdateCountry, + UpdateProduct, + UpdateProductVariants, } from './graphql/generated-e2e-admin-types'; import { ActiveOrderCustomerFragment, @@ -49,10 +53,14 @@ import { } from './graphql/generated-e2e-shop-types'; import { ATTEMPT_LOGIN, + DELETE_PRODUCT, + DELETE_PRODUCT_VARIANT, GET_COUNTRY_LIST, GET_CUSTOMER, GET_CUSTOMER_LIST, UPDATE_COUNTRY, + UPDATE_PRODUCT, + UPDATE_PRODUCT_VARIANTS, } from './graphql/shared-definitions'; import { ADD_ITEM_TO_ORDER, @@ -1651,6 +1659,122 @@ describe('Shop orders', () => { expect(removeAllOrderLines?.lines.length).toBe(0); }); }); + + describe('validation of product variant availability', () => { + const bonsaiProductId = 'T_20'; + const bonsaiVariantId = 'T_34'; + + beforeAll(async () => { + await shopClient.asAnonymousUser(); + }); + + it( + 'addItemToOrder errors when product is disabled', + assertThrowsWithMessage(async () => { + await adminClient.query(UPDATE_PRODUCT, { + input: { + id: bonsaiProductId, + enabled: false, + }, + }); + + await shopClient.query(ADD_ITEM_TO_ORDER, { + productVariantId: bonsaiVariantId, + quantity: 1, + }); + }, `No ProductVariant with the id '34' could be found`), + ); + + it( + 'addItemToOrder errors when product variant is disabled', + assertThrowsWithMessage(async () => { + await adminClient.query(UPDATE_PRODUCT, { + input: { + id: bonsaiProductId, + enabled: true, + }, + }); + await adminClient.query( + UPDATE_PRODUCT_VARIANTS, + { + input: [ + { + id: bonsaiVariantId, + enabled: false, + }, + ], + }, + ); + + await shopClient.query(ADD_ITEM_TO_ORDER, { + productVariantId: bonsaiVariantId, + quantity: 1, + }); + }, `No ProductVariant with the id '34' could be found`), + ); + it( + 'addItemToOrder errors when product is deleted', + assertThrowsWithMessage(async () => { + await adminClient.query(DELETE_PRODUCT, { + id: bonsaiProductId, + }); + + await shopClient.query(ADD_ITEM_TO_ORDER, { + productVariantId: bonsaiVariantId, + quantity: 1, + }); + }, `No ProductVariant with the id '34' could be found`), + ); + it( + 'addItemToOrder errors when product variant is deleted', + assertThrowsWithMessage(async () => { + await adminClient.query( + DELETE_PRODUCT_VARIANT, + { + id: bonsaiVariantId, + }, + ); + + await shopClient.query(ADD_ITEM_TO_ORDER, { + productVariantId: bonsaiVariantId, + quantity: 1, + }); + }, `No ProductVariant with the id '34' could be found`), + ); + + it('errors when transitioning to ArrangingPayment with deleted variant', async () => { + const orchidProductId = 'T_19'; + const orchidVariantId = 'T_33'; + + await shopClient.asUserWithCredentials('marques.sawayn@hotmail.com', 'test'); + const { addItemToOrder } = await shopClient.query< + AddItemToOrder.Mutation, + AddItemToOrder.Variables + >(ADD_ITEM_TO_ORDER, { + productVariantId: orchidVariantId, + quantity: 1, + }); + + orderResultGuard.assertSuccess(addItemToOrder); + + await adminClient.query(DELETE_PRODUCT, { + id: orchidProductId, + }); + + const { transitionOrderToState } = await shopClient.query< + TransitionToState.Mutation, + TransitionToState.Variables + >(TRANSITION_TO_STATE, { + state: 'ArrangingPayment', + }); + orderResultGuard.assertErrorResult(transitionOrderToState); + + expect(transitionOrderToState!.transitionError).toBe( + `Cannot transition to "ArrangingPayment" because the Order contains ProductVariants which are no longer available`, + ); + expect(transitionOrderToState!.errorCode).toBe(ErrorCode.ORDER_STATE_TRANSITION_ERROR); + }); + }); }); const GET_ORDER_CUSTOM_FIELDS = gql` diff --git a/packages/core/src/i18n/messages/en.json b/packages/core/src/i18n/messages/en.json index 45008af695..e7274e8253 100644 --- a/packages/core/src/i18n/messages/en.json +++ b/packages/core/src/i18n/messages/en.json @@ -92,6 +92,7 @@ "message": { "asset-to-be-deleted-is-featured": "The selected {assetCount, plural, one {Asset is} other {Assets are}} featured by {products, plural, =0 {} one {1 Product} other {# Products}} {variants, plural, =0 {} one { 1 ProductVariant} other { # ProductVariants}} {collections, plural, =0 {} one { 1 Collection} other { # Collections}}", "cannot-remove-tax-category-due-to-tax-rates": "Cannot remove TaxCategory \"{ name }\" as it is referenced by {count, plural, one {1 TaxRate} other {# TaxRates}}", + "cannot-transition-order-contains-products-which-are-unavailable": "Cannot transition to \"{ toState }\" because the Order contains ProductVariants which are no longer available", "cannot-transition-from-arranging-additional-payment": "Cannot transition away from \"ArrangingAdditionalPayment\" unless Order total is covered by Payments", "cannot-transition-order-from-to": "Cannot transition Order from \"{ fromState }\" to \"{ toState }\"", "cannot-transition-no-additional-payments-needed": "Cannot transition Order to the \"ArrangingAdditionalPayment\" state as no additional payments are needed", diff --git a/packages/core/src/service/helpers/order-state-machine/order-state-machine.ts b/packages/core/src/service/helpers/order-state-machine/order-state-machine.ts index 9c8da5bab4..375546e070 100644 --- a/packages/core/src/service/helpers/order-state-machine/order-state-machine.ts +++ b/packages/core/src/service/helpers/order-state-machine/order-state-machine.ts @@ -1,6 +1,7 @@ import { Injectable } from '@nestjs/common'; import { HistoryEntryType } from '@vendure/common/lib/generated-types'; import { ID } from '@vendure/common/lib/shared-types'; +import { unique } from '@vendure/common/lib/unique'; import { RequestContext } from '../../../api/common/request-context'; import { IllegalOperationError } from '../../../common/error/errors'; @@ -13,6 +14,7 @@ import { ConfigService } from '../../../config/config.service'; import { OrderModification } from '../../../entity/order-modification/order-modification.entity'; import { Order } from '../../../entity/order/order.entity'; import { Payment } from '../../../entity/payment/payment.entity'; +import { ProductVariant } from '../../../entity/product-variant/product-variant.entity'; import { HistoryService } from '../../services/history.service'; import { PromotionService } from '../../services/promotion.service'; import { StockMovementService } from '../../services/stock-movement.service'; @@ -100,6 +102,20 @@ export class OrderStateMachine { return `message.cannot-transition-from-arranging-additional-payment`; } } + if (fromState === 'AddingItems') { + const variantIds = unique(data.order.lines.map(l => l.productVariant.id)); + const qb = this.connection + .getRepository(data.ctx, ProductVariant) + .createQueryBuilder('variant') + .leftJoin('variant.product', 'product') + .where('variant.deletedAt IS NULL') + .andWhere('product.deletedAt IS NULL') + .andWhere('variant.id IN (:...variantIds)', { variantIds }); + const availableVariants = await qb.getMany(); + if (availableVariants.length !== variantIds.length) { + return `message.cannot-transition-order-contains-products-which-are-unavailable`; + } + } if (toState === 'ArrangingPayment') { if (data.order.lines.length === 0) { return `message.cannot-transition-to-payment-when-order-is-empty`; diff --git a/packages/core/src/service/services/order.service.ts b/packages/core/src/service/services/order.service.ts index 266332a3e4..04a8d75da5 100644 --- a/packages/core/src/service/services/order.service.ts +++ b/packages/core/src/service/services/order.service.ts @@ -384,7 +384,16 @@ export class OrderService { if (validationError) { return validationError; } - const variant = await this.connection.getEntityOrThrow(ctx, ProductVariant, productVariantId); + const variant = await this.connection.getEntityOrThrow(ctx, ProductVariant, productVariantId, { + relations: ['product'], + where: { + enabled: true, + deletedAt: null, + }, + }); + if (variant.product.enabled === false) { + throw new EntityNotFoundError('ProductVariant', productVariantId); + } const correctedQuantity = await this.orderModifier.constrainQuantityToSaleable( ctx, variant,