学习

使用 Redis 实现实时库存中的可承诺库存

Will Johnston
作者
Will Johnston, Redis 开发者增长经理
Prasan Kumar
作者
Prasan Kumar, Redis 技术解决方案开发人员
GITHUB 代码

以下命令用于克隆本教程中使用的应用程序的源代码

git clone https://github.com/redis-developer/redis-real-time-inventory-solutions

什么是可承诺库存 (ATP)?#

在 零售库存系统 中,主要需求是向购物者和门店员工提供准确的实时库存视图,从而实现线上购买、门店取货 (BOPIS)。优化来自多个库存地点的履行。

可承诺库存 (ATP) 是指预计可供出售的库存数量,不包括已分配的库存。它允许企业控制对客户的配送并预测库存。ATP 模型有助于零售商降低库存成本,例如订购成本、持有成本和缺货成本。只要消费者购买预测保持准确,ATP 就会有所帮助。对零售商而言,有效实施 ATP 流程可以决定持续增长与库存反复出现缺货(导致错失销售机会并损害客户体验)之间的差异。

如何计算可承诺库存#

计算可承诺库存相对简单。请完成以下公式,以准确分析可承诺库存能力

Available-to-promise = QuantityOnHand + Supply - Demand

此公式包含以下元素

  • 库存量:公司立即可用的产品总数
  • 供应:可供出售的特定产品的总库存
  • 需求:消费者愿意购买的特定产品的数量

实时库存中的当前挑战#

  • 库存过剩和不足:在采用多渠道业务模式(线上和线下)时,缺乏库存可见性会导致不同地区和门店的库存过剩和不足。
  • 消费者 寻求便利:能够跨区域门店位置搜索商品并 立即 取货,而不是等待配送,是零售商的关键差异化因素。
  • 消费者 寻求速度:所有零售商,即使是小型或家族式企业,也必须与阿里巴巴、FlipKart、Shopee 和亚马逊等大型线上零售商的 客户体验 竞争。
  • 高库存成本:零售商试图通过消除因缺货造成的漏单来降低库存成本,这也导致更高的“库存周转率”。
  • 品牌价值:不准确的门店库存计数会导致客户沮丧和销售额下降。运营上的痛苦会 影响现状

为什么要使用 Redis 来实现可承诺库存#

  • 提高库存可见性:Redis Cloud 提供高度可扩展的实时库存同步,在门店之间提供可承诺库存的视图。客户希望从能够跨多个地点检查库存并提供本地可用库存的实时视图的零售商处购买商品。
  • 增强客户体验:亚毫秒级的延迟意味着线上客户可以轻松获得购物车、定价和库存可用性的实时视图。Redis Cloud 内置搜索引擎提供实时库存的全文本和汇总分面搜索,扩展性能以立即搜索数百万种产品类型的库存,帮助客户更快地填满购物车,保持他们的参与度和忠诚度。
  • 规模化成本效率:Redis Cloud 提供门店之间的实时双向一致性和与企业系统的数据集成功能,而无需管理消息代理、审计和对账带来的复杂性和成本。

使用 Redis 的实时库存#

使用 Redis,系统可以实现跨门店、在途和仓库的实时库存同步。为零售商提供其整个门店网络中最准确、最及时的库存数据,并为消费者提供积极的搜索和定位库存的客户体验。

Redis 数据集成 (RDI) 功能可以实现准确的实时库存管理和系统记录同步。Redis 高级库存搜索和查询功能可以向多渠道和全渠道客户以及门店员工提供准确的可用库存信息。

此解决方案提高了库存周转率,从而降低了库存成本,提高了收入和利润。它还减少了客户搜索对系统记录和库存管理系统 (IMS) 的影响。

客户证明点#

GITHUB 代码

以下命令用于克隆本教程中使用的应用程序的源代码

git clone https://github.com/redis-developer/redis-real-time-inventory-solutions

管理库存或 SKU(库存单位) 流程包含一些活动,例如

  1. 1.RetrieveSKU:获取产品的当前数量
  2. 2.UpdateSKU:更新产品的最新数量
  3. 3.IncrementSKU:按特定值增加数量(例如,当采购更多产品时)
  4. 4.DecrementSKU:按特定值减少数量(例如,在产品订单履行后)
  5. 5.RetrieveManySKUs:获取 多个 产品的当前数量(例如,在付款前验证库存中的产品)
  6. 6.DecrementManySKUs:减少 多个 产品的数量(例如,在包含多个产品的订单履行后)

RetrieveSKU#

以下代码显示了 retrieveSKU 活动的 API 请求和响应示例。

retrieveSKU API 请求

GET http://localhost:3000/api/retrieveSKU?sku=1019688

retrieveSKU API 响应

{
  "data": {
    "sku": 1019688,
    "name": "5-Year Protection Plan - Geek Squad",
    "type": "BlackTie",
    "totalQuantity": 10
  },
  "error": null
}

当您发出请求时,它会通过 API 网关传递到库存服务。最终,它会调用一个 retrieveSKU 函数,如下所示

代码

src/inventory-service.ts

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;
}

UpdateSKU#

以下代码显示了 updateSKU 活动的 API 请求和响应示例。

updateSKU API 请求

POST http://localhost:3000/api/updateSKU
{
    "sku":1019688,
    "quantity":25
}

updateSKU API 响应

{
  "data": {
    "sku": 1019688,
    "name": "5-Year Protection Plan - Geek Squad",
    "type": "BlackTie",
    "totalQuantity": 25 //updated value
  },
  "error": null
}

当您发出请求时,它会通过 API 网关传递到库存服务。最终,它会调用一个 updateSKU 函数,如下所示

src/inventory-service.ts
 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;
    }

IncrementSKU#

以下代码显示了 incrementSKU 活动的 API 请求和响应示例。

incrementSKU API 请求

POST http://localhost:3000/api/incrementSKU
{
    "sku":1019688,
    "quantity":2
}

incrementSKU API 响应

{
  "data": {
    "sku": 1019688,
    "name": "5-Year Protection Plan - Geek Squad",
    "type": "BlackTie",
    "totalQuantity": 12 //previous value 10
  },
  "error": null
}

当您发出请求时,它会通过 API 网关传递到库存服务。最终,它会调用一个 incrementSKU 函数,如下所示

src/inventory-service.ts
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;
    }

DecrementSKU#

以下代码显示了 decrementSKU 活动的 API 请求和响应示例。

decrementSKU API 请求

POST http://localhost:3000/api/decrementSKU
{
    "sku":1019688,
    "quantity":4
}

decrementSKU API 响应

{
  "data": {
    "sku": 1019688,
    "name": "5-Year Protection Plan - Geek Squad",
    "type": "BlackTie",
    "totalQuantity": 16 //previous value 20
  },
  "error": null
}

当您发出请求时,它会通过 API 网关传递到库存服务。最终,它会调用一个 decrementSKU 函数,如下所示

src/inventory-service.ts
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;
    }

RetrieveManySKUs#

以下代码显示了 retrieveManySKUs 活动的 API 请求和响应示例。

检索多个 SKU 的 API 请求

POST http://localhost:3000/api/retrieveManySKUs
[{
    "sku":1019688
},{
    "sku":1003622
},{
    "sku":1006702
}]

检索多个 SKU 的 API 响应

{
  "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
}

当您发出请求时,它会通过 API 网关到达库存服务。最终,它会调用一个名为 retrieveManySKUs 的函数,如下所示

src/inventory-service.ts
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;
    }

DecrementManySKUs#

以下代码展示了 DecrementManySKUs 活动的 API 请求和响应示例。

DecrementManySKUs API 请求

POST http://localhost:3000/api/decrementManySKUs
[{
    "sku":1019688,
    "quantity":4
},{
    "sku":1003622,
     "quantity":2
},{
    "sku":1006702,
    "quantity":2
}]

DecrementManySKUs API 响应

{
  "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
}

当您发出请求时,它会通过 API 网关到达库存服务。最终,它会调用一个名为 decrementManySKUs 的函数,如下所示

src/inventory-service.ts
 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;
    }

准备好将 Redis 用于实时库存系统了吗?#

希望本教程能帮助您了解如何在实时库存系统中使用 Redis 来实现不同门店的产品可用性。有关此主题的更多资源,请查看以下链接

更多资源#