Просмотр исходного кода

feat(core): Allow Order/OrderLine customFields to be modified

Relates to #314
Michael Bromley 5 лет назад
Родитель
Сommit
ce656c4b4f

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

@@ -1406,7 +1406,7 @@ export type ModifyOrderInput = {
     dryRun: Scalars['Boolean'];
     orderId: Scalars['ID'];
     addItems?: Maybe<Array<AddItemInput>>;
-    adjustOrderLines?: Maybe<Array<OrderLineInput>>;
+    adjustOrderLines?: Maybe<Array<AdjustOrderLineInput>>;
     surcharges?: Maybe<Array<SurchargeInput>>;
     updateShippingAddress?: Maybe<UpdateOrderAddressInput>;
     updateBillingAddress?: Maybe<UpdateOrderAddressInput>;
@@ -1420,6 +1420,11 @@ export type AddItemInput = {
     quantity: Scalars['Int'];
 };
 
+export type AdjustOrderLineInput = {
+    orderLineId: Scalars['ID'];
+    quantity: Scalars['Int'];
+};
+
 export type SurchargeInput = {
     description: Scalars['String'];
     sku?: Maybe<Scalars['String']>;
@@ -3594,7 +3599,7 @@ export type OrderLine = Node & {
     discounts: Array<Adjustment>;
     taxLines: Array<TaxLine>;
     order: Order;
-    customFields?: Maybe<OrderLineCustomFields>;
+    customFields?: Maybe<Scalars['JSON']>;
 };
 
 export type Payment = Node & {
@@ -4310,11 +4315,6 @@ export type HistoryEntrySortParameter = {
     updatedAt?: Maybe<SortOrder>;
 };
 
-export type OrderLineCustomFields = {
-    test?: Maybe<Scalars['String']>;
-    test2?: Maybe<Scalars['String']>;
-};
-
 export type AuthenticationInput = {
     native?: Maybe<NativeAuthInput>;
 };

+ 1 - 14
packages/common/src/generated-shop-types.ts

@@ -181,7 +181,6 @@ export type Mutation = {
 export type MutationAddItemToOrderArgs = {
     productVariantId: Scalars['ID'];
     quantity: Scalars['Int'];
-    customFields?: Maybe<OrderLineCustomFieldsInput>;
 };
 
 export type MutationRemoveOrderLineArgs = {
@@ -191,7 +190,6 @@ export type MutationRemoveOrderLineArgs = {
 export type MutationAdjustOrderLineArgs = {
     orderLineId: Scalars['ID'];
     quantity: Scalars['Int'];
-    customFields?: Maybe<OrderLineCustomFieldsInput>;
 };
 
 export type MutationApplyCouponCodeArgs = {
@@ -1925,7 +1923,7 @@ export type OrderLine = Node & {
     discounts: Array<Adjustment>;
     taxLines: Array<TaxLine>;
     order: Order;
-    customFields?: Maybe<OrderLineCustomFields>;
+    customFields?: Maybe<Scalars['JSON']>;
 };
 
 export type Payment = Node & {
@@ -2722,17 +2720,6 @@ export type UpdateOrderInput = {
     customFields?: Maybe<Scalars['JSON']>;
 };
 
-export type OrderLineCustomFields = {
-    __typename?: 'OrderLineCustomFields';
-    test?: Maybe<Scalars['String']>;
-    test2?: Maybe<Scalars['String']>;
-};
-
-export type OrderLineCustomFieldsInput = {
-    test?: Maybe<Scalars['String']>;
-    test2?: Maybe<Scalars['String']>;
-};
-
 export type AuthenticationInput = {
     native?: Maybe<NativeAuthInput>;
 };

+ 7 - 8
packages/common/src/generated-types.ts

@@ -1567,7 +1567,7 @@ export type ModifyOrderInput = {
   dryRun: Scalars['Boolean'];
   orderId: Scalars['ID'];
   addItems?: Maybe<Array<AddItemInput>>;
-  adjustOrderLines?: Maybe<Array<OrderLineInput>>;
+  adjustOrderLines?: Maybe<Array<AdjustOrderLineInput>>;
   surcharges?: Maybe<Array<SurchargeInput>>;
   updateShippingAddress?: Maybe<UpdateOrderAddressInput>;
   updateBillingAddress?: Maybe<UpdateOrderAddressInput>;
@@ -1581,6 +1581,11 @@ export type AddItemInput = {
   quantity: Scalars['Int'];
 };
 
+export type AdjustOrderLineInput = {
+  orderLineId: Scalars['ID'];
+  quantity: Scalars['Int'];
+};
+
 export type SurchargeInput = {
   description: Scalars['String'];
   sku?: Maybe<Scalars['String']>;
@@ -3802,7 +3807,7 @@ export type OrderLine = Node & {
   discounts: Array<Adjustment>;
   taxLines: Array<TaxLine>;
   order: Order;
-  customFields?: Maybe<OrderLineCustomFields>;
+  customFields?: Maybe<Scalars['JSON']>;
 };
 
 export type Payment = Node & {
@@ -4547,12 +4552,6 @@ export type HistoryEntrySortParameter = {
   updatedAt?: Maybe<SortOrder>;
 };
 
-export type OrderLineCustomFields = {
-  __typename?: 'OrderLineCustomFields';
-  test?: Maybe<Scalars['String']>;
-  test2?: Maybe<Scalars['String']>;
-};
-
 export type AuthenticationInput = {
   native?: Maybe<NativeAuthInput>;
 };

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

@@ -1406,7 +1406,7 @@ export type ModifyOrderInput = {
     dryRun: Scalars['Boolean'];
     orderId: Scalars['ID'];
     addItems?: Maybe<Array<AddItemInput>>;
-    adjustOrderLines?: Maybe<Array<OrderLineInput>>;
+    adjustOrderLines?: Maybe<Array<AdjustOrderLineInput>>;
     surcharges?: Maybe<Array<SurchargeInput>>;
     updateShippingAddress?: Maybe<UpdateOrderAddressInput>;
     updateBillingAddress?: Maybe<UpdateOrderAddressInput>;
@@ -1420,6 +1420,11 @@ export type AddItemInput = {
     quantity: Scalars['Int'];
 };
 
+export type AdjustOrderLineInput = {
+    orderLineId: Scalars['ID'];
+    quantity: Scalars['Int'];
+};
+
 export type SurchargeInput = {
     description: Scalars['String'];
     sku?: Maybe<Scalars['String']>;
@@ -3594,7 +3599,7 @@ export type OrderLine = Node & {
     discounts: Array<Adjustment>;
     taxLines: Array<TaxLine>;
     order: Order;
-    customFields?: Maybe<OrderLineCustomFields>;
+    customFields?: Maybe<Scalars['JSON']>;
 };
 
 export type Payment = Node & {
@@ -4310,11 +4315,6 @@ export type HistoryEntrySortParameter = {
     updatedAt?: Maybe<SortOrder>;
 };
 
-export type OrderLineCustomFields = {
-    test?: Maybe<Scalars['String']>;
-    test2?: Maybe<Scalars['String']>;
-};
-
 export type AuthenticationInput = {
     native?: Maybe<NativeAuthInput>;
 };

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

@@ -179,7 +179,6 @@ export type Mutation = {
 export type MutationAddItemToOrderArgs = {
     productVariantId: Scalars['ID'];
     quantity: Scalars['Int'];
-    customFields?: Maybe<OrderLineCustomFieldsInput>;
 };
 
 export type MutationRemoveOrderLineArgs = {
@@ -189,7 +188,6 @@ export type MutationRemoveOrderLineArgs = {
 export type MutationAdjustOrderLineArgs = {
     orderLineId: Scalars['ID'];
     quantity: Scalars['Int'];
-    customFields?: Maybe<OrderLineCustomFieldsInput>;
 };
 
 export type MutationApplyCouponCodeArgs = {
@@ -1867,7 +1865,7 @@ export type OrderLine = Node & {
     discounts: Array<Adjustment>;
     taxLines: Array<TaxLine>;
     order: Order;
-    customFields?: Maybe<OrderLineCustomFields>;
+    customFields?: Maybe<Scalars['JSON']>;
 };
 
 export type Payment = Node & {
@@ -2613,16 +2611,6 @@ export type UpdateOrderInput = {
     customFields?: Maybe<Scalars['JSON']>;
 };
 
-export type OrderLineCustomFields = {
-    test?: Maybe<Scalars['String']>;
-    test2?: Maybe<Scalars['String']>;
-};
-
-export type OrderLineCustomFieldsInput = {
-    test?: Maybe<Scalars['String']>;
-    test2?: Maybe<Scalars['String']>;
-};
-
 export type AuthenticationInput = {
     native?: Maybe<NativeAuthInput>;
 };

+ 196 - 18
packages/core/e2e/order-modification.e2e-spec.ts

@@ -35,7 +35,6 @@ import {
     UpdateProductVariants,
 } from './graphql/generated-e2e-admin-types';
 import {
-    AddItemToOrder,
     AddItemToOrderMutationVariables,
     SetShippingAddress,
     SetShippingMethod,
@@ -50,12 +49,7 @@ import {
     GET_ORDER_HISTORY,
     UPDATE_PRODUCT_VARIANTS,
 } from './graphql/shared-definitions';
-import {
-    ADD_ITEM_TO_ORDER,
-    SET_SHIPPING_ADDRESS,
-    SET_SHIPPING_METHOD,
-    TRANSITION_TO_STATE,
-} from './graphql/shop-definitions';
+import { SET_SHIPPING_ADDRESS, SET_SHIPPING_METHOD, TRANSITION_TO_STATE } from './graphql/shop-definitions';
 import { addPaymentToOrder, proceedToArrangingPayment } from './utils/test-order-utils';
 
 const SHIPPING_GB = 500;
@@ -88,12 +82,17 @@ const testCalculator = new ShippingCalculator({
 describe('Order modification', () => {
     const { server, adminClient, shopClient } = createTestEnvironment(
         mergeConfig(testConfig, {
+            logger: new DefaultLogger(),
             paymentOptions: {
                 paymentMethodHandlers: [testSuccessfulPaymentMethod],
             },
             shippingOptions: {
                 shippingCalculators: [defaultShippingCalculator, testCalculator],
             },
+            customFields: {
+                Order: [{ name: 'points', type: 'int', defaultValue: 0 }],
+                OrderLine: [{ name: 'color', type: 'string', nullable: true }],
+            },
         }),
     );
 
@@ -158,11 +157,14 @@ describe('Order modification', () => {
 
         // create an order and check out
         await shopClient.asUserWithCredentials('hayden.zieme12@hotmail.com', 'test');
-        await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(ADD_ITEM_TO_ORDER, {
+        await shopClient.query(gql(ADD_ITEM_TO_ORDER_WITH_CUSTOM_FIELDS), {
             productVariantId: 'T_1',
             quantity: 1,
-        });
-        await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(ADD_ITEM_TO_ORDER, {
+            customFields: {
+                color: 'green',
+            },
+        } as any);
+        await shopClient.query(gql(ADD_ITEM_TO_ORDER_WITH_CUSTOM_FIELDS), {
             productVariantId: 'T_4',
             quantity: 2,
         });
@@ -389,6 +391,60 @@ describe('Order modification', () => {
             await assertOrderIsUnchanged(order!);
         });
 
+        it('addItems with existing variant id increments existing OrderLine', async () => {
+            const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
+                id: orderId,
+            });
+            const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
+                MODIFY_ORDER,
+                {
+                    input: {
+                        dryRun: true,
+                        orderId,
+                        addItems: [
+                            { productVariantId: 'T_1', quantity: 1, customFields: { color: 'green' } } as any,
+                        ],
+                    },
+                },
+            );
+            orderGuard.assertSuccess(modifyOrder);
+
+            const lineT1 = modifyOrder.lines.find(l => l.productVariant.id === 'T_1');
+            expect(modifyOrder.lines.length).toBe(2);
+            expect(lineT1?.quantity).toBe(2);
+            await assertOrderIsUnchanged(order!);
+        });
+
+        it('addItems with existing variant id but different customFields adds new OrderLine', async () => {
+            const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
+                id: orderId,
+            });
+            const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
+                MODIFY_ORDER,
+                {
+                    input: {
+                        dryRun: true,
+                        orderId,
+                        addItems: [
+                            { productVariantId: 'T_1', quantity: 1, customFields: { color: 'blue' } } as any,
+                        ],
+                    },
+                },
+            );
+            orderGuard.assertSuccess(modifyOrder);
+
+            const lineT1 = modifyOrder.lines.find(l => l.productVariant.id === 'T_1');
+            expect(modifyOrder.lines.length).toBe(3);
+            expect(
+                modifyOrder.lines.map(l => ({ variantId: l.productVariant.id, quantity: l.quantity })),
+            ).toEqual([
+                { variantId: 'T_1', quantity: 1 },
+                { variantId: 'T_4', quantity: 2 },
+                { variantId: 'T_1', quantity: 1 },
+            ]);
+            await assertOrderIsUnchanged(order!);
+        });
+
         it('adjustOrderLines up', async () => {
             const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
                 id: orderId,
@@ -688,6 +744,84 @@ describe('Order modification', () => {
             await assertModifiedOrderIsPersisted(modifyOrder);
         });
 
+        it('adjustOrderLines with changed customField value', async () => {
+            const order = await createOrderAndTransitionToModifyingState([
+                {
+                    productVariantId: 'T_1',
+                    quantity: 1,
+                    customFields: {
+                        color: 'green',
+                    },
+                },
+            ]);
+            const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
+                MODIFY_ORDER,
+                {
+                    input: {
+                        dryRun: false,
+                        orderId: order.id,
+                        adjustOrderLines: [
+                            {
+                                orderLineId: order!.lines[0].id,
+                                quantity: 1,
+                                customFields: { color: 'black' },
+                            } as any,
+                        ],
+                    },
+                },
+            );
+            orderGuard.assertSuccess(modifyOrder);
+            expect(modifyOrder.lines.length).toBe(1);
+
+            const { order: orderWithLines } = await adminClient.query(gql(GET_ORDER_WITH_CUSTOM_FIELDS), {
+                id: order.id,
+            });
+            expect(orderWithLines.lines[0]).toEqual({
+                id: order!.lines[0].id,
+                customFields: { color: 'black' },
+            });
+        });
+
+        it('adjustOrderLines handles quantity correctly', async () => {
+            await adminClient.query<UpdateProductVariants.Mutation, UpdateProductVariants.Variables>(
+                UPDATE_PRODUCT_VARIANTS,
+                {
+                    input: [
+                        {
+                            id: 'T_6',
+                            stockOnHand: 1,
+                            trackInventory: GlobalFlag.TRUE,
+                        },
+                    ],
+                },
+            );
+            const order = await createOrderAndTransitionToModifyingState([
+                {
+                    productVariantId: 'T_6',
+                    quantity: 1,
+                },
+            ]);
+            const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
+                MODIFY_ORDER,
+                {
+                    input: {
+                        dryRun: false,
+                        orderId: order.id,
+                        adjustOrderLines: [
+                            {
+                                orderLineId: order.lines[0].id,
+                                quantity: 1,
+                            },
+                        ],
+                        updateShippingAddress: {
+                            fullName: 'Jim',
+                        },
+                    },
+                },
+            );
+            orderGuard.assertSuccess(modifyOrder);
+        });
+
         it('surcharge positive', async () => {
             const order = await createOrderAndTransitionToModifyingState([
                 {
@@ -849,6 +983,36 @@ describe('Order modification', () => {
             await assertModifiedOrderIsPersisted(modifyOrder);
         });
 
+        it('update Order customFields', async () => {
+            const order = await createOrderAndTransitionToModifyingState([
+                {
+                    productVariantId: 'T_1',
+                    quantity: 1,
+                },
+            ]);
+            const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
+                MODIFY_ORDER,
+                {
+                    input: {
+                        dryRun: false,
+                        orderId: order.id,
+                        customFields: {
+                            points: 42,
+                        },
+                    } as any,
+                },
+            );
+            orderGuard.assertSuccess(modifyOrder);
+
+            const { order: orderWithCustomFields } = await adminClient.query(
+                gql(GET_ORDER_WITH_CUSTOM_FIELDS),
+                { id: order.id },
+            );
+            expect(orderWithCustomFields.customFields).toEqual({
+                points: 42,
+            });
+        });
+
         it('adds a history entry', async () => {
             const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
                 id: orderId,
@@ -876,7 +1040,7 @@ describe('Order modification', () => {
 
             expect(history.history.totalItems).toBe(1);
             expect(history.history.items[0].data).toEqual({
-                modificationId: 'T_8',
+                modificationId: modifyOrder.modifications[0].id,
             });
         });
     });
@@ -973,8 +1137,7 @@ describe('Order modification', () => {
             orderGuard.assertSuccess(addManualPaymentToOrder);
 
             expect(addManualPaymentToOrder.payments?.length).toBe(2);
-            expect(addManualPaymentToOrder.payments![1]).toEqual({
-                id: 'T_10',
+            expect(omit(addManualPaymentToOrder.payments![1], ['id'])).toEqual({
                 transactionId: 'ABC123',
                 state: 'Settled',
                 amount: 300,
@@ -1097,14 +1260,11 @@ describe('Order modification', () => {
     }
 
     async function createOrderAndTransitionToModifyingState(
-        items: AddItemToOrderMutationVariables[],
+        items: Array<AddItemToOrderMutationVariables & { customFields?: any }>,
     ): Promise<TestOrderWithPaymentsFragment> {
         await shopClient.asUserWithCredentials('hayden.zieme12@hotmail.com', 'test');
         for (const itemInput of items) {
-            await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(
-                ADD_ITEM_TO_ORDER,
-                itemInput,
-            );
+            await shopClient.query(gql(ADD_ITEM_TO_ORDER_WITH_CUSTOM_FIELDS), itemInput);
         }
 
         await shopClient.query<SetShippingAddress.Mutation, SetShippingAddress.Variables>(
@@ -1264,3 +1424,21 @@ export const ADD_MANUAL_PAYMENT = gql`
     }
     ${ORDER_WITH_MODIFICATION_FRAGMENT}
 `;
+
+// Note, we don't use the gql tag around these due to the customFields which
+// would cause a codegen error.
+const ADD_ITEM_TO_ORDER_WITH_CUSTOM_FIELDS = `
+    mutation AddItemToOrder($productVariantId: ID!, $quantity: Int!, $customFields: OrderLineCustomFieldsInput) {
+        addItemToOrder(productVariantId: $productVariantId, quantity: $quantity, customFields: $customFields) {
+            ...on Order { id }
+        }
+    }
+`;
+const GET_ORDER_WITH_CUSTOM_FIELDS = `
+    query GetOrderCustomFields($id: ID!) {
+        order(id: $id) {
+            customFields { points }
+            lines { id, customFields { color } }
+        }
+    }
+`;

+ 2 - 0
packages/core/src/api/config/configure-graphql-module.ts

@@ -32,6 +32,7 @@ import { generateListOptions } from './generate-list-options';
 import { generatePermissionEnum } from './generate-permissions';
 import {
     addGraphQLCustomFields,
+    addModifyOrderCustomFields,
     addOrderLineCustomFieldsInput,
     addRegisterCustomerCustomFieldsInput,
     addServerConfigCustomFields,
@@ -127,6 +128,7 @@ async function createGraphQLOptions(
         schema = generateListOptions(schema);
         schema = addGraphQLCustomFields(schema, customFields, apiType === 'shop');
         schema = addOrderLineCustomFieldsInput(schema, customFields.OrderLine || []);
+        schema = addModifyOrderCustomFields(schema, customFields.Order || []);
         schema = generateAuthenticationTypes(schema, authStrategies);
         schema = generateErrorCodeEnum(schema);
         if (apiType === 'admin') {

+ 79 - 25
packages/core/src/api/config/graphql-custom-fields.ts

@@ -1,6 +1,14 @@
 import { CustomFieldType } from '@vendure/common/lib/shared-types';
 import { assertNever } from '@vendure/common/lib/shared-utils';
-import { buildSchema, extendSchema, GraphQLInputObjectType, GraphQLSchema, parse } from 'graphql';
+import {
+    buildSchema,
+    extendSchema,
+    GraphQLInputObjectType,
+    GraphQLSchema,
+    InputObjectTypeDefinitionNode,
+    ObjectTypeDefinitionNode,
+    parse,
+} from 'graphql';
 
 import { CustomFieldConfig, CustomFields } from '../../config/custom-field/custom-field-types';
 
@@ -225,9 +233,34 @@ export function addRegisterCustomerCustomFieldsInput(
     return extendSchema(schema, parse(customFieldTypeDefs));
 }
 
+/**
+ * If CustomFields are defined on the Order entity, we add a `customFields` field to the ModifyOrderInput
+ * type.
+ */
+export function addModifyOrderCustomFields(
+    typeDefsOrSchema: string | GraphQLSchema,
+    orderCustomFields: CustomFieldConfig[],
+): GraphQLSchema {
+    const schema = typeof typeDefsOrSchema === 'string' ? buildSchema(typeDefsOrSchema) : typeDefsOrSchema;
+    if (!orderCustomFields || orderCustomFields.length === 0) {
+        return schema;
+    }
+    if (schema.getType('ModifyOrderInput') && schema.getType('UpdateOrderCustomFieldsInput')) {
+        const customFieldTypeDefs = `
+                extend input ModifyOrderInput {
+                    customFields: UpdateOrderCustomFieldsInput
+                }
+            `;
+
+        return extendSchema(schema, parse(customFieldTypeDefs));
+    }
+    return schema;
+}
+
 /**
  * If CustomFields are defined on the OrderLine entity, then an extra `customFields` argument
- * must be added to the `addItemToOrder` and `adjustOrderLine` mutations.
+ * must be added to the `addItemToOrder` and `adjustOrderLine` mutations, as well as the related
+ * fields in the `ModifyOrderInput` type.
  */
 export function addOrderLineCustomFieldsInput(
     typeDefsOrSchema: string | GraphQLSchema,
@@ -242,37 +275,58 @@ export function addOrderLineCustomFieldsInput(
     if (!mutationType) {
         return schema;
     }
-    const addItemToOrderMutation = mutationType.getFields().addItemToOrder;
-    const adjustOrderLineMutation = mutationType.getFields().adjustOrderLine;
-    if (!addItemToOrderMutation || !adjustOrderLineMutation) {
-        return schema;
-    }
     const input = new GraphQLInputObjectType({
         name: 'OrderLineCustomFieldsInput',
         fields: orderLineCustomFields.reduce((fields, field) => {
             return { ...fields, [field.name]: { type: schema.getType(getGraphQlType(field.type)) } };
         }, {}),
     });
-
     schemaConfig.types.push(input);
-    addItemToOrderMutation.args.push({
-        name: 'customFields',
-        type: input,
-        description: null,
-        defaultValue: null,
-        extensions: null,
-        astNode: null,
-    });
-    adjustOrderLineMutation.args.push({
-        name: 'customFields',
-        type: input,
-        description: null,
-        defaultValue: null,
-        extensions: null,
-        astNode: null,
-    });
 
-    return new GraphQLSchema(schemaConfig);
+    const addItemToOrderMutation = mutationType.getFields().addItemToOrder;
+    const adjustOrderLineMutation = mutationType.getFields().adjustOrderLine;
+    if (addItemToOrderMutation) {
+        addItemToOrderMutation.args.push({
+            name: 'customFields',
+            type: input,
+            description: null,
+            defaultValue: null,
+            extensions: null,
+            astNode: null,
+        });
+    }
+    if (adjustOrderLineMutation) {
+        adjustOrderLineMutation.args.push({
+            name: 'customFields',
+            type: input,
+            description: null,
+            defaultValue: null,
+            extensions: null,
+            astNode: null,
+        });
+    }
+
+    let extendedSchema = new GraphQLSchema(schemaConfig);
+    if (schema.getType('AddItemInput')) {
+        const customFieldTypeDefs = `
+            extend input AddItemInput {
+                customFields: OrderLineCustomFieldsInput
+            }
+        `;
+
+        extendedSchema = extendSchema(extendedSchema, parse(customFieldTypeDefs));
+    }
+    if (schema.getType('AdjustOrderLineInput')) {
+        const customFieldTypeDefs = `
+            extend input AdjustOrderLineInput {
+                customFields: OrderLineCustomFieldsInput
+            }
+        `;
+
+        extendedSchema = extendSchema(extendedSchema, parse(customFieldTypeDefs));
+    }
+
+    return extendedSchema;
 }
 
 type GraphQLFieldType = 'DateTime' | 'String' | 'Int' | 'Float' | 'Boolean' | 'ID';

+ 6 - 1
packages/core/src/api/schema/admin-api/order.api.graphql

@@ -115,7 +115,7 @@ input ModifyOrderInput {
     dryRun: Boolean!
     orderId: ID!
     addItems: [AddItemInput!]
-    adjustOrderLines: [OrderLineInput!]
+    adjustOrderLines: [AdjustOrderLineInput!]
     surcharges: [SurchargeInput!]
     updateShippingAddress: UpdateOrderAddressInput
     updateBillingAddress: UpdateOrderAddressInput
@@ -129,6 +129,11 @@ input AddItemInput {
     quantity: Int!
 }
 
+input AdjustOrderLineInput {
+    orderLineId: ID!
+    quantity: Int!
+}
+
 input OrderLineInput {
     orderLineId: ID!
     quantity: Int!

+ 30 - 12
packages/core/src/service/helpers/order-modifier/order-modifier.ts

@@ -4,7 +4,7 @@ import { ID } from '@vendure/common/lib/shared-types';
 import { summate } from '@vendure/common/lib/shared-utils';
 
 import { RequestContext } from '../../../api/common/request-context';
-import { ErrorResultUnion, isGraphQlErrorResult, JustErrorResults } from '../../../common/error/error-result';
+import { isGraphQlErrorResult, JustErrorResults } from '../../../common/error/error-result';
 import { EntityNotFoundError, InternalServerError, UserInputError } from '../../../common/error/errors';
 import {
     NoChangesSpecifiedError,
@@ -32,6 +32,7 @@ import { ProductVariantService } from '../../services/product-variant.service';
 import { StockMovementService } from '../../services/stock-movement.service';
 import { TransactionalConnection } from '../../transaction/transactional-connection';
 import { OrderCalculator } from '../order-calculator/order-calculator';
+import { patchEntity } from '../utils/patch-entity';
 import { translateDeep } from '../utils/translate-entity';
 
 /**
@@ -209,12 +210,14 @@ export class OrderModifier {
             orderItems: [],
         };
 
-        for (const { productVariantId, quantity } of input.addItems ?? []) {
+        for (const row of input.addItems ?? []) {
+            const { productVariantId, quantity } = row;
             if (quantity < 0) {
                 return new NegativeQuantityError();
             }
-            // TODO: add support for OrderLine customFields
-            const orderLine = await this.getOrCreateItemOrderLine(ctx, order, productVariantId);
+
+            const customFields = (row as any).customFields || {};
+            const orderLine = await this.getOrCreateItemOrderLine(ctx, order, productVariantId, customFields);
             const correctedQuantity = await this.constrainQuantityToSaleable(
                 ctx,
                 orderLine.productVariant,
@@ -234,7 +237,8 @@ export class OrderModifier {
             modification.orderItems.push(...orderLine.items.slice(initialQuantity));
         }
 
-        for (const { orderLineId, quantity } of input.adjustOrderLines ?? []) {
+        for (const row of input.adjustOrderLines ?? []) {
+            const { orderLineId, quantity } = row;
             if (quantity < 0) {
                 return new NegativeQuantityError();
             }
@@ -242,11 +246,16 @@ export class OrderModifier {
             if (!orderLine) {
                 throw new UserInputError(`error.order-does-not-contain-line-with-id`, { id: orderLineId });
             }
-            const correctedQuantity = await this.constrainQuantityToSaleable(
-                ctx,
-                orderLine.productVariant,
-                quantity,
-            );
+            const initialLineQuantity = orderLine.quantity;
+            let correctedQuantity = quantity;
+            if (initialLineQuantity < quantity) {
+                const additionalQuantity = await this.constrainQuantityToSaleable(
+                    ctx,
+                    orderLine.productVariant,
+                    quantity - initialLineQuantity,
+                );
+                correctedQuantity = initialLineQuantity + additionalQuantity;
+            }
             const resultingOrderTotalQuantity = currentItemsCount + correctedQuantity - orderLine.quantity;
             if (orderItemsLimit < resultingOrderTotalQuantity) {
                 return new OrderLimitError(orderItemsLimit);
@@ -256,7 +265,10 @@ export class OrderModifier {
             if (correctedQuantity < quantity) {
                 return new InsufficientStockError(correctedQuantity, order);
             } else {
-                const initialLineQuantity = orderLine.quantity;
+                const customFields = (row as any).customFields;
+                if (customFields) {
+                    patchEntity(orderLine, { customFields });
+                }
                 await this.updateOrderLineQuantity(ctx, orderLine, quantity, order);
                 if (correctedQuantity < initialLineQuantity) {
                     const qtyDelta = initialLineQuantity - correctedQuantity;
@@ -344,6 +356,11 @@ export class OrderModifier {
             recalculateShipping: input.options?.recalculateShipping,
         });
 
+        const orderCustomFields = (input as any).customFields;
+        if (orderCustomFields) {
+            patchEntity(order, { customFields: orderCustomFields });
+        }
+
         if (dryRun) {
             return { order, modification };
         }
@@ -389,7 +406,8 @@ export class OrderModifier {
             !input.addItems?.length &&
             !input.surcharges?.length &&
             !input.updateShippingAddress &&
-            !input.updateBillingAddress;
+            !input.updateBillingAddress &&
+            !(input as any).customFields;
         return noChanges;
     }
 

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

@@ -1406,7 +1406,7 @@ export type ModifyOrderInput = {
     dryRun: Scalars['Boolean'];
     orderId: Scalars['ID'];
     addItems?: Maybe<Array<AddItemInput>>;
-    adjustOrderLines?: Maybe<Array<OrderLineInput>>;
+    adjustOrderLines?: Maybe<Array<AdjustOrderLineInput>>;
     surcharges?: Maybe<Array<SurchargeInput>>;
     updateShippingAddress?: Maybe<UpdateOrderAddressInput>;
     updateBillingAddress?: Maybe<UpdateOrderAddressInput>;
@@ -1420,6 +1420,11 @@ export type AddItemInput = {
     quantity: Scalars['Int'];
 };
 
+export type AdjustOrderLineInput = {
+    orderLineId: Scalars['ID'];
+    quantity: Scalars['Int'];
+};
+
 export type SurchargeInput = {
     description: Scalars['String'];
     sku?: Maybe<Scalars['String']>;
@@ -3594,7 +3599,7 @@ export type OrderLine = Node & {
     discounts: Array<Adjustment>;
     taxLines: Array<TaxLine>;
     order: Order;
-    customFields?: Maybe<OrderLineCustomFields>;
+    customFields?: Maybe<Scalars['JSON']>;
 };
 
 export type Payment = Node & {
@@ -4310,11 +4315,6 @@ export type HistoryEntrySortParameter = {
     updatedAt?: Maybe<SortOrder>;
 };
 
-export type OrderLineCustomFields = {
-    test?: Maybe<Scalars['String']>;
-    test2?: Maybe<Scalars['String']>;
-};
-
 export type AuthenticationInput = {
     native?: Maybe<NativeAuthInput>;
 };

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
schema-admin.json


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
schema-shop.json


Некоторые файлы не были показаны из-за большого количества измененных файлов