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

feat(server): Implement initial shipping step of checkout flow

Michael Bromley 7 лет назад
Родитель
Сommit
4f71b805cf

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


+ 4 - 4
server/e2e/order.e2e-spec.ts

@@ -167,7 +167,7 @@ describe('Orders', () => {
                 }
             `);
 
-            expect(result.nextOrderStates).toEqual(['ArrangingShipping']);
+            expect(result.nextOrderStates).toEqual(['ArrangingPayment']);
         });
 
         it('transitionOrderToState() throws for an invalid state', async () => {
@@ -191,14 +191,14 @@ describe('Orders', () => {
         it('transitionOrderToState() transitions Order to the next valid state', async () => {
             const result = await client.query(gql`
                 mutation {
-                    transitionOrderToState(state: "ArrangingShipping") {
+                    transitionOrderToState(state: "ArrangingPayment") {
                         id
                         state
                     }
                 }
             `);
 
-            expect(result.transitionOrderToState).toEqual({ id: 'T_1', state: 'ArrangingShipping' });
+            expect(result.transitionOrderToState).toEqual({ id: 'T_1', state: 'ArrangingPayment' });
         });
     });
 
@@ -283,7 +283,7 @@ describe('Orders', () => {
                 }
             `);
 
-            expect(result.nextOrderStates).toEqual(['ArrangingShipping']);
+            expect(result.nextOrderStates).toEqual(['ArrangingPayment']);
         });
 
         it('logging out and back in again resumes the last active order', async () => {

+ 19 - 2
server/src/api/resolvers/order.resolver.ts

@@ -2,10 +2,12 @@ import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
 import {
     AddItemToOrderMutationArgs,
     AdjustItemQuantityMutationArgs,
+    CreateAddressInput,
     OrderQueryArgs,
     OrdersQueryArgs,
     Permission,
     RemoveItemFromOrderMutationArgs,
+    SetOrderShippingAddressMutationArgs,
     TransitionOrderToStateMutationArgs,
 } from 'shared/generated-types';
 import { PaginatedList } from 'shared/shared-types';
@@ -50,8 +52,23 @@ export class OrderResolver {
         if (ctx.authorizedAsOwnerOnly) {
             const sessionOrder = await this.getOrderFromContext(ctx);
             if (sessionOrder) {
-                const order = await this.orderService.findOne(ctx, sessionOrder.id);
-                return order;
+                return this.orderService.findOne(ctx, sessionOrder.id);
+            } else {
+                return;
+            }
+        }
+    }
+
+    @Mutation()
+    @Allow(Permission.Owner)
+    async setOrderShippingAddress(
+        @Ctx() ctx: RequestContext,
+        @Args() args: SetOrderShippingAddressMutationArgs,
+    ): Promise<Order | undefined> {
+        if (ctx.authorizedAsOwnerOnly) {
+            const sessionOrder = await this.getOrderFromContext(ctx);
+            if (sessionOrder) {
+                return this.orderService.setShippingAddress(ctx, sessionOrder.id, args.input);
             } else {
                 return;
             }

+ 1 - 0
server/src/api/types/order.api.graphql

@@ -10,6 +10,7 @@ type Mutation {
     removeItemFromOrder(orderItemId: ID!): Order
     adjustItemQuantity(orderItemId: ID!, quantity: Int!): Order
     transitionOrderToState(state: String!): Order
+    setOrderShippingAddress(input: CreateAddressInput!): Order
 }
 
 type OrderList implements PaginatedList {

+ 2 - 0
server/src/common/types/common-types.graphql

@@ -6,9 +6,11 @@ scalar Upload
 enum AdjustmentType {
     TAX
     PROMOTION
+    SHIPPING
     REFUND
     TAX_REFUND
     PROMOTION_REFUND
+    SHIPPING_REFUND
 }
 
 type Adjustment {

+ 11 - 3
server/src/entity/order/order.entity.ts

@@ -1,4 +1,4 @@
-import { Adjustment, AdjustmentType } from 'shared/generated-types';
+import { Adjustment, AdjustmentType, ShippingAddress } from 'shared/generated-types';
 import { DeepPartial } from 'shared/shared-types';
 import { Column, Entity, ManyToOne, OneToMany } from 'typeorm';
 
@@ -30,18 +30,26 @@ export class Order extends VendureEntity {
 
     @Column('simple-json') pendingAdjustments: Adjustment[];
 
+    @Column('simple-json') shippingAddress: ShippingAddress;
+
     @Column() subTotalBeforeTax: number;
 
     @Column() subTotal: number;
 
+    @Column({ nullable: true })
+    shippingMethod: string;
+
+    @Column({ default: 0 })
+    shipping: number;
+
     @Calculated()
     get totalBeforeTax(): number {
-        return this.subTotalBeforeTax + this.promotionAdjustmentsTotal;
+        return this.subTotalBeforeTax + this.promotionAdjustmentsTotal + (this.shipping || 0);
     }
 
     @Calculated()
     get total(): number {
-        return this.subTotal + this.promotionAdjustmentsTotal;
+        return this.subTotal + this.promotionAdjustmentsTotal + (this.shipping || 0);
     }
 
     @Calculated()

+ 15 - 0
server/src/entity/order/order.graphql

@@ -5,10 +5,25 @@ type Order implements Node {
     code: String!
     state: String!
     customer: Customer
+    shippingAddress: ShippingAddress
     lines: [OrderLine!]!
     adjustments: [Adjustment!]!
     subTotalBeforeTax: Int!
     subTotal: Int!
+    shipping: Int!
+    shippingMethod: String
     totalBeforeTax: Int!
     total: Int!
 }
+
+type ShippingAddress {
+    fullName: String
+    company: String
+    streetLine1: String
+    streetLine2: String
+    city: String
+    province: String
+    postalCode: String
+    country: String
+    phoneNumber: String
+}

+ 2 - 1
server/src/entity/promotion/promotion.entity.ts

@@ -3,6 +3,7 @@ import { DeepPartial } from 'shared/shared-types';
 import { Column, Entity, JoinTable, ManyToMany } from 'typeorm';
 
 import { AdjustmentSource } from '../../common/types/adjustment-source';
+import { ChannelAware } from '../../common/types/common-types';
 import { PromotionItemAction, PromotionOrderAction } from '../../config/promotion/promotion-action';
 import { PromotionCondition } from '../../config/promotion/promotion-condition';
 import { getConfig } from '../../config/vendure-config';
@@ -12,7 +13,7 @@ import { OrderLine } from '../order-line/order-line.entity';
 import { Order } from '../order/order.entity';
 
 @Entity()
-export class Promotion extends AdjustmentSource {
+export class Promotion extends AdjustmentSource implements ChannelAware {
     type = AdjustmentType.PROMOTION;
     private readonly allConditions: { [code: string]: PromotionCondition } = {};
     private readonly allActions: { [code: string]: PromotionItemAction | PromotionOrderAction } = {};

+ 35 - 0
server/src/entity/shipping-item/shipping-item.entity.ts

@@ -0,0 +1,35 @@
+import { Adjustment, AdjustmentType } from 'shared/generated-types';
+import { DeepPartial } from 'shared/shared-types';
+import { Column, Entity, ManyToOne } from 'typeorm';
+
+import { Calculated } from '../../common/calculated-decorator';
+import { VendureEntity } from '../base/base.entity';
+import { OrderLine } from '../order-line/order-line.entity';
+import { ShippingMethod } from '../shipping-method/shipping-method.entity';
+
+@Entity()
+export class ShippingItem extends VendureEntity {
+    constructor(input?: DeepPartial<ShippingItem>) {
+        super(input);
+    }
+
+    @Column() price: number;
+
+    @Column('simple-json') pendingAdjustments: Adjustment[];
+
+    @ManyToOne(type => ShippingMethod)
+    shippingMethod: ShippingMethod;
+
+    @Calculated()
+    get adjustments(): Adjustment[] {
+        return this.pendingAdjustments;
+    }
+
+    clearAdjustments(type?: AdjustmentType) {
+        if (!type) {
+            this.pendingAdjustments = [];
+        } else {
+            this.pendingAdjustments = this.pendingAdjustments.filter(a => a.type !== type);
+        }
+    }
+}

+ 37 - 3
server/src/entity/shipping-method/shipping-method.entity.ts

@@ -1,15 +1,27 @@
-import { AdjustmentOperation } from 'shared/generated-types';
+import { Adjustment, AdjustmentOperation, AdjustmentType } from 'shared/generated-types';
 import { DeepPartial } from 'shared/shared-types';
 import { Column, Entity, JoinTable, ManyToMany } from 'typeorm';
 
+import { AdjustmentSource } from '../../common/types/adjustment-source';
 import { ChannelAware } from '../../common/types/common-types';
-import { VendureEntity } from '../base/base.entity';
+import { ShippingCalculator } from '../../config/shipping-method/shipping-calculator';
+import { ShippingEligibilityChecker } from '../../config/shipping-method/shipping-eligibility-checker';
+import { getConfig } from '../../config/vendure-config';
 import { Channel } from '../channel/channel.entity';
+import { Order } from '../order/order.entity';
 
 @Entity()
-export class ShippingMethod extends VendureEntity implements ChannelAware {
+export class ShippingMethod extends AdjustmentSource implements ChannelAware {
+    type = AdjustmentType.SHIPPING;
+    private readonly allCheckers: { [code: string]: ShippingEligibilityChecker } = {};
+    private readonly allCalculators: { [code: string]: ShippingCalculator } = {};
+
     constructor(input?: DeepPartial<ShippingMethod>) {
         super(input);
+        const checkers = getConfig().shippingOptions.shippingEligibilityCheckers || [];
+        const calculators = getConfig().shippingOptions.shippingCalculators || [];
+        this.allCheckers = checkers.reduce((hash, o) => ({ ...hash, [o.code]: o }), {});
+        this.allCalculators = calculators.reduce((hash, o) => ({ ...hash, [o.code]: o }), {});
     }
 
     @Column() code: string;
@@ -23,4 +35,26 @@ export class ShippingMethod extends VendureEntity implements ChannelAware {
     @ManyToMany(type => Channel)
     @JoinTable()
     channels: Channel[];
+
+    apply(order: Order): Adjustment | undefined {
+        const calculator = this.allCalculators[this.calculator.code];
+        if (calculator) {
+            const amount = calculator.calculate(order, this.calculator.args);
+            return {
+                amount,
+                type: this.type,
+                description: this.description,
+                adjustmentSource: this.getSourceId(),
+            };
+        }
+    }
+
+    test(order: Order): boolean {
+        const checker = this.allCheckers[this.checker.code];
+        if (checker) {
+            return checker.check(order, this.checker.args);
+        } else {
+            return false;
+        }
+    }
 }

+ 6 - 4
server/src/service/helpers/order-calculator/order-calculator.spec.ts

@@ -8,6 +8,7 @@ import { Order } from '../../../entity/order/order.entity';
 import { TaxCategory } from '../../../entity/tax-category/tax-category.entity';
 import { TaxRateService } from '../../services/tax-rate.service';
 import { ListQueryBuilder } from '../list-query-builder/list-query-builder';
+import { ShippingCalculator } from '../shipping-calculator/shipping-calculator';
 import { TaxCalculator } from '../tax-calculator/tax-calculator';
 import {
     createRequestContext,
@@ -27,6 +28,7 @@ describe('OrderCalculator', () => {
                 OrderCalculator,
                 TaxCalculator,
                 TaxRateService,
+                { provide: ShippingCalculator, useValue: { getEligibleShippingMethods: () => [] } },
                 { provide: Connection, useClass: MockConnection },
                 { provide: ListQueryBuilder, useValue: {} },
             ],
@@ -66,7 +68,7 @@ describe('OrderCalculator', () => {
             const order = createOrder({
                 lines: [{ unitPrice: 123, taxCategory: taxCategoryStandard, quantity: 1 }],
             });
-            orderCalculator.applyTaxesAndPromotions(ctx, order, []);
+            orderCalculator.applyPriceAdjustments(ctx, order, []);
 
             expect(order.subTotal).toBe(148);
             expect(order.subTotalBeforeTax).toBe(123);
@@ -77,7 +79,7 @@ describe('OrderCalculator', () => {
             const order = createOrder({
                 lines: [{ unitPrice: 123, taxCategory: taxCategoryStandard, quantity: 3 }],
             });
-            orderCalculator.applyTaxesAndPromotions(ctx, order, []);
+            orderCalculator.applyPriceAdjustments(ctx, order, []);
 
             expect(order.subTotal).toBe(444);
             expect(order.subTotalBeforeTax).toBe(369);
@@ -88,7 +90,7 @@ describe('OrderCalculator', () => {
             const order = createOrder({
                 lines: [{ unitPrice: 123, taxCategory: taxCategoryStandard, quantity: 1 }],
             });
-            orderCalculator.applyTaxesAndPromotions(ctx, order, []);
+            orderCalculator.applyPriceAdjustments(ctx, order, []);
 
             expect(order.subTotal).toBe(123);
             expect(order.subTotalBeforeTax).toBe(102);
@@ -101,7 +103,7 @@ describe('OrderCalculator', () => {
                 subTotal: 148,
                 subTotalBeforeTax: 123,
             });
-            orderCalculator.applyTaxesAndPromotions(ctx, order, []);
+            orderCalculator.applyPriceAdjustments(ctx, order, []);
 
             expect(order.subTotal).toBe(0);
             expect(order.subTotalBeforeTax).toBe(0);

+ 19 - 6
server/src/service/helpers/order-calculator/order-calculator.ts

@@ -5,28 +5,33 @@ import { RequestContext } from '../../../api/common/request-context';
 import { Order } from '../../../entity/order/order.entity';
 import { Promotion } from '../../../entity/promotion/promotion.entity';
 import { Zone } from '../../../entity/zone/zone.entity';
-
 import { TaxRateService } from '../../services/tax-rate.service';
+import { ShippingCalculator } from '../shipping-calculator/shipping-calculator';
 import { TaxCalculator } from '../tax-calculator/tax-calculator';
 
 @Injectable()
 export class OrderCalculator {
-    constructor(private taxRateService: TaxRateService, private taxCalculator: TaxCalculator) {}
+    constructor(
+        private taxRateService: TaxRateService,
+        private taxCalculator: TaxCalculator,
+        private shippingCalculator: ShippingCalculator,
+    ) {}
 
     /**
      * Applies taxes and promotions to an Order. Mutates the order object.
      */
-    applyTaxesAndPromotions(ctx: RequestContext, order: Order, promotions: Promotion[]): Order {
+    applyPriceAdjustments(ctx: RequestContext, order: Order, promotions: Promotion[]): Order {
         const activeZone = ctx.channel.defaultTaxZone;
         order.clearAdjustments();
         if (order.lines.length) {
             // First apply taxes to the non-discounted prices
-            this.applyTaxes(order, activeZone, ctx);
+            this.applyTaxes(ctx, order, activeZone);
             // Then test and apply promotions
             this.applyPromotions(order, promotions);
             // Finally, re-calculate taxes because the promotions may have
             // altered the unit prices, which in turn will alter the tax payable.
-            this.applyTaxes(order, activeZone, ctx);
+            this.applyTaxes(ctx, order, activeZone);
+            this.applyShipping(ctx, order);
         } else {
             this.calculateOrderTotals(order);
         }
@@ -36,7 +41,7 @@ export class OrderCalculator {
     /**
      * Applies the correct TaxRate to each OrderItem in the order.
      */
-    private applyTaxes(order: Order, activeZone: Zone, ctx: RequestContext) {
+    private applyTaxes(ctx: RequestContext, order: Order, activeZone: Zone) {
         for (const line of order.lines) {
             line.clearAdjustments(AdjustmentType.TAX);
 
@@ -96,6 +101,14 @@ export class OrderCalculator {
         }
     }
 
+    private applyShipping(ctx: RequestContext, order: Order) {
+        const results = this.shippingCalculator.getEligibleShippingMethods(ctx, order);
+        if (results && results.length) {
+            order.shipping = results[0].price;
+            order.shippingMethod = results[0].method.description;
+        }
+    }
+
     private calculateOrderTotals(order: Order) {
         let totalPrice = 0;
         let totalTax = 0;

+ 2 - 10
server/src/service/helpers/order-state-machine/order-state.ts

@@ -5,19 +5,11 @@ import { Order } from '../../../entity/order/order.entity';
  * These are the default states of the Order process. They can be augmented via
  * the orderProcessOptions property in VendureConfig.
  */
-export type OrderState =
-    | 'AddingItems'
-    | 'ArrangingShipping'
-    | 'ArrangingPayment'
-    | 'OrderComplete'
-    | 'Cancelled';
+export type OrderState = 'AddingItems' | 'ArrangingPayment' | 'OrderComplete' | 'Cancelled';
 
 export const orderStateTransitions: Transitions<OrderState> = {
     AddingItems: {
-        to: ['ArrangingShipping'],
-    },
-    ArrangingShipping: {
-        to: ['ArrangingPayment', 'AddingItems'],
+        to: ['ArrangingPayment'],
     },
     ArrangingPayment: {
         to: ['OrderComplete', 'AddingItems'],

+ 36 - 0
server/src/service/helpers/shipping-calculator/shipping-calculator.ts

@@ -0,0 +1,36 @@
+import { Injectable } from '@nestjs/common';
+import { notNullOrUndefined } from 'shared/shared-utils';
+
+import { RequestContext } from '../../../api/common/request-context';
+import { Order } from '../../../entity/order/order.entity';
+import { ShippingMethod } from '../../../entity/shipping-method/shipping-method.entity';
+import { ShippingMethodService } from '../../services/shipping-method.service';
+
+@Injectable()
+export class ShippingCalculator {
+    constructor(private shippingMethodService: ShippingMethodService) {}
+
+    /**
+     * Returns an array of each eligible ShippingMethod for the given Order and sorts them by
+     * price, with the cheapest first.
+     */
+    getEligibleShippingMethods(
+        ctx: RequestContext,
+        order: Order,
+    ): Array<{ method: ShippingMethod; price: number }> {
+        const shippingMethods = this.shippingMethodService.getActiveShippingMethods(ctx.channel);
+        return shippingMethods
+            .filter(sm => sm.test(order))
+            .map(sm => {
+                const adjustment = sm.apply(order);
+                if (adjustment) {
+                    return {
+                        method: sm,
+                        price: adjustment.amount,
+                    };
+                }
+            })
+            .filter(notNullOrUndefined)
+            .sort((a, b) => a.price - b.price);
+    }
+}

+ 4 - 0
server/src/service/service.module.ts

@@ -9,6 +9,7 @@ import { OrderCalculator } from './helpers/order-calculator/order-calculator';
 import { OrderMerger } from './helpers/order-merger/order-merger';
 import { OrderStateMachine } from './helpers/order-state-machine/order-state-machine';
 import { PasswordCiper } from './helpers/password-cipher/password-ciper';
+import { ShippingCalculator } from './helpers/shipping-calculator/shipping-calculator';
 import { TaxCalculator } from './helpers/tax-calculator/tax-calculator';
 import { TranslatableSaver } from './helpers/translatable-saver/translatable-saver';
 import { AdministratorService } from './services/administrator.service';
@@ -73,6 +74,7 @@ const exportedProviders = [
         OrderStateMachine,
         OrderMerger,
         ListQueryBuilder,
+        ShippingCalculator,
     ],
     exports: exportedProviders,
 })
@@ -82,6 +84,7 @@ export class ServiceModule implements OnModuleInit {
         private roleService: RoleService,
         private administratorService: AdministratorService,
         private taxRateService: TaxRateService,
+        private shippingMethodService: ShippingMethodService,
     ) {}
 
     async onModuleInit() {
@@ -89,5 +92,6 @@ export class ServiceModule implements OnModuleInit {
         await this.roleService.initRoles();
         await this.administratorService.initAdministrators();
         await this.taxRateService.initTaxRates();
+        await this.shippingMethodService.initShippingMethods();
     }
 }

+ 16 - 4
server/src/service/services/order.service.ts

@@ -1,4 +1,5 @@
 import { InjectConnection } from '@nestjs/typeorm';
+import { CreateAddressInput } from 'shared/generated-types';
 import { ID, PaginatedList } from 'shared/shared-types';
 import { Connection } from 'typeorm';
 
@@ -88,6 +89,7 @@ export class OrderService {
             code: generatePublicId(),
             state: this.orderStateMachine.getInitialState(),
             lines: [],
+            shippingAddress: {},
             pendingAdjustments: [],
             subTotal: 0,
             subTotalBeforeTax: 0,
@@ -151,14 +153,14 @@ export class OrderService {
             orderLine.items = orderLine.items.slice(0, quantity);
         }
         await this.connection.getRepository(OrderLine).save(orderLine);
-        return this.applyTaxesAndPromotions(ctx, order);
+        return this.applyPriceAdjustments(ctx, order);
     }
 
     async removeItemFromOrder(ctx: RequestContext, orderId: ID, orderLineId: ID): Promise<Order> {
         const order = await this.getOrderOrThrow(ctx, orderId);
         const orderLine = this.getOrderLineOrThrow(order, orderLineId);
         order.lines = order.lines.filter(line => !idsAreEqual(line.id, orderLineId));
-        const updatedOrder = await this.applyTaxesAndPromotions(ctx, order);
+        const updatedOrder = await this.applyPriceAdjustments(ctx, order);
         await this.connection.getRepository(OrderLine).remove(orderLine);
         return updatedOrder;
     }
@@ -167,6 +169,13 @@ export class OrderService {
         return this.orderStateMachine.getNextStates(order);
     }
 
+    async setShippingAddress(ctx: RequestContext, orderId: ID, input: CreateAddressInput): Promise<Order> {
+        const order = await this.getOrderOrThrow(ctx, orderId);
+        order.shippingAddress = input;
+        await this.applyPriceAdjustments(ctx, order);
+        return this.connection.getRepository(Order).save(order);
+    }
+
     async transitionToState(ctx: RequestContext, orderId: ID, state: OrderState): Promise<Order> {
         const order = await this.getOrderOrThrow(ctx, orderId);
         await this.orderStateMachine.transition(order, state);
@@ -250,12 +259,15 @@ export class OrderService {
         }
     }
 
-    private async applyTaxesAndPromotions(ctx: RequestContext, order: Order): Promise<Order> {
+    /**
+     * Applies promotions, taxes and shipping to the Order.
+     */
+    private async applyPriceAdjustments(ctx: RequestContext, order: Order): Promise<Order> {
         const promotions = await this.connection.getRepository(Promotion).find({
             where: { enabled: true },
             order: { priorityScore: 'ASC' },
         });
-        order = this.orderCalculator.applyTaxesAndPromotions(ctx, order, promotions);
+        order = this.orderCalculator.applyPriceAdjustments(ctx, order, promotions);
         await this.connection.getRepository(Order).save(order);
         await this.connection.getRepository(OrderItem).save(order.getOrderItems());
         await this.connection.getRepository(OrderLine).save(order.lines);

+ 21 - 2
server/src/service/services/shipping-method.service.ts

@@ -15,6 +15,7 @@ import { assertFound } from '../../common/utils';
 import { ConfigService } from '../../config/config.service';
 import { ShippingCalculator } from '../../config/shipping-method/shipping-calculator';
 import { ShippingEligibilityChecker } from '../../config/shipping-method/shipping-eligibility-checker';
+import { Channel } from '../../entity/channel/channel.entity';
 import { ShippingMethod } from '../../entity/shipping-method/shipping-method.entity';
 import { I18nError } from '../../i18n/i18n-error';
 import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-builder';
@@ -26,6 +27,7 @@ import { ChannelService } from './channel.service';
 export class ShippingMethodService {
     shippingEligibilityCheckers: ShippingEligibilityChecker[];
     shippingCalculators: ShippingCalculator[];
+    private activeShippingMethods: ShippingMethod[];
 
     constructor(
         @InjectConnection() private connection: Connection,
@@ -38,6 +40,10 @@ export class ShippingMethodService {
         this.shippingCalculators = this.configService.shippingOptions.shippingCalculators || [];
     }
 
+    async initShippingMethods() {
+        await this.updateActiveShippingMethods();
+    }
+
     findAll(options?: ListQueryOptions<ShippingMethod>): Promise<PaginatedList<ShippingMethod>> {
         return this.listQueryBuilder
             .build(ShippingMethod, options, ['channels'])
@@ -62,7 +68,9 @@ export class ShippingMethodService {
             calculator: this.parseOperationArgs(input.calculator, this.getCalculator(input.calculator.code)),
         });
         shippingMethod.channels = [this.channelService.getDefaultChannel()];
-        return this.connection.manager.save(shippingMethod);
+        const newShippingMethod = await this.connection.manager.save(shippingMethod);
+        await this.updateActiveShippingMethods();
+        return assertFound(this.findOne(newShippingMethod.id));
     }
 
     async update(input: UpdateShippingMethodInput): Promise<ShippingMethod> {
@@ -83,10 +91,11 @@ export class ShippingMethodService {
         if (input.calculator) {
             updatedShippingMethod.calculator = this.parseOperationArgs(
                 input.calculator,
-                this.getChecker(input.calculator.code),
+                this.getCalculator(input.calculator.code),
             );
         }
         await this.connection.manager.save(updatedShippingMethod);
+        await this.updateActiveShippingMethods();
         return assertFound(this.findOne(shippingMethod.id));
     }
 
@@ -98,6 +107,10 @@ export class ShippingMethodService {
         return this.shippingCalculators.map(this.toAdjustmentOperation);
     }
 
+    getActiveShippingMethods(channel: Channel): ShippingMethod[] {
+        return this.activeShippingMethods.filter(sm => sm.channels.find(c => c.id === channel.id));
+    }
+
     private toAdjustmentOperation(source: ShippingCalculator | ShippingEligibilityChecker) {
         return {
             code: source.code,
@@ -142,4 +155,10 @@ export class ShippingMethodService {
         }
         return match;
     }
+
+    private async updateActiveShippingMethods() {
+        this.activeShippingMethods = await this.connection.getRepository(ShippingMethod).find({
+            relations: ['channels'],
+        });
+    }
 }

+ 102 - 0
shared/generated-types.ts

@@ -293,14 +293,29 @@ export interface Order extends Node {
     code: string;
     state: string;
     customer?: Customer | null;
+    shippingAddress?: ShippingAddress | null;
     lines: OrderLine[];
     adjustments: Adjustment[];
     subTotalBeforeTax: number;
     subTotal: number;
+    shipping: number;
+    shippingMethod?: string | null;
     totalBeforeTax: number;
     total: number;
 }
 
+export interface ShippingAddress {
+    fullName?: string | null;
+    company?: string | null;
+    streetLine1?: string | null;
+    streetLine2?: string | null;
+    city?: string | null;
+    province?: string | null;
+    postalCode?: string | null;
+    country?: string | null;
+    phoneNumber?: string | null;
+}
+
 export interface OrderLine extends Node {
     id: string;
     createdAt: DateTime;
@@ -563,6 +578,7 @@ export interface Mutation {
     removeItemFromOrder?: Order | null;
     adjustItemQuantity?: Order | null;
     transitionOrderToState?: Order | null;
+    setOrderShippingAddress?: Order | null;
     createProductOptionGroup: ProductOptionGroup;
     updateProductOptionGroup: ProductOptionGroup;
     createProduct: Product;
@@ -1437,6 +1453,9 @@ export interface AdjustItemQuantityMutationArgs {
 export interface TransitionOrderToStateMutationArgs {
     state: string;
 }
+export interface SetOrderShippingAddressMutationArgs {
+    input: CreateAddressInput;
+}
 export interface CreateProductOptionGroupMutationArgs {
     input: CreateProductOptionGroupInput;
 }
@@ -1750,9 +1769,11 @@ export enum AssetType {
 export enum AdjustmentType {
     TAX = 'TAX',
     PROMOTION = 'PROMOTION',
+    SHIPPING = 'SHIPPING',
     REFUND = 'REFUND',
     TAX_REFUND = 'TAX_REFUND',
     PROMOTION_REFUND = 'PROMOTION_REFUND',
+    SHIPPING_REFUND = 'SHIPPING_REFUND',
 }
 
 export namespace QueryResolvers {
@@ -2720,10 +2741,13 @@ export namespace OrderResolvers {
         code?: CodeResolver<string, any, Context>;
         state?: StateResolver<string, any, Context>;
         customer?: CustomerResolver<Customer | null, any, Context>;
+        shippingAddress?: ShippingAddressResolver<ShippingAddress | null, any, Context>;
         lines?: LinesResolver<OrderLine[], any, Context>;
         adjustments?: AdjustmentsResolver<Adjustment[], any, Context>;
         subTotalBeforeTax?: SubTotalBeforeTaxResolver<number, any, Context>;
         subTotal?: SubTotalResolver<number, any, Context>;
+        shipping?: ShippingResolver<number, any, Context>;
+        shippingMethod?: ShippingMethodResolver<string | null, any, Context>;
         totalBeforeTax?: TotalBeforeTaxResolver<number, any, Context>;
         total?: TotalResolver<number, any, Context>;
     }
@@ -2738,6 +2762,11 @@ export namespace OrderResolvers {
         Parent,
         Context
     >;
+    export type ShippingAddressResolver<R = ShippingAddress | null, Parent = any, Context = any> = Resolver<
+        R,
+        Parent,
+        Context
+    >;
     export type LinesResolver<R = OrderLine[], Parent = any, Context = any> = Resolver<R, Parent, Context>;
     export type AdjustmentsResolver<R = Adjustment[], Parent = any, Context = any> = Resolver<
         R,
@@ -2750,6 +2779,12 @@ export namespace OrderResolvers {
         Context
     >;
     export type SubTotalResolver<R = number, Parent = any, Context = any> = Resolver<R, Parent, Context>;
+    export type ShippingResolver<R = number, Parent = any, Context = any> = Resolver<R, Parent, Context>;
+    export type ShippingMethodResolver<R = string | null, Parent = any, Context = any> = Resolver<
+        R,
+        Parent,
+        Context
+    >;
     export type TotalBeforeTaxResolver<R = number, Parent = any, Context = any> = Resolver<
         R,
         Parent,
@@ -2758,6 +2793,62 @@ export namespace OrderResolvers {
     export type TotalResolver<R = number, Parent = any, Context = any> = Resolver<R, Parent, Context>;
 }
 
+export namespace ShippingAddressResolvers {
+    export interface Resolvers<Context = any> {
+        fullName?: FullNameResolver<string | null, any, Context>;
+        company?: CompanyResolver<string | null, any, Context>;
+        streetLine1?: StreetLine1Resolver<string | null, any, Context>;
+        streetLine2?: StreetLine2Resolver<string | null, any, Context>;
+        city?: CityResolver<string | null, any, Context>;
+        province?: ProvinceResolver<string | null, any, Context>;
+        postalCode?: PostalCodeResolver<string | null, any, Context>;
+        country?: CountryResolver<string | null, any, Context>;
+        phoneNumber?: PhoneNumberResolver<string | null, any, Context>;
+    }
+
+    export type FullNameResolver<R = string | null, Parent = any, Context = any> = Resolver<
+        R,
+        Parent,
+        Context
+    >;
+    export type CompanyResolver<R = string | null, Parent = any, Context = any> = Resolver<
+        R,
+        Parent,
+        Context
+    >;
+    export type StreetLine1Resolver<R = string | null, Parent = any, Context = any> = Resolver<
+        R,
+        Parent,
+        Context
+    >;
+    export type StreetLine2Resolver<R = string | null, Parent = any, Context = any> = Resolver<
+        R,
+        Parent,
+        Context
+    >;
+    export type CityResolver<R = string | null, Parent = any, Context = any> = Resolver<R, Parent, Context>;
+    export type ProvinceResolver<R = string | null, Parent = any, Context = any> = Resolver<
+        R,
+        Parent,
+        Context
+    >;
+    export type PostalCodeResolver<R = string | null, Parent = any, Context = any> = Resolver<
+        R,
+        Parent,
+        Context
+    >;
+    export type CountryResolver<R = string | null, Parent = any, Context = any> = Resolver<
+        R,
+        Parent,
+        Context
+    >;
+    export type PhoneNumberResolver<R = string | null, Parent = any, Context = any> = Resolver<
+        R,
+        Parent,
+        Context
+    >;
+}
+
 export namespace OrderLineResolvers {
     export interface Resolvers<Context = any> {
         id?: IdResolver<string, any, Context>;
@@ -3448,6 +3539,7 @@ export namespace MutationResolvers {
         removeItemFromOrder?: RemoveItemFromOrderResolver<Order | null, any, Context>;
         adjustItemQuantity?: AdjustItemQuantityResolver<Order | null, any, Context>;
         transitionOrderToState?: TransitionOrderToStateResolver<Order | null, any, Context>;
+        setOrderShippingAddress?: SetOrderShippingAddressResolver<Order | null, any, Context>;
         createProductOptionGroup?: CreateProductOptionGroupResolver<ProductOptionGroup, any, Context>;
         updateProductOptionGroup?: UpdateProductOptionGroupResolver<ProductOptionGroup, any, Context>;
         createProduct?: CreateProductResolver<Product, any, Context>;
@@ -3742,6 +3834,16 @@ export namespace MutationResolvers {
         state: string;
     }
 
+    export type SetOrderShippingAddressResolver<R = Order | null, Parent = any, Context = any> = Resolver<
+        R,
+        Parent,
+        Context,
+        SetOrderShippingAddressArgs
+    >;
+    export interface SetOrderShippingAddressArgs {
+        input: CreateAddressInput;
+    }
+
     export type CreateProductOptionGroupResolver<
         R = ProductOptionGroup,
         Parent = any,

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