Sometimes it would be useful to know how your code has been reached. For example, how and why is this sales line being inserted, or this sales order released? You might want to react differently in your code depending on the situation.
In my case I wanted to know if my code has been reached because a configuration package is currently being applied. In the end we decided this wasn’t needed in the product but I thought it was interesting enough to share anyway.
What options do we have? Or, click don’t patronise me, just skip to the interesting part.
It might be enough just to know which field the user was validating when your code was reached, in which case you can just check CurrFieldNo.
Consider how you are going to write tests for this though. I try to avoid using TestPage variables in tests so you need some other way to simulate the user validating the field on the page. It isn’t big or clever but you can have a method to set CurrFieldNo.
internal procedure SetCurrFieldNo(FieldNo: Integer)
begin
CurrFieldNo := FieldNo;
end;
----
[Test]
procedure TestingSomethingOrOther()
var
SalesLine: Record "Sales Line";
begin
...
SalesLine.SetCurrFieldNo(SalesLine.FieldNo("No."));
SalesLine.Validate("No.", Item."No.");
...
end;
If you extend table triggers with table extensions they will only be called with Insert/Modify/Delete(true);
If you subscribe to the OnBefore/OnAfter Insert/Modify/Delete events in a codeunit then pay attention to the RunTrigger parameter.
Don’t forget to also pay attention to whether the record is temporary with Rec.IsTemporary(); You probably want your code to behave differently depending on whether the record is temporary or not.
Sometimes the base app (or other app that you are extending) might have anticipated that you need to be able to distinguish between certain scenarios.
For example, Release Sales Document might be called by a user clicking on the Release action on the Sales Order page or it might be called deep in the warehouse posting routine. Those are very different contexts and you might need to react differently depending on which it is.
In this case, Release Sales Document has the concept of a “manual” release for when it has been invoked by the user. There are separate events you can subscribe to depending on if you only want to react to a manual release or all releases.
I’m not keen on this design in the base app – but that ship has long since sailed, carrying approvals and pre-payments with it.
We could capture the current callstack, see where we’ve come from and choose how to react.
Possible, but don’t.
You might consider creating a new object where you can save some state to retrieve later. Set a boolean flag to true at the start of the process and then retrieve the value of that boolean later on.
Good idea, but where are we going to save that value?
A SingleInstance codeunit might be an obvious place to start. Create a global variable in that codeunit, set its value at the start of the process (maybe with an event subscription) and then check its value when you need it.
At face value this looks like a good and easy solution but quickly becomes quite difficult.
You have to clear the state of the codeunit at some point otherwise your flag will never get set to false again (until the user logs off/switches company).
If there is an appropriate OnAfter
event then surely we can just subscribe to that and unset the flag? Yes, but what if that event never gets called? What if there is an error midway through the process? Your flag remains set. That could lead to some problems.
OK, could we use a field in a table as the flag instead? Write into that table at the start of the process and delete the record at the end. If an error occurs then the record changes will be rolled back with everything else. Possibly, but consider:
None of this is insurmountable, but it isn’t elegant either.
Ideally we are looking for a flag that:
OnAfter
eventA manually bound codeunit might be what we’re looking for.
I’ve created two new codeunits:
Apply Config. Watcher
– this has a public method which we can use to ask if a configuration is currently being applied. We might call this from OnInsert of a table or field validation, for example. It throws an (internal) event to determine whether a config package is being applied or notApply Config. Flag
– this is a manually bound codeunit which just subscribers to the event and sets the boolean to trueNow we need to make sure that an instance of our codeunit is bound when a configuration package is being applied and is not bound when it isn’t.
One way to do this is to keep a bound instance of the codeunit as a global variable inside a record variable. As long as the record variable is in scope, so is our flag codeunit. Now the tricky part, we need an event which passes an instance of a record variable (i.e. passes it by var
) which we can store our codeunit in.
The standard Config. Package Management codeunit has an event that we can use, OnBeforeApplyPackageRecords
. This event includes the ConfigPackageRecord variable, passed by var. I’ve extended that table with a RaiseApplyConfigFlag
() method.
tableextension 50500 "Config Package Record" extends "Config. Package Record"
{
var
ApplyConfigFlag: Codeunit "Apply Config. Flag";
internal procedure RaiseApplyConfigFlag()
var
SubscriptionBound: Boolean;
begin
#pragma warning disable AA0206
SubscriptionBound := BindSubscription(ApplyConfigFlag);
#pragma warning restore AA0206
end;
}
This table extension adds our ApplyConfigFlag codeunit as a global variable and binds its subscriptions. This way, as long as this Config. Package Record variable is in scope the Apply Config. Watcher
codeunit will return that we are applying a configuration package.
As soon as that variable is out of scope (when the Config. Package Management codeunit has finished with it or an error has occurred) then Apply Config. Watcher
will return false.
There may be other contexts that you want to handle differently – releasing a sales document, posting a warehouse shipment or some third party process. If there is:
then this may be an option for identifying that context without messing around with single instance codeunits or records to store user state.
Check james’s original post https://jpearson.blog/2023/08/09/flagging-how-your-code-got-called/ on jpearson.blog which was published 2023-08-09 18:40:00