Below is a command to the clone the source code for the application used in this tutorial
git clone https://github.com/redis-developer/redis-real-time-inventory-solutions
Below is a command to the clone the source code for the application used in this tutorial
git clone https://github.com/redis-developer/redis-real-time-inventory-solutions
The major requirement in a retail inventory system is presenting an accurate, real-time view of inventory to shoppers and store associates enabling buy-online-pickup-in-store (BOPIS). Optimizing fulfillment from multiple inventory locations.
Available to promise (ATP) is the projected amount of inventory left available to sell, not including allocated inventory. It allows businesses to control distribution to their customers and predict inventory. The ATP model helps retailers keep inventory costs down such as ordering costs, carrying costs and stock-out costs. ATP is helpful as long as consumer buying forecasts remain correct. Implementing ATP processes effectively for retailers can mean the difference between sustained growth and an inventory that repeatedly runs out of customer's favorite products missing sales opportunities and harming customer experience.
Calculating available-to-promise is a relatively simple undertaking. Complete the following formula for an accurate breakdown of available-to-promise capabilities:
This formula includes the following elements:
Using Redis, System delivers real-time synchronization of inventory across stores, in transit and warehouses. Provide retailers the most accurate, timely data on inventory across their entire store network and consumers positive customer experiences searching and locating inventory.
Redis Data Integration (RDI) capabilities enable accurate real-time inventory management and system of record synchronization. Redis advanced inventory search and query capabilities provide accurate available inventory information to multichannel and omnichannel customers and store associates.
This solution increases inventory turnover ratios resulting in lower inventory costs, higher revenue and profits. It also reduces the impact of customer searches on Systems of Record and Inventory Management Systems (IMS).
Managing inventory or a SKU (stock keeping unit) process contains some activities like :
The code that follows shows an example API request and response for retrieveSKU activity.
retrieveSKU API Request
retrieveSKU API Response
When you make a request, it goes through the API gateway to the inventory service. Ultimately, it ends up calling a retrieveSKU
function which looks as follows:
code
The code that follows shows an example API request and response for updateSKU activity.
updateSKU API Request
updateSKU API Response
When you make a request, it goes through the API gateway to the inventory service. Ultimately, it ends up calling a updateSKU
function which looks as follows:
The code that follows shows an example API request and response for incrementSKU activity.
incrementSKU API Request
incrementSKU API Response
When you make a request, it goes through the API gateway to the inventory service. Ultimately, it ends up calling a incrementSKU
function which looks as follows:
The code that follows shows an example API request and response for decrementSKU activity.
decrementSKU API Request
decrementSKU API Response
When you make a request, it goes through the API gateway to the inventory service. Ultimately, it ends up calling a decrementSKU
function which looks as follows:
The code that follows shows an example API request and response for retrieveManySKUs activity.
retrieveManySKUs API Request
retrieveManySKUs API Response
When you make a request, it goes through the API gateway to the inventory service. Ultimately, it ends up calling a retrieveManySKUs
function which looks as follows:
The code that follows shows an example API request and response for decrementManySKUs activity.
decrementManySKUs API Request
decrementManySKUs API Response
When you make a request, it goes through the API gateway to the inventory service. Ultimately, it ends up calling a decrementManySKUs
function which looks as follows:
Hopefully, this tutorial has helped you visualize how to use Redis in a Real time inventory system for product availability across different location stores. For additional resources related to this topic, check out the links below:
Real time inventory with Redis
General
Available-to-promise = QuantityOnHand + Supply - Demand
GET http://localhost:3000/api/retrieveSKU?sku=1019688
{
"data": {
"sku": 1019688,
"name": "5-Year Protection Plan - Geek Squad",
"type": "BlackTie",
"totalQuantity": 10
},
"error": null
}
static async retrieveSKU(_productId: number): Promise<IProduct> {
/**
Get current Quantity of a Product.
:param _productId: Product Id
:return: Product with Quantity
*/
const repository = ProductRepo.getRepository();
let retItem: IProduct = {};
if (repository && _productId) {
//fetch product by ID (using redis om library)
const product = <IProduct>await repository.fetch(_productId.toString());
if (product) {
retItem = {
sku: product.sku,
name: product.name,
type: product.type,
totalQuantity: product.totalQuantity
}
}
else {
throw `Product with Id ${_productId} not found`;
}
}
else {
throw `Input params failed !`;
}
return retItem;
}
POST http://localhost:3000/api/updateSKU
{
"sku":1019688,
"quantity":25
}
{
"data": {
"sku": 1019688,
"name": "5-Year Protection Plan - Geek Squad",
"type": "BlackTie",
"totalQuantity": 25 //updated value
},
"error": null
}
static async updateSKU(_productId: number, _quantity: number): Promise<IProduct> {
/**
Set Quantity of a Product.
:param _productId: Product Id
:param _quantity: new quantity
:return: Product with Quantity
*/
const repository = ProductRepo.getRepository();
let retItem: IProduct = {};
if (repository && _productId && _quantity >= 0) {
//fetch product by ID (using redis om library)
const product = <IProduct>await repository.fetch(_productId.toString());
if (product) {
//update the product fields
product.totalQuantity = _quantity;
// save the modified product
const savedItem = <IProduct>await repository.save(<RedisEntity>product);
retItem = {
sku: savedItem.sku,
name: savedItem.name,
type: savedItem.type,
totalQuantity: savedItem.totalQuantity
}
}
else {
throw `Product with Id ${_productId} not found`;
}
}
else {
throw `Input params failed !`;
}
return retItem;
}
POST http://localhost:3000/api/incrementSKU
{
"sku":1019688,
"quantity":2
}
{
"data": {
"sku": 1019688,
"name": "5-Year Protection Plan - Geek Squad",
"type": "BlackTie",
"totalQuantity": 12 //previous value 10
},
"error": null
}
static async incrementSKU(_productId: number, _incrQuantity: number, _isDecrement: boolean, _isReturnProduct: boolean): Promise<IProduct> {
/**
increment quantity of a Product.
:param _productId: Product Id
:param _incrQuantity: new increment quantity
:return: Product with Quantity
*/
const redisOmClient = getRedisOmClient();
let retItem: IProduct = {};
if (!_incrQuantity) {
_incrQuantity = 1;
}
if (_isDecrement) {
_incrQuantity = _incrQuantity * -1;
}
if (redisOmClient && _productId && _incrQuantity) {
const updateKey = `${ProductRepo.PRODUCT_KEY_PREFIX}:${_productId}`;
//increment json number field by specific (positive/ negative) value
await redisOmClient.redis?.json.numIncrBy(updateKey, '$.totalQuantity', _incrQuantity);
if (_isReturnProduct) {
retItem = await InventoryServiceCls.retrieveSKU(_productId);
}
}
else {
throw `Input params failed !`;
}
return retItem;
}
POST http://localhost:3000/api/decrementSKU
{
"sku":1019688,
"quantity":4
}
{
"data": {
"sku": 1019688,
"name": "5-Year Protection Plan - Geek Squad",
"type": "BlackTie",
"totalQuantity": 16 //previous value 20
},
"error": null
}
static async decrementSKU(_productId: number, _decrQuantity: number): Promise<IProduct> {
/**
decrement quantity of a Product.
:param _productId: Product Id
:param _decrQuantity: new decrement quantity
:return: Product with Quantity
*/
let retItem: IProduct = {};
//validating if product in stock
let isValid = await InventoryServiceCls.validateQuantityOnDecrementSKU(_productId, _decrQuantity);
if (isValid) {
const isDecrement = true; //increments with negative value
const isReturnProduct = true;
retItem = await InventoryServiceCls.incrementSKU(_productId, _decrQuantity, isDecrement, isReturnProduct);
}
return retItem;
}
static async validateQuantityOnDecrementSKU(_productId: number, _decrQuantity?: number): Promise<boolean> {
let isValid = false;
if (!_decrQuantity) {
_decrQuantity = 1;
}
if (_productId) {
const product = await InventoryServiceCls.retrieveSKU(_productId);
if (product && product.totalQuantity && product.totalQuantity > 0
&& (product.totalQuantity - _decrQuantity >= 0)) {
isValid = true;
}
else {
throw `For product with Id ${_productId}, available quantity(${product.totalQuantity}) is lesser than decrement quantity(${_decrQuantity})`;
}
}
return isValid;
}
POST http://localhost:3000/api/retrieveManySKUs
[{
"sku":1019688
},{
"sku":1003622
},{
"sku":1006702
}]
{
"data": [
{
"sku": 1019688,
"name": "5-Year Protection Plan - Geek Squad",
"type": "BlackTie",
"totalQuantity": 24
},
{
"sku": 1003622,
"name": "Aquarius - Fender Stratocaster 1,000-Piece Jigsaw Puzzle - Black/Red/White/Yellow/Green/Orange/Blue",
"type": "HardGood",
"totalQuantity": 10
},
{
"sku": 1006702,
"name": "Clash of the Titans [DVD] [2010]",
"type": "Movie",
"totalQuantity": 10
}
],
"error": null
}
static async retrieveManySKUs(_productWithIds: IProductBodyFilter[]): Promise<IProduct[]> {
/**
Get current Quantity of specific Products.
:param _productWithIds: Product list with Id
:return: Product list
*/
const repository = ProductRepo.getRepository();
let retItems: IProduct[] = [];
if (repository && _productWithIds && _productWithIds.length) {
//string id array
const idArr = _productWithIds.map((product) => {
return product.sku?.toString() || ""
});
//fetch products by IDs (using redis om library)
const result = await repository.fetch(...idArr);
let productsArr: IProduct[] = [];
if (idArr.length == 1) {
productsArr = [<IProduct>result];
}
else {
productsArr = <IProduct[]>result;
}
if (productsArr && productsArr.length) {
retItems = productsArr.map((product) => {
return {
sku: product.sku,
name: product.name,
type: product.type,
totalQuantity: product.totalQuantity
}
});
}
else {
throw `No products found !`;
}
}
else {
throw `Input params failed !`;
}
return retItems;
}
POST http://localhost:3000/api/decrementManySKUs
[{
"sku":1019688,
"quantity":4
},{
"sku":1003622,
"quantity":2
},{
"sku":1006702,
"quantity":2
}]
{
"data": [
{
"sku": 1019688,
"name": "5-Year Protection Plan - Geek Squad",
"type": "BlackTie",
"totalQuantity": 28 //previous value 32
},
{
"sku": 1003622,
"name": "Aquarius - Fender Stratocaster 1,000-Piece Jigsaw Puzzle - Black/Red/White/Yellow/Green/Orange/Blue",
"type": "HardGood",
"totalQuantity": 8 //previous value 10
},
{
"sku": 1006702,
"name": "Clash of the Titans [DVD] [2010]",
"type": "Movie",
"totalQuantity": 8 //previous value 10
}
],
"error": null
}
static async decrementManySKUs(_productsFilter: IProductBodyFilter[]): Promise<IProduct[]> {
/**
decrement quantity of specific Products.
:param _productWithIds: Product list with Id
:return: Product list
*/
let retItems: IProduct[] = [];
if (_productsFilter && _productsFilter.length) {
//validation only
const promArr: Promise<boolean>[] = [];
for (let p of _productsFilter) {
if (p.sku) {
//validating if all products in stock
const promObj = InventoryServiceCls.validateQuantityOnDecrementSKU(p.sku, p.quantity);
promArr.push(promObj)
}
}
await Promise.all(promArr);
//decrement only
const promArr2: Promise<IProduct>[] = [];
for (let p of _productsFilter) {
if (p.sku && p.quantity) {
const isDecrement = true; //increments with negative value
const isReturnProduct = false;
const promObj2 = InventoryServiceCls.incrementSKU(p.sku, p.quantity, isDecrement, isReturnProduct);
promArr2.push(promObj2)
}
}
await Promise.all(promArr2);
//retrieve updated products
retItems = await InventoryServiceCls.retrieveManySKUs(_productsFilter);
}
else {
throw `Input params failed !`;
}
return retItems;
}