Forráskód Böngészése

feat(core): Warn when deleting a Zone used as a Channel default

Michael Bromley 5 éve
szülő
commit
945c36def4

+ 16 - 26
packages/core/e2e/channel.e2e-spec.ts

@@ -5,7 +5,7 @@ import gql from 'graphql-tag';
 import path from 'path';
 
 import { initialData } from '../../../e2e-common/e2e-initial-data';
-import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config';
+import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
 
 import {
     AssignProductsToChannel,
@@ -34,6 +34,7 @@ import {
     GET_PRODUCT_WITH_VARIANTS,
     ME,
     REMOVE_PRODUCT_FROM_CHANNEL,
+    UPDATE_CHANNEL,
 } from './graphql/shared-definitions';
 import { assertThrowsWithMessage } from './utils/assert-throws-with-message';
 
@@ -102,8 +103,8 @@ describe('Channels', () => {
 
         expect(me!.channels.length).toBe(2);
 
-        const secondChannelData = me!.channels.find((c) => c.token === SECOND_CHANNEL_TOKEN);
-        const nonOwnerPermissions = Object.values(Permission).filter((p) => p !== Permission.Owner);
+        const secondChannelData = me!.channels.find(c => c.token === SECOND_CHANNEL_TOKEN);
+        const nonOwnerPermissions = Object.values(Permission).filter(p => p !== Permission.Owner);
         expect(secondChannelData!.permissions).toEqual(nonOwnerPermissions);
     });
 
@@ -113,7 +114,7 @@ describe('Channels', () => {
 
         expect(me!.channels.length).toBe(2);
 
-        const secondChannelData = me!.channels.find((c) => c.token === SECOND_CHANNEL_TOKEN);
+        const secondChannelData = me!.channels.find(c => c.token === SECOND_CHANNEL_TOKEN);
         expect(me!.channels).toEqual([
             {
                 code: DEFAULT_CHANNEL_CODE,
@@ -172,7 +173,7 @@ describe('Channels', () => {
             },
         });
 
-        expect(createAdministrator.user.roles.map((r) => r.description)).toEqual(['second channel admin']);
+        expect(createAdministrator.user.roles.map(r => r.description)).toEqual(['second channel admin']);
     });
 
     it(
@@ -290,7 +291,7 @@ describe('Channels', () => {
                 },
             });
 
-            expect(assignProductsToChannel[0].channels.map((c) => c.id).sort()).toEqual(['T_1', 'T_2']);
+            expect(assignProductsToChannel[0].channels.map(c => c.id).sort()).toEqual(['T_1', 'T_2']);
             await adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
             const { product } = await adminClient.query<
                 GetProductWithVariants.Query,
@@ -299,12 +300,12 @@ describe('Channels', () => {
                 id: product1.id,
             });
 
-            expect(product!.variants.map((v) => v.price)).toEqual(
-                product1.variants.map((v) => v.price * PRICE_FACTOR),
+            expect(product!.variants.map(v => v.price)).toEqual(
+                product1.variants.map(v => v.price * PRICE_FACTOR),
             );
             // Second Channel is configured to include taxes in price, so they should be the same.
-            expect(product!.variants.map((v) => v.priceWithTax)).toEqual(
-                product1.variants.map((v) => v.price * PRICE_FACTOR),
+            expect(product!.variants.map(v => v.priceWithTax)).toEqual(
+                product1.variants.map(v => v.price * PRICE_FACTOR),
             );
         });
 
@@ -319,7 +320,7 @@ describe('Channels', () => {
                 },
             });
 
-            expect(assignProductsToChannel[0].channels.map((c) => c.id).sort()).toEqual(['T_1', 'T_2']);
+            expect(assignProductsToChannel[0].channels.map(c => c.id).sort()).toEqual(['T_1', 'T_2']);
         });
 
         it(
@@ -350,7 +351,7 @@ describe('Channels', () => {
                 },
             });
 
-            expect(removeProductsFromChannel[0].channels.map((c) => c.id)).toEqual(['T_1']);
+            expect(removeProductsFromChannel[0].channels.map(c => c.id)).toEqual(['T_1']);
         });
     });
 
@@ -417,7 +418,7 @@ describe('Channels', () => {
                 productIds: [PROD_ID],
             },
         });
-        expect(assignProductsToChannel[0].channels.map((c) => c.id).sort()).toEqual(['T_1', 'T_2']);
+        expect(assignProductsToChannel[0].channels.map(c => c.id).sort()).toEqual(['T_1', 'T_2']);
 
         const { deleteChannel } = await adminClient.query<DeleteChannel.Mutation, DeleteChannel.Variables>(
             DELETE_CHANNEL,
@@ -429,7 +430,7 @@ describe('Channels', () => {
         expect(deleteChannel.result).toBe(DeletionResult.DELETED);
 
         const { channels } = await adminClient.query<GetChannels.Query>(GET_CHANNELS);
-        expect(channels.map((c) => c.id).sort()).toEqual(['T_1', 'T_3']);
+        expect(channels.map(c => c.id).sort()).toEqual(['T_1', 'T_3']);
 
         const { product } = await adminClient.query<
             GetProductWithVariants.Query,
@@ -437,7 +438,7 @@ describe('Channels', () => {
         >(GET_PRODUCT_WITH_VARIANTS, {
             id: PROD_ID,
         });
-        expect(product!.channels.map((c) => c.id)).toEqual(['T_1']);
+        expect(product!.channels.map(c => c.id)).toEqual(['T_1']);
     });
 });
 
@@ -451,17 +452,6 @@ const GET_CHANNELS = gql`
     }
 `;
 
-const UPDATE_CHANNEL = gql`
-    mutation UpdateChannel($input: UpdateChannelInput!) {
-        updateChannel(input: $input) {
-            id
-            code
-            defaultLanguageCode
-            currencyCode
-        }
-    }
-`;
-
 const DELETE_CHANNEL = gql`
     mutation DeleteChannel($id: ID!) {
         deleteChannel(id: $id) {

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

@@ -639,9 +639,9 @@ export enum CurrencyCode {
     /** Canadian dollar */
     CAD = 'CAD',
     /** Congolese franc */
-    CHE = 'CHE',
+    CDF = 'CDF',
     /** Swiss franc */
-    CHW = 'CHW',
+    CHF = 'CHF',
     /** Chilean peso */
     CLP = 'CLP',
     /** Renminbi (Chinese) yuan */

+ 2 - 2
packages/core/e2e/graphql/generated-e2e-shop-types.ts

@@ -363,9 +363,9 @@ export enum CurrencyCode {
     /** Canadian dollar */
     CAD = 'CAD',
     /** Congolese franc */
-    CHE = 'CHE',
+    CDF = 'CDF',
     /** Swiss franc */
-    CHW = 'CHW',
+    CHF = 'CHF',
     /** Chilean peso */
     CLP = 'CLP',
     /** Renminbi (Chinese) yuan */

+ 11 - 0
packages/core/e2e/graphql/shared-definitions.ts

@@ -364,3 +364,14 @@ export const DELETE_ASSET = gql`
         }
     }
 `;
+
+export const UPDATE_CHANNEL = gql`
+    mutation UpdateChannel($input: UpdateChannelInput!) {
+        updateChannel(input: $input) {
+            id
+            code
+            defaultLanguageCode
+            currencyCode
+        }
+    }
+`;

+ 50 - 2
packages/core/e2e/zone.e2e-spec.ts

@@ -3,7 +3,7 @@ import gql from 'graphql-tag';
 import path from 'path';
 
 import { initialData } from '../../../e2e-common/e2e-initial-data';
-import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config';
+import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
 
 import { ZONE_FRAGMENT } from './graphql/fragments';
 import {
@@ -15,9 +15,10 @@ import {
     GetZone,
     GetZones,
     RemoveMembersFromZone,
+    UpdateChannel,
     UpdateZone,
 } from './graphql/generated-e2e-admin-types';
-import { GET_COUNTRY_LIST } from './graphql/shared-definitions';
+import { GET_COUNTRY_LIST, UPDATE_CHANNEL } from './graphql/shared-definitions';
 
 // tslint:disable:no-non-null-assertion
 
@@ -140,6 +141,53 @@ describe('Facet resolver', () => {
             const result2 = await adminClient.query<GetZones.Query>(GET_ZONE_LIST);
             expect(result2.zones.find(c => c.id === oceania.id)).not.toBeUndefined();
         });
+
+        it('does not delete Zone that is used as a Channel defaultTaxZone', async () => {
+            await adminClient.query<UpdateChannel.Mutation, UpdateChannel.Variables>(UPDATE_CHANNEL, {
+                input: {
+                    id: 'T_1',
+                    defaultTaxZoneId: oceania.id,
+                },
+            });
+
+            const result1 = await adminClient.query<DeleteZone.Mutation, DeleteZone.Variables>(DELETE_ZONE, {
+                id: oceania.id,
+            });
+
+            expect(result1.deleteZone).toEqual({
+                result: DeletionResult.NOT_DELETED,
+                message:
+                    'The selected Zone cannot be deleted as it used as a default in the following Channels: ' +
+                    '__default_channel__',
+            });
+
+            const result2 = await adminClient.query<GetZones.Query>(GET_ZONE_LIST);
+            expect(result2.zones.find(c => c.id === oceania.id)).not.toBeUndefined();
+        });
+
+        it('does not delete Zone that is used as a Channel defaultShippingZone', async () => {
+            await adminClient.query<UpdateChannel.Mutation, UpdateChannel.Variables>(UPDATE_CHANNEL, {
+                input: {
+                    id: 'T_1',
+                    defaultTaxZoneId: 'T_1',
+                    defaultShippingZoneId: oceania.id,
+                },
+            });
+
+            const result1 = await adminClient.query<DeleteZone.Mutation, DeleteZone.Variables>(DELETE_ZONE, {
+                id: oceania.id,
+            });
+
+            expect(result1.deleteZone).toEqual({
+                result: DeletionResult.NOT_DELETED,
+                message:
+                    'The selected Zone cannot be deleted as it used as a default in the following Channels: ' +
+                    '__default_channel__',
+            });
+
+            const result2 = await adminClient.query<GetZones.Query>(GET_ZONE_LIST);
+            expect(result2.zones.find(c => c.id === oceania.id)).not.toBeUndefined();
+        });
     });
 });
 

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

@@ -80,6 +80,7 @@
     "facet-used": "The selected Facet includes FacetValues which are assigned to {products, plural, =0 {} one {1 Product} other {# Products}}{both, select, both { , } single {}}{variants, plural, =0 {} one {1 ProductVariant} other {# ProductVariants}}",
     "facet-value-force-deleted": "The selected FacetValue was removed from {products, plural, =0 {} one {1 Product} other {# Products}}{both, select, both { , } single {}}{variants, plural, =0 {} one {1 ProductVariant} other {# ProductVariants}} and deleted",
     "facet-value-used": "The selected FacetValue is assigned to {products, plural, =0 {} one {1 Product} other {# Products}}{both, select, both { , } single {}}{variants, plural, =0 {} one {1 ProductVariant} other {# ProductVariants}}",
+    "zone-used-in-channels": "The selected Zone cannot be deleted as it used as a default in the following Channels: { channelCodes }",
     "zone-used-in-tax-rates": "The selected Zone cannot be deleted as it is used in the following TaxRates: { taxRateNames }"
   }
 }

+ 17 - 1
packages/core/src/service/services/zone.service.ts

@@ -14,7 +14,7 @@ import { Connection } from 'typeorm';
 
 import { RequestContext } from '../../api/common/request-context';
 import { assertFound } from '../../common/utils';
-import { TaxRate } from '../../entity';
+import { Channel, TaxRate } from '../../entity';
 import { Country } from '../../entity/country/country.entity';
 import { Zone } from '../../entity/zone/zone.entity';
 import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
@@ -75,6 +75,22 @@ export class ZoneService implements OnModuleInit {
     async delete(ctx: RequestContext, id: ID): Promise<DeletionResponse> {
         const zone = await getEntityOrThrow(this.connection, Zone, id);
 
+        const channelsUsingZone = await this.connection
+            .getRepository(Channel)
+            .createQueryBuilder('channel')
+            .where('channel.defaultTaxZone = :id', { id })
+            .orWhere('channel.defaultShippingZone = :id', { id })
+            .getMany();
+
+        if (0 < channelsUsingZone.length) {
+            return {
+                result: DeletionResult.NOT_DELETED,
+                message: ctx.translate('message.zone-used-in-channels', {
+                    channelCodes: channelsUsingZone.map(t => t.code).join(', '),
+                }),
+            };
+        }
+
         const taxRatesUsingZone = await this.connection
             .getRepository(TaxRate)
             .createQueryBuilder('taxRate')