Browse Source

feat(core): Implement editing & deletion of Order/Customer notes

Relates to #310
Michael Bromley 5 years ago
parent
commit
90bacf52c6

+ 35 - 0
packages/admin-ui/src/lib/core/src/common/generated-types.ts

@@ -1869,10 +1869,12 @@ export type Mutation = {
   deleteCustomerAddress: Scalars['Boolean'];
   /** Delete a CustomerGroup */
   deleteCustomerGroup: DeletionResponse;
+  deleteCustomerNote: DeletionResponse;
   /** Delete an existing Facet */
   deleteFacet: DeletionResponse;
   /** Delete one or more FacetValues */
   deleteFacetValues: Array<DeletionResponse>;
+  deleteOrderNote: DeletionResponse;
   /** Delete a Product */
   deleteProduct: DeletionResponse;
   /** Delete a ProductVariant */
@@ -1930,11 +1932,13 @@ export type Mutation = {
   updateCustomerAddress: Address;
   /** Update an existing CustomerGroup */
   updateCustomerGroup: CustomerGroup;
+  updateCustomerNote: HistoryEntry;
   /** Update an existing Facet */
   updateFacet: Facet;
   /** Update one or more FacetValues */
   updateFacetValues: Array<FacetValue>;
   updateGlobalSettings: GlobalSettings;
+  updateOrderNote: HistoryEntry;
   /** Update an existing PaymentMethod */
   updatePaymentMethod: PaymentMethod;
   /** Update an existing Product */
@@ -2142,6 +2146,11 @@ export type MutationDeleteCustomerGroupArgs = {
 };
 
 
+export type MutationDeleteCustomerNoteArgs = {
+  id: Scalars['ID'];
+};
+
+
 export type MutationDeleteFacetArgs = {
   id: Scalars['ID'];
   force?: Maybe<Scalars['Boolean']>;
@@ -2154,6 +2163,11 @@ export type MutationDeleteFacetValuesArgs = {
 };
 
 
+export type MutationDeleteOrderNoteArgs = {
+  id: Scalars['ID'];
+};
+
+
 export type MutationDeleteProductArgs = {
   id: Scalars['ID'];
 };
@@ -2315,6 +2329,11 @@ export type MutationUpdateCustomerGroupArgs = {
 };
 
 
+export type MutationUpdateCustomerNoteArgs = {
+  input: UpdateCustomerNoteInput;
+};
+
+
 export type MutationUpdateFacetArgs = {
   input: UpdateFacetInput;
 };
@@ -2330,6 +2349,11 @@ export type MutationUpdateGlobalSettingsArgs = {
 };
 
 
+export type MutationUpdateOrderNoteArgs = {
+  input: UpdateOrderNoteInput;
+};
+
+
 export type MutationUpdatePaymentMethodArgs = {
   input: UpdatePaymentMethodInput;
 };
@@ -3627,6 +3651,11 @@ export type UpdateCustomerInput = {
   customFields?: Maybe<Scalars['JSON']>;
 };
 
+export type UpdateCustomerNoteInput = {
+  noteId: Scalars['ID'];
+  note: Scalars['String'];
+};
+
 export type UpdateFacetInput = {
   id: Scalars['ID'];
   isPrivate?: Maybe<Scalars['Boolean']>;
@@ -3648,6 +3677,12 @@ export type UpdateGlobalSettingsInput = {
   customFields?: Maybe<Scalars['JSON']>;
 };
 
+export type UpdateOrderNoteInput = {
+  noteId: Scalars['ID'];
+  note?: Maybe<Scalars['String']>;
+  isPublic?: Maybe<Scalars['Boolean']>;
+};
+
 export type UpdatePaymentMethodInput = {
   id: Scalars['ID'];
   code?: Maybe<Scalars['String']>;

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

@@ -1853,6 +1853,8 @@ export type Mutation = {
     /** Update an existing Address */
     deleteCustomerAddress: Scalars['Boolean'];
     addNoteToCustomer: Customer;
+    updateCustomerNote: HistoryEntry;
+    deleteCustomerNote: DeletionResponse;
     /** Create a new Facet */
     createFacet: Facet;
     /** Update an existing Facet */
@@ -1875,6 +1877,8 @@ export type Mutation = {
     refundOrder: Refund;
     settleRefund: Refund;
     addNoteToOrder: Order;
+    updateOrderNote: HistoryEntry;
+    deleteOrderNote: DeletionResponse;
     /** Update an existing PaymentMethod */
     updatePaymentMethod: PaymentMethod;
     /** Create a new ProductOptionGroup */
@@ -2069,6 +2073,14 @@ export type MutationAddNoteToCustomerArgs = {
     input: AddNoteToCustomerInput;
 };
 
+export type MutationUpdateCustomerNoteArgs = {
+    input: UpdateCustomerNoteInput;
+};
+
+export type MutationDeleteCustomerNoteArgs = {
+    id: Scalars['ID'];
+};
+
 export type MutationCreateFacetArgs = {
     input: CreateFacetInput;
 };
@@ -2132,6 +2144,14 @@ export type MutationAddNoteToOrderArgs = {
     input: AddNoteToOrderInput;
 };
 
+export type MutationUpdateOrderNoteArgs = {
+    input: UpdateOrderNoteInput;
+};
+
+export type MutationDeleteOrderNoteArgs = {
+    id: Scalars['ID'];
+};
+
 export type MutationUpdatePaymentMethodArgs = {
     input: UpdatePaymentMethodInput;
 };
@@ -3463,6 +3483,11 @@ export type UpdateCustomerInput = {
     customFields?: Maybe<Scalars['JSON']>;
 };
 
+export type UpdateCustomerNoteInput = {
+    noteId: Scalars['ID'];
+    note: Scalars['String'];
+};
+
 export type UpdateFacetInput = {
     id: Scalars['ID'];
     isPrivate?: Maybe<Scalars['Boolean']>;
@@ -3484,6 +3509,12 @@ export type UpdateGlobalSettingsInput = {
     customFields?: Maybe<Scalars['JSON']>;
 };
 
+export type UpdateOrderNoteInput = {
+    noteId: Scalars['ID'];
+    note?: Maybe<Scalars['String']>;
+    isPublic?: Maybe<Scalars['Boolean']>;
+};
+
 export type UpdatePaymentMethodInput = {
     id: Scalars['ID'];
     code?: Maybe<Scalars['String']>;

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

@@ -1852,6 +1852,8 @@ export type Mutation = {
   /** Update an existing Address */
   deleteCustomerAddress: Scalars['Boolean'];
   addNoteToCustomer: Customer;
+  updateCustomerNote: HistoryEntry;
+  deleteCustomerNote: DeletionResponse;
   /** Create a new Facet */
   createFacet: Facet;
   /** Update an existing Facet */
@@ -1874,6 +1876,8 @@ export type Mutation = {
   refundOrder: Refund;
   settleRefund: Refund;
   addNoteToOrder: Order;
+  updateOrderNote: HistoryEntry;
+  deleteOrderNote: DeletionResponse;
   /** Update an existing PaymentMethod */
   updatePaymentMethod: PaymentMethod;
   /** Create a new ProductOptionGroup */
@@ -2098,6 +2102,16 @@ export type MutationAddNoteToCustomerArgs = {
 };
 
 
+export type MutationUpdateCustomerNoteArgs = {
+  input: UpdateCustomerNoteInput;
+};
+
+
+export type MutationDeleteCustomerNoteArgs = {
+  id: Scalars['ID'];
+};
+
+
 export type MutationCreateFacetArgs = {
   input: CreateFacetInput;
 };
@@ -2176,6 +2190,16 @@ export type MutationAddNoteToOrderArgs = {
 };
 
 
+export type MutationUpdateOrderNoteArgs = {
+  input: UpdateOrderNoteInput;
+};
+
+
+export type MutationDeleteOrderNoteArgs = {
+  id: Scalars['ID'];
+};
+
+
 export type MutationUpdatePaymentMethodArgs = {
   input: UpdatePaymentMethodInput;
 };
@@ -3579,6 +3603,11 @@ export type UpdateCustomerInput = {
   customFields?: Maybe<Scalars['JSON']>;
 };
 
+export type UpdateCustomerNoteInput = {
+  noteId: Scalars['ID'];
+  note: Scalars['String'];
+};
+
 export type UpdateFacetInput = {
   id: Scalars['ID'];
   isPrivate?: Maybe<Scalars['Boolean']>;
@@ -3600,6 +3629,12 @@ export type UpdateGlobalSettingsInput = {
   customFields?: Maybe<Scalars['JSON']>;
 };
 
+export type UpdateOrderNoteInput = {
+  noteId: Scalars['ID'];
+  note?: Maybe<Scalars['String']>;
+  isPublic?: Maybe<Scalars['Boolean']>;
+};
+
 export type UpdatePaymentMethodInput = {
   id: Scalars['ID'];
   code?: Maybe<Scalars['String']>;

+ 64 - 0
packages/core/e2e/customer.e2e-spec.ts

@@ -23,6 +23,7 @@ import {
     CreateCustomer,
     DeleteCustomer,
     DeleteCustomerAddress,
+    DeleteCustomerNote,
     DeletionResult,
     GetCustomer,
     GetCustomerHistory,
@@ -31,6 +32,7 @@ import {
     GetCustomerWithUser,
     UpdateAddress,
     UpdateCustomer,
+    UpdateCustomerNote,
 } from './graphql/generated-e2e-admin-types';
 import { AddItemToOrder } from './graphql/generated-e2e-shop-types';
 import { GET_CUSTOMER, GET_CUSTOMER_HISTORY, GET_CUSTOMER_LIST } from './graphql/shared-definitions';
@@ -497,6 +499,8 @@ describe('Customer resolver', () => {
     });
 
     describe('customer notes', () => {
+        let noteId: string;
+
         it('addNoteToCustomer', async () => {
             const { addNoteToCustomer } = await adminClient.query<
                 AddNoteToCustomer.Mutation,
@@ -531,6 +535,47 @@ describe('Customer resolver', () => {
                     },
                 },
             ]);
+
+            noteId = customer?.history.items[0].id!;
+        });
+
+        it('update note', async () => {
+            const { updateCustomerNote } = await adminClient.query<
+                UpdateCustomerNote.Mutation,
+                UpdateCustomerNote.Variables
+            >(UPDATE_CUSTOMER_NOTE, {
+                input: {
+                    noteId,
+                    note: 'An updated note',
+                },
+            });
+
+            expect(updateCustomerNote.data).toEqual({
+                note: 'An updated note',
+            });
+        });
+
+        it('delete note', async () => {
+            const { customer: before } = await adminClient.query<
+                GetCustomerHistory.Query,
+                GetCustomerHistory.Variables
+            >(GET_CUSTOMER_HISTORY, { id: firstCustomer.id });
+            const historyCount = before?.history.totalItems!;
+
+            const { deleteCustomerNote } = await adminClient.query<
+                DeleteCustomerNote.Mutation,
+                DeleteCustomerNote.Variables
+            >(DELETE_CUSTOMER_NOTE, {
+                id: noteId,
+            });
+
+            expect(deleteCustomerNote.result).toBe(DeletionResult.DELETED);
+
+            const { customer: after } = await adminClient.query<
+                GetCustomerHistory.Query,
+                GetCustomerHistory.Variables
+            >(GET_CUSTOMER_HISTORY, { id: firstCustomer.id });
+            expect(after?.history.totalItems).toBe(historyCount - 1);
         });
     });
 });
@@ -631,3 +676,22 @@ const ADD_NOTE_TO_CUSTOMER = gql`
     }
     ${CUSTOMER_FRAGMENT}
 `;
+
+export const UPDATE_CUSTOMER_NOTE = gql`
+    mutation UpdateCustomerNote($input: UpdateCustomerNoteInput!) {
+        updateCustomerNote(input: $input) {
+            id
+            data
+            isPublic
+        }
+    }
+`;
+
+export const DELETE_CUSTOMER_NOTE = gql`
+    mutation DeleteCustomerNote($id: ID!) {
+        deleteCustomerNote(id: $id) {
+            result
+            message
+        }
+    }
+`;

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

@@ -1853,6 +1853,8 @@ export type Mutation = {
     /** Update an existing Address */
     deleteCustomerAddress: Scalars['Boolean'];
     addNoteToCustomer: Customer;
+    updateCustomerNote: HistoryEntry;
+    deleteCustomerNote: DeletionResponse;
     /** Create a new Facet */
     createFacet: Facet;
     /** Update an existing Facet */
@@ -1875,6 +1877,8 @@ export type Mutation = {
     refundOrder: Refund;
     settleRefund: Refund;
     addNoteToOrder: Order;
+    updateOrderNote: HistoryEntry;
+    deleteOrderNote: DeletionResponse;
     /** Update an existing PaymentMethod */
     updatePaymentMethod: PaymentMethod;
     /** Create a new ProductOptionGroup */
@@ -2069,6 +2073,14 @@ export type MutationAddNoteToCustomerArgs = {
     input: AddNoteToCustomerInput;
 };
 
+export type MutationUpdateCustomerNoteArgs = {
+    input: UpdateCustomerNoteInput;
+};
+
+export type MutationDeleteCustomerNoteArgs = {
+    id: Scalars['ID'];
+};
+
 export type MutationCreateFacetArgs = {
     input: CreateFacetInput;
 };
@@ -2132,6 +2144,14 @@ export type MutationAddNoteToOrderArgs = {
     input: AddNoteToOrderInput;
 };
 
+export type MutationUpdateOrderNoteArgs = {
+    input: UpdateOrderNoteInput;
+};
+
+export type MutationDeleteOrderNoteArgs = {
+    id: Scalars['ID'];
+};
+
 export type MutationUpdatePaymentMethodArgs = {
     input: UpdatePaymentMethodInput;
 };
@@ -3463,6 +3483,11 @@ export type UpdateCustomerInput = {
     customFields?: Maybe<Scalars['JSON']>;
 };
 
+export type UpdateCustomerNoteInput = {
+    noteId: Scalars['ID'];
+    note: Scalars['String'];
+};
+
 export type UpdateFacetInput = {
     id: Scalars['ID'];
     isPrivate?: Maybe<Scalars['Boolean']>;
@@ -3484,6 +3509,12 @@ export type UpdateGlobalSettingsInput = {
     customFields?: Maybe<Scalars['JSON']>;
 };
 
+export type UpdateOrderNoteInput = {
+    noteId: Scalars['ID'];
+    note?: Maybe<Scalars['String']>;
+    isPublic?: Maybe<Scalars['Boolean']>;
+};
+
 export type UpdatePaymentMethodInput = {
     id: Scalars['ID'];
     code?: Maybe<Scalars['String']>;
@@ -4051,6 +4082,22 @@ export type AddNoteToCustomerMutation = { __typename?: 'Mutation' } & {
     addNoteToCustomer: { __typename?: 'Customer' } & CustomerFragment;
 };
 
+export type UpdateCustomerNoteMutationVariables = {
+    input: UpdateCustomerNoteInput;
+};
+
+export type UpdateCustomerNoteMutation = { __typename?: 'Mutation' } & {
+    updateCustomerNote: { __typename?: 'HistoryEntry' } & Pick<HistoryEntry, 'id' | 'data' | 'isPublic'>;
+};
+
+export type DeleteCustomerNoteMutationVariables = {
+    id: Scalars['ID'];
+};
+
+export type DeleteCustomerNoteMutation = { __typename?: 'Mutation' } & {
+    deleteCustomerNote: { __typename?: 'DeletionResponse' } & Pick<DeletionResponse, 'result' | 'message'>;
+};
+
 export type ReindexMutationVariables = {};
 
 export type ReindexMutation = { __typename?: 'Mutation' } & {
@@ -4887,15 +4934,15 @@ export type GetCustomerHistoryQueryVariables = {
 export type GetCustomerHistoryQuery = { __typename?: 'Query' } & {
     customer?: Maybe<
         { __typename?: 'Customer' } & Pick<Customer, 'id'> & {
-                history: { __typename?: 'HistoryEntryList' } & {
-                    items: Array<
-                        { __typename?: 'HistoryEntry' } & Pick<HistoryEntry, 'id' | 'type' | 'data'> & {
-                                administrator?: Maybe<
-                                    { __typename?: 'Administrator' } & Pick<Administrator, 'id'>
-                                >;
-                            }
-                    >;
-                };
+                history: { __typename?: 'HistoryEntryList' } & Pick<HistoryEntryList, 'totalItems'> & {
+                        items: Array<
+                            { __typename?: 'HistoryEntry' } & Pick<HistoryEntry, 'id' | 'type' | 'data'> & {
+                                    administrator?: Maybe<
+                                        { __typename?: 'Administrator' } & Pick<Administrator, 'id'>
+                                    >;
+                                }
+                        >;
+                    };
             }
     >;
 };
@@ -5082,6 +5129,22 @@ export type AddNoteToOrderMutation = { __typename?: 'Mutation' } & {
     addNoteToOrder: { __typename?: 'Order' } & Pick<Order, 'id'>;
 };
 
+export type UpdateOrderNoteMutationVariables = {
+    input: UpdateOrderNoteInput;
+};
+
+export type UpdateOrderNoteMutation = { __typename?: 'Mutation' } & {
+    updateOrderNote: { __typename?: 'HistoryEntry' } & Pick<HistoryEntry, 'id' | 'data' | 'isPublic'>;
+};
+
+export type DeleteOrderNoteMutationVariables = {
+    id: Scalars['ID'];
+};
+
+export type DeleteOrderNoteMutation = { __typename?: 'Mutation' } & {
+    deleteOrderNote: { __typename?: 'DeletionResponse' } & Pick<DeletionResponse, 'result' | 'message'>;
+};
+
 export type ProductOptionGroupFragment = { __typename?: 'ProductOptionGroup' } & Pick<
     ProductOptionGroup,
     'id' | 'code' | 'name'
@@ -5950,6 +6013,18 @@ export namespace AddNoteToCustomer {
     export type AddNoteToCustomer = CustomerFragment;
 }
 
+export namespace UpdateCustomerNote {
+    export type Variables = UpdateCustomerNoteMutationVariables;
+    export type Mutation = UpdateCustomerNoteMutation;
+    export type UpdateCustomerNote = UpdateCustomerNoteMutation['updateCustomerNote'];
+}
+
+export namespace DeleteCustomerNote {
+    export type Variables = DeleteCustomerNoteMutationVariables;
+    export type Mutation = DeleteCustomerNoteMutation;
+    export type DeleteCustomerNote = DeleteCustomerNoteMutation['deleteCustomerNote'];
+}
+
 export namespace Reindex {
     export type Variables = ReindexMutationVariables;
     export type Mutation = ReindexMutation;
@@ -6622,6 +6697,18 @@ export namespace AddNoteToOrder {
     export type AddNoteToOrder = AddNoteToOrderMutation['addNoteToOrder'];
 }
 
+export namespace UpdateOrderNote {
+    export type Variables = UpdateOrderNoteMutationVariables;
+    export type Mutation = UpdateOrderNoteMutation;
+    export type UpdateOrderNote = UpdateOrderNoteMutation['updateOrderNote'];
+}
+
+export namespace DeleteOrderNote {
+    export type Variables = DeleteOrderNoteMutationVariables;
+    export type Mutation = DeleteOrderNoteMutation;
+    export type DeleteOrderNote = DeleteOrderNoteMutation['deleteOrderNote'];
+}
+
 export namespace ProductOptionGroup {
     export type Fragment = ProductOptionGroupFragment;
     export type Options = NonNullable<ProductOptionGroupFragment['options'][0]>;

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

@@ -381,6 +381,7 @@ export const GET_CUSTOMER_HISTORY = gql`
         customer(id: $id) {
             id
             history(options: $options) {
+                totalItems
                 items {
                     id
                     administrator {

+ 88 - 22
packages/core/e2e/order.e2e-spec.ts

@@ -17,6 +17,7 @@ import {
     AddNoteToOrder,
     CancelOrder,
     CreateFulfillment,
+    DeleteOrderNote,
     GetCustomerList,
     GetOrder,
     GetOrderFulfillmentItems,
@@ -32,9 +33,10 @@ import {
     SettlePayment,
     SettleRefund,
     StockMovementType,
+    UpdateOrderNote,
     UpdateProductVariants,
 } from './graphql/generated-e2e-admin-types';
-import { AddItemToOrder, GetActiveOrder } from './graphql/generated-e2e-shop-types';
+import { AddItemToOrder, DeletionResult, GetActiveOrder } from './graphql/generated-e2e-shop-types';
 import {
     GET_CUSTOMER_LIST,
     GET_PRODUCT_WITH_VARIANTS,
@@ -103,7 +105,7 @@ describe('Orders resolver', () => {
 
     it('orders', async () => {
         const result = await adminClient.query<GetOrderList.Query>(GET_ORDERS_LIST);
-        expect(result.orders.items.map(o => o.id)).toEqual(['T_1', 'T_2']);
+        expect(result.orders.items.map((o) => o.id)).toEqual(['T_1', 'T_2']);
     });
 
     it('order', async () => {
@@ -237,7 +239,7 @@ describe('Orders resolver', () => {
                     CREATE_FULFILLMENT,
                     {
                         input: {
-                            lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: l.quantity })),
+                            lines: order!.lines.map((l) => ({ orderLineId: l.id, quantity: l.quantity })),
                             method: 'Test',
                         },
                     },
@@ -275,7 +277,7 @@ describe('Orders resolver', () => {
                     CREATE_FULFILLMENT,
                     {
                         input: {
-                            lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: 0 })),
+                            lines: order!.lines.map((l) => ({ orderLineId: l.id, quantity: 0 })),
                             method: 'Test',
                         },
                     },
@@ -295,7 +297,7 @@ describe('Orders resolver', () => {
                 CreateFulfillment.Variables
             >(CREATE_FULFILLMENT, {
                 input: {
-                    lines: lines.map(l => ({ orderLineId: l.id, quantity: 1 })),
+                    lines: lines.map((l) => ({ orderLineId: l.id, quantity: 1 })),
                     method: 'Test1',
                     trackingCode: '111',
                 },
@@ -317,10 +319,10 @@ describe('Orders resolver', () => {
             expect(result.order!.lines[0].items[0].fulfillment!.id).toBe(fulfillOrder!.id);
             expect(
                 result.order!.lines[1].items.filter(
-                    i => i.fulfillment && i.fulfillment.id === fulfillOrder.id,
+                    (i) => i.fulfillment && i.fulfillment.id === fulfillOrder.id,
                 ).length,
             ).toBe(1);
-            expect(result.order!.lines[1].items.filter(i => i.fulfillment == null).length).toBe(2);
+            expect(result.order!.lines[1].items.filter((i) => i.fulfillment == null).length).toBe(2);
         });
 
         it('creates a second partial fulfillment', async () => {
@@ -345,8 +347,8 @@ describe('Orders resolver', () => {
                 id: 'T_2',
             });
             expect(result.order!.state).toBe('PartiallyFulfilled');
-            expect(result.order!.lines[1].items.filter(i => i.fulfillment != null).length).toBe(2);
-            expect(result.order!.lines[1].items.filter(i => i.fulfillment == null).length).toBe(1);
+            expect(result.order!.lines[1].items.filter((i) => i.fulfillment != null).length).toBe(2);
+            expect(result.order!.lines[1].items.filter((i) => i.fulfillment == null).length).toBe(1);
         });
 
         it(
@@ -383,7 +385,7 @@ describe('Orders resolver', () => {
                 (items, line) => [...items, ...line.items],
                 [] as OrderItemFragment[],
             );
-            const unfulfilledItem = order!.lines[1].items.find(i => i.fulfillment == null)!;
+            const unfulfilledItem = order!.lines[1].items.find((i) => i.fulfillment == null)!;
 
             const { fulfillOrder } = await adminClient.query<
                 CreateFulfillment.Mutation,
@@ -594,8 +596,11 @@ describe('Orders resolver', () => {
                     },
                 },
             );
-            expect(cancelOrder.lines.map(l => l.items.map(pick(['id', 'cancelled'])))).toEqual([
-                [{ id: 'T_11', cancelled: true }, { id: 'T_12', cancelled: true }],
+            expect(cancelOrder.lines.map((l) => l.items.map(pick(['id', 'cancelled'])))).toEqual([
+                [
+                    { id: 'T_11', cancelled: true },
+                    { id: 'T_12', cancelled: true },
+                ],
             ]);
             const { order: order2 } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
                 id: testOrder.orderId,
@@ -661,7 +666,7 @@ describe('Orders resolver', () => {
                 await adminClient.query<CancelOrder.Mutation, CancelOrder.Variables>(CANCEL_ORDER, {
                     input: {
                         orderId,
-                        lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: 1 })),
+                        lines: order!.lines.map((l) => ({ orderLineId: l.id, quantity: 1 })),
                     },
                 });
             }, 'Cannot cancel OrderLines from an Order in the "AddingItems" state'),
@@ -678,7 +683,7 @@ describe('Orders resolver', () => {
                 await adminClient.query<CancelOrder.Mutation, CancelOrder.Variables>(CANCEL_ORDER, {
                     input: {
                         orderId,
-                        lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: 1 })),
+                        lines: order!.lines.map((l) => ({ orderLineId: l.id, quantity: 1 })),
                     },
                 });
             }, 'Cannot cancel OrderLines from an Order in the "ArrangingPayment" state'),
@@ -708,7 +713,7 @@ describe('Orders resolver', () => {
                 await adminClient.query<CancelOrder.Mutation, CancelOrder.Variables>(CANCEL_ORDER, {
                     input: {
                         orderId,
-                        lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: 0 })),
+                        lines: order!.lines.map((l) => ({ orderLineId: l.id, quantity: 0 })),
                     },
                 });
             }, 'Nothing to cancel'),
@@ -740,7 +745,7 @@ describe('Orders resolver', () => {
                 {
                     input: {
                         orderId,
-                        lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: 1 })),
+                        lines: order!.lines.map((l) => ({ orderLineId: l.id, quantity: 1 })),
                         reason: 'cancel reason 1',
                     },
                 },
@@ -784,7 +789,7 @@ describe('Orders resolver', () => {
             await adminClient.query<CancelOrder.Mutation, CancelOrder.Variables>(CANCEL_ORDER, {
                 input: {
                     orderId,
-                    lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: 1 })),
+                    lines: order!.lines.map((l) => ({ orderLineId: l.id, quantity: 1 })),
                     reason: 'cancel reason 2',
                 },
             });
@@ -900,7 +905,7 @@ describe('Orders resolver', () => {
 
                 await adminClient.query<RefundOrder.Mutation, RefundOrder.Variables>(REFUND_ORDER, {
                     input: {
-                        lines: order.lines.map(l => ({ orderLineId: l.id, quantity: 1 })),
+                        lines: order.lines.map((l) => ({ orderLineId: l.id, quantity: 1 })),
                         shipping: 0,
                         adjustment: 0,
                         paymentId,
@@ -926,7 +931,7 @@ describe('Orders resolver', () => {
 
                 await adminClient.query<RefundOrder.Mutation, RefundOrder.Variables>(REFUND_ORDER, {
                     input: {
-                        lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: 0 })),
+                        lines: order!.lines.map((l) => ({ orderLineId: l.id, quantity: 0 })),
                         shipping: 0,
                         adjustment: 0,
                         paymentId,
@@ -965,7 +970,7 @@ describe('Orders resolver', () => {
                     REFUND_ORDER,
                     {
                         input: {
-                            lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: l.quantity })),
+                            lines: order!.lines.map((l) => ({ orderLineId: l.id, quantity: l.quantity })),
                             shipping: 100,
                             adjustment: 0,
                             paymentId: 'T_1',
@@ -983,7 +988,7 @@ describe('Orders resolver', () => {
                 REFUND_ORDER,
                 {
                     input: {
-                        lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: l.quantity })),
+                        lines: order!.lines.map((l) => ({ orderLineId: l.id, quantity: l.quantity })),
                         shipping: order!.shipping,
                         adjustment: 0,
                         reason: 'foo',
@@ -1010,7 +1015,7 @@ describe('Orders resolver', () => {
                     REFUND_ORDER,
                     {
                         input: {
-                            lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: l.quantity })),
+                            lines: order!.lines.map((l) => ({ orderLineId: l.id, quantity: l.quantity })),
                             shipping: order!.shipping,
                             adjustment: 0,
                             paymentId,
@@ -1098,6 +1103,7 @@ describe('Orders resolver', () => {
 
     describe('order notes', () => {
         let orderId: string;
+        let firstNoteId: string;
 
         beforeAll(async () => {
             const result = await createTestOrder(
@@ -1143,6 +1149,8 @@ describe('Orders resolver', () => {
                 },
             ]);
 
+            firstNoteId = order!.history.items[0].id;
+
             const { activeOrder } = await shopClient.query<GetActiveOrder.Query>(GET_ACTIVE_ORDER);
 
             expect(activeOrder!.history.items.map(pick(['type', 'data']))).toEqual([]);
@@ -1192,6 +1200,45 @@ describe('Orders resolver', () => {
                 },
             ]);
         });
+
+        it('update note', async () => {
+            const { updateOrderNote } = await adminClient.query<
+                UpdateOrderNote.Mutation,
+                UpdateOrderNote.Variables
+            >(UPDATE_ORDER_NOTE, {
+                input: {
+                    noteId: firstNoteId,
+                    note: 'An updated note',
+                },
+            });
+
+            expect(updateOrderNote.data).toEqual({
+                note: 'An updated note',
+            });
+        });
+
+        it('delete note', async () => {
+            const { order: before } = await adminClient.query<
+                GetOrderHistory.Query,
+                GetOrderHistory.Variables
+            >(GET_ORDER_HISTORY, { id: orderId });
+            expect(before?.history.totalItems).toBe(2);
+
+            const { deleteOrderNote } = await adminClient.query<
+                DeleteOrderNote.Mutation,
+                DeleteOrderNote.Variables
+            >(DELETE_ORDER_NOTE, {
+                id: firstNoteId,
+            });
+
+            expect(deleteOrderNote.result).toBe(DeletionResult.DELETED);
+
+            const { order: after } = await adminClient.query<
+                GetOrderHistory.Query,
+                GetOrderHistory.Variables
+            >(GET_ORDER_HISTORY, { id: orderId });
+            expect(after?.history.totalItems).toBe(1);
+        });
     });
 });
 
@@ -1393,3 +1440,22 @@ export const ADD_NOTE_TO_ORDER = gql`
         }
     }
 `;
+
+export const UPDATE_ORDER_NOTE = gql`
+    mutation UpdateOrderNote($input: UpdateOrderNoteInput!) {
+        updateOrderNote(input: $input) {
+            id
+            data
+            isPublic
+        }
+    }
+`;
+
+export const DELETE_ORDER_NOTE = gql`
+    mutation DeleteOrderNote($id: ID!) {
+        deleteOrderNote(id: $id) {
+            result
+            message
+        }
+    }
+`;

+ 13 - 0
packages/core/src/api/resolvers/admin/customer.resolver.ts

@@ -7,8 +7,10 @@ import {
     MutationCreateCustomerArgs,
     MutationDeleteCustomerAddressArgs,
     MutationDeleteCustomerArgs,
+    MutationDeleteCustomerNoteArgs,
     MutationUpdateCustomerAddressArgs,
     MutationUpdateCustomerArgs,
+    MutationUpdateCustomerNoteArgs,
     Permission,
     QueryCustomerArgs,
     QueryCustomersArgs,
@@ -100,4 +102,15 @@ export class CustomerResolver {
     async addNoteToCustomer(@Ctx() ctx: RequestContext, @Args() args: MutationAddNoteToCustomerArgs) {
         return this.customerService.addNoteToCustomer(ctx, args.input);
     }
+
+    @Mutation()
+    @Allow(Permission.UpdateCustomer)
+    async updateCustomerNote(@Ctx() ctx: RequestContext, @Args() args: MutationUpdateCustomerNoteArgs) {
+        return this.customerService.updateCustomerNote(ctx, args.input);
+    }
+    @Mutation()
+    @Allow(Permission.UpdateCustomer)
+    async deleteCustomerNote(@Ctx() ctx: RequestContext, @Args() args: MutationDeleteCustomerNoteArgs) {
+        return this.customerService.deleteCustomerNote(ctx, args.id);
+    }
 }

+ 14 - 0
packages/core/src/api/resolvers/admin/order.resolver.ts

@@ -2,10 +2,12 @@ import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
 import {
     MutationAddNoteToOrderArgs,
     MutationCancelOrderArgs,
+    MutationDeleteOrderNoteArgs,
     MutationFulfillOrderArgs,
     MutationRefundOrderArgs,
     MutationSettlePaymentArgs,
     MutationSettleRefundArgs,
+    MutationUpdateOrderNoteArgs,
     Permission,
     QueryOrderArgs,
     QueryOrdersArgs,
@@ -70,4 +72,16 @@ export class OrderResolver {
     async addNoteToOrder(@Ctx() ctx: RequestContext, @Args() args: MutationAddNoteToOrderArgs) {
         return this.orderService.addNoteToOrder(ctx, args.input);
     }
+
+    @Mutation()
+    @Allow(Permission.UpdateOrder)
+    async updateOrderNote(@Ctx() ctx: RequestContext, @Args() args: MutationUpdateOrderNoteArgs) {
+        return this.orderService.updateOrderNote(ctx, args.input);
+    }
+
+    @Mutation()
+    @Allow(Permission.UpdateOrder)
+    async deleteOrderNote(@Ctx() ctx: RequestContext, @Args() args: MutationDeleteOrderNoteArgs) {
+        return this.orderService.deleteOrderNote(ctx, args.id);
+    }
 }

+ 8 - 0
packages/core/src/api/schema/admin-api/customer.api.graphql

@@ -23,6 +23,8 @@ type Mutation {
     deleteCustomerAddress(id: ID!): Boolean!
 
     addNoteToCustomer(input: AddNoteToCustomerInput!): Customer!
+    updateCustomerNote(input: UpdateCustomerNoteInput!): HistoryEntry!
+    deleteCustomerNote(id: ID!): DeletionResponse!
 }
 
 type Customer implements Node {
@@ -48,3 +50,9 @@ input AddNoteToCustomerInput {
     note: String!
     isPublic: Boolean!
 }
+
+input UpdateCustomerNoteInput {
+    noteId: ID!
+    note: String!
+}
+

+ 8 - 0
packages/core/src/api/schema/admin-api/order.api.graphql

@@ -10,6 +10,8 @@ type Mutation {
     refundOrder(input: RefundOrderInput!): Refund!
     settleRefund(input: SettleRefundInput!): Refund!
     addNoteToOrder(input: AddNoteToOrderInput!): Order!
+    updateOrderNote(input: UpdateOrderNoteInput!): HistoryEntry!
+    deleteOrderNote(id: ID!): DeletionResponse!
 }
 
 # generated by generateListOptions function
@@ -52,3 +54,9 @@ input AddNoteToOrderInput {
     note: String!
     isPublic: Boolean!
 }
+
+input UpdateOrderNoteInput {
+    noteId: ID!
+    note: String
+    isPublic: Boolean
+}

+ 25 - 0
packages/core/src/service/services/customer.service.ts

@@ -10,6 +10,7 @@ import {
     HistoryEntryType,
     UpdateAddressInput,
     UpdateCustomerInput,
+    UpdateCustomerNoteInput,
 } from '@vendure/common/lib/generated-types';
 import { ID, PaginatedList } from '@vendure/common/lib/shared-types';
 import { Connection } from 'typeorm';
@@ -27,6 +28,7 @@ import { ConfigService } from '../../config/config.service';
 import { Address } from '../../entity/address/address.entity';
 import { CustomerGroup } from '../../entity/customer-group/customer-group.entity';
 import { Customer } from '../../entity/customer/customer.entity';
+import { HistoryEntry } from '../../entity/history-entry/history-entry.entity';
 import { User } from '../../entity/user/user.entity';
 import { EventBus } from '../../event-bus/event-bus';
 import { AccountRegistrationEvent } from '../../event-bus/events/account-registration-event';
@@ -488,6 +490,29 @@ export class CustomerService {
         return customer;
     }
 
+    async updateCustomerNote(ctx: RequestContext, input: UpdateCustomerNoteInput): Promise<HistoryEntry> {
+        return this.historyService.updateCustomerHistoryEntry(ctx, {
+            type: HistoryEntryType.CUSTOMER_NOTE,
+            data: input.note ? { note: input.note } : undefined,
+            ctx,
+            entryId: input.noteId,
+        });
+    }
+
+    async deleteCustomerNote(ctx: RequestContext, id: ID): Promise<DeletionResponse> {
+        try {
+            await this.historyService.deleteCustomerHistoryEntry(id);
+            return {
+                result: DeletionResult.DELETED,
+            };
+        } catch (e) {
+            return {
+                result: DeletionResult.NOT_DELETED,
+                message: e.message,
+            };
+        }
+    }
+
     private async enforceSingleDefaultAddress(addressId: ID, input: CreateAddressInput | UpdateAddressInput) {
         const result = await this.connection
             .getRepository(Address)

+ 75 - 6
packages/core/src/service/services/history.service.ts

@@ -10,6 +10,7 @@ import { ID, PaginatedList, Type } from '@vendure/common/lib/shared-types';
 import { Connection } from 'typeorm';
 
 import { RequestContext } from '../../api/common/request-context';
+import { Administrator } from '../../entity/administrator/administrator.entity';
 import { CustomerHistoryEntry } from '../../entity/history-entry/customer-history-entry.entity';
 import { HistoryEntry } from '../../entity/history-entry/history-entry.entity';
 import { OrderHistoryEntry } from '../../entity/history-entry/order-history-entry.entity';
@@ -17,6 +18,7 @@ import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-build
 import { OrderState } from '../helpers/order-state-machine/order-state';
 import { PaymentState } from '../helpers/payment-state-machine/payment-state';
 import { RefundState } from '../helpers/refund-state-machine/refund-state';
+import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
 
 import { AdministratorService } from './administrator.service';
 
@@ -107,6 +109,21 @@ export interface CreateOrderHistoryEntryArgs<T extends keyof OrderHistoryEntryDa
     data: OrderHistoryEntryData[T];
 }
 
+export interface UpdateOrderHistoryEntryArgs<T extends keyof OrderHistoryEntryData> {
+    entryId: ID;
+    ctx: RequestContext;
+    type: T;
+    isPublic?: boolean;
+    data?: OrderHistoryEntryData[T];
+}
+
+export interface UpdateCustomerHistoryEntryArgs<T extends keyof CustomerHistoryEntryData> {
+    entryId: ID;
+    ctx: RequestContext;
+    type: T;
+    data?: CustomerHistoryEntryData[T];
+}
+
 /**
  * The HistoryService is reponsible for creating and retrieving HistoryEntry entities.
  */
@@ -143,9 +160,7 @@ export class HistoryService {
         isPublic = true,
     ): Promise<OrderHistoryEntry> {
         const { ctx, data, orderId, type } = args;
-        const administrator = ctx.activeUserId
-            ? await this.administratorService.findOneByUserId(ctx.activeUserId)
-            : undefined;
+        const administrator = await this.getAdministratorFromContext(ctx);
         const entry = new OrderHistoryEntry({
             type,
             isPublic,
@@ -181,9 +196,7 @@ export class HistoryService {
         isPublic = false,
     ): Promise<CustomerHistoryEntry> {
         const { ctx, data, customerId, type } = args;
-        const administrator = ctx.activeUserId
-            ? await this.administratorService.findOneByUserId(ctx.activeUserId)
-            : undefined;
+        const administrator = await this.getAdministratorFromContext(ctx);
         const entry = new CustomerHistoryEntry({
             type,
             isPublic,
@@ -193,4 +206,60 @@ export class HistoryService {
         });
         return this.connection.getRepository(CustomerHistoryEntry).save(entry);
     }
+
+    async updateOrderHistoryEntry<T extends keyof OrderHistoryEntryData>(
+        ctx: RequestContext,
+        args: UpdateOrderHistoryEntryArgs<T>,
+    ) {
+        const entry = await getEntityOrThrow(this.connection, OrderHistoryEntry, args.entryId, {
+            where: { type: args.type },
+        });
+
+        if (args.data) {
+            entry.data = args.data;
+        }
+        if (typeof args.isPublic === 'boolean') {
+            entry.isPublic = args.isPublic;
+        }
+        const administrator = await this.getAdministratorFromContext(ctx);
+        if (administrator) {
+            entry.administrator = administrator;
+        }
+        return this.connection.getRepository(OrderHistoryEntry).save(entry);
+    }
+
+    async deleteOrderHistoryEntry(id: ID): Promise<void> {
+        const entry = await getEntityOrThrow(this.connection, OrderHistoryEntry, id);
+        await this.connection.getRepository(OrderHistoryEntry).remove(entry);
+    }
+
+    async updateCustomerHistoryEntry<T extends keyof CustomerHistoryEntryData>(
+        ctx: RequestContext,
+        args: UpdateCustomerHistoryEntryArgs<T>,
+    ) {
+        const entry = await getEntityOrThrow(this.connection, CustomerHistoryEntry, args.entryId, {
+            where: { type: args.type },
+        });
+
+        if (args.data) {
+            entry.data = args.data;
+        }
+        const administrator = await this.getAdministratorFromContext(ctx);
+        if (administrator) {
+            entry.administrator = administrator;
+        }
+        return this.connection.getRepository(CustomerHistoryEntry).save(entry);
+    }
+
+    async deleteCustomerHistoryEntry(id: ID): Promise<void> {
+        const entry = await getEntityOrThrow(this.connection, CustomerHistoryEntry, id);
+        await this.connection.getRepository(CustomerHistoryEntry).remove(entry);
+    }
+
+    private async getAdministratorFromContext(ctx: RequestContext): Promise<Administrator | undefined> {
+        const administrator = ctx.activeUserId
+            ? await this.administratorService.findOneByUserId(ctx.activeUserId)
+            : undefined;
+        return administrator;
+    }
 }

+ 28 - 0
packages/core/src/service/services/order.service.ts

@@ -4,12 +4,15 @@ import {
     AddNoteToOrderInput,
     CancelOrderInput,
     CreateAddressInput,
+    DeletionResponse,
+    DeletionResult,
     FulfillOrderInput,
     HistoryEntryType,
     OrderLineInput,
     RefundOrderInput,
     SettleRefundInput,
     ShippingMethodQuote,
+    UpdateOrderNoteInput,
 } from '@vendure/common/lib/generated-types';
 import { ID, PaginatedList } from '@vendure/common/lib/shared-types';
 import { notNullOrUndefined } from '@vendure/common/lib/shared-utils';
@@ -29,6 +32,7 @@ import { assertFound, idsAreEqual } from '../../common/utils';
 import { ConfigService } from '../../config/config.service';
 import { Customer } from '../../entity/customer/customer.entity';
 import { Fulfillment } from '../../entity/fulfillment/fulfillment.entity';
+import { HistoryEntry } from '../../entity/history-entry/history-entry.entity';
 import { OrderItem } from '../../entity/order-item/order-item.entity';
 import { OrderLine } from '../../entity/order-line/order-line.entity';
 import { Order } from '../../entity/order/order.entity';
@@ -712,6 +716,30 @@ export class OrderService {
         return order;
     }
 
+    async updateOrderNote(ctx: RequestContext, input: UpdateOrderNoteInput): Promise<HistoryEntry> {
+        return this.historyService.updateOrderHistoryEntry(ctx, {
+            type: HistoryEntryType.ORDER_NOTE,
+            data: input.note ? { note: input.note } : undefined,
+            isPublic: input.isPublic ?? undefined,
+            ctx,
+            entryId: input.noteId,
+        });
+    }
+
+    async deleteOrderNote(ctx: RequestContext, id: ID): Promise<DeletionResponse> {
+        try {
+            await this.historyService.deleteOrderHistoryEntry(id);
+            return {
+                result: DeletionResult.DELETED,
+            };
+        } catch (e) {
+            return {
+                result: DeletionResult.NOT_DELETED,
+                message: e.message,
+            };
+        }
+    }
+
     /**
      * When a guest user with an anonymous Order signs in and has an existing Order associated with that Customer,
      * we need to reconcile the contents of the two orders.

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

@@ -1853,6 +1853,8 @@ export type Mutation = {
     /** Update an existing Address */
     deleteCustomerAddress: Scalars['Boolean'];
     addNoteToCustomer: Customer;
+    updateCustomerNote: HistoryEntry;
+    deleteCustomerNote: DeletionResponse;
     /** Create a new Facet */
     createFacet: Facet;
     /** Update an existing Facet */
@@ -1875,6 +1877,8 @@ export type Mutation = {
     refundOrder: Refund;
     settleRefund: Refund;
     addNoteToOrder: Order;
+    updateOrderNote: HistoryEntry;
+    deleteOrderNote: DeletionResponse;
     /** Update an existing PaymentMethod */
     updatePaymentMethod: PaymentMethod;
     /** Create a new ProductOptionGroup */
@@ -2069,6 +2073,14 @@ export type MutationAddNoteToCustomerArgs = {
     input: AddNoteToCustomerInput;
 };
 
+export type MutationUpdateCustomerNoteArgs = {
+    input: UpdateCustomerNoteInput;
+};
+
+export type MutationDeleteCustomerNoteArgs = {
+    id: Scalars['ID'];
+};
+
 export type MutationCreateFacetArgs = {
     input: CreateFacetInput;
 };
@@ -2132,6 +2144,14 @@ export type MutationAddNoteToOrderArgs = {
     input: AddNoteToOrderInput;
 };
 
+export type MutationUpdateOrderNoteArgs = {
+    input: UpdateOrderNoteInput;
+};
+
+export type MutationDeleteOrderNoteArgs = {
+    id: Scalars['ID'];
+};
+
 export type MutationUpdatePaymentMethodArgs = {
     input: UpdatePaymentMethodInput;
 };
@@ -3463,6 +3483,11 @@ export type UpdateCustomerInput = {
     customFields?: Maybe<Scalars['JSON']>;
 };
 
+export type UpdateCustomerNoteInput = {
+    noteId: Scalars['ID'];
+    note: Scalars['String'];
+};
+
 export type UpdateFacetInput = {
     id: Scalars['ID'];
     isPrivate?: Maybe<Scalars['Boolean']>;
@@ -3484,6 +3509,12 @@ export type UpdateGlobalSettingsInput = {
     customFields?: Maybe<Scalars['JSON']>;
 };
 
+export type UpdateOrderNoteInput = {
+    noteId: Scalars['ID'];
+    note?: Maybe<Scalars['String']>;
+    isPublic?: Maybe<Scalars['Boolean']>;
+};
+
 export type UpdatePaymentMethodInput = {
     id: Scalars['ID'];
     code?: Maybe<Scalars['String']>;

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