(Q1) Nella generazione del buffer Report quale è la differenza tra un evento di tipo layout e un evento classico?

Durante la generazione del buffer, sia buffer di testata che buffer di riga, spesso sono presenti due eventi gemelli invocati in sequenza.

Ad esempio:

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

La differenza sostanziale tra i due è che il secondo attiva tutta la modalità layout, cioè l’utente nel setup report può liberamente scegliere se eseguire o no questa elaborazione e può inoltre decidere la sequenza di elaborazione tra sezioni simili. Ha quindi una discrezionalità su cosa eseguire e cosa no.

Il primo evento invece è automatico, l’utente non può decidere nulla a meno che il sottoscrittore implementi in autonomia una gui per utente.

La prima modalità deve essere pertanto seguita ogni volta che l’utente debba avere una scelta nella configurazione.

(Q2) Come sono copiati i campi dalle tabelle standard ai buffer?

Il concetto base è che i campi sono copiati in modalità “Transferfields”, cioè a parità di ID campo il valore viene trasferito automaticamente.

A questo si aggiungono alcune eccezioni:

  • I campi disabilitati o obsoleti sono esclusi
  • I campi di identico ID ma tipologia differente sono esclusi
  • Se il campo nel buffer è un flowfield viene escluso
  • Se il campo nella tabella di origine è un flowfield viene calcolato prima di copiarne il contenuto nel buffer.

Questa funzione è pubblica e quindi può essere richiamata dalle estensioni che dichiarano dipendenza dall’estensione ADR.

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

(Q3) Ho campi con ID disallineati, come posso gestirli?

Sono disponibili eventi per gestire i campi di testata e di riga e sono nella classica modalità “Handled”, cioè viene prima invocato il metodo “OnBefore” che permette di sostituirsi alla elaborazione standard” e un “OnAfter” che viene sollevato dopo l’assegnazione dei campi e permette di raffinare dove serve.

Ad esempio:

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)

e il corrispondente evento “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) Popolando manualmente i record della buffer line, quali campi devo valorizzare?

La modalità più appropriata per aggiungere righe al buffer è quella di “Append”, cioè l’accodamento dei record a quelli esistenti.

Il metodo da usare per questa modalità è:

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

Il numero di riga viene popolato automaticamente pertanto, rispetto alla versione C/AL, è necessario passare solo la testata come parametro.

A titolo di esempio questo pezzo di codice è il minimo indispensabile per inserire una riga di tipo commento:

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);

Per inserire una riga con un numero specifico di “Line No.” è meglio usare questo approccio:

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) A cosa serve l’evento “OnApplyFiltersToDocumentLine”?

Il concetto generale dell’Advanced Reporting è che esso è semplicemente un motore senza alcuna cognizione delle tabelle da cui generare il buffer e in seguito la stampa.

Alla funzione di generazione (“EOS Advanced Reporting Mngt”.PrepareBuffer) pertanto arriva il record principale, ad esempio la “Sales Header”, ma internamente non è cablato da alcuna parte che le righe siano necessariamente le “Sales Lines”.

Questo fa sì che l’elaborazione sia estremamente generica ma guidata ed estendibile a qualsiasi tabella, sia standard che custom.

L’evento “OnApplyFiltersToDocumentLine” è già stato sottoscritto per le tabelle supportate nativamente dal modulo e deve essere necessariamente sottoscritto da ogni nuova coppia testata/righe.

/// <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)

Sottoscrivendo questo evento è indispensabile restituire un RecordRef “Lines” aperto e opportunamente filtrato. Nell’esempio precedente le “Sales Lines” sono filtrate in questo modo:

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;

Se non esiste una tabella “righe” per il record in stampa il RecordRef va lasciato non inizializzato ed è indispensabile restituire “Handled” a true. Se nessuno gestisce l’evento e Handled ritorna al chiamante col valore “false” verrà sollevato un errore e l’elaborazione interrotta.

(Q6) Vorrei stampare un ordine di vendita prendendo le righe da un’altra tabella. Come posso fare?

E’ possibile farlo in diverse maniere. La più semplice è agganciarsi all’evento:

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")

Dopodichè eliminare tutto il buffer “RBLine” e riempirlo nuovamente come lo si preferisce:

//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);

E’ bene tenere a mente che qualsiasi elaborazione relative alle righe (come l’aggiunta dei testi estesi, CrossReferences, etc) sarà impossibile da gestire in quanto tutto l’eventuale elaborato andrà perso con la DeleteAll().

Se la testata del documento che vogliamo stampare è standard, come la “Sale Header” ma le righe che vogliamo stampare non sono la “Sales Line” ma una tabella custom, come ad esempio un rielaborato o un raggruppato, possiamo agganciarci a questo evento e sostituirci alla funzione standard che “aggancerebbe” le “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)

Ritornando le “Lines” aperte sulla nostra tabella e filtrata opportunamente. A titolo di esempio le “Sales Lines” vengono aperte in questo modo:

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;

Nota: Questo evento viene sollevato per ogni documento di vendita (ordini, offerte, spedizioni, resi, fatture, note credito, storici, …) pertanto quando il documento in elaborazione NON vi interessa, uscite con una “Exit”.

A questo punto il motore elaborerà la nostra tabella al posto della “Sales Line” standard sollevando ogni evento di preriga, parsing, postriga etc etc.

Per completezza, se lo ritenete necessario, sarebbe meglio agganciarvi agli eventi di gestione “CrossReference”, “Testi Estesi”, “Commenti di riga”, … così da implementare il supporto della vostra tabella custom in quelle section standard.

Ad esempio:

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) Vorrei che la stampa della fattura provvosoria avesse un titolo differente rispetto alla fattura registrata. Come posso fare?

In generale il titolo dei report possono essere personalizzati in due modi.

Il primo è quello di forzare un proprio titolo prima che venga, eventualmente, popolato con quello predefinito dal tipo tabella.

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)

Questo evento viene invocato passando il record testata originale (HeaderRecRef) e la testata buffer (RBHeader) già popolata. Il valore del parametro ReportTitle viene cercato in base a questa sequenza:

  • Traduzione definita sul Setup Report;
  • Titolo definito sul setup Report;
  • Titolo predefinito in base alla tabella in stampa;

Può essere liberamente modificato in questo evento e il valore sarà infine salvato nel campo RBHeader.“EOS Report Title”.

Il secondo metodo è quello di agganciarci all’ultimo evento di preparazione del buffer:

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;

A questo punto il campo RBHeader.“EOS Report Title” è quello che finirà in stampa e quindi possiamo modificarlo a nostro piacimento aggiungendo ad esempio:

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 -