-
-
Notifications
You must be signed in to change notification settings - Fork 962
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adjustments (Order price calculation) #29
Comments
Design ProposalFirstly, I looked into creating a DSL (e.g. using Chevrotain) and this seems far too complex for the task at hand within the given time constrains. Perhaps it would be an interesting exploration for a future release. I am formulating a general-purpose pattern which could cover all types of Adjustment in a way that gives sensible defaults but also allows arbitrary extension by a developer: Adjustment ConditionsEach Adjustment has one or more conditions, which are rules which decide whether or not to apply the Adjustment to the target in question. A condition would have 2 representations: a form with inputs for configuring the condition in the UI, and a predicate function which is evaluated when deciding whether to apply the Adjustment. We can define a data structure which encompasses both of these representations: export interface AdjustmentCondition {
name: string;
args: Array<{ name: string; type: 'int' | 'money' | 'string' | 'datetime'; }>;
predicate: (target: OrderItem | Order, args: { [argName: string]: any; }) => boolean;
}
// example
const minimumOrderPriceCondition: AdjustmentCondition = {
name: 'Minimum order price',
args: [
{ name: 'price', type: 'money' }
],
predicate: (order, args) => {
return order.price > args.price;
},
} There would be a number of commonly-used conditions built-in to the framework (minimum order price, minimum item quantity, date range, voucher code applied etc) but creating custom conditions would be as simple as passing a new Adjustment ActionsOnce it has been established that an AdjustmentSource should be applied to the target (i.e. every AdjustmentCondition's predicate evaluates to "true"), then one or more AdjustmentActions will be applied to the target. Actions would be defined in a similar way to conditions - with configuration defining the arguments and used to determine how the form is generated in the admin ui, and a function which uses those arguments to return an adjustment amount: export interface AdjustmentAction {
name: string;
args: Array<{ name: string; type: 'percentage' | 'money' }>;
calculate: (target: OrderItem | Order, args: { [argName: string]: any; }) => number;
}
const orderPercentageDiscountAction: AdjustmentAction = {
name: 'Percentage discount on order',
args: [
{ name: 'percentage', type: 'percentage' },
],
calculate: (order, args) => {
return order.price * args.percentage;
},
}; As with conditions, custom actions could be defined in the config object. Example Adjustments:Using the above pattern of 1..n conditions and 1..n actions, here is how we could formulate some typical adjustments: Buy 1 widget, get 1 freeConditions: OrderItem minimum, 2 10% off orders over £100Conditions: Order total greater than, 10000 Tax AdjustmentsTax adjustments would be handled in much the same way, but we would should additionally have a "tax category" field in the ProductVariant entity which points at a given AdjustmentSource representing that tax category. In the UK for example, the current VAT rates are zero (0%), reduced (5%) and standard (20%). Here are rates in other EU countries Taxes will further need to take into account the location of the customer, which will need to be available in the Shipping AdjustmentsShipping adjustments will have conditions relating to the contents, weight and destination of the Order. The |
Relates to #31 #26 #29 This merge introduces the basis of what seems to be a workable tax & promotions system. There is still more to do, most importantly solving the problem of how the admin can set the gross price of a ProductVariant, as well as working out how the typical range of promotion actions can be implemented (buy 1 get 1 free, money off order etc). However, this work can now be continued on the master branch.
UpdateTaxThe tax system is now implemented to a degree that it seems like it is a workable design. (ff31e03). Currently taxes are only applied to OrderItems, not Orders as a whole. ShippingI now think shipping might work better as a specialized type of OrderLine, so we can optionally apply taxes to it using the existing tax method. PromotionsPromotions are currently implemented to a basic degree, but now the design needs to be refined so that we can be sure it is flexible enough to cover the cases we need to support. Conditions we need to support
Actions we need to support
|
"Collections" in promotionsIn the comment above I mention "collections" as a means to limit the scope of a promotion. For the Alpha, rather than implementing another type of entity, we can do it with FacetValues. So, e.g. the admin could create a new Facet named "promotions" with the value "spring offer", and then apply that FacetValue to all Products which are to be included in the "Spring Offer" promotion. Then there should be a PromotionCondition of order contains at least n with facet values, which accepts as an argument 1) the number And a corresponding PromotionAction of apply percentage discount on products with facet values |
With 96209cd, async conditions and actions are now supported in Promotions, as well as a mechanism for injecting helper methods via the |
(Relates to #26)
The price of a ProductVariant is stored as an integer representing the price of the item in cents (or pence etc) without any taxes applied.
When putting together an order, the final price of the OrderItem is therefore subject to:
Furthermore, the overall Order price is the aggregate of each OrderItem as well as:
The price modifications listed above are known as Adjustments.
How Adjustments work
Determining whether to apply to a target
The basic idea is that each AdjustmentSource would have a function which if called for each potential target (e.g. each OrderItem in an Order) and this function should return true if an Adjustment should be applied or false if not.
The hard part will be figuring out how to allow the administrator to write this function. We don't want arbitrary JavaScript to be written and executed. A couple of alternatives are:
✅ Foolproof - no arbitrary or unexpected code allowed
✅ Forms inputs are familiar
👎 In order to express all possible combinations of condition & result, the form becomes very complex
👎 Limited expressiveness and less efficient for power users.
✅ Very expressive and potentially efficient.
✅ Potentially much more readable than forms.
👎 Very complex to implement - if no existing solution exists, I'd have to create our own DSL -> JavaScript "compiler".
Research is needed to figure out the real costs & benefits of each approach, including:
The text was updated successfully, but these errors were encountered: