* docs: added documentation pages for experimental features * fix content lint issues * fixed lint errors * added migration step * added workflows introduction * add installation guides * added installation guides for modules + generated workflows reference * added missing workflows reference link * Added warning message for experimental features * fix note
471 lines
14 KiB
Plaintext
471 lines
14 KiB
Plaintext
import Tabs from '@theme/Tabs';
|
||
import TabItem from '@theme/TabItem';
|
||
|
||
# Prices Calculation
|
||
|
||
In this document, you'll learn how prices are calculated under the hood when you use the `calculatePrices` method of the Pricing module interface.
|
||
|
||
## Overview
|
||
|
||
The `calculatePrices` method accepts the ID of one or more price sets and a context. For each price set, it selects two types of prices that best match the context; one belongs to a price list, and one doesn't.
|
||
|
||
Then, it returns an array of price objects, each price for every supplied price set ID.
|
||
|
||
---
|
||
|
||
## Calculation Context
|
||
|
||
The context is an object passed to the method. It must contain at least the `currency_code`. Only money amounts in the price sets with the same currency code are considered for the price selection.
|
||
|
||
For example:
|
||
|
||
```ts
|
||
import {
|
||
initialize as initializePricingModule,
|
||
} from "@medusajs/pricing"
|
||
async function calculatePrice(
|
||
priceSetId: string,
|
||
currencyCode: string
|
||
) {
|
||
const pricingService = await initializePricingModule()
|
||
|
||
const price = await pricingService.calculatePrices(
|
||
{ id: [priceSetId] },
|
||
{
|
||
context: {
|
||
currency_code: currencyCode,
|
||
},
|
||
}
|
||
)
|
||
}
|
||
```
|
||
|
||
The context object can also contain any custom rules, with the key being the `rule_attribute` of a rule type and its value being the rule's value.
|
||
|
||
For example:
|
||
|
||
```ts
|
||
import {
|
||
initialize as initializePricingModule,
|
||
} from "@medusajs/pricing"
|
||
async function calculatePrice(
|
||
priceSetId: string,
|
||
currencyCode: string
|
||
) {
|
||
const pricingService = await initializePricingModule()
|
||
|
||
const price = await pricingService.calculatePrices(
|
||
{ id: [priceSetId] },
|
||
{
|
||
context: {
|
||
currency_code: currencyCode,
|
||
region_id: "US",
|
||
},
|
||
}
|
||
)
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Prices Selection
|
||
|
||
For each price set, the method selects two money amounts:
|
||
|
||
- The calculated price: a money amount that belongs to a price list. If there are no money amounts associated with a price list, it’ll be the same as the original price.
|
||
- The original price: a money amount that doesn't belong to a price list.
|
||
|
||
### Calculated Price Selection Process
|
||
|
||

|
||
|
||
- Find the price set’s associated valid price lists. A price list is considered valid if:
|
||
- The current date is between its start and end dates.
|
||
- The price list's rules satisfy the context's rules.
|
||
- If valid price lists are found, the money amounts within them are sorted by their amount in ascending order. The one having the lowest amount is selected as the calculated price.
|
||
- If no valid price list is found, the selected calculated price will be the same as the original price.
|
||
|
||
### Original Price Selection Process
|
||
|
||

|
||
|
||
- If the price list associated with the calculated price is of type `override`, the selected original price is set to the calculated price.
|
||
- If no rules are provided in the context other than the `currency_code`, the default money amount is selected as the original price. The default money amount is a money amount having no rules applied to it.
|
||
- Otherwise, if a money amount exists in any price set with the same rules provided in the context, it's selected as the original price.
|
||
- If no money amount exists with the same rules as the context, all money amounts satisfying any combination of the provided rules are retrieved.
|
||
- The money amounts are sorted in descending order by the associated `PriceSetMoneyAmount`'s `number_rules`, the `default_priority` of the rule types, and the `priority` of the associated `PriceRule`. The `priority` attribute has a higher precedence than the `default_priority`.
|
||
- The highest money amount sorted is selected as the original price since it's considered the best price.
|
||
|
||
---
|
||
|
||
## Returned Calculated Price
|
||
|
||
After the original and calculated prices are selected, the method will use them to create the following price object for each pr
|
||
|
||
```ts
|
||
const price = {
|
||
id: priceSetId,
|
||
is_calculated_price_price_list:
|
||
!!calculatedPrice?.price_list_id,
|
||
calculated_amount: parseInt(calculatedPrice?.amount || "") ||
|
||
null,
|
||
|
||
is_original_price_price_list: !!originalPrice?.price_list_id,
|
||
original_amount: parseInt(originalPrice?.amount || "") ||
|
||
null,
|
||
|
||
currency_code: calculatedPrice?.currency_code ||
|
||
null,
|
||
|
||
calculated_price: {
|
||
money_amount_id: calculatedPrice?.id || null,
|
||
price_list_id: calculatedPrice?.price_list_id || null,
|
||
price_list_type: calculatedPrice?.price_list_type || null,
|
||
min_quantity: parseInt(
|
||
calculatedPrice?.min_quantity || ""
|
||
) || null,
|
||
max_quantity: parseInt(
|
||
calculatedPrice?.max_quantity || ""
|
||
) || null,
|
||
},
|
||
|
||
original_price: {
|
||
money_amount_id: originalPrice?.id || null,
|
||
price_list_id: originalPrice?.price_list_id || null,
|
||
price_list_type: originalPrice?.price_list_type || null,
|
||
min_quantity: parseInt(originalPrice?.min_quantity || "") ||
|
||
null,
|
||
max_quantity: parseInt(originalPrice?.max_quantity || "") ||
|
||
null,
|
||
},
|
||
}
|
||
```
|
||
|
||
Where:
|
||
|
||
- `id`: The ID of the price set from which the money amount was selected.
|
||
- `is_calculated_price_price_list`: whether the calculated price belongs to a price list. As mentioned earlier, if no valid price list is found, the calculated price is set to the original price, which doesn't belong to a price list.
|
||
- `calculated_amount`: The amount of the calculated price, or `null` if there isn't a calculated price.
|
||
- `is_original_price_price_list`: whether the original price belongs to a price list. As mentioned earlier, if the price list of the calculated price is of type `override`, the original price will be the same as the calculated price.
|
||
- `original_amount`: The amount of the original price, or `null` if there isn't an original price.
|
||
- `currency_code`: The currency code of the calculated price, or `null` if there isn't a calculated price.
|
||
- `calculated_price`: An object containing the calculated price's money amount details and potentially its associated price list.
|
||
- `original_price`: An object containing the original price's money amount details and potentially its associated price list.
|
||
|
||
The method returns an array of these price objects.
|
||
|
||
---
|
||
|
||
## Example
|
||
|
||
Consider the following rule types and price sets:
|
||
|
||
```ts
|
||
const ruleTypes = await pricingService.createRuleTypes([
|
||
{
|
||
name: "Region",
|
||
rule_attribute: "region_id",
|
||
},
|
||
{
|
||
name: "City",
|
||
rule_attribute: "city",
|
||
},
|
||
])
|
||
|
||
const priceSet = await service.create({
|
||
rules: [
|
||
{ rule_attribute: "region_id" },
|
||
{ rule_attribute: "city" },
|
||
],
|
||
prices: [
|
||
//default
|
||
{
|
||
amount: 500,
|
||
currency_code: "EUR",
|
||
rules: {},
|
||
},
|
||
// prices with rules
|
||
{
|
||
amount: 400,
|
||
currency_code: "EUR",
|
||
rules: {
|
||
region_id: "PL",
|
||
},
|
||
},
|
||
{
|
||
amount: 450,
|
||
currency_code: "EUR",
|
||
rules: {
|
||
city: "krakow",
|
||
},
|
||
},
|
||
{
|
||
amount: 500,
|
||
currency_code: "EUR",
|
||
rules: {
|
||
city: "warsaw",
|
||
region_id: "PL",
|
||
},
|
||
},
|
||
],
|
||
})
|
||
```
|
||
|
||
### Default Price Selection
|
||
|
||
<Tabs groupId="pricing-calculation-example">
|
||
<TabItem value="code" label="Code" default>
|
||
|
||
```ts
|
||
const price = await pricingService.calculatePrices(
|
||
{ id: [priceSet.id] },
|
||
{
|
||
context: {
|
||
currency_code: "EUR"
|
||
}
|
||
}
|
||
)
|
||
```
|
||
|
||
</TabItem>
|
||
<TabItem value="result" label="Result">
|
||
|
||
The returned price is:
|
||
|
||
```ts
|
||
const price = {
|
||
id: "<PRICE_SET_ID>",
|
||
is_calculated_price_price_list: false,
|
||
calculated_amount: 500,
|
||
|
||
is_original_price_price_list: false,
|
||
original_amount: 500,
|
||
|
||
currency_code: "EUR",
|
||
|
||
calculated_price: {
|
||
money_amount_id: "<DEFAULT_MONEY_AMOUNT_ID>",
|
||
price_list_id: null,
|
||
price_list_type: null,
|
||
min_quantity: null,
|
||
max_quantity: null,
|
||
},
|
||
|
||
original_price: {
|
||
money_amount_id: "<DEFAULT_MONEY_AMOUNT_ID>",
|
||
price_list_id: null,
|
||
price_list_type: null,
|
||
min_quantity: null,
|
||
max_quantity: null,
|
||
},
|
||
}
|
||
```
|
||
|
||
- Original price selection: since there are no provided rules in the context, the original price is the default money amount.
|
||
- Calculated price selection: since there are no associated price lists, the calculated price is set to the original price.
|
||
|
||
</TabItem>
|
||
</Tabs>
|
||
|
||
### Exact Match Rule in Context
|
||
|
||
<Tabs>
|
||
<TabItem value="code" label="Code" default>
|
||
|
||
```ts
|
||
const price = await pricingService.calculatePrices(
|
||
{ id: [priceSet.id] },
|
||
{
|
||
context: {
|
||
currency_code: "EUR",
|
||
region_id: "PL"
|
||
}
|
||
}
|
||
)
|
||
```
|
||
|
||
</TabItem>
|
||
<TabItem value="result" label="Result">
|
||
|
||
The returned price is:
|
||
|
||
```ts
|
||
const price = {
|
||
id: "<PRICE_SET_ID>",
|
||
is_calculated_price_price_list: false,
|
||
calculated_amount: 400,
|
||
|
||
is_original_price_price_list: false,
|
||
original_amount: 400,
|
||
|
||
currency_code: "EUR",
|
||
|
||
calculated_price: {
|
||
money_amount_id: "<SECOND_MONEY_AMOUNT_ID>",
|
||
price_list_id: null,
|
||
price_list_type: null,
|
||
min_quantity: null,
|
||
max_quantity: null,
|
||
},
|
||
|
||
original_price: {
|
||
money_amount_id: "<SECOND_MONEY_AMOUNT_ID>",
|
||
price_list_id: null,
|
||
price_list_type: null,
|
||
min_quantity: null,
|
||
max_quantity: null,
|
||
},
|
||
}
|
||
```
|
||
|
||
- Original price selection: Since the second money amount of the price set has the same context as the provided one, it's selected as the original price.
|
||
- Calculated price selection: since there are no associated price lists, the calculated price is set to the original price.
|
||
|
||
</TabItem>
|
||
</Tabs>
|
||
|
||
### Context without Exact Matching Rules
|
||
|
||
<Tabs>
|
||
<TabItem value="code" label="Code" default>
|
||
|
||
```ts
|
||
const price = await pricingService.calculatePrices(
|
||
{ id: [priceSet.id] },
|
||
{
|
||
context: {
|
||
currency_code: "EUR",
|
||
region_id: "PL",
|
||
city: "krakow"
|
||
}
|
||
}
|
||
)
|
||
```
|
||
|
||
</TabItem>
|
||
<TabItem value="result" label="Result">
|
||
|
||
The returned price is:
|
||
|
||
```ts
|
||
const price = {
|
||
id: "<PRICE_SET_ID>",
|
||
is_calculated_price_price_list: false,
|
||
calculated_amount: 500,
|
||
|
||
is_original_price_price_list: false,
|
||
original_amount: 500,
|
||
|
||
currency_code: "EUR",
|
||
|
||
calculated_price: {
|
||
money_amount_id: "<FOURTH_MONEY_AMOUNT_ID>",
|
||
price_list_id: null,
|
||
price_list_type: null,
|
||
min_quantity: null,
|
||
max_quantity: null,
|
||
},
|
||
|
||
original_price: {
|
||
money_amount_id: "<FOURTH_MONEY_AMOUNT_ID>",
|
||
price_list_id: null,
|
||
price_list_type: null,
|
||
min_quantity: null,
|
||
max_quantity: null,
|
||
},
|
||
}
|
||
```
|
||
|
||
- Original price selection: The fourth money amount in the price list is selected based on the following process:
|
||
- There are no money amounts having the same rules as the context.
|
||
- Retrieve all money amounts matching some combination of the rules in the context.
|
||
- Sort the money amounts in descending order by their number of rules, each rule type's default priority, and the priority of the rule values.
|
||
- All rule types, in this case, don't have a priority. So, the sorting depends on the number of rules each money amount has.
|
||
- The fourth money amount has two rules, and the second and third have one rule. So, the fourth money amount is selected as the original price.
|
||
- Calculated price selection: since there are no associated price lists, the calculated price is set to the original price.
|
||
|
||
</TabItem>
|
||
</Tabs>
|
||
|
||
### Price Selection with Price List
|
||
|
||
<Tabs>
|
||
<TabItem value="code" label="Code" default>
|
||
|
||
```ts
|
||
const priceList = pricingModuleService.createPriceList({
|
||
name: "Test Price List",
|
||
starts_at: Date.parse("01/10/2023"),
|
||
ends_at: Date.parse("31/10/2023"),
|
||
rules: {
|
||
region_id: ['PL']
|
||
},
|
||
type: "sale"
|
||
prices: [
|
||
{
|
||
amount: 400,
|
||
currency_code: "EUR",
|
||
price_set_id: priceSet.id,
|
||
},
|
||
{
|
||
amount: 450,
|
||
currency_code: "EUR",
|
||
price_set_id: priceSet.id,
|
||
},
|
||
],
|
||
});
|
||
|
||
const price = await pricingService.calculatePrices(
|
||
{ id: [priceSet.id] },
|
||
{
|
||
context: {
|
||
currency_code: "EUR",
|
||
region_id: "PL",
|
||
city: "krakow"
|
||
}
|
||
}
|
||
)
|
||
```
|
||
|
||
</TabItem>
|
||
<TabItem value="result" label="Result">
|
||
|
||
The returned price is:
|
||
|
||
```ts
|
||
const price = {
|
||
id: "<PRICE_SET_ID>",
|
||
is_calculated_price_price_list: true,
|
||
calculated_amount: 400,
|
||
|
||
is_original_price_price_list: false,
|
||
original_amount: 500,
|
||
|
||
currency_code: "EUR",
|
||
|
||
calculated_price: {
|
||
money_amount_id: "<FOURTH_MONEY_AMOUNT_ID>",
|
||
price_list_id: null,
|
||
price_list_type: null,
|
||
min_quantity: null,
|
||
max_quantity: null,
|
||
},
|
||
|
||
original_price: {
|
||
money_amount_id: "<PL_MONEY_AMOUNT_ID_1>",
|
||
price_list_id: "<PRICE_LIST_ID>",
|
||
price_list_type: "sale",
|
||
min_quantity: null,
|
||
max_quantity: null,
|
||
},
|
||
}
|
||
```
|
||
|
||
- Original price selection: The process is explained in the Context without Exact Matching Rules section.
|
||
- Calculated price selection: The first money amount of the price list is selected based on the following process:
|
||
- The price set has a price list associated with it. So, retrieve its money amounts and sort them in ascending order by their amount.
|
||
- Since the first money amount of the price list has the lowest amount, it's selected as the calculated price.
|
||
|
||
</TabItem>
|
||
</Tabs>
|