There is a new Posting Preview Type in Business Central. See how that works!

With BC v19 one of the Application changes affects the Posting Preview functionality.

Read more here.

The new Posting Preview feature can be enabled in the General Ledger Setup:

The way the posting preview worked until now is covered by Posting Preview Type = Standard.

So, if you don’t like the new Posting Preview (Extended) you can always use the previous one.

But let’s recall how the original Posting Preview looks like.

First, Search for General Ledger Setup and set the Posting Preview Type to Standard:

Open a sales order and choose Preview Posting; the image below shows only one group of ledgers, the Related Entries group:

Let’s now head to the General Ledger Setup and set the Posting Preview Type to Extended:

Then re-open the Sales Order and click on Post – > Preview Posting:

Notes:

  • we can see now 3 groups:
    • G/L Entries -> this is the place where will find the G/L Entries
    • VAT Entries -> records from VAT Entry table
    • Related Entries -> all the rest of the ledgers, including extension or custom entries
  • Show Hierarchical View is a toggle on how G/L entries and VAT entries in the posting preview weather grouped by Account No.(if Hierarchical View is on) or as a list (if Hierarchical View is off).

And if we want to view the details we can use the toggle in the upper right corner of the group to expand or collapse the groups:

Of course, the new Posting Preview on journals looks and feels similar to the documents’ Posting Preview.

Hope this helps!

How checking financial journal in background works

With BC 2020 wave 2 a new feature was introduced that allows for background checks on journal lines.

See more about this new feature here.

“On the General Journal Batch page, you can choose Background Error Check to have Business Central validate financial journals, such as general or payment journals, while you’re working on them. When the validation is enabled, the Journal Check FactBox displays next to the journal lines and will show issues in the current line and the whole batch. Validation happens when you load a financial journal batch, and when you choose another journal line”.

Let’s see how that works.

From the General Journal page, lookup into Gen. Journal Batches:

Enable “Background Error Check”.

In the Default general journal batch we can see now a new factbox : “Journal Check”:

We can observe that while we edit the journal the background check takes place.

If we click on the 3rd cue, “Issues Total”, we can see the errors:

We see that the Amount on the first line is 0.

Let’s update it to “12”:

We can now see that the error “Amount must not be empty” is gone, but we still have the error: “Document No. … is out of balance”.

Let’s update one of the lines so that the sum of all lines is 0.

After we update the Amount on the first line with -11 the errors are gone:

How is this checking in the background working?

With BC 2019 wave 2 release introduces a way for AL developers to start programming using multithreading/asynchronous concepts. 

Developers can now calculate expensive operations in a page without blocking the UI, and then update the UI once the calculation is complete.

Read Microsoft document on background tasks here.

If we were to look at page 1921 “Journal Errors Factbox”, in the OnAfterGetCurrRecord()

The method CheckErrorsInBackground() contains a line that enqueues the codeunit responsible with the general journal lines checking:

The check is done in the codeunit 9081 “Check Gen. Jnl. Line. Backgr.” in the OnRun() method:

Digging deeper, ultimately the standard codeunit 11 “Gen. Jnl.-Check Line” is run for each line.

For each journal line, errors are collected and made available for Counts to the factbox:

For example, for the second cue, “Lines with Issues” the system uses the factbox method GetNumberOfLinesWithErrors();

What about custom validations?

How can we catch those?

In a table extension I added a text field:

Exposed it on the page via a page extension:

At last, in a codeunit, I subscribed to an event from codeunit 11 “Gen. Jnl.-Check Line”

And, if we remove the value of “My test field” in one or more of the lines we can see the TestField error captured by the background task:

For more details, including the implementation of a completely new page background task check Tobias Fenster article.

Leveraging “Filter Tokens” codeunit to expand Business Central users’ filtering experience

Hello Readers!

A few weeks back, I watched Erik Hougaard‘s youtube video “Make your own Date Filters in AL and Business Central” and thought of trying it and adding my own bit to it.

First, what was the intention with custom filter tokens?

The standard application comes already with some tokens.

Think of dates, when you press “t” in a date field you get the today’s date, or when you press “q” in a date filter field you get the current quarter, and so on.

But what if we want to build our own tokens?

Custom Date Filters

For example, let’s assume that if I type “sv1” in a date filter I want the system to process my token into Jan 1st – Jan 31st. If I type “sv2” in a date filter I want the system to translate “sv2” into Feb 1st to Feb 29th or 28th depending on the current year, leap or not, and so on.

How can we do that? Extend the event OnResolveDateFilterToken from System Application codeunit “Filter Tokens” like in my sample code below:

[EventSubscriber(ObjectType::Codeunit, Codeunit::"Filter Tokens", 'OnResolveDateFilterToken', '', false, false)]
    local procedure CustomDateFilter(DateToken: Text; var FromDate: Date; var Handled: Boolean; var ToDate: Date)
    begin
        DateToken := UpperCase(DateToken);
        case DateToken of
            'SV1':
                begin
                    FromDate := GetFromDate(Today(), 1);
                    ToDate := GetToDate(Today(), 1);
                    Handled := true;
                end;
            'SV2':
                begin
                    FromDate := GetFromDate(Today(), 2);
                    ToDate := GetToDate(Today(), 2);
                    Handled := true;
                end;
            'SV3':
                begin
                    FromDate := GetFromDate(Today(), 3);
                    ToDate := GetToDate(Today(), 3);
                    Handled := true;
                end;
            'SV4':
                begin
                    FromDate := GetFromDate(Today(), 4);
                    ToDate := GetToDate(Today(), 4);
                    Handled := true;
                end;
            'SV5':
                begin
                    FromDate := GetFromDate(Today(), 5);
                    ToDate := GetToDate(Today(), 5);
                    Handled := true;
                end;
            'SV6':
                begin
                    FromDate := GetFromDate(Today(), 6);
                    ToDate := GetToDate(Today(), 6);
                    Handled := true;
                end;
            'SV7':
                begin
                    FromDate := GetFromDate(Today(), 7);
                    ToDate := GetToDate(Today(), 7);
                    Handled := true;
                end;
            'SV8':
                begin
                    FromDate := GetFromDate(Today(), 8);
                    ToDate := GetToDate(Today(), 8);
                    Handled := true;
                end;

            'SV9':
                begin
                    FromDate := GetFromDate(Today(), 9);
                    ToDate := GetToDate(Today(), 9);
                    Handled := true;
                end;
            'SV10':
                begin
                    FromDate := GetFromDate(Today(), 10);
                    ToDate := GetToDate(Today(), 10);
                    Handled := true;
                end;
            'SV11':
                begin
                    FromDate := GetFromDate(Today(), 11);
                    ToDate := GetToDate(Today(), 11);
                    Handled := true;
                end;
            'SV12':
                begin
                    FromDate := GetFromDate(Today(), 12);
                    ToDate := GetToDate(Today(), 12);
                    Handled := true;
                end;
        end;
    end;

    local procedure GetFromDate(Dt: Date; mo: integer): Date
    begin
        Exit(DMY2Date(1, mo, Date2DMY(Dt, 3)));
    end;

    local procedure GetToDate(Dt: Date; mo: integer): Date
    begin
        case mo of
            1, 3, 5, 7, 8, 10, 12:
                Exit(DMY2Date(31, mo, Date2DMY(Dt, 3)));
            4, 6, 9, 11:
                Exit(DMY2Date(30, mo, Date2DMY(Dt, 3)));
            2:
                begin
                    if Date2DMY(Dt, 3) div 4 = 0 then
                        Exit(DMY2Date(29, mo, Date2DMY(Dt, 3)))
                    else
                        Exit(DMY2Date(28, mo, Date2DMY(Dt, 3)))
                end;
        end

The code could be refactored into a function that parses a 4 characters token of form “svxy” and call once GetToDate and once GetFromDate instead of 12 calls, but that’s not the goal of this blog.

Let’s test it.

Open Chart of Accounts page and use the flow filters in the “Filter Totals By” section of the page as below:

What about text filters? Can we customize them?

Custom Text Filters

This is the use case: each user has access to his list of customers (My Customer page):

Users can edit their own list of customers, adding/removing customers.

We also want, when we are in the Customers list, to be able to quickly filter the list of customers to the list in My Customers.

We can create a custom text filter token and by subscribing to event OnResolveTextFilterToken in codeunit “Filter Tokens” we get the functionality desired, like below:

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Filter Tokens", 'OnResolveTextFilterToken', '', true, true)]
    local procedure CustomTextFilter(TextToken: Text; var TextFilter: Text; var Handled: Boolean)
    var
        _mc: Record "My Customer";
        _maxloops: integer;
    begin
        _maxloops := 10;
        TextToken := UpperCase(TextToken);
        Handled := true;
        case TextToken of
            'SV':
                begin
                    _mc.SetRange("User ID", UserId());
                    if _mc.FindSet() then begin
                        _maxloops -= 1;
                        _maxloops -= 1;
                        TextFilter := _mc."Customer No.";
                        if _mc.Next() <> 0 then
                            repeat
                                _maxloops -= 1;
                                TextFilter += '|' + _mc."Customer No.";
                            until (_mc.Next() = 0) or (_maxloops <= 0);
                    end
                end;
        end;
    end;

In the Customers List we can now use the new token:

When users filter the “No.” field to “%sv” the system finds all Customer “No.” in My Customer list and populates the filter for “No.” field.

My Customer list consists of customers 20000,30000, and 50000 and therefore when using custom text filter “sv” I get the list of my customers.

You could similarly create a custom token to filter Chart of Accounts to G/L accounts in “My Accounts”.

Things to consider

The token above “sv” would be triggered and parsed in any page.

For example, if we are in the Vendors list the same list (20000,30000 and 50000) will be the result of parsing “sv” token. And that might not be what we need.

A possible solution is to specialize the custom filters to customers or to vendors, like having 2 tokens: “csv” for customers and “vsv” for vendors.

For more considerations when using custom tokens read here.

Go on, try them!