Announcing Vendure v0.17.0
June 09, 2023
Improved stock control
This release put you in control of how you manage your stock. First let's define a few new concepts:
Stock on hand: This refers to the number of physical units of a particular variant which you have in stock right now. This can be zero or more, but not negative.
Allocated: This refers to the number of units which have been assigned to Orders, but which have not yet been fulfilled.
Out-of-stock threshold: This value determines the stock level at which the variant is considered "out of stock". This value is set globally, but can be overridden for specific variants. It defaults to
0
.Saleable: This means the number of units that can be sold right now. The formula is:
saleable = stockOnHand - allocated - outOfStockThreshold
Here's a table to better illustrate the relationship between these concepts:
Stock on hand | Allocated | Out-of-stock threshold | Saleable --------------|-----------|------------------------|---------- 10 | 0 | 0 | 10 10 | 0 | 3 | 7 10 | 5 | 0 | 5 10 | 5 | 3 | 2 10 | 10 | 0 | 0 10 | 10 | -5 | 5
The saleable value is what determines whether the customer is able to add a variant to an order. If there is 0 saleable stock, then any attempt to add to the order will result in a new InsufficientStockError
.
Back orders
You may have noticed that the outOfStockThreshold
value can be set to a negative number. This allows you to sell variants even when you don't physically have them in stock. This is known as "back orders".
Back orders can be really useful to allow orders to keep flowing even when stockOnHand temporarily drops to zero. For many businesses with predictable re-supply schedules they make a lot of sense.
Once a customer completes checkout, those variants in the order are marked as allocated
. When a Fulfillment is created, those allocations are converted to Sales and the stockOnHand
of each variant is adjusted. Fulfillments may only be created if there is sufficient stock on hand.
Configurable stock allocation
By default, stock is allocated when checkout completes, which means when the Order transitions to the 'PaymentAuthorized'
or 'PaymentSettled'
state. However, you may have special requirements which mean you wish to allocate stock earlier or later in the order process. With the new StockAllocationStrategy you can tailor allocation to your exact needs.
Further details in the new Stock Control Guide
Custom permissions
Vendure has a powerful role-based access control system built in. However, up until now it has been impossible to define your own custom permissions. This release adds support for easily defining custom permissions and using them to secure your own GraphQL & REST operations & end-points. These permissions can then be used in creating Roles, giving you total control over exactly which users may make use of the capabilities defined by your plugins.
For an example of how simple it is to define new permissions, see the new Defining Custom Permissions guide.
What's more, your custom permissions will automatically appear in the Admin UI Roles editor, allowing seamless management of your plugin permissions!
Tax improvements
Tax can be a confusing subject. Our own tax handling was also a little confusing too - but this release aims to improve the situation!
Consistent OrderItem.unitPrice
The GraphQL OrderItem.unitPrice
field used to be given with or without taxes included, depending on the Channel settings. Now it is always the pre-tax price, and the unitPriceWithTax
field is always the tax-included price.
Note: To convert existing Orders, you'll need to add a custom query to your database migration - see the migration guide below.
More detailed tax information
There are also a couple of new fields which give more information about taxes applied to an Order:
The
OrderLine
GraphQL type now includes new tax-related fields:linePrice
The total price of the line excluding taxlinePriceWithTax
The total price of the line including taxlineTax
The total tax on this linetaxRate
The percentage rate of tax applied to this line
The
Order
GraphQL type now includes ataxSummary
field which provides a breakdown of how much tax is being charged at each applicable tax rate, which looks like this:{ "data": { "activeOrder": { "code": "WFJQG3YL1XVPHZCG", "totalBeforeTax": 8188, "total": 9756, "taxSummary": [ { "taxRate": 20, "taxBase": 7489, "taxTotal": 1498 }, { "taxRate": 10, "taxBase": 699, "taxTotal": 70 } ] } } }
List filter improvements
List queries in the GraphQL APIs can now be filtered with the new in
and regex
filter.
The in
filter allows filtering by matching the field against one of the provided strings:
query {
orders(options: {
filter: {
state: {
in: ["PaymentAuthorized", "PaymentSettled"]
}
}
}) {
items {
id
# ...etc
}
}
}
The regex
filter allows matching the field against a regular expression:
query {
customers(options: {
filter: {
emailAddress: {
regex: "g(oogle)?mail\\.com$"
}
}
}) {
items {
id
# ...etc
}
}
}
Note: the specific supported regex syntax varies between the different database drivers.
These new filters enabled a much improved list view for orders in the Admin UI:
Other notable improvements
The
ShippingMethod
entity is now translatable, and now features bothname
anddescription
fields, allowing richer information to be provided to customers.All methods of configurable strategies (e.g. PaymentMethodHandler, ShippingCalculator etc.) now receive the
RequestContext
object.The
Fulfillment
entity now supports custom fields.Performance of ShippingEligibilityCheckers has been drastically improved. Previously every checker was executed on every change to an Order. This could have resulted in hundreds of needless calls. For async checkers, especially those which call out to third-party APIs, this lead to poor performance. Now only the checker of the selected ShippingMethod is ever called, and even those calls can be minimized using the new
ShouldRunCheck
function.
See all changes in the v0.17.0 Changelog
BREAKING CHANGES / Migration Guide
🚧 Read this section carefully
For general instructions on upgrading, please see the new Updating Vendure guide.
Database migration
Note: The SQL syntax given here is for MySQL. If you are using Postgres, adjust the syntax accordingly, i.e. double-quotes for tables/column names, single-quote for string literals etc.
Generate a migration script as described in the Migrations guide.
If you are using MySQL or MariaDB, you'll need to disable foreign key checks for the migration, due to an open TypeORM issue:
public async up(queryRunner: QueryRunner): Promise<any> { await queryRunner.query("SET FOREIGN_KEY_CHECKS=0;", undefined); // ... the generated migration statements await queryRunner.query("SET FOREIGN_KEY_CHECKS=1;", undefined); }
Add the following line immediately before the first generated migration query:
// Migrate existing OrderItems to use the new tax convention await queryRunner.query("UPDATE `order_item` SET `unitPrice` = ROUND(`unitPrice` / ((`taxRate` + 100) / 100)) WHERE `unitPriceIncludesTax` = 1", undefined);
Add the following line immediately after the
CREATE TABLE shipping_method_translation
statement:// Migrate existing ShippingMethod names to the new translatable schema structure await queryRunner.query("INSERT INTO `shipping_method_translation` (`languageCode`, `name`, `description`, `baseId`) SELECT 'en', `description`, '', id FROM `shipping_method`", undefined);
IMPORTANT test the migration first on data you are prepared to lose to ensure that it works as expected. Do not run on production data without testing. For production data, make a full backup first!
Adding RequestContext
If you have created your own custom strategies, you'll need to add the RequestContext argument as the first argument in any methods. TypeScript will tell you about this when you try to compile. For example:
export const myShippingCalculator = new ShippingCalculator({
code: 'my-shipping-calculator',
description: [/* */],
args: {},
- calculate: (order, args) => {
+ calculate: (ctx, order, args) => {
return { price: args.rate, priceWithTax: args.rate * ((100 + args.taxRate) / 100) };
},
});
Update references to ShippingMethod.description
If your code references the ShippingMethod.description
field, change it to ShippingMethod.name
. The description is now used for an (optional) long-form detailed description.
Deprecated fields
The following fields in the GraphQL APIs are deprecated (but still exist for now):
OrderItem.unitPriceIncludesTax
: unitPrice is now always without taxOrderLine.totalPrice
: UselinePriceWithTax
instead
Create your first commerce experience with Vendure in less than 2 minutes
Vendure is a registered trademark. Our trademark policy ensures that our brand and products are protected. Feel free to reach out if you have any questions about our trademarks.
Documentation
Newsletter
Get the latest product news and announcements delivered directly to your inbox.