Recommendations
Now that we have our data structures in order, let’s move on to the GenerateRestockReco
subroutine. GenerateRestockReco
is supposed to analyze inventory and sales data, ultimately providing recommendations for restocking inventory items. We’re going to show off our knowlege of collections, control flow, and comparisons. Let’s explore this subroutine in detail, understanding its intricacies and the role of each segment in achieving its objective.
Subroutine signature
.include "InventoryItem" REPOSITORY, structure, end
.include "Order" REPOSITORY, structure, end
.include "Trend" REPOSITORY, structure, end
.include "Restock" REPOSITORY, structure, end
subroutine GenerateRestockReco
in inventory, @ArrayList
in orders, @ArrayList
in analysisDate, d8
out trends, @ArrayList
out result, @ArrayList
endparams
;;data division records go here
record
oneMonth, int
oneYear, int
orderTrends, @StringDictionary
proc
;; implementation goes here
endsubroutine
Initialization and preparations
result = new ArrayList()
trends = new ArrayList()
oneMonth = %jperiod(analysisDate) - 30
oneYear = %jperiod(analysisDate) - 365
orderTrends = new StringDictionary()
In this initial section, the subroutine sets up necessary variables. Variables oneMonth
and oneYear
are calculated to represent the date thresholds for recent and historical data analysis. The %JPERIOD function converts analysisDate
into a Julian date format, from which 30 and 365 days are subtracted to get dates one month and one year prior, respectively. The variable orderTrends
is initialized as a StringDictionary
, which will be used to map item IDs to their corresponding sales trend data.
Processing orders
foreach data targetOrder in orders as @Order
begin
data orderDate, int, %jperiod(targetOrder.OrderDate)
data targetTrend, Trend
init targetTrend
targetTrend.ItemId = targetOrder.ItemId
if (orderTrends.Contains(targetOrder.ItemId))
begin
targetTrend = (Trend)orderTrends.Get(targetOrder.ItemId)
end
if(orderDate >= oneMonth) then
begin
targetTrend.ItemCount += targetOrder.Quantity
targetTrend.OrderCount += 1
end
else if(orderDate >= oneYear) then
begin
targetTrend.HistoricCount += targetOrder.Quantity
end
else
nextloop
orderTrends.Set(targetOrder.ItemId, (@*)targetTrend)
end
In this block, the subroutine iterates through each order in the orders
ArrayList. Because ArrayList only understands an untyped object
, we tell the compiler what the expected type is using the AS syntax. For each order, the routine calculates the Julian date (orderDate
) and initializes a Trend
structure (targetTrend
). This structure is then populated with data depending on whether the order falls within the one-month or one-year threshold. Orders within these thresholds contribute to the item count and order count or the historic count, reflecting recent and historical sales data. The updated trend data for each item is stored back into the orderTrends
dictionary.
Analyzing inventory and creating recommendations
foreach data targetItem in inventory as @InventoryItem
begin
if (orderTrends.Contains(targetItem.ItemId))
begin
data targetTrend = (Trend)orderTrends.Get(targetItem.ItemId)
data historicCount, d28.10, targetTrend.HistoricCount
data recentQuantity, d28.10, targetTrend.ItemCount
data historicalAverage, d28.10, historicCount / 12.0
if (recentQuantity > 1.5 * historicalAverage ||
& targetItem.Quantity < historicalAverage)
begin
data restockRequest, Restock
restockRequest.ItemId = targetItem.ItemId
restockRequest.Quantity = %integer(recentQuantity > historicalAverage ?
& recentQuantity - targetItem.Quantity:
& historicalAverage - targetItem.Quantity)
if(restockRequest.Quantity > 0)
result.Add((@*)restockRequest)
end
trends.Add((@*)targetTrend)
end
end
This segment iterates over each item in the inventory. For items that have corresponding trend data in orderTrends
, the subroutine calculates the historical average sales and compares that value with the recent sales quantity. If the recent sales exceed 1.5 times the historical average or if the current inventory is below the historical average, a restock request is created. This request includes the item ID and the calculated restock quantity, which is determined based on whether the recent sales or the historical average is greater. Restock requests with a positive quantity are added to the result
ArrayList, which contains all recommendations. Additionally, the trend data for each item is added to the trends
ArrayList for potential further analysis.
Test data
We’re going to need to build some test data to run this routine against. We’ll need a few helper routines to accomplish this. Let’s start with the MakeItem
function. This function will take in an item ID, name, and quantity and return an InventoryItem
structure, which we’ll use to populate our inventory.
function MakeItem, InventoryItem
itemId, n
name, string
quantity, int
endparams
record
inv, InventoryItem
proc
inv.ItemId = %string(itemId)
inv.Name = name
inv.Quantity = quantity
freturn inv
endfunction
There’s not really anything new here; we’re just taking in some parameters and returning a structure. This saves us a few repeated lines later on since we’re going to create a few items. Next, we’ll need a function to build orders. This function will take in an item ID, quantity, order count, and order date. It will then generate orders for that item for the specified quantity, count, and date. We’ll use what’s generated to populate a large number of orders.
subroutine BuildOrders
itemId, n
quantity, int
orderCount, int
date, d8
orders, @ArrayList
endparams
record
ord, Order
i, int
proc
for i from 1 thru orderCount by 1
begin
ord.ItemId = %string(itemId)
ord.OrderDate = date
ord.OrderId = %string(orders.Count)
ord.Quantity = quantity
orders.Add((@Order)ord)
end
xreturn
endsubroutine
The above code is doing just a little bit more than the MakeItem
function. Because we’re going to be generating a lot of orders, we’re going to use a FOR loop to do it. We’re also going to be adding these orders to an ArrayList` so we can keep track of them. Now that we have our helper routines, let’s build some test data.
main
record
restockRequests, @ArrayList
trends, @ArrayList
inventory, @ArrayList
orders, @ArrayList
analysisDate, d8
proc
inventory = new ArrayList()
orders = new ArrayList()
;;this is YYYYMMDD for September 28th, 2023
;;this is the format that %jperiod expects
analysisDate = 20230928
;;Let's be explicit and box the returned structures
inventory.Add((@InventoryItem)MakeItem(1, "widget", 50))
inventory.Add((@InventoryItem)MakeItem(2, "doodad", 25))
inventory.Add((@InventoryItem)MakeItem(3, "thingy", 100))
inventory.Add((@InventoryItem)MakeItem(4, "whatchacallit", 0))
;;all of these dates are YYYYMMDD
BuildOrders(1, 5, 20, 20230928, orders)
BuildOrders(1, 3, 20, 20230728, orders)
BuildOrders(1, 3, 20, 20230503, orders)
BuildOrders(1, 3, 20, 20230328, orders)
BuildOrders(2, 5, 20, 20230928, orders)
BuildOrders(3, 5, 20, 20230404, orders)
BuildOrders(4, 5, 2000, 20230104, orders)
endmain
Most of this code is starting to be old hat, but there are a few things to call out. Because ArrayList only understands Object
, we need to box our structures by casting them to @InventoryItem
or @Order
. You may see other developers write this as (@*)
, which is shorthand for casting to Object
, but the problem with that is you don’t really know if that is going to result in a boxed alpha representation of your structure or an actual boxed InventoryItem
. The practical effect of this distinction is minimal in Traditional DBL, but if you’re running on .NET, the type system is much more strict. We’re also going to be using the BuildOrders
function to generate a lot of orders into our orders
variable.
Generating restock recommendations
Time for the moment of truth. Let’s add our GenerateRestockReco
subroutine to the bottom of our main and see what we get.
GenerateRestockReco(inventory, orders, analysisDate, trends, restockRequests)
This call to GenerateRestockReco
is going to process the inventory and order data to generate restocking recommendations. The subroutine outputs two ArrayLists: trends
, which holds trend data for each item, and restockRequests
, which contains the restocking recommendations. Let’s go through the steps to build this project and see what happens.
Building the project
Make sure you have the DBL environment set up and you’re still in the project directory. Additionally, make sure you’ve still got RPSMFIL and RPSTFIL set from the repository section.
We’re making use of the StringDictionary that we built back in Dictionary, and to use that, we’re going to need to write that source to a file in our project directory. I’ve named it StringDictionary.dbl
, and also I’ve named the source we were just working on Program.dbl
. Now run the following command:
dbl Program.dbl StringDictionary.dbl
dblink Program
dbs Program
The first two commands should complete without error. If you get an error from the dbl
compile step, make sure your repository is set up, that you have the most recent version of DBL installed, and that you’ve written both Program.dbl and StringDictionary.dbl to the project directory. The dbs
command will run the program and output the results to the console. You should see something like this:
%DBR-S-STPMSG, STOP
We didn’t get any output! What happened? Well, we didn’t tell the program to output anything. Let’s add some code to do that.
Outputting restock recommendations
After the call to GenerateRestockReco
, add the following code:
foreach data restockReq in restockRequests as @Restock
begin
;;We can go grab the item from inventory and show the name
data item = (@InventoryItem)inventory[%integer(restockReq.ItemId) - 1]
Console.WriteLine("restock request for " + %atrim(item.Name) +
& " with quantity " + %string(restockReq.Quantity))
end
This loop iterates over the restockRequests
ArrayList. For each restock request, it retrieves the corresponding inventory item to display the item’s name and the recommended restock quantity. This is a very simple way to output the recommendations, but at least we can see that the subroutine is working. Let’s build and run the program again.
dbl Program.dbl StringDictionary.dbl
dblink Program
dbs Program
This time we should see some output:
dbs Program
restock request for widget with quantity 50
restock request for doodad with quantity 75
restock request for whatchacallit with quantity 833
%DBR-S-STPMSG, STOP
We can see that the subroutine is working as expected. It’s recommending restocking for the widget, doodad, and whatchacallit items. The widget and doodad items have been selling well recently, and the whatchacallit item has been selling well historically. The subroutine is also recommending a higher quantity for the whatchacallit item because it has a history of massive sales spikes. Although this is a very simple example, it shows how we can use the tools we’ve learned to build a useful routine. We can do better though. In the next section, we’re going to explore how we can use the trends
ArrayList to generate a very simple PDF report of the sales trends for each item.