Просмотр исходного кода

fix(core): Fix product option deletion logic

Michael Bromley 3 лет назад
Родитель
Сommit
1feec2e3b3

+ 1 - 1
packages/core/e2e/payment-method.e2e-spec.ts

@@ -66,7 +66,7 @@ describe('PaymentMethod resolver', () => {
 
     const { server, adminClient, shopClient } = createTestEnvironment({
         ...testConfig(),
-        logger: new DefaultLogger(),
+        // logger: new DefaultLogger(),
         paymentOptions: {
             paymentMethodEligibilityCheckers: [minPriceChecker],
             paymentMethodHandlers: [dummyPaymentHandler],

+ 1 - 1
packages/core/e2e/payment-process.e2e-spec.ts

@@ -133,7 +133,7 @@ describe('Payment process', () => {
 
     const { server, adminClient, shopClient } = createTestEnvironment(
         mergeConfig(testConfig(), {
-            logger: new DefaultLogger(),
+            // logger: new DefaultLogger(),
             orderOptions: {
                 process: [customOrderProcess as any],
                 orderPlacedStrategy: new TestOrderPlacedStrategy(),

+ 2 - 0
packages/core/e2e/product.e2e-spec.ts

@@ -1,6 +1,7 @@
 import { omit } from '@vendure/common/lib/omit';
 import { pick } from '@vendure/common/lib/pick';
 import { notNullOrUndefined } from '@vendure/common/lib/shared-utils';
+import { DefaultLogger } from '@vendure/core';
 import { createErrorResultGuard, createTestEnvironment, ErrorResultGuard } from '@vendure/testing';
 import gql from 'graphql-tag';
 import path from 'path';
@@ -66,6 +67,7 @@ import { assertThrowsWithMessage } from './utils/assert-throws-with-message';
 describe('Product resolver', () => {
     const { server, adminClient, shopClient } = createTestEnvironment({
         ...testConfig(),
+        // logger: new DefaultLogger(),
     });
 
     const removeOptionGuard: ErrorResultGuard<ProductWithOptionsFragment> = createErrorResultGuard(

+ 26 - 20
packages/core/src/service/services/product-option-group.service.ts

@@ -10,10 +10,10 @@ import { FindManyOptions, Like } from 'typeorm';
 import { RequestContext } from '../../api/common/request-context';
 import { RelationPaths } from '../../api/index';
 import { Translated } from '../../common/types/locale-types';
-import { assertFound } from '../../common/utils';
+import { assertFound, idsAreEqual } from '../../common/utils';
 import { Logger } from '../../config/index';
 import { TransactionalConnection } from '../../connection/transactional-connection';
-import { Product, ProductOptionTranslation, ProductVariant } from '../../entity/index';
+import { Product, ProductOption, ProductOptionTranslation, ProductVariant } from '../../entity/index';
 import { ProductOptionGroupTranslation } from '../../entity/product-option-group/product-option-group-translation.entity';
 import { ProductOptionGroup } from '../../entity/product-option-group/product-option-group.entity';
 import { EventBus } from '../../event-bus';
@@ -132,12 +132,7 @@ export class ProductOptionGroupService {
         const optionGroup = await this.connection.getEntityOrThrow(ctx, ProductOptionGroup, id, {
             relations: ['options', 'product'],
         });
-        const inUseByActiveProducts = await this.isInUseByOtherProducts(
-            ctx,
-            optionGroup,
-            productId,
-            'active',
-        );
+        const inUseByActiveProducts = await this.isInUseByOtherProducts(ctx, optionGroup, productId);
         if (0 < inUseByActiveProducts) {
             return {
                 result: DeletionResult.NOT_DELETED,
@@ -155,24 +150,29 @@ export class ProductOptionGroupService {
                 return { result, message };
             }
         }
-        const isInUseBySoftDeletedVariants = await this.isInUseByOtherProducts(
-            ctx,
-            optionGroup,
-            productId,
-            'soft-deleted',
-        );
-        if (0 < isInUseBySoftDeletedVariants) {
+        const hasOptionsWhichAreInUse = await this.groupOptionsAreInUse(ctx, optionGroup);
+        if (0 < hasOptionsWhichAreInUse) {
             // soft delete
             optionGroup.deletedAt = new Date();
             await this.connection.getRepository(ctx, ProductOptionGroup).save(optionGroup, { reload: false });
         } else {
             // hard delete
+
+            const product = await this.connection
+                .getRepository(ctx, Product)
+                .findOne(productId, { relations: ['optionGroups'] });
+            if (product) {
+                product.optionGroups = product.optionGroups.filter(og => !idsAreEqual(og.id, id));
+                await this.connection.getRepository(ctx, Product).save(product, { reload: false });
+            }
+
             // TODO: V2 rely on onDelete: CASCADE rather than this manual loop
             for (const translation of optionGroup.translations) {
                 await this.connection
                     .getRepository(ctx, ProductOptionGroupTranslation)
                     .remove(translation as ProductOptionGroupTranslation);
             }
+
             try {
                 await this.connection.getRepository(ctx, ProductOptionGroup).remove(optionGroup);
             } catch (e) {
@@ -189,17 +189,23 @@ export class ProductOptionGroupService {
         ctx: RequestContext,
         productOptionGroup: ProductOptionGroup,
         targetProductId: ID,
-        productState: 'active' | 'soft-deleted',
     ): Promise<number> {
-        const [products, count] = await this.connection
+        return this.connection
             .getRepository(ctx, Product)
             .createQueryBuilder('product')
             .leftJoin('product.optionGroups', 'optionGroup')
-            .where(productState === 'active' ? 'product.deletedAt IS NULL' : 'product.deletedAt IS NOT NULL')
+            .where('product.deletedAt IS NULL')
             .andWhere('optionGroup.id = :id', { id: productOptionGroup.id })
             .andWhere('product.id != :productId', { productId: targetProductId })
-            .getManyAndCount();
+            .getCount();
+    }
 
-        return count;
+    private async groupOptionsAreInUse(ctx: RequestContext, productOptionGroup: ProductOptionGroup) {
+        return this.connection
+            .getRepository(ctx, ProductVariant)
+            .createQueryBuilder('variant')
+            .leftJoin('variant.options', 'option')
+            .where('option.groupId = :groupId', { groupId: productOptionGroup.id })
+            .getCount();
     }
 }

+ 2 - 3
packages/core/src/service/services/product-option.service.ts

@@ -145,13 +145,12 @@ export class ProductOptionService {
         productOption: ProductOption,
         variantState: 'active' | 'soft-deleted',
     ): Promise<number> {
-        const [variants, count] = await this.connection
+        return this.connection
             .getRepository(ctx, ProductVariant)
             .createQueryBuilder('variant')
             .leftJoin('variant.options', 'option')
             .where(variantState === 'active' ? 'variant.deletedAt IS NULL' : 'variant.deletedAt IS NOT NULL')
             .andWhere('option.id = :id', { id: productOption.id })
-            .getManyAndCount();
-        return count;
+            .getCount();
     }
 }