Browse Source

fix(core): Soft-delete variants when a product is soft-deleted

Fixes #1096
Michael Bromley 4 years ago
parent
commit
ff1ae905fb

+ 26 - 5
packages/core/e2e/product.e2e-spec.ts

@@ -1617,7 +1617,7 @@ describe('Product resolver', () => {
 
 
     describe('deletion', () => {
     describe('deletion', () => {
         let allProducts: GetProductList.Items[];
         let allProducts: GetProductList.Items[];
-        let productToDelete: GetProductList.Items;
+        let productToDelete: GetProductWithVariants.Product;
 
 
         beforeAll(async () => {
         beforeAll(async () => {
             const result = await adminClient.query<GetProductList.Query, GetProductList.Variables>(
             const result = await adminClient.query<GetProductList.Query, GetProductList.Variables>(
@@ -1634,24 +1634,45 @@ describe('Product resolver', () => {
         });
         });
 
 
         it('deletes a product', async () => {
         it('deletes a product', async () => {
-            productToDelete = allProducts[0];
+            const { product } = await adminClient.query<
+                GetProductWithVariants.Query,
+                GetProductWithVariants.Variables
+            >(GET_PRODUCT_WITH_VARIANTS, {
+                id: allProducts[0].id,
+            });
             const result = await adminClient.query<DeleteProduct.Mutation, DeleteProduct.Variables>(
             const result = await adminClient.query<DeleteProduct.Mutation, DeleteProduct.Variables>(
                 DELETE_PRODUCT,
                 DELETE_PRODUCT,
-                { id: productToDelete.id },
+                { id: product!.id },
             );
             );
 
 
             expect(result.deleteProduct).toEqual({ result: DeletionResult.DELETED });
             expect(result.deleteProduct).toEqual({ result: DeletionResult.DELETED });
+
+            productToDelete = product!;
         });
         });
 
 
         it('cannot get a deleted product', async () => {
         it('cannot get a deleted product', async () => {
-            const result = await adminClient.query<
+            const { product } = await adminClient.query<
                 GetProductWithVariants.Query,
                 GetProductWithVariants.Query,
                 GetProductWithVariants.Variables
                 GetProductWithVariants.Variables
             >(GET_PRODUCT_WITH_VARIANTS, {
             >(GET_PRODUCT_WITH_VARIANTS, {
                 id: productToDelete.id,
                 id: productToDelete.id,
             });
             });
 
 
-            expect(result.product).toBe(null);
+            expect(product).toBe(null);
+        });
+
+        // https://github.com/vendure-ecommerce/vendure/issues/1096
+        it('variants of deleted product are also deleted', async () => {
+            for (const variant of productToDelete.variants) {
+                const { productVariant } = await adminClient.query<
+                    GetProductVariant.Query,
+                    GetProductVariant.Variables
+                >(GET_PRODUCT_VARIANT, {
+                    id: variant.id,
+                });
+
+                expect(productVariant).toBe(null);
+            }
         });
         });
 
 
         it('deleted product omitted from list', async () => {
         it('deleted product omitted from list', async () => {

+ 12 - 6
packages/core/src/service/services/product-variant.service.ts

@@ -102,7 +102,10 @@ export class ProductVariantService {
     findOne(ctx: RequestContext, productVariantId: ID): Promise<Translated<ProductVariant> | undefined> {
     findOne(ctx: RequestContext, productVariantId: ID): Promise<Translated<ProductVariant> | undefined> {
         const relations = ['product', 'product.featuredAsset', 'taxCategory'];
         const relations = ['product', 'product.featuredAsset', 'taxCategory'];
         return this.connection
         return this.connection
-            .findOneInChannel(ctx, ProductVariant, productVariantId, ctx.channelId, { relations })
+            .findOneInChannel(ctx, ProductVariant, productVariantId, ctx.channelId, {
+                relations,
+                where: { deletedAt: null },
+            })
             .then(async result => {
             .then(async result => {
                 if (result) {
                 if (result) {
                     return translateDeep(await this.applyChannelPriceAndTax(result, ctx), ctx.languageCode, [
                     return translateDeep(await this.applyChannelPriceAndTax(result, ctx), ctx.languageCode, [
@@ -498,11 +501,14 @@ export class ProductVariantService {
         return this.connection.getRepository(ctx, ProductVariantPrice).save(variantPrice);
         return this.connection.getRepository(ctx, ProductVariantPrice).save(variantPrice);
     }
     }
 
 
-    async softDelete(ctx: RequestContext, id: ID): Promise<DeletionResponse> {
-        const variant = await this.connection.getEntityOrThrow(ctx, ProductVariant, id);
-        variant.deletedAt = new Date();
-        await this.connection.getRepository(ctx, ProductVariant).save(variant, { reload: false });
-        this.eventBus.publish(new ProductVariantEvent(ctx, [variant], 'deleted'));
+    async softDelete(ctx: RequestContext, id: ID | ID[]): Promise<DeletionResponse> {
+        const ids = Array.isArray(id) ? id : [id];
+        const variants = await this.connection.getRepository(ctx, ProductVariant).findByIds(ids);
+        for (const variant of variants) {
+            variant.deletedAt = new Date();
+        }
+        await this.connection.getRepository(ctx, ProductVariant).save(variants, { reload: false });
+        this.eventBus.publish(new ProductVariantEvent(ctx, variants, 'deleted'));
         return {
         return {
             result: DeletionResult.DELETED,
             result: DeletionResult.DELETED,
         };
         };

+ 5 - 0
packages/core/src/service/services/product.service.ts

@@ -220,10 +220,15 @@ export class ProductService {
     async softDelete(ctx: RequestContext, productId: ID): Promise<DeletionResponse> {
     async softDelete(ctx: RequestContext, productId: ID): Promise<DeletionResponse> {
         const product = await this.connection.getEntityOrThrow(ctx, Product, productId, {
         const product = await this.connection.getEntityOrThrow(ctx, Product, productId, {
             channelId: ctx.channelId,
             channelId: ctx.channelId,
+            relations: ['variants'],
         });
         });
         product.deletedAt = new Date();
         product.deletedAt = new Date();
         await this.connection.getRepository(ctx, Product).save(product, { reload: false });
         await this.connection.getRepository(ctx, Product).save(product, { reload: false });
         this.eventBus.publish(new ProductEvent(ctx, product, 'deleted'));
         this.eventBus.publish(new ProductEvent(ctx, product, 'deleted'));
+        await this.productVariantService.softDelete(
+            ctx,
+            product.variants.map(v => v.id),
+        );
         return {
         return {
             result: DeletionResult.DELETED,
             result: DeletionResult.DELETED,
         };
         };