Skip to content

Commit

Permalink
feat(core): Add quantity arg to OrderItemPriceCalculationStrategy
Browse files Browse the repository at this point in the history
Relates to #1920. This feature is the fundamental piece that allows price lists /
tiered pricing to be implemented. The exact implementation will depend on the
project requirements.
  • Loading branch information
michaelbromley committed Apr 13, 2023
1 parent 4a080cb commit 02a0864
Show file tree
Hide file tree
Showing 7 changed files with 46 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -1,23 +1,30 @@
import {
CalculatedPrice,
PriceCalculationResult,
Order,
OrderItemPriceCalculationStrategy,
ProductVariant,
RequestContext,
roundMoney,
} from '@vendure/core';

/**
* Adds $5 for items with gift wrapping.
* Adds $5 for items with gift wrapping, halves the price when buying 3 or more
*/
export class TestOrderItemPriceCalculationStrategy implements OrderItemPriceCalculationStrategy {
calculateUnitPrice(
ctx: RequestContext,
productVariant: ProductVariant,
orderLineCustomFields: { [p: string]: any },
): CalculatedPrice | Promise<CalculatedPrice> {
order: Order,
quantity: number,
): PriceCalculationResult | Promise<PriceCalculationResult> {
let price = productVariant.price;
if (orderLineCustomFields.giftWrap) {
price += 500;
}
if (quantity > 3) {
price = roundMoney(price / 2);
}
return {
price,
priceIncludesTax: productVariant.listPriceIncludesTax,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,20 @@ describe('custom OrderItemPriceCalculationStrategy', () => {
expect(adjustOrderLine.lines[1].unitPrice).toEqual(variantPrice);
expect(adjustOrderLine.subTotal).toEqual(variantPrice + variantPrice);
});

it('applies discount for quantity greater than 3', async () => {
const { adjustOrderLine } = await shopClient.query(ADJUST_ORDER_LINE_CUSTOM_FIELDS, {
orderLineId: secondOrderLineId,
quantity: 4,
customFields: {
giftWrap: false,
},
});

const variantPrice = (variants[0].price as SinglePrice).value;
expect(adjustOrderLine.lines[1].unitPrice).toEqual(variantPrice / 2);
expect(adjustOrderLine.subTotal).toEqual(variantPrice + (variantPrice / 2) * 4);
});
});

const ORDER_WITH_LINES_AND_ITEMS_FRAGMENT = gql`
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export * from './injector';
export * from './permission-definition';
export * from './ttl-cache';
export * from './self-refreshing-cache';
export * from './round-money';
export * from './types/common-types';
export * from './types/entity-relation-paths';
export * from './types/injectable-strategy';
Expand Down
7 changes: 7 additions & 0 deletions packages/core/src/common/round-money.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ import { MoneyStrategy } from '../config/entity/money-strategy';

let moneyStrategy: MoneyStrategy;

/**
* @description
* Rounds a monetary value according to the configured {@link MoneyStrategy}.
*
* @docsCategory money
* @since 2.0.0
*/
export function roundMoney(value: number, quantity = 1): number {
if (!moneyStrategy) {
moneyStrategy = getConfig().entityOptions.moneyStrategy;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@ import { ProductVariant } from '../../entity/product-variant/product-variant.ent
* ### OrderItemPriceCalculationStrategy vs Promotions
* Both the OrderItemPriceCalculationStrategy and Promotions can be used to alter the price paid for a product.
*
* The main difference is when a Promotion is applied, it adds a `discount` line to the Order, and the regular
* price is used for the value of `OrderLine.listPrice` property, whereas
* the OrderItemPriceCalculationStrategy actually alters the value of `OrderLine.listPrice` itself, and does not
* add any discounts to the Order.
*
* Use OrderItemPriceCalculationStrategy if:
*
* * The price is not dependent on quantity or on the other contents of the Order.
* * The price calculation is based on the properties of the ProductVariant and any CustomFields
* specified on the OrderLine, for example via a product configurator.
* * The logic is a permanent part of your business requirements.
Expand All @@ -41,6 +45,8 @@ import { ProductVariant } from '../../entity/product-variant/product-variant.ent
* a gift-wrapping surcharge would be added to the price.
* * A product-configurator where e.g. various finishes, colors, and materials can be selected and stored
* as OrderLine custom fields (see [Customizing models](/docs/developer-guide/customizing-models/#configurable-order-products).
* * Price lists or bulk pricing, where different price bands are stored e.g. in a customField on the ProductVariant, and this
* is used to calculate the price based on the current quantity.
*
* @docsCategory Orders
*/
Expand All @@ -51,13 +57,16 @@ export interface OrderItemPriceCalculationStrategy extends InjectableStrategy {
* the price for a single unit.
*
* Note: if you have any `relation` type custom fields defined on the OrderLine entity, they will only be
* passed in to this method if they are set to `eager: true`.
* passed in to this method if they are set to `eager: true`. Otherwise, you can use the {@link EntityHydrator}
* to join the missing relations.
*
* Note: the `quantity` argument was added in v2.0.0
*/
calculateUnitPrice(
ctx: RequestContext,
productVariant: ProductVariant,
orderLineCustomFields: { [key: string]: any },
order: Order,
// TODO: v2 - pass the quantity to allow bulk discounts
quantity: number,
): PriceCalculationResult | Promise<PriceCalculationResult>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ export class OrderTestingService {
productVariant,
orderLine.customFields || {},
mockOrder,
orderLine.quantity,
);
const taxRate = productVariant.taxRateApplied;
orderLine.listPrice = price;
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/service/services/order.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1670,6 +1670,7 @@ export class OrderService {
variant,
updatedOrderLine.customFields || {},
order,
updatedOrderLine.quantity,
);
const initialListPrice = updatedOrderLine.initialListPrice ?? priceResult.price;
if (initialListPrice !== priceResult.price) {
Expand Down

0 comments on commit 02a0864

Please sign in to comment.