From 5f3e3560db06a0d14fd905daf20945a863a8ad68 Mon Sep 17 00:00:00 2001 From: bc-apostoliuk Date: Thu, 19 Feb 2026 14:29:03 +0200 Subject: [PATCH] feat(backorders): BACK-471 display backordered quantity message on PDP Show a dynamic message indicating how many items will be backordered based on the selected quantity, available on-hand stock, and backorder availability. Co-authored-by: Cursor --- assets/js/theme/common/product-details-base.js | 17 +++++++++++++++++ assets/js/theme/common/product-details.js | 4 ++++ templates/components/products/product-view.html | 3 +++ 3 files changed, 24 insertions(+) diff --git a/assets/js/theme/common/product-details-base.js b/assets/js/theme/common/product-details-base.js index 0ea8d23d7e..c2c9c8a550 100644 --- a/assets/js/theme/common/product-details-base.js +++ b/assets/js/theme/common/product-details-base.js @@ -187,6 +187,7 @@ export default class ProductDetailsBase { $container: $('.form-field--stock', $scope), $input: $('[data-product-stock]', $scope), }, + $backordered: $('[data-product-backordered]', $scope), sku: { $label: $('dt.sku-label', $scope), $value: $('[data-product-sku]', $scope), @@ -220,6 +221,22 @@ export default class ProductDetailsBase { viewModel.priceWithoutTax.$div.hide(); } + updateQtyBackorderedMessage(qty) { + const viewModel = this.getViewModel(this.$scope); + + if (!viewModel.$backordered.length) return; + + const onHand = parseInt(this.context.availableOnHand, 10) || 0; + const maxBackorder = parseInt(this.context.availableForBackorder, 10) || 0; + const backordered = Math.max(0, Math.min(qty - onHand, maxBackorder)); + + if (backordered > 0) { + viewModel.$backordered.text(`${backordered} will be backordered`).show(); + } else { + viewModel.$backordered.hide(); + } + } + /** * Update the view of price, messages, SKU and stock options when a product option changes * @param {Object} data Product attribute data diff --git a/assets/js/theme/common/product-details.js b/assets/js/theme/common/product-details.js index 33e7679c5c..f7fc53d802 100644 --- a/assets/js/theme/common/product-details.js +++ b/assets/js/theme/common/product-details.js @@ -370,6 +370,7 @@ export default class ProductDetails extends ProductDetailsBase { this.addToCartValidator.performCheck(); this.updateProductDetailsData(); + this.updateQtyBackorderedMessage(qty); }); // Prevent triggering quantity change when pressing enter @@ -383,7 +384,10 @@ export default class ProductDetails extends ProductDetailsBase { }); this.$scope.on('keyup', '.form-input--incrementTotal', () => { + const viewModel = this.getViewModel(this.$scope); + const qty = parseInt(viewModel.quantity.$input.val(), 10) || 0; this.updateProductDetailsData(); + this.updateQtyBackorderedMessage(qty); }); } diff --git a/templates/components/products/product-view.html b/templates/components/products/product-view.html index ece23c029e..14b1ffbe4f 100644 --- a/templates/components/products/product-view.html +++ b/templates/components/products/product-view.html @@ -1,4 +1,6 @@ {{inject 'outOfStockDefaultMessage' (lang 'products.out_of_stock_default_message')}} +{{inject 'availableOnHand' product.available_on_hand}} +{{inject 'availableForBackorder' product.available_for_backorder}}
{{product.stock_level}} {{> components/products/backorder-availability-prompt show_prompt=product.show_backorder_availability_prompt prompt=product.backorder_availability_prompt available_to_sell=product.available_to_sell available_for_backorder=product.available_for_backorder}} +
{{> components/products/add-to-cart with_wallet_buttons=true}}