Call us: +1 (716) 989 6531 or email at:

Forecasting Software for sales, demand and call volumes

RSS RSS

Navigation





Search the wiki
»

PoweredBy

Custom Inventory Adapter in C# / .NET for the Safety Stock Calculator

RSS
Download Free trial

Lokad News


Supported App.

  • ADempiere
  • Avactic
  • Compiere
  • CRE Loaded
  • CS-Cart
  • CubeCart
  • Excel
  • JFire
  • LiveCart
  • Magento
  • Mediachase
  • Neogia
  • opentaps
  • Openbravo
  • OpenERP
  • osCommerce
  • Prestashop
  • QuickBooks
  • Sage
  • Shop-Script
  • SoftSlate
  • ViArt
  • VirtueMart
  • WebERP
  • X-Cart
  • ZenCart
  • (more are coming...)

Products » Safety Stock Calculator » Here

Design a custom inventory adapter in .NET

Intended audience: .NET developers

This document explains how to create a completely custom adapter that allows LSSC to load inventory and sales data from a 3rd party application.


Big picture

LSSC is simple BI (business intelligence) application that features inventory analysis based on the Lokad forecasting technology. LSSC includes a generic framework to retrieve inventory data (i.e. sales, stock levels, suppliers, ...) from virtually any 3rd party applications (most usually ERP, eCommerce or accounting applications).

The elementary piece of code that connects to the 3rd party application in order to retrieve the inventory data is called an inventory adapter in the LSSC terminology.

If the data of the 3rd party application can be accessed through SQL, then the most straightforward solution consists of using the LSSC Editor, a GUI tool that let you easily design and validate the adapter that wraps the actual SQL queries.

Yet, there are situations where the data cannot be accessed through SQL. Such situations typically include Web Services and any kind of custom API. In such cases, a custom .NET adapter is required. This guide will detail how to implement such an adapter.

Getting the source code

LSSC is an open source project with the source code is kept in a Subversion repository. You need to check-out the code hosted at Google Project (look for trunk/dot-net-legacy).

Use Microsoft Visual Studio 2008 to open the solution named Lokad.SafetyStock.Windows.sln. Visual Studio will prompt you because the project files have been modified. It's OK, we have indeed customized MsBuild project files, but it will not cause any harm. Just accept to load the projects normally.

There should be no particular dependencies, thus you should be able to compile the solution right away. Select Lokad.SafetyStock.Windows as the StartUp Project, hit F5 and LSSC should run.

Understanding how adapters work

The solution contains a dozen of C# projects. Yet, there are only two things that are relevant to the design of a custom inventory adapter:
  • Lokad.SafetyStock.InventoryAdapter C# project that contains the classes defining the framework for inventory adapters.
  • Special XML file format used to list inventory adapters being loaded by LSSC on start-up.

Basically, implementing a new inventory adapter consists of inheriting two classes
  • Lokad.SafetyStock.IInventoryAdapter
  • Lokad.SafetyStock.IInventoryAdapterFactory

This design is nothing but a simple Factory design pattern. The factory is responsible for instantiating the adapter - usually by passing some settings such as a connection string for example.

When LSSC gets started, all factories (i.e. inheritors of IInventoryAdapterFactory) listed in Adapters.ladx file from the project directory and any other *.ladx files from this directory or My Documents\My Lokad Extensions directory get loaded into the default instance of InventoryAdapterFactoryCollection. The factories are used to instantiate on-demand new IInventoryAdapter instances.

Then, once an IInventoryAdapter is instantiated, it can be used to retrieve inventory data that typically includes sales data, stock levels, suppliers ...

For a simple adapter implementation, we suggest to have a look at the TsvInventoryAdapter.cs code file in Lokad.SafetyStock.InventoryAdapter project. This implementation loads inventory from a TSV (tab separated value) file with a simple syntax. Although, don't reuse this TSV adapter as base implementation for your own adapter because this adapter is not a real implementation in many ways (implementation is not scalable, retrieved data is supposed immutable, ...).

This guide is not a substitute for reference documentation. The latest documentation of all those classes is available at build.lokad.com/doc/lssc. We suggest to refer to this reference documentation when actually inheriting classes.

Setting up a dedicated project

Implementing your custom inventory adapters requires introducing two classes. Although you could obviously add those class files to the Lokad.SafetyStock.InventoryAdapter project, we suggest not to touch any existing project because it would complicate the maintenance the day you want to upgrade LSSC to its latest version.

Instead, we suggest to add a new C# Library project to the solution. This project will contain your classes. Add Lokad.SafetyStock.InventoryAdapter as a project reference.

We suggest to create up-front two classes

  • MyInventoryAdapterFactory that inherits InventoryAdapterFactoryBase.
  • MyInventoryAdapter that inherits InventoryAdapterBase.

You can use the Visual Studio 2008 refactoring tools to auto-generate mock implementations of those two classes. In particular, it will auto-generate the methods GetSkuNames and GetOrders for the InventoryAdapterBase implementation; those methods will be further detailed in the following.

Closer look at InventoryAdapterFactoryBase

The first class to be implemented is the inheritor of IInventoryAdapterFactory. In practice we suggest to inherit InventoryAdapterBase which provides handy default behaviors. The factory contains a very limited amount of logic, thus coding the factory is actually quite simple.

public abstract class InventoryAdapterFactoryBase
{
  IInventoryAdapterDefinition Definition { get; }
  IInventoryAdapter GetInventoryAdapter(IInventoryAdapterSettings settings);
}

The Definition property is nothing but descriptive meta-data (such as display name, adapter description, ...) that are exposed to the user while setting up a new report in LSSC. We suggest to create a private class MyDefinition, located within your InventoryAdapterFactoryBase class, that inherits IInventoryAdapterDefinition. If all the meta-data are static (which is usually the case), you can make MyDefinition a singleton.

The method GetInventoryAdapter is responsible for instantiating the adapter instances. The settings argument usually contains the credentials used to connect to the 3rd party data source. At this point, we suggest to return a new instance of your un-implemented adapter calling the default constructor.

Referencing your adapter in LSSC

Now that you have setup a minimal project for your custom inventory adapter, we are going to reference this adapter in LSSC. This should facilitate the testing of the adapter by directly launching LSSC.

Note: the IInventoryAdapterFactory.Definition must be implemented in order not to crash LSSC when the list of available factories will be displayed.

Step 1: Open extensions folder by clicking "Tools | Locate Extensions Folder" from the LSSC Menu (the folder will be created if it does not exist).

Step 2: Copy your compiled factory dll to that folder. Let's say it was named MyLibrary.dll.

Step 3: Create MyLSSCAdapters.ladx file and open it with your favorite XML editor. You can use Notepad or Notepad++ which is a free editor with XML editing capabilities.

Note: LADX file is a simple header file with custom XML format. It lists adapters that should be loaded by LSSC when it starts up. You can have as many LADX files as you want. LSSC will automatically find and load all files from the Extensions folder.

Step 4: Add following XML to the empty file and save it:

<?xml version="1.0" encoding="utf-8"?>
<lokad 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <lssc-adapters>
    <adapter 
      id="MyFirstAdapter"
      factory="MyLibrary.MyCustomAdapterFactory, MyLibrary"
      factory-assembly="MyLibrary.dll" />
  </lssc-adapters>
</lokad>

Tip: Lokad Safety Stock Calculator includes a lot of custom adapters in its distribution. They can serve as a reference, should you want to use some advanced features like optional parameter passing. Just look for the xml\Adapters.ladx in Lokad.SafetyStock.InventoryAdapter project.

Step 5: Restart the LSSC application. Your new adaper should now be visible within the adapters list. Should there be any errors or warnings while loading this adapter, they will be displayed in the separate window.

Optional Step 6: in order to redistribute LSSC adapters you need to copy LADX file and DLL files that it references to the Extensions folder on another machine.

Wizard tips and other remarks

During the LSSC setup will be asked for your Lokad credentials, yet you do not need a Lokad account to actually test your inventory adapter. Thus, we suggest you enter mock credentials (anything will do), just to move on the next wizard panels. Then, for the report settings, you can just leave the default settings.

After completing the wizard, we suggest that you save your report, using File»Save. It will save you the trouble of re-setting up your report each time.

Finally, the inventory retrieval can be performed through Tools»Partial Refresh»Retrieve Inventory. If errors happen, then you can use View»Event Logs to get the full detail of the exception that has been thrown.

Closer look at InventoryAdapterBase

The second class to be implemented is the inheritor of IInventoryAdapter. In practice, we suggest to inherit InventoryAdapterBase (as opposed to inheriting IInventoryAdapter directly) as this class provides handy default behaviors.

public abstract InventoryAdapterBase
{
  public abstract SkuName[] GetSkuNames();
  public abstract SkuOrder[] GetOrders(
    int index, int pageSize, DateTime startTime, DateTime endTime);
  // ...
}

There are only two methods marked as abstract: GetSkuNames and GetOrders.

GetSkuNames

The method GetSkuNames is straightforward. SKU stands for Stock Keeping Unit. Check the SKU definition if you are not familiar with this concept. Each SKU has an identifier (SkuName.Identifier), which will be uploaded to Lokad and a name (SkuName.Name), which will be actually displayed to the user. Optionally, you can use the same value for SKU identifier and SKU name.

Still, there is a privacy catch with GetSkuNames. The SkuName class comes with only two properties:
public class SkuName : SkuInfo<string>
{
  public string Identifier { get; } // uploaded to Lokad
  public string Name { get; } // NOT uploaded to Lokad
}
Although those two properties Identifier and Name have similar roles, there is a major difference between the two: only the Identifier gets uploaded to Lokad. This pattern is a required in order to enforce the basic obfuscation policy of Lokad. Thus, we strongly suggest to use some anonymous values for the Identifiers. Typically, database primary keys are perfect for that since they do not carry any information about the item or the SKU itself.

Then, there is yet another reason not to use non-anonymous data (typically product names) for the Identifier values: the string tokens must be compatible with the naming rules enforced by the Lokad Web Services for time-series names. Indeed, as detailed on the TimeSeries.asmx API:

Naming: time-series and forecasting tasks have names (i.e. strings used as identifiers). Those names could not include more than 64 characters. Additionally, the following list of characters ; < > \ / " ' are prohibited.

Finally, please note that, like all the other IInventoryAdapter.GetFoo() methods, GetSkuNames is expected to return a sorted array. The expected sort order is the one associated to SkuIdHolder (the base class of SkuInfo).

GetOrders

The method GetOrders is slightly more complicated. Basically, this method returns demand orders i.e. anything that actually decreases the inventory levels (it can be sales orders but also internal inventory consumption for example). The SkuOrder is very simple class:

public class SkuOrder : SkuIdHolder
{
  // public string Identifier { get; } - inherited from SkuIdHolder
  public DateTime OrderDate { get; }
  public double Quantity { get; }
}

The reason why GetOrder takes argument is simple: performance. The number of orders can be very large, and it might be very unpractical to try to retrieve all the data at once. Thus, GetOrders supports, through its argument, two behaviors
  • data paging that indicates the segment of data to be returned.
  • date constraints that enable incremental data retrieval.

The GetOrders takes a startTime argument. While designing your inventory adapter, you may wonder how does the value of this argument gets chosen by LSSC. It's actually quite simple:
  • for the first retrieval for the report, startTime is set as 1901-01-01 which will retrieve, hopefully, even the oldest orders.
  • for later retrievals, startTime is equal to the last past period date, as retrieved during the previous retrieval operation.
    This scheme ensures that the inventory data gets incrementally retrieved.

For developers (yet not for users), this behavior can be a bit surprising, because if you happen to create old orders after the first retrieval, then those orders are likely to be ignored by a later Retrieve operation simply because LSSC assume that such orders do not exist in the first place. Indeed, in a real situation, those orders would have been already retrieved.

Optional methods

The abstract class InventoryAdapterBase contains several methods that can be optionally implemented. Indeed, not all 3rd party applications are born equal, and certain 3rd party application may not support lead times or suppliers for example. In such a situation, the end user will still be able to manually enter the data into LSSC. Yet, it is advised to implement as many methods as possible since it will significantly save time for the end user.

public abstract class InventoryAdapterBase
{
  public virtual bool IsProductNameAvailable { get; }
  public virtual SkuProductName[] GetProductNames();

  public virtual bool IsStockOnHandAvailable { get; }
  public virtual SkuQuantity[] GetStocksOnHand();

  public virtual bool IsSupplierAvailable { get; }
  public virtual SkuSupplier[] GetSuppliers();
  // ...
}

Those methods GetFoo are all associated with an IsFooAvailable property. The default implementation in InventoryAdapterBase returns false for all those properties. If you provide an implementation for GetProductNames, you must override IsProductNameAvailable as well (returning true obviously), otherwise your implementation will simply be ignored by LSSC.

Unit testing your adapter implementation

It is usually considered a good practice to validate your code through unit testing. The LSSC project comes with its own testing suite that relies on NUnit.

LSSC includes a reusable set of unit tests targeting inventory adapters. This means that you don't need to actually implement any unit-test, you just need to reference your new inventory adapter to be tested against the default set of unit tests provided by LSSC.

We suggest that you create a new Class library project named MyInventoryAdapter.Tests. This assembly will need to reference a couple of libraries including

  • Your own custom library MyInventoryAdapter
  • nunit.framework.dll, located in /lib
  • Lokad.SafetyStock.InventoryAdapter
  • Lokad.SafetyStock.InventoryAdapter.Tests

In this new project, you need to add a single class MyInventoryAdapterTests that inherits InventoryAdapterCommonTests. See the implementation of TsvInventoryAdapterTests pasted below as an example.

using NUnit.Framework;
using Lokad.SafetyStock;
using Lokad.SafetyStock.Tests;

[TestFixture]
public class TsvInventoryAdapterTests : InventoryAdapterCommonTests
{
    public TsvInventoryAdapterTests() 
        : base(GetInventoryAdapter(),
            new DateTime(2005, 01, 01), // start date provided for GetOrders
            new DateTime(2009, 01, 01)) // end date provided for GetOrders
    {
    }

    // Helper method to create a default instance of the adapter.
    public static InventoryAdapterBase GetInventoryAdapter()
    {
        // The string passed below as argument is the connection string.
        return new TsvInventoryAdapter(null,
            new InventoryAdapterSettings(@"..\..\MockTsvInventoryAdapterData.txt"));
    }
}

Finally, use NUnit to execute the testing suite contained in your MyInventoryAdapter.Tests library.