Extending Item Tracking in Business Central

I’ve been pretty quiet on the blogging front for the last few months – settling in to a new team and a new role.

For the first post of 2021 what better topic than Item Tracking? Let’s be honest, the Item Tracking Lines page is a mess. Over 3,000 lines of code, tonnes of global variables and more than 60 methods. I don’t think the fundamental design of the page has changed much since it was introduced – it’s a bullet that no one wants to bite.

To be fair, making radical changes to the design would be very disruptive to the base application and I guess to lots of solutions and bespoke that have been written on top of it. So instead the page now has a layer of 44 (yes, forty four) events on top of it – not including the built-in page events. Now we are going to add another layer of code on top and try not to knock the whole jenga tower down in the process.

I’m sorry, that’s harsh. The point of this post isn’t really to criticise the design of item tracking – it is what it is – but share a few things I’ve found trying to extend it.

Global Variables

protected variables

Item Tracking Lines makes extensive use of global variables which can be a problem when you are trying to change the behaviour of the page. Some of the variables are shared in the protected var section.

As these variables are protected (rather than local) they can be accessed by a page extension for the Item Tracking Lines page. You can just refer directly to the variable and use it in your page extension code.

You’ll notice that all the variables are xyzVisible and xyzEditable which control whether you can see and/or edit various controls on the page.

Get/SetVariables

You can get access to some more of the important global variables on the page with the GetVariables method. There is a corresponding SetVariables method which allows you to override the Tracking Specification records to insert, modify or delete.

If you want to override the value of other global variables on the page then you will have to hunt through the list of events. As there are so many events on the page its likely that there is going to be one that allows you to handle the calculation of the variable that you are interested in.

Calling Page Methods

As noted above, a lot of business logic exists in the Item Tracking Lines page itself. Some important functions are in the Item Tracking Management and Item Tracking Data Collection codeunits but it is likely that you are going to need to call one or more of the methods on the page.

I wanted to avoid having all my code in the Item Tracking Lines page extension and instead split it out into one or more codeunits to handle my specific functionality.

OK, but if my code needs to call the methods on the page itself, how am I going to be able to do that from code inside a codeunit?

Passing the Current Instance of the Page to a Codeunit

Easy, I thought. I’ll just pass the current instance of the page as a parameter to my codeunit. Declare a parameter of type Page of the Item Tracking Line page, pass it by reference with the var keyword – Robert is your mother’s brother.

procedure FunkyItemTracking(var TrackingSpecification: Record "Tracking Specification" temporary; var ItemTrackingLines: Page "Item Tracking Lines")
begin
    //some funky code 
end;

Erm…no. How do you get the current instance of a page? With CurrPage. CurrPage is an object of type CurrPage not Page. Which gives the following compilation error: “Cannot convert from CurrPage “Item Tracking Lines” to var Page “Item Tracking Lines”.

Exhibit A – cannot convert from Item Tracking Lines to Item Tracking Lines 🤦‍♂️

There is another way. An event can IncludeSender which passes the calling object along with any other parameters a subscriber.

I don’t want anyone else to subscribe to this event and mess about with it. For that, we can use the InternalEvent attribute. From the docs: “Specifies that the method is published as an internal event. It can only be subscribed to from within the same module.”

You can create a new internal event on your Item Tracking Lines page extension, including the sender and subscribe to it in a separate codeunit. That way you’ve got an instance of the page whose methods you can call while keeping your code separated. Have and eat your cake.

I’ve also manually bound my subscribing codeunit. That way I can have multiple subscribers in the codeunit and maintain some state in its variables.

//Item Tracking Lines page extension

actions
{
    action(FunkyItemTrackig)
    {
        trigger OnAction()
        begin
            OnCallingFunkyItemTracking(Rec);
        end;
    }
}

trigger OnOpenPage()
begin
    BindSubscription(FunkyItemTracking);
end;

var
    FunkyItemTracking: Codeunit "Funky Item Tracking";

[InternalEvent(true)]
local procedure OnCallingFunkyItemTracking(var TempTrackingSpecification: "Tracking Specification" temporary)
begin
end;

//Subscribing codeunit

local procedure FunkyItemTracking(var TempTrackingSpecification: Record "Tracking Specification" temporary; var ItemTrackingLines: Page "Item Tracking Lines")
var
    ...
begin
    ItemTrackingLines.GetVariables(...);
    //some funky item tracking customisation
end;

[EventSubscriber(ObjectType::Page, Page::"Item Tracking Lines", 'OnCallingFunkyItemTracking', '', false, false)]
local procedure OnCallingFunkyItemTracking(var Sender: Page "Item Tracking Lines"; var TempTrackingSpecification: Record "Tracking Specification" temporary)
begin
    FunkyItemTracking(TempTrackingSpecification, ItemTrackingLines);
end;

Simplifying GetVariables

I’ve reused the same concept the simplifying retrieving variable values. If you want to read the source quantity array, undefined quantity array, the Item record or Tracking Specification records to insert, modify or delete you’ll need to call the page’s GetVariables method.

That’s fine – but that method uses 10 var parameters to retrieve the variables. If you’re only trying to get at the value of one of those variables – or in my case, one element of one the arrays – it’s just a bit messy to create 10 variables that you don’t even need.

//Item Tracking Lines page extensions

...
var
    //just define the variable that you are interested in, keeps your code easier to read
    UndefinedQty: Decimal;
...
    OnGetUndefinedQty(UndefinedQty);
...

[InternalEvent(true)]
local procedure OnGetUndefinedQty(var UndefinedQty: Decimal)
begin
end;

//Subscribing codeunit

[EventSubscriber(ObjectType::Page, Page::"Item Tracking Lines", 'OnGetUndefinedQty', '', false, false)]
local procedure OnGetUndefinedQty(var Sender: Page "Item Tracking Lines"; var UndefinedQty: Decimal)
var
    TempTrackingSpecInsert, TempTrackingSpecModify, TempTrackingSpecDelete : Record "Tracking Specification" temporary;
    Item: Record Item;
    UndefinedQtyArray: Array[3] of Decimal;
    SourceQuantityArray: Array[5] of Decimal;
    CurrentSignFactor: Integer;
    InsertIsBlocked, DeleteIsBlocked, BlockCommit : Boolean;
begin
    Sender.GetVariables(TempTrackingSpecInsert, TempTrackingSpecModify, TempTrackingSpecDelete, Item, UndefinedQtyArray, SourceQuantityArray, CurrentSignFactor, InsertIsBlocked, DeleteIsBlocked, BlockCommit);
    UndefinedQty := UndefinedQtyArray[1];
end;

There’s a lot more to say about the Item Tracking Lines page, but I’ll leave it there for now. Maybe somebody will find this interesting and/or useful.

3 thoughts on “Extending Item Tracking in Business Central

  1. This subject touch me a lot. I have a lot of discussions arguing why Item tracking code is bad.

    There are two opposite forces to make our code fragile: duplication and coupling. Item tracking is an example of code coupling: they joined two subjects in one single table: Reservation and item tracking. In early 3.0 nav version they are isolated, but then someone though that was a good idea join reservation and item tracking in the same “Reservation entry” table. And the result is complex and hard to extend.

    If you see how an Item journal with tracking post, is other complex and bad designed idea: two calls to the same function, one with the original line to split the line, and the other with the split line. In my opinion, the only logic choice was split the journal lines setting lot numbers before call to 22 codeunit.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s