Browse Source

feat(server): Implement delete for Zone entity

Relates to #21
Michael Bromley 7 years ago
parent
commit
9c5fbec45f

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


+ 162 - 0
server/e2e/zone.e2e-spec.ts

@@ -0,0 +1,162 @@
+import gql from 'graphql-tag';
+
+import {
+    ADD_MEMBERS_TO_ZONE,
+    CREATE_ZONE,
+    GET_COUNTRY_LIST,
+    GET_ZONE,
+    REMOVE_MEMBERS_FROM_ZONE,
+    UPDATE_ZONE,
+} from '../../admin-ui/src/app/data/definitions/settings-definitions';
+import {
+    AddMembersToZone,
+    CreateZone,
+    DeletionResult,
+    GetCountryList,
+    GetZone,
+    RemoveMembersFromZone,
+    UpdateZone,
+} from '../../shared/generated-types';
+
+import { TEST_SETUP_TIMEOUT_MS } from './config/test-config';
+import { TestClient } from './test-client';
+import { TestServer } from './test-server';
+
+// tslint:disable:no-non-null-assertion
+
+describe('Facet resolver', () => {
+    const client = new TestClient();
+    const server = new TestServer();
+    let countries: GetCountryList.Items[];
+    let zones: Array<{ id: string; name: string }>;
+    let oceania: { id: string; name: string };
+    let pangaea: { id: string; name: string; members: any[] };
+
+    beforeAll(async () => {
+        await server.init({
+            productCount: 2,
+            customerCount: 1,
+        });
+        await client.init();
+
+        const result = await client.query<GetCountryList.Query>(GET_COUNTRY_LIST, {});
+        countries = result.countries.items;
+    }, TEST_SETUP_TIMEOUT_MS);
+
+    afterAll(async () => {
+        await server.destroy();
+    });
+
+    it('zones', async () => {
+        const result = await client.query(GET_ZONE_LIST);
+
+        expect(result.zones.length).toBe(6);
+        zones = result.zones;
+        oceania = zones[0];
+    });
+
+    it('zone', async () => {
+        const result = await client.query<GetZone.Query, GetZone.Variables>(GET_ZONE, {
+            id: zones[0].id,
+        });
+
+        expect(result.zone!.name).toBe('Oceania');
+    });
+
+    it('updateZone', async () => {
+        const result = await client.query<UpdateZone.Mutation, UpdateZone.Variables>(UPDATE_ZONE, {
+            input: {
+                id: oceania.id,
+                name: 'oceania2',
+            },
+        });
+
+        expect(result.updateZone.name).toBe('oceania2');
+    });
+
+    it('createZone', async () => {
+        const result = await client.query<CreateZone.Mutation, CreateZone.Variables>(CREATE_ZONE, {
+            input: {
+                name: 'Pangaea',
+                memberIds: [countries[0].id, countries[1].id],
+            },
+        });
+
+        pangaea = result.createZone;
+        expect(pangaea.name).toBe('Pangaea');
+        expect(pangaea.members.map(m => m.name)).toEqual([countries[0].name, countries[1].name]);
+    });
+
+    it('addMembersToZone', async () => {
+        const result = await client.query<AddMembersToZone.Mutation, AddMembersToZone.Variables>(
+            ADD_MEMBERS_TO_ZONE,
+            {
+                zoneId: oceania.id,
+                memberIds: [countries[2].id, countries[3].id],
+            },
+        );
+
+        expect(result.addMembersToZone.members.map(m => m.name)).toEqual([
+            countries[0].name,
+            countries[2].name,
+            countries[3].name,
+        ]);
+    });
+
+    it('removeMembersFromZone', async () => {
+        const result = await client.query<RemoveMembersFromZone.Mutation, RemoveMembersFromZone.Variables>(
+            REMOVE_MEMBERS_FROM_ZONE,
+            {
+                zoneId: oceania.id,
+                memberIds: [countries[0].id, countries[2].id],
+            },
+        );
+
+        expect(result.removeMembersFromZone.members.map(m => m.name)).toEqual([countries[3].name]);
+    });
+
+    describe('deletion', () => {
+        it('deletes Zone not used in any TaxRate', async () => {
+            const result1 = await client.query(DELETE_ZONE, { id: pangaea.id });
+
+            expect(result1.deleteZone).toEqual({
+                result: DeletionResult.DELETED,
+                message: '',
+            });
+
+            const result2 = await client.query(GET_ZONE_LIST, {});
+            expect(result2.zones.find(c => c.id === pangaea.id)).toBeUndefined();
+        });
+
+        it('does not delete Zone that is used in one or more TaxRates', async () => {
+            const result1 = await client.query(DELETE_ZONE, { id: oceania.id });
+
+            expect(result1.deleteZone).toEqual({
+                result: DeletionResult.NOT_DELETED,
+                message:
+                    'The selected Zone cannot be deleted as it is used in the following TaxRates: Standard Tax for Oceania',
+            });
+
+            const result2 = await client.query(GET_ZONE_LIST, {});
+            expect(result2.zones.find(c => c.id === oceania.id)).not.toBeUndefined();
+        });
+    });
+});
+
+const DELETE_ZONE = gql`
+    mutation DeleteZone($id: ID!) {
+        deleteZone(id: $id) {
+            result
+            message
+        }
+    }
+`;
+
+const GET_ZONE_LIST = gql`
+    query GetZones {
+        zones {
+            id
+            name
+        }
+    }
+`;

+ 11 - 0
server/src/api/resolvers/zone.resolver.ts

@@ -3,6 +3,8 @@ import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
 import {
     AddMembersToZoneMutationArgs,
     CreateZoneMutationArgs,
+    DeleteZoneMutationArgs,
+    DeletionResponse,
     Permission,
     RemoveMembersFromZoneMutationArgs,
     UpdateZoneMutationArgs,
@@ -44,6 +46,15 @@ export class ZoneResolver {
         return this.zoneService.update(ctx, args.input);
     }
 
+    @Mutation()
+    @Allow(Permission.DeleteSettings)
+    async deleteZone(
+        @Ctx() ctx: RequestContext,
+        @Args() args: DeleteZoneMutationArgs,
+    ): Promise<DeletionResponse> {
+        return this.zoneService.delete(ctx, args.id);
+    }
+
     @Mutation()
     @Allow(Permission.UpdateSettings)
     @Decode('zoneId', 'memberIds')

+ 6 - 0
server/src/api/types/zone.api.graphql

@@ -6,10 +6,16 @@ type Query {
 type Mutation {
     "Create a new Zone"
     createZone(input: CreateZoneInput!): Zone!
+
     "Update an existing Zone"
     updateZone(input: UpdateZoneInput!): Zone!
+
+    "Delete a Zone"
+    deleteZone(id: ID!): DeletionResponse!
+
     "Add members to a Zone"
     addMembersToZone(zoneId: ID!, memberIds: [ID!]!): Zone!
+
     "Remove members from a Zone"
     removeMembersFromZone(zoneId: ID!, memberIds: [ID!]!): Zone!
 }

+ 2 - 1
server/src/i18n/messages/en.json

@@ -27,6 +27,7 @@
     "facet-force-deleted": "The Facet was deleted and its FacetValues were removed from {products, plural, =0 {} one {1 Product} other {# Products}}{both, select, both { , } single {}}{variants, plural, =0 {} one {1 ProductVariant} other {# ProductVariants}}",
     "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}}. To delete anyway, set \"force: true\"",
     "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}}. To delete anyway, set \"force: true\""
+    "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}}. To delete anyway, set \"force: true\"",
+    "zone-used-in-tax-rates": "The selected Zone cannot be deleted as it is used in the following TaxRates: { taxRateNames }"
   }
 }

+ 29 - 0
server/src/service/services/zone.service.ts

@@ -5,6 +5,8 @@ import { Connection } from 'typeorm';
 import {
     AddMembersToZoneMutationArgs,
     CreateZoneInput,
+    DeletionResponse,
+    DeletionResult,
     RemoveMembersFromZoneMutationArgs,
     UpdateZoneInput,
 } from '../../../../shared/generated-types';
@@ -12,6 +14,7 @@ import { ID } from '../../../../shared/shared-types';
 import { unique } from '../../../../shared/unique';
 import { RequestContext } from '../../api/common/request-context';
 import { assertFound } from '../../common/utils';
+import { 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';
@@ -69,6 +72,32 @@ export class ZoneService implements OnModuleInit {
         return assertFound(this.findOne(ctx, zone.id));
     }
 
+    async delete(ctx: RequestContext, id: ID): Promise<DeletionResponse> {
+        const zone = await getEntityOrThrow(this.connection, Zone, id);
+
+        const taxRatesUsingZone = await this.connection
+            .getRepository(TaxRate)
+            .createQueryBuilder('taxRate')
+            .where('taxRate.zoneId = :id', { id })
+            .getMany();
+
+        if (0 < taxRatesUsingZone.length) {
+            return {
+                result: DeletionResult.NOT_DELETED,
+                message: ctx.translate('message.zone-used-in-tax-rates', {
+                    taxRateNames: taxRatesUsingZone.map(t => t.name).join(', '),
+                }),
+            };
+        } else {
+            await this.connection.getRepository(Zone).remove(zone);
+            await this.updateZonesCache();
+            return {
+                result: DeletionResult.DELETED,
+                message: '',
+            };
+        }
+    }
+
     async addMembersToZone(ctx: RequestContext, input: AddMembersToZoneMutationArgs): Promise<Zone> {
         const countries = await this.getCountriesFromIds(input.memberIds);
         const zone = await getEntityOrThrow(this.connection, Zone, input.zoneId, { relations: ['members'] });

+ 15 - 0
shared/generated-types.ts

@@ -733,6 +733,7 @@ export interface Mutation {
     updateTaxRate: TaxRate;
     createZone: Zone;
     updateZone: Zone;
+    deleteZone: DeletionResponse;
     addMembersToZone: Zone;
     removeMembersFromZone: Zone;
     requestStarted: number;
@@ -1827,6 +1828,9 @@ export interface CreateZoneMutationArgs {
 export interface UpdateZoneMutationArgs {
     input: UpdateZoneInput;
 }
+export interface DeleteZoneMutationArgs {
+    id: string;
+}
 export interface AddMembersToZoneMutationArgs {
     zoneId: string;
     memberIds: string[];
@@ -4477,6 +4481,7 @@ export namespace MutationResolvers {
         updateTaxRate?: UpdateTaxRateResolver<TaxRate, any, Context>;
         createZone?: CreateZoneResolver<Zone, any, Context>;
         updateZone?: UpdateZoneResolver<Zone, any, Context>;
+        deleteZone?: DeleteZoneResolver<DeletionResponse, any, Context>;
         addMembersToZone?: AddMembersToZoneResolver<Zone, any, Context>;
         removeMembersFromZone?: RemoveMembersFromZoneResolver<Zone, any, Context>;
         requestStarted?: RequestStartedResolver<number, any, Context>;
@@ -5142,6 +5147,16 @@ export namespace MutationResolvers {
         input: UpdateZoneInput;
     }
 
+    export type DeleteZoneResolver<R = DeletionResponse, Parent = any, Context = any> = Resolver<
+        R,
+        Parent,
+        Context,
+        DeleteZoneArgs
+    >;
+    export interface DeleteZoneArgs {
+        id: string;
+    }
+
     export type AddMembersToZoneResolver<R = Zone, Parent = any, Context = any> = Resolver<
         R,
         Parent,

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