Skip to content

Commit

Permalink
feat(core): Warn when deleting a Zone used as a Channel default
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelbromley committed Apr 22, 2020
1 parent 4ace4ed commit 945c36d
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 33 deletions.
42 changes: 16 additions & 26 deletions packages/core/e2e/channel.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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';

Expand Down Expand Up @@ -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);
});

Expand All @@ -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,
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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,
Expand All @@ -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),
);
});

Expand All @@ -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(
Expand Down Expand Up @@ -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']);
});
});

Expand Down Expand Up @@ -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,
Expand All @@ -429,15 +430,15 @@ 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,
GetProductWithVariants.Variables
>(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']);
});
});

Expand All @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions packages/core/e2e/graphql/generated-e2e-admin-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
4 changes: 2 additions & 2 deletions packages/core/e2e/graphql/generated-e2e-shop-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
11 changes: 11 additions & 0 deletions packages/core/e2e/graphql/shared-definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
`;
52 changes: 50 additions & 2 deletions packages/core/e2e/zone.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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

Expand Down Expand Up @@ -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();
});
});
});

Expand Down
1 change: 1 addition & 0 deletions packages/core/src/i18n/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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 }"
}
}
18 changes: 17 additions & 1 deletion packages/core/src/service/services/zone.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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')
Expand Down

0 comments on commit 945c36d

Please sign in to comment.