Browse Source

fix(core): Ensure FacetValues also get assigned to channel

Michael Bromley 3 years ago
parent
commit
1a2639e22f

+ 1 - 0
packages/asset-server-plugin/e2e/graphql/generated-e2e-asset-server-plugin-types.ts

@@ -1451,6 +1451,7 @@ export type FacetFilterParameter = {
 export type FacetInUseError = ErrorResult & {
   errorCode: ErrorCode;
   message: Scalars['String'];
+  facetCode: Scalars['String'];
   productCount: Scalars['Int'];
   variantCount: Scalars['Int'];
 };

+ 1 - 0
packages/common/src/generated-types.ts

@@ -1497,6 +1497,7 @@ export type FacetInUseError = ErrorResult & {
   __typename?: 'FacetInUseError';
   errorCode: ErrorCode;
   message: Scalars['String'];
+  facetCode: Scalars['String'];
   productCount: Scalars['Int'];
   variantCount: Scalars['Int'];
 };

+ 2 - 2
packages/core/e2e/facet.e2e-spec.ts

@@ -374,7 +374,7 @@ describe('Facet resolver', () => {
 
             expect(result1.deleteFacet).toEqual({
                 result: DeletionResult.NOT_DELETED,
-                message: `The selected Facet includes FacetValues which are assigned to 1 Product`,
+                message: `The Facet "speaker-type" includes FacetValues which are assigned to 1 Product`,
             });
 
             expect(result2.facet).not.toBe(null);
@@ -668,7 +668,7 @@ describe('Facet resolver', () => {
                 {
                     errorCode: 'FACET_IN_USE_ERROR',
                     message:
-                        'The selected Facet includes FacetValues which are assigned to 1 Product 1 ProductVariant',
+                        'The Facet "channel-facet" includes FacetValues which are assigned to 1 Product 1 ProductVariant',
                     productCount: 1,
                     variantCount: 1,
                 },

+ 1 - 0
packages/core/e2e/graphql/generated-e2e-admin-types.ts

@@ -1451,6 +1451,7 @@ export type FacetFilterParameter = {
 export type FacetInUseError = ErrorResult & {
   errorCode: ErrorCode;
   message: Scalars['String'];
+  facetCode: Scalars['String'];
   productCount: Scalars['Int'];
   variantCount: Scalars['Int'];
 };

+ 1 - 0
packages/core/src/api/schema/admin-api/facet.api.graphql

@@ -91,6 +91,7 @@ input RemoveFacetsFromChannelInput {
 type FacetInUseError implements ErrorResult {
     errorCode: ErrorCode!
     message: String!
+    facetCode: String!
     productCount: Int!
     variantCount: Int!
 }

+ 1 - 0
packages/core/src/common/error/generated-graphql-admin-errors.ts

@@ -133,6 +133,7 @@ export class FacetInUseError extends ErrorResult {
   readonly errorCode = 'FACET_IN_USE_ERROR' as any;
   readonly message = 'FACET_IN_USE_ERROR';
   constructor(
+    public facetCode: Scalars['String'],
     public productCount: Scalars['Int'],
     public variantCount: Scalars['Int'],
   ) {

+ 3 - 1
packages/core/src/i18n/messages/en.json

@@ -9,6 +9,7 @@
     "cannot-transition-payment-from-to": "Cannot transition Payment from \"{ fromState }\" to \"{ toState }\"",
     "cannot-transition-refund-from-to": "Cannot transition Refund from \"{ fromState }\" to \"{ toState }\"",
     "cannot-transition-fulfillment-from-to": "Cannot transition Fulfillment from \"{ fromState }\" to \"{ toState }\"",
+    "collections-cannot-be-removed-from-default-channel": "Collections cannot be removed from the default Channel",
     "collection-id-or-slug-must-be-provided": "Either the Collection id or slug must be provided",
     "collection-id-slug-mismatch": "The provided id and slug refer to different Collections",
     "conditions-required-for-action": "The PromotionAction '{ action }' requires the following conditions: { conditions }",
@@ -18,6 +19,7 @@
     "default-channel-not-found": "Default channel not found",
     "entity-has-no-translation-in-language": "Translatable entity '{ entityName }' has not been translated into the requested language ({ languageCode })",
     "entity-with-id-not-found": "No { entityName } with the id '{ id }' could be found",
+    "facets-cannot-be-removed-from-default-channel": "Facets cannot be removed from the default Channel",
     "facetfilterinput-invalid-input": "A FacetValueFilterInput object may not specify the 'and' and 'or' fields simultaneously",
     "field-invalid-datetime-range-max": "The custom field '{ name }' value [{ value }] is greater than the maximum [{ max }]",
     "field-invalid-datetime-range-min": "The custom field '{ name }' value [{ value }] is less than the minimum [{ min }]",
@@ -60,7 +62,7 @@
     "CREATE_FULFILLMENT_ERROR": "An error occurred when attempting to create the Fulfillment",
     "EMAIL_ADDRESS_CONFLICT_ERROR": "The email address is not available.",
     "EMPTY_ORDER_LINE_SELECTION_ERROR": "At least one OrderLine must be specified",
-    "FACET_IN_USE_ERROR": "The selected Facet includes FacetValues which are assigned to {productCount, plural, =0 {} one {1 Product} other {# Products}} {variantCount, plural, =0 {} one {1 ProductVariant} other {# ProductVariants}}",
+    "FACET_IN_USE_ERROR": "The Facet \"{ facetCode }\" includes FacetValues which are assigned to {productCount, plural, =0 {} one {1 Product} other {# Products}} {variantCount, plural, =0 {} one {1 ProductVariant} other {# ProductVariants}}",
     "IDENTIFIER_CHANGE_TOKEN_INVALID_ERROR": "Identifier change token not recognized",
     "INELIGIBLE_SHIPPING_METHOD_ERROR": "This Order is not eligible for the selected ShippingMethod",
     "INSUFFICIENT_STOCK_ERROR": "{quantityAvailable, plural, =0 {No items were} one {Only 1 item was} other {Only # items were}} added to the order due to insufficient stock",

+ 22 - 6
packages/core/src/service/services/facet-value.service.ts

@@ -176,24 +176,40 @@ export class FacetValueService {
     async checkFacetValueUsage(
         ctx: RequestContext,
         facetValueIds: ID[],
+        channelId?: ID,
     ): Promise<{ productCount: number; variantCount: number }> {
-        const consumingProducts = await this.connection
+        const consumingProductsQb = this.connection
             .getRepository(ctx, Product)
             .createQueryBuilder('product')
             .leftJoinAndSelect('product.facetValues', 'facetValues')
             .where('facetValues.id IN (:...facetValueIds)', { facetValueIds })
-            .getMany();
+            .andWhere('product.deletedAt IS NULL');
 
-        const consumingVariants = await this.connection
+        const consumingVariantsQb = this.connection
             .getRepository(ctx, ProductVariant)
             .createQueryBuilder('variant')
             .leftJoinAndSelect('variant.facetValues', 'facetValues')
             .where('facetValues.id IN (:...facetValueIds)', { facetValueIds })
-            .getMany();
+            .andWhere('variant.deletedAt IS NULL');
+
+        if (channelId) {
+            consumingProductsQb
+                .leftJoin('product.channels', 'product_channel')
+                .leftJoin('facetValues.channels', 'channel')
+                .andWhere('product_channel.id = :channelId')
+                .andWhere('channel.id = :channelId')
+                .setParameter('channelId', channelId);
+            consumingVariantsQb
+                .leftJoin('variant.channels', 'variant_channel')
+                .leftJoin('facetValues.channels', 'channel')
+                .andWhere('variant_channel.id = :channelId')
+                .andWhere('channel.id = :channelId')
+                .setParameter('channelId', channelId);
+        }
 
         return {
-            productCount: consumingProducts.length,
-            variantCount: consumingVariants.length,
+            productCount: await consumingProductsQb.getCount(),
+            variantCount: await consumingVariantsQb.getCount(),
         };
     }
 }

+ 24 - 14
packages/core/src/service/services/facet.service.ts

@@ -22,6 +22,7 @@ import { ConfigService } from '../../config/config.service';
 import { TransactionalConnection } from '../../connection/transactional-connection';
 import { FacetTranslation } from '../../entity/facet/facet-translation.entity';
 import { Facet } from '../../entity/facet/facet.entity';
+import { FacetValue } from '../../entity/index';
 import { EventBus } from '../../event-bus';
 import { FacetEvent } from '../../event-bus/events/facet-event';
 import { CustomFieldRelationService } from '../helpers/custom-field-relation/custom-field-relation.service';
@@ -263,13 +264,22 @@ export class FacetService {
         if (!hasPermission) {
             throw new ForbiddenError();
         }
-        const facetsToAssign = await this.connection.getRepository(ctx, Facet).findByIds(input.facetIds);
+        const facetsToAssign = await this.connection
+            .getRepository(ctx, Facet)
+            .findByIds(input.facetIds, { relations: ['values'] });
+        const valuesToAssign = facetsToAssign.reduce(
+            (values, facet) => [...values, ...facet.values],
+            [] as FacetValue[],
+        );
 
-        await Promise.all(
-            facetsToAssign.map(async facet => {
+        await Promise.all<any>([
+            ...facetsToAssign.map(async facet => {
                 return this.channelService.assignToChannels(ctx, Facet, facet.id, [input.channelId]);
             }),
-        );
+            ...valuesToAssign.map(async value =>
+                this.channelService.assignToChannels(ctx, FacetValue, value.id, [input.channelId]),
+            ),
+        ]);
 
         return this.connection
             .findByIdsInChannel(
@@ -301,19 +311,20 @@ export class FacetService {
         if (idsAreEqual(input.channelId, defaultChannel.id)) {
             throw new UserInputError('error.facets-cannot-be-removed-from-default-channel');
         }
-        const facets = await this.connection
+        const facetsToRemove = await this.connection
             .getRepository(ctx, Facet)
             .findByIds(input.facetIds, { relations: ['values'] });
 
         const results: Array<ErrorResultUnion<RemoveFacetFromChannelResult, Facet>> = [];
 
-        for (const facet of facets) {
+        for (const facet of facetsToRemove) {
             let productCount = 0;
             let variantCount = 0;
             if (facet.values.length) {
                 const counts = await this.facetValueService.checkFacetValueUsage(
                     ctx,
                     facet.values.map(fv => fv.id),
+                    input.channelId,
                 );
                 productCount = counts.productCount;
                 variantCount = counts.variantCount;
@@ -323,20 +334,19 @@ export class FacetService {
                 const i18nVars = { products: productCount, variants: variantCount, both };
                 let result: Translated<Facet> | undefined;
 
-                if (!isInUse) {
-                    await this.channelService.removeFromChannels(ctx, Facet, facet.id, [input.channelId]);
-                    result = await this.findOne(ctx, facet.id);
-                    if (result) {
-                        results.push(result);
-                    }
-                } else if (input?.force) {
+                if (!isInUse || input.force) {
                     await this.channelService.removeFromChannels(ctx, Facet, facet.id, [input.channelId]);
+                    await Promise.all(
+                        facet.values.map(fv =>
+                            this.channelService.removeFromChannels(ctx, FacetValue, fv.id, [input.channelId]),
+                        ),
+                    );
                     result = await this.findOne(ctx, facet.id);
                     if (result) {
                         results.push(result);
                     }
                 } else {
-                    results.push(new FacetInUseError(productCount, variantCount));
+                    results.push(new FacetInUseError(facet.code, productCount, variantCount));
                 }
             }
         }

+ 1 - 0
packages/elasticsearch-plugin/e2e/graphql/generated-e2e-elasticsearch-plugin-types.ts

@@ -1451,6 +1451,7 @@ export type FacetFilterParameter = {
 export type FacetInUseError = ErrorResult & {
   errorCode: ErrorCode;
   message: Scalars['String'];
+  facetCode: Scalars['String'];
   productCount: Scalars['Int'];
   variantCount: Scalars['Int'];
 };

+ 1 - 0
packages/payments-plugin/e2e/graphql/generated-admin-types.ts

@@ -1451,6 +1451,7 @@ export type FacetFilterParameter = {
 export type FacetInUseError = ErrorResult & {
   errorCode: ErrorCode;
   message: Scalars['String'];
+  facetCode: Scalars['String'];
   productCount: Scalars['Int'];
   variantCount: Scalars['Int'];
 };

File diff suppressed because it is too large
+ 0 - 0
schema-admin.json


Some files were not shown because too many files changed in this diff