Prepare for the new invoice posting engine!

6 Dec

Introduction

In version 2021 wave 2 (v19) a new feature was introduced, called “Extend general ledger posting aggregations”.

The reason for this new feature is that the table "Invoice Post. Buffer" could not easily be extended. This table is used to buffer the entries that will be created during a posting procedure. Entries are combined (amounts are added up) based on their values in the primary key, which consists of 15 different fields. A number of localizations include modifications to this table, resulting in significant changes. This includes changes to the primary key because of certain requirements to further break down the posting entries that will be created during the posting process.

The table will be replaced by a new table "Invoice Posting Buffer" (note that this table has almost the same name!) that has a different table structure. The primary key is a Text[1000] field that by default is composed of the same fields as before. But the function BuildPrimaryKey now features an event OnAfterBuildPrimaryKey that allows extending the value in the primary key with extra values.

New Interface “Invoice Posting”

Together with the move to a new buffer table, a new interface Invoice Posting has been introduced. The codeunits "Sales-Post", "Purch.-Post" and "Serv-Documents Mgt." have been redesigned to make use of this new interface. The redesign of the code consists of moving pieces of code out of the mentioned codeunits to a new invoice posting codeunit that implements the new interface. The new posting codeunits are "Sales Post Invoice", "Purch. Post Invoice" and "Service Post Invoice".

The new invoice posting codeunits do not just copy over the code from the original posting codeunits. The code is partially redesigned as well to better align with current coding standards or to have a more logical flow. The original code in the posting codeunits has been marked as obsolete and will eventually be removed.

The interface "Invoice Posting" allows Microsoft to create localized versions of the invoice posting engine and it also allows partners to create their own invoice posting engine. It must implement the interface, but internally it may implement a different flow, add additional checks or rules, etc.

Switching to the new invoice posting engine is controlled by Feature Management. Originally, a year ago in v19, this was controlled as a setting in the setup pages of the sales, purchase and service modules. But in v21 this has been replaced by Feature Management. According to the description of the feature, the new posting routines will be enabled by default in v23 (2023 wave 2).

The posting routines contain a check to determine whether the old code (referred to as ‘legacy’) should be used or the new interface is enabled. The legacy code uses the existing table "Invoice Post. Buffer" while the implementations of the interface use the new table "Invoice Posting Buffer".

In this function we can also see that production environments can currently not use the new implementation.

WARNING 1 – No control over switching to the new invoice posting engine

Using a feature switch to enable the new invoice posting engine is quite risky in my opinion. When this feature becomes available for production environments, a customer could enable it before the code is ready. This could lead to unpredictable results and potentially incorrect postings.

The default implementation codeunit is one of the above-mentioned new invoice posting codeunits. These codeunits are configured in an enum for Invoice Posting. Here is enum "Sales Invoice Posting". The enums for Purchase and Service module are similar.

Inside the posting codeunits, code in function GetInvoicepostingSetup controls which implementation codeunit will be used:

With an event subscriber, it would be possible to overwrite the standard implementation codeunit with your own invoice posting codeunit. It is not necessary to use an enumextension for that, just a reference to a codeunit would also work. The enum seems to be a leftover from the original setting in the setup tables before it was moved to Feature Management. In the current implementation, I don’t see any added value for the usage of an enum.

But be careful! Because it is now an event instead of a setting, there can theoretically be multiple subscribers trying to pass their own invoice posting codeunit. That could again lead to unpredictable situations. It’s not very likely for this to happen, but by changing to an event instead of a setting this became an option. One scenario when this may not be so hypothetical is when Microsoft introduces localized versions of the invoice posting codeunits. When that is done with a localization app, then it will most probably be using the same event. Nobody can predict what will happen if an ISV solution is installed that wants to use its own invoice posting codeunit.

In my opinion, it would be better if the choice for Feature Management is reverted back to the original: a setting based on the selected value of an enum. You can argue that it does not belong to a setup page, but at least we will not run into the situation with multiple subscribers to such an important event. What’s more, why is this a feature that end-users should be able to switch on? Usually, an application feature adds something to the user experience. But this is a feature that will be totally hidden for the user, they will not see any difference during the posting or in the resulting entries. If there was a difference, then something is really wrong!

As I said, switching on the feature can lead to unpredictable results. Maybe it’s just a temporary feature, only for the period of 1 year. And when it will be switched on by default, the feature will be removed anyway together with the obsolete code. That will break all code currently using the obsolete object and events, which is exactly what we need because it forces us to move to the new invoice posting engine. But in case the obsolete code is here to stay a little longer, then there will be no break in the code and customers will run into trouble. If that is the plan, then I would recommend Microsoft to introduce a warning before enable this feature. Or maybe do a check on the obsolete event subscribers and verify if there are also event subscribers for the replacements.

Which leads me to events…

WARNING 2 – New home for events

The code that has been copied over to the new invoice posting codeunits also contains a lot of events. As long as the feature is not enabled, the existing events in the posting codeunits for Sales, Purchase and Service will be active. But when the switch has been made to the new invoice posting engine, then any code that depends on events in the posting routines will break.

The new invoice posting codeunits do not directly raise any event. Because an extension may implement an alternative posting invoice engine, overwriting the default posting invoice codeunit, any event subscriber would automatically break. The alternative codeunit could raise similar events, or maybe other events as well.

To mitigate this problem, new codeunits have been introduced as a central home for the events. The new posting invoice codeunits will raise the events from these central codeunits. There are three new codeunits: "Sales Post Invoice Events", "Purch. Post Invoice Events" and "Service Post Invoice Events".

The original events in the posting codeunits have been marked as obsolete with a comment ‘Moved to Sales Invoice Posting implementation’. Example:

The corresponding events can be found in the new codeunits for invoice posting events. The events are raised by calling a Run function. For example:

Results in event OnBeforePrepareLine from codeunit "Sales Post Invoice Events".

In fact, the comment ‘Moved to Sales Invoice Posting implementation’ is not entirely true. Because the code has been redesigned, including new function names, many events have also been renamed. For example, the original function FillInvoicePostingBuffer has been renamed to PrepareLine. Events from this function have also been renamed to reflect the new name of the function. Because of this, you can’t simply find new events by searching with the old name. There is even a renamed event that got the same name as a different event in the original codeunit! So be careful when you rewrite your code to make use of the new events!

Not only events have been renamed, but they can also contain different parameters. Obviously, the new buffer table is used a lot as a parameter. But sometimes other parameters are missing or new parameters have been added.

Prepare for the change

In order to prepare for the change, all code of the event subscribers for events that are subject to move to the new invoice posting engine needs to be rewritten to support the new events. But as long as the new invoice posting engine can’t be enabled in production environments, the old events must still be supported as well. We don’t know yet if there will be a period of time in which both can be used, or if it will be a big bang scenario in 2023 wave 2 (v23). But in sandbox environments, the new invoice posting engine can already be enabled. It’s highly recommended to do this and test it before the switch will be made. Mind you: on-prem environments are also recognized as production environments. Docker containers based on the sandbox image are fine.

The change does not only apply to events, any change to the table Invoice Post. Buffer must also be copied to the new table Invoice Posting Buffer. This includes any event subscriber! The events in the new table Invoice Posting Buffer look very similar, although they do have new names.

Overview of moved events

Here is an overview of the events from the "Sales-Post" codeunit and the corresponding events in the "Sales Post Invoice Events" codeunit. Including comments when the parameters are different (apart from the buffer table). The events in the Purchase and Service routines are similar.

Old eventNew eventComment
Procedure FillInvoicePostingBuffer
OnBeforeFillInvoicePostingBufferOnBeforePrepareLine
OnAfterInvoicePostingBufferAssignAmountsOnAfterInitTotalAmountsSplit into two new events
OnPrepareLineOnAfterAssignAmounts
OnFillInvoicePostingBufferOnAfterCalcInvoiceDiscountPostingOnPrepareLineOnAfterSetInvoiceDiscountPosting
OnBeforeCalcInvoiceDiscountPostingOnPrepareLineOnBeforeCalcInvoiceDiscountPosting
OnFillInvoicePostingBufferOnBeforeSetInvDiscAccountOnPrepareLineOnBeforeSetInvoiceDiscAccount
OnFillInvoicePostingBufferOnAfterSetInvDiscAccountOnPrepareLineOnAfterSetInvoiceDiscAccountParameter Sales Header removed
OnFillInvoicePostingBufferOnAfterCalcLineDiscountPostingOnPrepareLineOnAfterSetLineDiscountPosting
OnBeforeCalcLineDiscountPostingOnPrepareLineOnBeforeCalcLineDiscountPostingNOTE - there is an event in Sales Posting Invoice Events with the same name, but that is a different event!
OnFillInvoicePostingBufferOnBeforeSetLineDiscAccountOnPrepareLineOnBeforeSetLineDiscAccount
OnFillInvoicePostingBufferOnAfterSetLineDiscAccountOnPrepareLineOnAfterSetLineDiscAccount
OnFillInvoicePostingBufferOnBeforeDeferralsOnPrepareLineOnBeforeAdjustTotalAmounts
OnBeforeInvoicePostingBufferSetAmountsOnPrepareLineOnBeforeSetAmountsNew parameter: SalesLineACY: Record “Sales Line”;
New parameter: IsHandled: Boolean, if set to true then InvoicePostingBuffer.SetAmounts will be skipped.
OnAfterInvoicePostingBufferSetAmountsOnPrepareLineOnAfterSetAmounts
OnFillInvoicePostingBufferOnBeforeSetAccountOnPrepareLineOnBeforeSetAccount
OnAfterFillInvoicePostBufferOnPrepareLineOnAfterFillInvoicePostingBufferRemoved paremeter: CommitIsSuppressed
OnFillInvoicePostingBufferOnAfterUpdateInvoicePostBufferOnPrepareLineOnAfterUpdateInvoicePostingBuffer
OnBeforeFillDeferralPostingBufferOnPrepareLineOnBeforePrepareDeferralLineSalesLine not by var;
InvoicePostingBuffer not by var;
global TempInvoicePostBuffer missing;
CommitIsSuppressed renamed to SuppressCommit
OnAfterFillDeferralPostingBufferOnPrepareLineOnAfterPrepareDeferralLineSalesLine not by var;
InvoicePostingBuffer not by var;
global TempInvoicePostBuffer missing;
CommitIsSuppressed renamed to SuppressCommit
Procedure CalcInvoiceDiscountPosting
OnBeforeCalcInvoiceDiscountPostingProcedureOnBeforeCalcInvoiceDiscountPosting
OnAfterCalcInvoiceDiscountPostingNew event in Sales Post Invoice Events
Procedure CalcLineDiscountPosting
OnCalcLineDiscountPostingProcedureOnBeforeCalcLineDiscountPostingNOTE an event with the same name did exist in Sales-Post in procedure FillInvoicePostingBuffer, but this is a different event!
OnAfterCalcLineDiscountPostingNew event in Sales Post Invoice Events
Procedure GetSalesAccount
OnBeforeGetSalesAccountOnBeforeGetSalesAccount
OnAfterGetSalesAccountOnAfterGetSalesAccount
Procedure CreatePostedDeferralScheduleFromSalesDoc
OnAfterCreatePostedDeferralScheduleFromSalesDocOnAfterCreatePostedDeferralSchedule
Procedure PostInvoicePostBuffer
OnBeforePostInvoicePostBufferOnBeforePostLinesParameters removed: TotalSalesLine, TotalSalesLineLCY
OnPostInvoicePostBufferOnAfterPostSalesGLAccountsNO REPLACEMENTObsolete warning says it has been moved, but a new event is missing in the corresponding code in PostLines in codeunit Sales Post Invoice
OnPostInvoicePostBufferOnBeforeTempInvoicePostBufferDeleteAllOnPostLinesOnBeforeTempInvoicePostingBufferDeleteAllParameters GenJnlLineDocType, GenJnlLineDocNo, GenJnlLineExtDocNo, SrcCode moved to new parameter InvoicePostingParameters
Procedure PostInvoicePostBufferLine
OnPostInvoicePostBufferLineOnAfterCopyFromInvoicePostBufferOnPrepareGenJnlLineOnAfterCopyToGenJnlLineEvent has been moved to a few lines later which seems to be a better position. Possible alternatives: OnAfterCopyGenJnlLineFromSalesHeader in table “Gen. Journal Line”, OnAfterCopyToGenJnlLine in table “Invoice Posting Buffer”
OnBeforePostInvPostBufferOnPostLinesOnBeforeGenJnlLinePostRemoved var from paramter SalesHeader
CommitIsSuppressed renamed to SuppressCommit
OnAfterPostInvPostBufferOnPostLinesOnAfterGenJnlLinePostSalesHeader not by var
Parameters removed: SalesLine, GenJnlLineDocNo, GenJnlLineExtDocNo, GenJnlLineDocType
Parameter added: PreviewMode added
Parameter CommitIsSuppressed renamed SuppressCommit
Procedure InitNewLineFromInvoicePostBuffer
OnBeforeInitNewLineFromInvoicePostBufferOnBeforeInitGenJnlLine
Procedure RunGenJnlPostLine
OnBeforeRunGenJnlPostLineOnBeforeRunGenJnlPostLineParameter removed: SalesInvHeader
Parameter added: GenJnlPostLine: Codeunit “Gen. Jnl.-Post Line”
Procedure PostCustomerEntry
OnBeforeRunPostCustomerEntryOnBeforePostLedgerEntryParameters DocType, DocNo, ExtDocNo, SourceCode moved to new parameter InvoicePostingParameters
Parameter CommitIsSuppressed renamed to SuppressCommit
OnBeforePostCustomerEntryOnPostLedgerEntryOnBeforeGenJnlPostLineNew parameter: PreviewMode (Boolean)
Parameter CommitIsSuppressed renamed to SuppressCommit
OnAfterPostCustomerEntryOnPostLedgerEntryOnAfterGenJnlPostLineNew parameter: PreviewMode (Boolean)
Parameter CommitIsSuppressed renamed to SuppressCommit
Procedure PostBalancingEntry
OnPostBalancingEntryOnBeforeFindCustLedgEntryOnPostBalancingEntryOnBeforeFindCustLedgEntryParameters DocType, DocNo, ExtDocNo, SourceCode moved to new parameter InvoicePostingParameters
OnPostBalancingEntryOnAfterFindCustLedgEntryOnPostBalancingEntryOnAfterFindCustLedgEntry
OnPostBalancingEntryOnAfterInitNewLineNO REPLACEMENTObsolete warning says it has been moved, but a new event is missing in the corresponding code in PostLines in codeunit Sales Post Invoice
OnBeforePostBalancingEntryOnPostBalancingEntryOnBeforeGenJnlPostLineAdded var to parameter SalesHeader
New parameter: PreviewMode: Boolean
New parameter: GenJnlPostLine: Codeunit “Gen. Jnl.-Post Line”
Parameter CommitIsSuppressed renamed to SuppressCommit
OnAfterPostBalancingEntryOnPostBalancingEntryOnAfterGenJnlPostLineNew parameter: PreviewMode (Boolean)
Parameter CommitIsSuppressed renamed to SuppressCommit
Procedure SetAmountsForBalancingEntry
OnBeforeSetAmountsForBalancingEntryOnBeforeSetAmountsForBalancingEntry
Procedure SetApplyToDocNo
OnAfterSetApplyToDocNoOnAfterSetApplyToDocNo
Procedure CalcDeferralAmounts
OnBeforeTempDeferralLineInsertOnBeforeTempDeferralLineInsert
Other events
OnInsertInvoiceHeaderOnBeforeSetPaymentInstructionsNO REPLACEMENTEvent is not in use since v19

I hope this was useful. It was quite some work to compose the list of old and new events. In case I missed something, please let me know in the comments or contact me directly. Good luck with preparing for this breaking change!

4 thoughts on “Prepare for the new invoice posting engine!

  1. Your post was sadly a week to late.
    Last week I created a pull request for the Base App to improve the obsoletion comments for the Sales Post Codeunit.
    And it is sometimes really hard to find the correct event and there are even missing event replacements or parameters.

    • Too bad! I started working on it about a week ago. Agreed, it was quite hard to find all the correct events.
      I need to verify if we found the same events. 🙂

  2. Pingback: New Interface “Invoice Posting” Engine – in Business Central - Dynamics 365 Business Central Community

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.