microsoft / ALAppExtensions

Repository for collaboration on Microsoft AL application add-on and localization extensions for Microsoft Dynamics 365 Business Central.
MIT License
786 stars 621 forks source link

IncludeSender in Format Address subscriber and add a GetLanguageCode() procedure. #26293

Open deerware opened 7 months ago

deerware commented 7 months ago

Describe the request

The codeunit 365 "Format Address" has publishers in all of its functions like Company, Customer, etc, which allow global customization of how the addresses are structured.

Unfortunately, these events do not contain the Language Code property, which is a problem, because the codeunit is not using the global language, but the language usually set on the record (when calling from a report). So when the function is bypassed (using IsHandled = true), there is no way how to translate the country or other information correctly using the currently set LanguageCode.

I suggest either:

  1. Add LanguageCode to the publishers, or
  2. Change publishers to IncludeSender = true and add a GetLanguageCode global procedure
    • This way, in subscribers we could just use sender.FormatAddr(), since that is already global, and not worry about the Language Code, in cases where we only change the information that is sent, not its order.

Additional context

In our customizations, we sometimes change the language code on factors other then the Language Code of the printed document, like manually changing the language from the request page. So we can't rely on the Language Code of the document that is sent to the function.

In some cases (like Company), there even is no Language Code. Adding the country to the company address is a frequent request of our customers and we currently can't simply add it using a single subscriber, because of the language code. Internal work item: AB#525470

Groenbech96 commented 1 month ago

Can you be more specific with example of events you are referring to? And how they should be changed?

deerware commented 1 month ago

Hi, @Groenbech96

For example in the report 208 "Sales - Shipment" there is the following code:

// dataitem("Sales Shipment Header"; "Sales Shipment Header") 
trigger OnAfterGetRecord()
begin
    CurrReport.Language := LanguageMgt.GetLanguageIdOrDefault("Language Code");
    CurrReport.FormatRegion := LanguageMgt.GetFormatRegionOrDefault("Format Region");
    FormatAddr.SetLanguageCode("Language Code"); // Language Code is passed into the Format Address Codeunit

    FormatAddressFields("Sales Shipment Header");
    FormatDocumentFields("Sales Shipment Header");

    if not CompanyBankAccount.Get("Sales Shipment Header"."Company Bank Account Code") then
        CompanyBankAccount.CopyBankFieldsFromCompanyInfo(CompanyInfo);

    DimSetEntry1.SetRange("Dimension Set ID", "Dimension Set ID");
end;

local procedure FormatAddressFields(SalesShipmentHeader: Record "Sales Shipment Header")
begin
    FormatAddr.GetCompanyAddr(SalesShipmentHeader."Responsibility Center", RespCenter, CompanyInfo, CompanyAddr);
    FormatAddr.SalesShptShipTo(ShipToAddr, SalesShipmentHeader);
    ShowCustAddr := FormatAddr.SalesShptBillTo(CustAddr, ShipToAddr, SalesShipmentHeader);
end;

The procedure FormatAddr.SalesShiptShipTo in codeunit 365 "Format Address" procedure contains a publisher with IsHandled, which we wanted to use to generate our own address array, using our custom fields, unfortunately, the global LanguageCode is not included in the publisher arguments, so we didn't know in which Language we should show for example the Country translations.

The procedure and publisher look like this:

procedure SalesShptShipTo(var AddrArray: array[8] of Text[100]; var SalesShptHeader: Record "Sales Shipment Header")
var
    Handled: Boolean;
begin
    OnBeforeSalesShptShipTo(AddrArray, SalesShptHeader, Handled); // Missing LanguageCode
    if Handled then
        exit;

    FormatAddr(
        AddrArray, SalesShptHeader."Ship-to Name", SalesShptHeader."Ship-to Name 2", SalesShptHeader."Ship-to Contact", SalesShptHeader."Ship-to Address", SalesShptHeader."Ship-to Address 2",
        SalesShptHeader."Ship-to City", SalesShptHeader."Ship-to Post Code", SalesShptHeader."Ship-to County", SalesShptHeader."Ship-to Country/Region Code");
end;

[IntegrationEvent(false, false)]
local procedure OnBeforeSalesShptShipTo(var AddrArray: array[8] of Text[100]; var SalesShipmentHeader: Record "Sales Shipment Header"; var Handled: Boolean)
begin
end;

This could be, in my opinion, fixed by either adding the global LanguageCode to individual publishers, or by adding a global GetLanguageCode() and enableing IncludeSender for the publishers. With the IncludeSender we could also just call the sender.FormatAddr() procedure with our custom fields in the subscriber and not worry about LanguageCode, since it is already correctly set in the sender.

Let me know if you need any more details.

Milan

deerware commented 1 month ago

PS: As for why we can't just use the Language Code from the document - please refer to Additional Context of my original submission.

Groenbech96 commented 1 month ago

Is there a way for you to use existing OnAfterFormatAddress?

deerware commented 1 month ago

The OnAfterFormatAddress does not contain any information about the record that we are formatting. In out case, when formatting Company, we needed to replace some baseapp fields with custom ones, plus add the baseapp Country, which we cannot do in the OnAfterFormatAddress, where the record is already deconstructed. We need to work with the record.

It's also called after the code so to avoid duplicitous calling of all the formatting functions, OnFormatAddrOnAfterGetCountry would be better, but it still doesn't work for us, since is too deep. We only want to alter some specific documents, not all formatting, and need to substitute the fields sent into the functions, not their order or something.

For example (this is not our case) if we had custom fields in the Company Information just for printing alternative company address for foreign customers, because the normal local address normally contains some weird characters with accents or whatever, and wanted to hijack the Company(CompanyInformation: Record ...) procedure and replace the Name, Address, City, .... with our own fields, we can't do that in the OnAfterFormatAddress subscriber (or any other subscriber in the FormatAddr procedure), because we don't have the original record anymore, and we also don't know from where the FormatAddr was called, and don't want to replace the City in all addresses (customers, ship to, sell to, ...), just in the Company one.

The only way to do it correctly is to hijack the Company and then ideally call the FormatAddr() with our own deconstruction of the record, but now we can't retain the LanguageCode with that approach.