(Q1) What is the difference between a layout event and a classic event in Report buffer generation?

When generating the buffer, both header buffers and row buffers, there are often two twin events invoked sequentially.

For example,:

OnAfterLinesProcessing(HeaderRecRef, RBHeader, RBLine);
AdvRptLayoutMngt.ExecuteBodyFooterProcessing(DocVariant, RBHeader, RBLine);

The fundamental difference between the two is that the second activates the entire layout mode, that is, the user in the report setup can freely choose whether or not to perform this processing and can also decide the processing sequence between similar sections. He therefore has discretion over what to perform and what not to perform.

The first event is automatic, the user cannot decide anything unless the subscriber implements a gui per user on their own.

Therefore, the first mode must be followed whenever the user needs to have a choice in the configuration.

(Q2) How are fields copied from standard tables to buffers?

The basic concept is that the fields are copied in “Transferfields” mode, that is, with the same field ID the value is transferred automatically.

In addition, there are a few exceptions:

  • Disabled or obsolete fields are excluded
  • Fields of the same ID but different type are excluded
  • If the field in the buffer is a flowfield it is excluded
  • If the field in the source table is a flowfield it is calculated before copying its contents to the buffer.

This function is public and can then be invoked by extensions that declare dependency on the ADR extension.

codeunit 18122020 "EOS AdvRpt Routines"
procedure TryTransferFields(Source: Variant; var TargetRecRef: RecordRef)

(Q3) I have fields with misaligned IDs, how can I handle them?

Events are available to handle the header and row fields and are in the classic “Handled” mode, that is, the “OnBefore” method is first invoked, which replaces standard processing, and an “OnAfter” method that is raised after assigning fields and allows you to refine where you need them.

For example:

codeunit 18122007 "EOS Advanced Reporting Mngt"
/// <summary>This event is raised before the default header parsing (transferfields).
/// You can replace the default parsing to support tables that are too different from the buffer header. </summary>
/// <param name="HeaderRecRef">Source document reference</param>
/// <param name="ReportHeader">Current report header buffer</param>
/// <param name="Handled">Returning "true" default engine transferfields will be skipped.</param>
[IntegrationEvent(false, false)]
local procedure OnBeforeHeaderParsing(HeaderRecRef: RecordRef;
                                        var ReportHeader: Record "EOS Report Buffer Header";
                                        var Handled: Boolean)

and the corresponding event “OnAfter”

/// <summary>This event is raised after the default/custom header parsing.</summary>
/// <param name="HeaderRecRef">Source document reference</param>
/// <param name="ReportHeader">Current report header buffer</param>
[IntegrationEvent(false, false)]
local procedure OnAfterHeaderParsing(HeaderRecRef: RecordRef;
                                        var ReportHeader: Record "EOS Report Buffer Header")

(Q4) By manually populating buffer line records, which fields should I value?

The most appropriate way to add rows to the buffer is to append records to existing rows.

The method to use for this mode is:

table 18122009 "EOS Report Buffer Line"
procedure Appendline(RefRBHeader: Record "EOS Report Buffer Header")

The row number is automatically populated, so compared to the C/AL version, you only need to pass the header as a parameter.

As an example, this piece of code is the bare minimum for inserting a comment line:

RBLine2.Init();
RBLine2."EOS Line No." := 0;
RBLine2."EOS Extension Code" := ExtensionCode;
RBLine2."EOS Extension Guid" := ExtensionGuid;
RBLine2."EOC Description" := 'My Custom Lçine Description';
RBLine2."EOS Line type" := RBLine2."EOS Line type"::EOSLineComment;
RBLine2.Appendline(RBHeader);

To insert a line with a specific number of “Line No.” it is best to use this approach:

RBLine2.Init();
RBLine2."EOS Line No." := 1000;
RBLine2."EOS Extension Code" := ExtensionCode;
RBLine2."EOS Extension Guid" := ExtensionGuid;
RBLine2."EOC Description" := 'My Custom Lçine Description';
RBLine2."EOS Line type" := RBLine2."EOS Line type"::EOSLineComment;
RBLine2.PopulateLineFields(RBHeader);
RBLine2.Insert();

(Q5) What is the “OnApplyFiltersToDocumentLine” event for?

The general concept of Advanced Reporting is that it is simply an engine without any knowledge of the tables from which to generate the buffer and then printing.

To the build function (“EOS Advanced Reporting Mngt”. PrepareBuffer) therefore comes the main record, such as the “Sales Header”, but internally it is not wired anywhere that the lines are necessarily the “Sales Lines”.

This causes the processing to be extremely generic but driven and extensible to any table, both standard and custom.

The “OnApplyFiltersToDocumentLine” event has already been subscribed to the tables natively supported by the module and must be subscribed by each new header/row pair.

/// <summary>We must sort/filters all lines according to the header, but we don't know the header/lines relationship so we ask to subscribers to do it for us</summary>
/// <param name="HeaderRecRef">Source document reference</param>
/// <param name="ReportHeader">Current report header buffer</param>
/// <param name="Lines">Current report line buffer</param>
/// <param name="Handled">If after the execution of all the Handled subscribers is false an error is raised.</param>
[IntegrationEvent(false, false)]
local procedure OnApplyFiltersToDocumentLine(HeaderRecRef: RecordRef;
                                                var ReportHeader: Record "EOS Report Buffer Header";
                                                var Lines: RecordRef;
                                                var Handled: Boolean)

By subscribing to this event, it is imperative to return an open and appropriately filtered RecordRef. In the example above, the “Sales Lines” are filtered like this:

case HeaderRecRef.Number() of
    database::"Sales Header":
        begin
            Lines.Open(database::"Sales Line", false, HeaderRecRef.CurrentCompany());
            Lines.field(SalesLine.FieldNo("Document Type")).SetRange(HeaderRecRef.field(SalesHeader.FieldNo("Document Type")).Value());
            Lines.field(SalesLine.FieldNo("Document No.")).SetRange(HeaderRecRef.field(SalesHeader.FieldNo("No.")).Value());
            Lines.field(SalesLine.FieldNo("Attached to Line No.")).SetRange(0);
            Handled := true;
        end;

If there is no “rows” table for the record printed, the RecordRef should be left uninitialized, and you must return ““Handled” to true. If no one handles the event and Handled returns to the caller with the value “false” an error will be raised and processing aborted.

(Q6) I would like to print a sales order by taking the rows from another table. How can I do that?

It is possible to do this in different ways. The simplest is to hook up to the event:

codeunit 18122021 "EOS AdvRpt Layout Mngt"

[IntegrationEvent(false, false)]
local procedure OnExecuteBodyFooterProcessing(ExtensionGuid: Guid; ExtensionCode: Code[20]; DocVariant: Variant; var RBHeader: Record "EOS Report Buffer Header"; var RBLine: Record "EOS Report Buffer Line")

Then delete all the buffer “RBLine” and fill it again as you prefer:

//RBLine is temporary so we can do whatever we want
RBLine.Reset();
RBLine.SetFilter("EOS Line type", '<>%1', "EOS Line type"::EOSHeaderComment); //Skip Header Line comments
RBLine.DeleteAll();
RBLine.Reset();

RBLine.Init();
RBLine."EOS Entry ID" := RBHeader."EOS Entry ID";
RBLine."EOS Source Table ID" := Database::"Sales Line";
RBLine."EOS Line type" := RBLine."EOS Line type"::EOSDocumentLine;

//it's not necessary to manually handle "Line No." because "AppendLine" method always adds lines at the and of current dataset.
RBLine."EOS Description" := 'This is a replacement line 1';
RBLine.Appendline(RBHeader);

It is good to keep in mind that any processing related to the lines (such as the addition of extended texts, CrossReferences, etc.) will be impossible to handle as any possible processing will be lost with the DeleteAll().

If the header of the document we want to print is standard, such as the “Sale Header” but the rows we want to print are not the “Sales Line” but a custom table, such as a reworked or a grouped, we can hook up to this event and replace the standard function that would “aggregate” the “Sales Lines”.

codeunit 18122009 "EOS AdvRpt Std Sales Ext"
local procedure OnBeforeApplyFiltersToDocumentLine(HeaderRecRef: RecordRef;
                                                        var ReportHeader: Record "EOS Report Buffer Header";
                                                        var Lines: RecordRef;
                                                        var Handled: Boolean)

Returning the “Lines” opened on our table and filtered appropriately. As an example, the “Sales Lines” are opened as follows:

case HeaderRecRef.Number() of
    database::"Sales Header":
        begin
            Lines.Open(database::"Sales Line", false, HeaderRecRef.CurrentCompany());
            Lines.field(SalesLine.FieldNo("Document Type")).SetRange(HeaderRecRef.field(SalesHeader.FieldNo("Document Type")).Value());
            Lines.field(SalesLine.FieldNo("Document No.")).SetRange(HeaderRecRef.field(SalesHeader.FieldNo("No.")).Value());
            Lines.field(SalesLine.FieldNo("Attached to Line No.")).SetRange(0);
            Handled := true;
        end;

Note: This event is raised for each sales document (orders, quotes, shipments, returns, invoices, credit memos, …) so when the document being processed is NOT of interest to you, exit with an “Exit”.

At this point the engine will process our table in place of the standard “Sales Line” raising each event of preline, parsing, postriga etc.

For completeness, if you think it’s necessary, it’d be better to hook up to the “CrossReference” management events, “Extended Texts”, “Line Comments”, … so that you can implement the support of your custom table in those standard sections.

For example:

codeunit 18122008 "EOS AdvRpt Std Layout Ext"
/// <summary>This event is raised before standard Line Comments routines. Is useful for adding custom logic/tables without adding a new report layout section</summary>
/// <param name="DocVariant">Source Document Header </param>
/// <param name="LineRecRef">Current Line</param>
/// <param name="RBHeader">Current Report Header</param>
/// <param name="RBLine">Current Report Line Buffer. Add new line comments to this buffer</param>
/// <param name="Handled">Return true to avoid standard logic</param>
[IntegrationEvent(false, false)]
local procedure OnBeforeComments(DocVariant: Variant;
                                var LineRecRef: RecordRef;
                                var RBHeader: Record "EOS Report Buffer Header";
                                var RBLine: Record "EOS Report Buffer Line";
                                var Handled: Boolean)

(Q7) I would like the printing of the provisional invoice to have a different title than the posted invoice. How can I do that?

In general, the title of reports can be customized in two ways.

The first is to force its own title before it is, if necessary, populated with the default title by the table type.

table 18122008 "EOS Report Buffer Header"

[IntegrationEvent(false, false)]
    local procedure OnUpdateDefaultReportTitle(var HeaderRecRef: RecordRef; var RBHeader: Record "EOS Report Buffer Header"; var ReportTitle: Text)

This event is invoked by passing in the original header record (HeaderRecRef) and the buffer header (RBHeader) already populated. The value of the ReportTitle parameter is searched for this sequence:

  • Translation defined on Report Setup;
  • Title defined on the Report setup;
  • Default title based on the printed table;

It can be freely edited in this event, and the value will eventually be saved in the RBHeader field.

The second method is to hook us to the last buffer preparation event:

codeunit 18122007 "EOS Advanced Reporting Mngt"
[IntegrationEvent(false, false)]
local procedure OnAfterDocumentProcessing(HeaderRecRef: RecordRef;
                                        var RBHeader: Record "EOS Report Buffer Header";
                                        var RBLine: Record "EOS Report Buffer Line");
begin
end;

At this point, the RBHeader field is the one that will end up in print, so we can edit it to our liking by adding, for example, At this point, the RBHeader field is the one that will end up in print, so we can edit it to our liking by adding, for example:

if RBHeader."EOS Report ID" = Report::"EOS Invoice Document" then
    if RBHeader."EOS Source Table ID" = database::"Sales Header" then
        RBHeader."EOS Report Title" += ' *** TEST ***';

EOS Labs -