ソースを参照

feat(core): Implement multi-deletion of StockLocations

Michael Bromley 2 年 前
コミット
3ca76b13b8

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

@@ -361,10 +361,12 @@ export type Channel = Node & {
   defaultShippingZone?: Maybe<Zone>;
   defaultTaxZone?: Maybe<Zone>;
   id: Scalars['ID'];
+  /** Not yet used - will be implemented in a future release. */
   outOfStockThreshold?: Maybe<Scalars['Int']>;
   pricesIncludeTax: Scalars['Boolean'];
   seller?: Maybe<Seller>;
   token: Scalars['String'];
+  /** Not yet used - will be implemented in a future release. */
   trackInventory?: Maybe<Scalars['Boolean']>;
   updatedAt: Scalars['DateTime'];
 };
@@ -2636,7 +2638,7 @@ export type Mutation = {
   /** Delete multiple ShippingMethods */
   deleteShippingMethods: Array<DeletionResponse>;
   deleteStockLocation: DeletionResponse;
-  deleteStockLocations: DeletionResponse;
+  deleteStockLocations: Array<DeletionResponse>;
   /** Delete an existing Tag */
   deleteTag: DeletionResponse;
   /** Deletes multiple TaxCategories */

+ 2 - 0
packages/common/src/generated-shop-types.ts

@@ -146,10 +146,12 @@ export type Channel = Node & {
   defaultShippingZone?: Maybe<Zone>;
   defaultTaxZone?: Maybe<Zone>;
   id: Scalars['ID'];
+  /** Not yet used - will be implemented in a future release. */
   outOfStockThreshold?: Maybe<Scalars['Int']>;
   pricesIncludeTax: Scalars['Boolean'];
   seller?: Maybe<Seller>;
   token: Scalars['String'];
+  /** Not yet used - will be implemented in a future release. */
   trackInventory?: Maybe<Scalars['Boolean']>;
   updatedAt: Scalars['DateTime'];
 };

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

@@ -374,10 +374,12 @@ export type Channel = Node & {
   defaultShippingZone?: Maybe<Zone>;
   defaultTaxZone?: Maybe<Zone>;
   id: Scalars['ID'];
+  /** Not yet used - will be implemented in a future release. */
   outOfStockThreshold?: Maybe<Scalars['Int']>;
   pricesIncludeTax: Scalars['Boolean'];
   seller?: Maybe<Seller>;
   token: Scalars['String'];
+  /** Not yet used - will be implemented in a future release. */
   trackInventory?: Maybe<Scalars['Boolean']>;
   updatedAt: Scalars['DateTime'];
 };
@@ -2718,7 +2720,7 @@ export type Mutation = {
   /** Delete multiple ShippingMethods */
   deleteShippingMethods: Array<DeletionResponse>;
   deleteStockLocation: DeletionResponse;
-  deleteStockLocations: DeletionResponse;
+  deleteStockLocations: Array<DeletionResponse>;
   /** Delete an existing Tag */
   deleteTag: DeletionResponse;
   /** Deletes multiple TaxCategories */

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

@@ -361,10 +361,12 @@ export type Channel = Node & {
   defaultShippingZone?: Maybe<Zone>;
   defaultTaxZone?: Maybe<Zone>;
   id: Scalars['ID'];
+  /** Not yet used - will be implemented in a future release. */
   outOfStockThreshold?: Maybe<Scalars['Int']>;
   pricesIncludeTax: Scalars['Boolean'];
   seller?: Maybe<Seller>;
   token: Scalars['String'];
+  /** Not yet used - will be implemented in a future release. */
   trackInventory?: Maybe<Scalars['Boolean']>;
   updatedAt: Scalars['DateTime'];
 };
@@ -2636,7 +2638,7 @@ export type Mutation = {
   /** Delete multiple ShippingMethods */
   deleteShippingMethods: Array<DeletionResponse>;
   deleteStockLocation: DeletionResponse;
-  deleteStockLocations: DeletionResponse;
+  deleteStockLocations: Array<DeletionResponse>;
   /** Delete an existing Tag */
   deleteTag: DeletionResponse;
   /** Deletes multiple TaxCategories */

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

@@ -139,10 +139,12 @@ export type Channel = Node & {
   defaultShippingZone?: Maybe<Zone>;
   defaultTaxZone?: Maybe<Zone>;
   id: Scalars['ID'];
+  /** Not yet used - will be implemented in a future release. */
   outOfStockThreshold?: Maybe<Scalars['Int']>;
   pricesIncludeTax: Scalars['Boolean'];
   seller?: Maybe<Seller>;
   token: Scalars['String'];
+  /** Not yet used - will be implemented in a future release. */
   trackInventory?: Maybe<Scalars['Boolean']>;
   updatedAt: Scalars['DateTime'];
 };

+ 15 - 0
packages/core/e2e/stock-location.e2e-spec.ts

@@ -151,6 +151,21 @@ describe('Stock location', () => {
         });
     });
 
+    it('cannot delete last remaining stock location', async () => {
+        const { deleteStockLocation } = await adminClient.query(TestDeleteStockLocationDocument, {
+            input: {
+                id: defaultStockLocationId,
+            },
+        });
+
+        expect(deleteStockLocation.result).toBe(DeletionResult.NOT_DELETED);
+        expect(deleteStockLocation.message).toBe('The last remaining StockLocation cannot be deleted');
+
+        const { stockLocations } = await adminClient.query(TestGetStockLocationsListDocument);
+
+        expect(stockLocations.items.length).toBe(1);
+    });
+
     describe('multi channel', () => {
         const SECOND_CHANNEL_TOKEN = 'second_channel_token';
         let channelStockLocationId: string;

+ 8 - 0
packages/core/src/api/resolvers/admin/stock-location.resolver.ts

@@ -3,6 +3,7 @@ import {
     MutationAssignStockLocationsToChannelArgs,
     MutationCreateStockLocationArgs,
     MutationDeleteStockLocationArgs,
+    MutationDeleteStockLocationsArgs,
     MutationRemoveStockLocationsFromChannelArgs,
     MutationUpdateStockLocationArgs,
     Permission,
@@ -53,6 +54,13 @@ export class StockLocationResolver {
         return this.stockLocationService.delete(ctx, args.input);
     }
 
+    @Mutation()
+    @Transaction()
+    @Allow(Permission.DeleteStockLocation)
+    deleteStockLocations(@Ctx() ctx: RequestContext, @Args() args: MutationDeleteStockLocationsArgs) {
+        return Promise.all(args.input.map(input => this.stockLocationService.delete(ctx, input)));
+    }
+
     @Mutation()
     @Transaction()
     @Allow(Permission.CreateStockLocation)

+ 1 - 1
packages/core/src/api/schema/admin-api/stock-location.api.graphql

@@ -7,7 +7,7 @@ type Mutation {
     createStockLocation(input: CreateStockLocationInput!): StockLocation!
     updateStockLocation(input: UpdateStockLocationInput!): StockLocation!
     deleteStockLocation(input: DeleteStockLocationInput!): DeletionResponse!
-    deleteStockLocations(input: [DeleteStockLocationInput!]!): DeletionResponse!
+    deleteStockLocations(input: [DeleteStockLocationInput!]!): [DeletionResponse!]!
 
     "Assigns StockLocations to the specified Channel"
     assignStockLocationsToChannel(input: AssignStockLocationsToChannelInput!): [StockLocation!]!

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

@@ -100,6 +100,7 @@
   },
   "message": {
     "asset-to-be-deleted-is-featured": "The selected {assetCount, plural, one {Asset is} other {Assets are}} featured by {products, plural, =0 {} one {1 Product} other {# Products}} {variants, plural, =0 {} one { 1 ProductVariant} other { # ProductVariants}} {collections, plural, =0 {} one { 1 Collection} other { # Collections}}",
+    "cannot-delete-last-stock-location": "The last remaining StockLocation cannot be deleted",
     "cannot-remove-tax-category-due-to-tax-rates": "Cannot remove TaxCategory \"{ name }\" as it is referenced by {count, plural, one {1 TaxRate} other {# TaxRates}}",
     "cannot-transition-order-contains-products-which-are-unavailable": "Cannot transition to \"{ toState }\" because the Order contains ProductVariants which are no longer available",
     "cannot-transition-from-arranging-additional-payment": "Cannot transition away from \"ArrangingAdditionalPayment\" unless Order total is covered by Payments",

+ 24 - 2
packages/core/src/service/services/stock-location.service.ts

@@ -13,7 +13,13 @@ import { ID, PaginatedList } from '@vendure/common/lib/shared-types';
 
 import { RelationPaths, RequestContext } from '../../api/index';
 import { RequestContextCacheService } from '../../cache/index';
-import { ForbiddenError, idsAreEqual, ListQueryOptions, UserInputError } from '../../common/index';
+import {
+    EntityNotFoundError,
+    ForbiddenError,
+    idsAreEqual,
+    ListQueryOptions,
+    UserInputError,
+} from '../../common/index';
 import { ConfigService } from '../../config/index';
 import { TransactionalConnection } from '../../connection/index';
 import { OrderLine, StockLevel } from '../../entity/index';
@@ -88,7 +94,23 @@ export class StockLocationService {
     }
 
     async delete(ctx: RequestContext, input: DeleteStockLocationInput): Promise<DeletionResponse> {
-        const stockLocation = await this.connection.getEntityOrThrow(ctx, StockLocation, input.id);
+        const stockLocation = await this.connection.findOneInChannel(
+            ctx,
+            StockLocation,
+            input.id,
+            ctx.channelId,
+        );
+        if (!stockLocation) {
+            throw new EntityNotFoundError('StockLocation', input.id);
+        }
+        // Do not allow the last StockLocation to be deleted
+        const allStockLocations = await this.connection.getRepository(ctx, StockLocation).find();
+        if (allStockLocations.length === 1) {
+            return {
+                result: DeletionResult.NOT_DELETED,
+                message: ctx.translate('message.cannot-delete-last-stock-location'),
+            };
+        }
         if (input.transferToLocationId) {
             // This is inefficient, and it would be nice to be able to do this as a single
             // SQL `update` statement with a nested `select` subquery, but TypeORM doesn't

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

@@ -361,10 +361,12 @@ export type Channel = Node & {
   defaultShippingZone?: Maybe<Zone>;
   defaultTaxZone?: Maybe<Zone>;
   id: Scalars['ID'];
+  /** Not yet used - will be implemented in a future release. */
   outOfStockThreshold?: Maybe<Scalars['Int']>;
   pricesIncludeTax: Scalars['Boolean'];
   seller?: Maybe<Seller>;
   token: Scalars['String'];
+  /** Not yet used - will be implemented in a future release. */
   trackInventory?: Maybe<Scalars['Boolean']>;
   updatedAt: Scalars['DateTime'];
 };
@@ -2636,7 +2638,7 @@ export type Mutation = {
   /** Delete multiple ShippingMethods */
   deleteShippingMethods: Array<DeletionResponse>;
   deleteStockLocation: DeletionResponse;
-  deleteStockLocations: DeletionResponse;
+  deleteStockLocations: Array<DeletionResponse>;
   /** Delete an existing Tag */
   deleteTag: DeletionResponse;
   /** Deletes multiple TaxCategories */

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

@@ -361,10 +361,12 @@ export type Channel = Node & {
   defaultShippingZone?: Maybe<Zone>;
   defaultTaxZone?: Maybe<Zone>;
   id: Scalars['ID'];
+  /** Not yet used - will be implemented in a future release. */
   outOfStockThreshold?: Maybe<Scalars['Int']>;
   pricesIncludeTax: Scalars['Boolean'];
   seller?: Maybe<Seller>;
   token: Scalars['String'];
+  /** Not yet used - will be implemented in a future release. */
   trackInventory?: Maybe<Scalars['Boolean']>;
   updatedAt: Scalars['DateTime'];
 };
@@ -2636,7 +2638,7 @@ export type Mutation = {
   /** Delete multiple ShippingMethods */
   deleteShippingMethods: Array<DeletionResponse>;
   deleteStockLocation: DeletionResponse;
-  deleteStockLocations: DeletionResponse;
+  deleteStockLocations: Array<DeletionResponse>;
   /** Delete an existing Tag */
   deleteTag: DeletionResponse;
   /** Deletes multiple TaxCategories */

+ 2 - 0
packages/payments-plugin/e2e/graphql/generated-shop-types.ts

@@ -139,10 +139,12 @@ export type Channel = Node & {
   defaultShippingZone?: Maybe<Zone>;
   defaultTaxZone?: Maybe<Zone>;
   id: Scalars['ID'];
+  /** Not yet used - will be implemented in a future release. */
   outOfStockThreshold?: Maybe<Scalars['Int']>;
   pricesIncludeTax: Scalars['Boolean'];
   seller?: Maybe<Seller>;
   token: Scalars['String'];
+  /** Not yet used - will be implemented in a future release. */
   trackInventory?: Maybe<Scalars['Boolean']>;
   updatedAt: Scalars['DateTime'];
 };

+ 2 - 0
packages/payments-plugin/src/mollie/graphql/generated-shop-types.ts

@@ -150,10 +150,12 @@ export type Channel = Node & {
   defaultShippingZone?: Maybe<Zone>;
   defaultTaxZone?: Maybe<Zone>;
   id: Scalars['ID'];
+  /** Not yet used - will be implemented in a future release. */
   outOfStockThreshold?: Maybe<Scalars['Int']>;
   pricesIncludeTax: Scalars['Boolean'];
   seller?: Maybe<Seller>;
   token: Scalars['String'];
+  /** Not yet used - will be implemented in a future release. */
   trackInventory?: Maybe<Scalars['Boolean']>;
   updatedAt: Scalars['DateTime'];
 };

ファイルの差分が大きいため隠しています
+ 0 - 0
schema-admin.json


ファイルの差分が大きいため隠しています
+ 0 - 0
schema-shop.json


この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません