...
- Encourage sales ("Buy this item now, it's almost gone")
- Warn of longer delivery times ("Stock is low, next delivery time is X")
API
YC The platform provides ProductAvailabilityModel interface in order to distill information relevant to specific Product or ProductSku. This model is available in runtime for both domain objects and search index objects.
...
For example examining ProductInListView you come across this snippet:
Code Block | ||
---|---|---|
| ||
final long browsingShopId = getCurrentCustomerShopId(); final ProductAvailabilityModel skuPam = productServiceFacade.getProductAvailability(product, browsingShopId); final ShoppingCart cart = getCurrentCart(); final PriceModel model = productServiceFacade.getSkuPrice(cart, null, skuPam.getDefaultSkuCode(), BigDecimal.ONE); final boolean ableToAddDefault = !model.isPriceUponRequest() && skuPam.isAvailable() && skuPam.getDefaultSkuCode().equals(skuPam.getFirstAvailableSkuCode()); add(links.newAddToCartLink(ADD_TO_CART_LINK, skuPam.getDefaultSkuCode(), null, getPage().getPageParameters()) .add(new Label(ADD_TO_CART_LINK_LABEL, skuPam.isInStock() || skuPam.isPerpetual() ? getLocalizer().getString("addToCart", this) : getLocalizer().getString("preorderCart", this))) .setVisible(ableToAddDefault) ); add(links.newProductLink(PRODUCT_LINK_ALT, product.getId(), getPage().getPageParameters()) .add(new Label(VIEW_ALT_LABEL, getLocalizer().getString("viewAllVariants", this))) .setVisible(!ableToAddDefault) ); |
...
skuPam is the product availability model and by checking its properties, specifically specifically:textbox
Code Block |
---|
...
skuPam.isAvailable() && |
...
skuPam.getDefaultSkuCode().equals(skuPam.getFirstAvailableSkuCode()) |
we we are able to determine if the product is in stock. Further we can detect if it is preorder of regular products by checking checking:textbox
Code Block |
---|
...
skuPam.isInStock() || skuPam.isPerpetual() |
.Now that we are familiar with how product availability model we can explore how we can use this to display low stock indicators. And in fact it is fairly simple. Product availability model has getAvailableToSellQuantity(sku) method that return exact quantity of the inventory available. Thus we can use some business rules say "if quantity is less than 3 then display the message".
Code Block | ||
---|---|---|
| ||
final BigDecimal qty = skuPam.getAvailableToSellQuantity(skuPam.getDefaultSkuCode()); if (qty == null || qty.compareTo(new BigDecimal(3)) < 0) { warn(getLocalizer().getString("lowStockMessage", this)); } |
...
However checking for hard coded quantity is not particularly useful since well it is hard coded. However we could have an attribute say on SKU or PRODUCT to have this set dynamically. The snippet could be improved to the following:
Code Block | ||
---|---|---|
| ||
final Pair<String, I18NModel> lowStockAv = product.getAttributes().getValue("LOW_STOCK_QTY"); final BigDecimal lowStock = new BigDecimal(NumberUtils.toDouble(lowStockAv != null ? lowStockAv.getFirst() : "3", 3d)); final BigDecimal qty = skuPam.getAvailableToSellQuantity(skuPam.getDefaultSkuCode()); if (qty == null || qty.compareTo(lowStock) < 0) { warn(getLocalizer().getString("lowStockMessage", this)); } |
...
We can further improve to have a shop specific setting for low stock:
Code Block | ||
---|---|---|
| ||
final String shopLowStockAv = getCurrentShop().getAttributeValueByCode("SHOP_LOW_STOCK_QTY"); final double shopLowStock = NumberUtils.toDouble(shopLowStockAv); final Pair<String, I18NModel> lowStockAv = product.getAttributes().getValue("LOW_STOCK_QTY"); final BigDecimal lowStock = new BigDecimal(NumberUtils.toDouble(lowStockAv != null ? lowStockAv.getFirst() : shopLowStockAv, shopLowStock)); final BigDecimal qty = skuPam.getAvailableToSellQuantity(skuPam.getDefaultSkuCode()); if (qty == null || qty.compareTo(lowStock) < 0) { warn(getLocalizer().getString("lowStockMessage", this)); } |
Now we have a solution which has product specific low stock setting and a shop fallback setting to display low stock message. Of course using the values in snipped you can build even more complex solution with several levels or even display a message which is an attribute of the product e.g.:
Code Block | ||
---|---|---|
| ||
final String shopLowStockAv = getCurrentShop().getAttributeValueByCode("SHOP_LOW_STOCK_QTY"); final double shopLowStock = NumberUtils.toDouble(shopLowStockAv); final Pair<String, I18NModel> lowStockAv = product.getAttributes().getValue("LOW_STOCK_QTY"); final BigDecimal lowStock = new BigDecimal(NumberUtils.toDouble(lowStockAv != null ? lowStockAv.getFirst() : shopLowStockAv, shopLowStock)); final BigDecimal qty = skuPam.getAvailableToSellQuantity(skuPam.getDefaultSkuCode()); if (qty == null || qty.compareTo(lowStock) < 0) { final Pair<String, I18NModel> lowStockMsgAv = product.getAttributes().getValue("LOW_STOCK_QTY_MSG"); if (lowStockMsgAv != null) { warn(lowStockMsgAv); } } |
...
REST API exposes ProductAvailabilityModelRO object which has all the information which is available in ProductAvailabilityModel. Consumers of API response can apply similar concepts in order to provide messaging to customers or alter application behaviour.
Groovy
...
App Label Body
Body |
---|
...
SaaS Colour info
SaaS | |
Colour | info |
---|
Groovy App relies on the same concepts as already mentioned in previous sections. Additional MO (model) abstraction level exposes ProductAvailabilityModelMO object.
...
For example if we define an include: category_product_low_stock_include we can dynamically render it injecting model as an attribute like so on ProductList.groovy:
Code Block | ||
---|---|---|
| ||
div('class': 'attr-info') { mkp.yieldUnescaped(ctx.dynamicContent('category_product_low_stock_include', ['product': _product])); } |
Above snippet includes a dynamic content include and supplies product model as an argument. The model already contains the ProductAvailabilityModelMO as a property. Thus the CMS content that displays the messaging could be:
Code Block | ||
---|---|---|
| ||
div('class': 'attr-info') { mkp.yieldUnescaped(ctx.dynamicContent('category_product_low_stock_include', ['product': _product])); } |
And accompanying CMS content that will render the information:
Code Block | ||
---|---|---|
| ||
<ul class="text-left"> <% if (product.productAvailabilityModel.available) { if (product.productAvailabilityModel.inStock) { def _qty = product.productAvailabilityModel.availableToSellQuantity[product.productAvailabilityModel.defaultSkuCode]; if (_qty != null) { def _enough = _qty > 3; def _color = _enough ? 'success' : 'warning'; %> <li><b>Stock</b> <span class="label label-pill label-${_color}">${_qty.setScale(0, java.math.RoundingMode.DOWN).intValueExact()}</span></li> <% if (!_enough) { %><li class="attr-grid-view"><span>Delivery in 2-3days</span></li><% } %> <% } else { %> <li><b>Stock</b> <span class="label label-pill label-danger">0</span></li> <li class="attr-grid-view"><span>Delivery in 2-3days</span></li> <% } } else { %> <li><b>Stock</b> <span class="label label-pill label-danger">0</span></li> <li class="attr-grid-view"><span>Item is not longer available</span></li> <% } %> </ul> |
...