| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253 |
- /* eslint-disable @typescript-eslint/no-non-null-assertion */
- import { summate } from '@vendure/common/lib/shared-utils';
- import {
- defaultShippingCalculator,
- defaultShippingEligibilityChecker,
- manualFulfillmentHandler,
- mergeConfig,
- Order,
- OrderLine,
- OrderService,
- RequestContext,
- RequestContextService,
- ShippingLine,
- ShippingLineAssignmentStrategy,
- } from '@vendure/core';
- import { createErrorResultGuard, createTestEnvironment, ErrorResultGuard } from '@vendure/testing';
- import path from 'path';
- import { afterAll, beforeAll, describe, expect, it } from 'vitest';
- import { initialData } from '../../../e2e-common/e2e-initial-data';
- import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config';
- import { CreateShippingMethodDocument, LanguageCode } from './graphql/generated-e2e-admin-types';
- import * as CodegenShop from './graphql/generated-e2e-shop-types';
- import {
- ADD_ITEM_TO_ORDER,
- GET_ACTIVE_ORDER,
- GET_ELIGIBLE_SHIPPING_METHODS,
- REMOVE_ITEM_FROM_ORDER,
- SET_SHIPPING_ADDRESS,
- SET_SHIPPING_METHOD,
- } from './graphql/shop-definitions';
- declare module '@vendure/core/dist/entity/custom-entity-fields' {
- interface CustomShippingMethodFields {
- minPrice: number;
- maxPrice: number;
- }
- }
- class CustomShippingLineAssignmentStrategy implements ShippingLineAssignmentStrategy {
- assignShippingLineToOrderLines(
- ctx: RequestContext,
- shippingLine: ShippingLine,
- order: Order,
- ): OrderLine[] | Promise<OrderLine[]> {
- const { minPrice, maxPrice } = shippingLine.shippingMethod.customFields;
- return order.lines.filter(l => l.linePriceWithTax >= minPrice && l.linePriceWithTax <= maxPrice);
- }
- }
- describe('Multiple shipping orders', () => {
- const { server, adminClient, shopClient } = createTestEnvironment(
- mergeConfig(testConfig(), {
- customFields: {
- ShippingMethod: [
- { name: 'minPrice', type: 'int' },
- { name: 'maxPrice', type: 'int' },
- ],
- },
- shippingOptions: {
- shippingLineAssignmentStrategy: new CustomShippingLineAssignmentStrategy(),
- },
- }),
- );
- type OrderSuccessResult =
- | CodegenShop.UpdatedOrderFragment
- | CodegenShop.TestOrderFragmentFragment
- | CodegenShop.TestOrderWithPaymentsFragment
- | CodegenShop.ActiveOrderCustomerFragment;
- const orderResultGuard: ErrorResultGuard<OrderSuccessResult> = createErrorResultGuard(
- input => !!input.lines,
- );
- let lessThan100MethodId: string;
- let greaterThan100MethodId: string;
- let orderService: OrderService;
- beforeAll(async () => {
- await server.init({
- initialData,
- productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
- customerCount: 3,
- });
- await adminClient.asSuperAdmin();
- orderService = server.app.get(OrderService);
- }, TEST_SETUP_TIMEOUT_MS);
- afterAll(async () => {
- await server.destroy();
- });
- it('setup shipping methods', async () => {
- const result1 = await adminClient.query(CreateShippingMethodDocument, {
- input: {
- code: 'less-than-100',
- translations: [{ languageCode: LanguageCode.en, name: 'Less than 100', description: '' }],
- fulfillmentHandler: manualFulfillmentHandler.code,
- checker: {
- code: defaultShippingEligibilityChecker.code,
- arguments: [{ name: 'orderMinimum', value: '0' }],
- },
- calculator: {
- code: defaultShippingCalculator.code,
- arguments: [
- { name: 'rate', value: '1000' },
- { name: 'taxRate', value: '0' },
- { name: 'includesTax', value: 'auto' },
- ],
- },
- customFields: {
- minPrice: 0,
- maxPrice: 100_00,
- },
- },
- });
- const result2 = await adminClient.query(CreateShippingMethodDocument, {
- input: {
- code: 'greater-than-100',
- translations: [{ languageCode: LanguageCode.en, name: 'Greater than 200', description: '' }],
- fulfillmentHandler: manualFulfillmentHandler.code,
- checker: {
- code: defaultShippingEligibilityChecker.code,
- arguments: [{ name: 'orderMinimum', value: '0' }],
- },
- calculator: {
- code: defaultShippingCalculator.code,
- arguments: [
- { name: 'rate', value: '2000' },
- { name: 'taxRate', value: '0' },
- { name: 'includesTax', value: 'auto' },
- ],
- },
- customFields: {
- minPrice: 100_00,
- maxPrice: 500000_00,
- },
- },
- });
- expect(result1.createShippingMethod.id).toBe('T_4');
- expect(result2.createShippingMethod.id).toBe('T_5');
- lessThan100MethodId = result1.createShippingMethod.id;
- greaterThan100MethodId = result2.createShippingMethod.id;
- });
- it('assigns shipping methods to correct order lines', async () => {
- await shopClient.query<
- CodegenShop.AddItemToOrderMutation,
- CodegenShop.AddItemToOrderMutationVariables
- >(ADD_ITEM_TO_ORDER, {
- productVariantId: 'T_1',
- quantity: 1,
- });
- await shopClient.query<
- CodegenShop.AddItemToOrderMutation,
- CodegenShop.AddItemToOrderMutationVariables
- >(ADD_ITEM_TO_ORDER, {
- productVariantId: 'T_11',
- quantity: 1,
- });
- await shopClient.query<
- CodegenShop.SetShippingAddressMutation,
- CodegenShop.SetShippingAddressMutationVariables
- >(SET_SHIPPING_ADDRESS, {
- input: {
- streetLine1: '12 the street',
- postalCode: '123456',
- countryCode: 'US',
- },
- });
- const { eligibleShippingMethods } = await shopClient.query<CodegenShop.GetShippingMethodsQuery>(
- GET_ELIGIBLE_SHIPPING_METHODS,
- );
- expect(eligibleShippingMethods.map(m => m.id).includes(lessThan100MethodId)).toBe(true);
- expect(eligibleShippingMethods.map(m => m.id).includes(greaterThan100MethodId)).toBe(true);
- const { setOrderShippingMethod } = await shopClient.query<
- CodegenShop.SetShippingMethodMutation,
- CodegenShop.SetShippingMethodMutationVariables
- >(SET_SHIPPING_METHOD, {
- id: [lessThan100MethodId, greaterThan100MethodId],
- });
- orderResultGuard.assertSuccess(setOrderShippingMethod);
- const order = await getInternalOrder(setOrderShippingMethod.id);
- expect(order?.lines[0].shippingLine?.shippingMethod.code).toBe('greater-than-100');
- expect(order?.lines[1].shippingLine?.shippingMethod.code).toBe('less-than-100');
- expect(order?.shippingLines.length).toBe(2);
- });
- it('removes shipping methods that are no longer applicable', async () => {
- const { activeOrder } = await shopClient.query<CodegenShop.GetActiveOrderQuery>(GET_ACTIVE_ORDER);
- const { removeOrderLine } = await shopClient.query<
- CodegenShop.RemoveItemFromOrderMutation,
- CodegenShop.RemoveItemFromOrderMutationVariables
- >(REMOVE_ITEM_FROM_ORDER, {
- orderLineId: activeOrder!.lines[0].id,
- });
- orderResultGuard.assertSuccess(removeOrderLine);
- const order = await getInternalOrder(activeOrder!.id);
- expect(order?.lines.length).toBe(1);
- expect(order?.shippingLines.length).toBe(1);
- expect(order?.shippingLines[0].shippingMethod.code).toBe('less-than-100');
- const { activeOrder: activeOrder2 } =
- await shopClient.query<CodegenShop.GetActiveOrderQuery>(GET_ACTIVE_ORDER);
- expect(activeOrder2?.shippingWithTax).toBe(summate(activeOrder2!.shippingLines, 'priceWithTax'));
- });
- it('removes remaining shipping method when removing all items', async () => {
- const { activeOrder } = await shopClient.query<CodegenShop.GetActiveOrderQuery>(GET_ACTIVE_ORDER);
- const { removeOrderLine } = await shopClient.query<
- CodegenShop.RemoveItemFromOrderMutation,
- CodegenShop.RemoveItemFromOrderMutationVariables
- >(REMOVE_ITEM_FROM_ORDER, {
- orderLineId: activeOrder!.lines[0].id,
- });
- orderResultGuard.assertSuccess(removeOrderLine);
- const order = await getInternalOrder(activeOrder!.id);
- expect(order?.lines.length).toBe(0);
- const { activeOrder: activeOrder2 } =
- await shopClient.query<CodegenShop.GetActiveOrderQuery>(GET_ACTIVE_ORDER);
- expect(activeOrder2?.shippingWithTax).toBe(0);
- });
- async function getInternalOrder(externalOrderId: string): Promise<Order> {
- const ctx = await server.app.get(RequestContextService).create({ apiType: 'admin' });
- const internalOrderId = +externalOrderId.replace('T_', '');
- const order = await orderService.findOne(ctx, internalOrderId, [
- 'lines',
- 'lines.shippingLine',
- 'lines.shippingLine.shippingMethod',
- 'shippingLines',
- 'shippingLines.shippingMethod',
- ]);
- return order!;
- }
- });
|