Sample Report over Codeunit
Codeunit 5138176 "SIM_DPS Sample Report over CU" demonstrates how the Business Portals action type Report over Codeunit can select a record through custom logic and return a Business Central report as a PDF download.
The sample uses SIM_DPS OL as its source table. It resolves a record, applies the configured report request-page settings, renders the configured report into SIM_DPS OL."File Blob", and lets Business Portals return that file to the frontend.
When to use Report over Codeunit
A standard Report action runs a report for the dataset record. Use Report over Codeunit when custom AL logic must decide which record is passed to the report. For example, an action can print a sales order selected in an input field instead of the record displayed by the current dataset table.
This sample provides both approaches:
| Function | Record source |
|---|---|
PrintOrderConfirmation |
Reads a record ID from the SALESORDER action input field. |
PrintOrderConfirmationFromTable |
Uses SIM_DPS OL."Dataset Table Record", which represents the current dataset record. |
Function discovery and dispatch
Business Portals runs the codeunit without an action code when it needs the list of available functions. OnRun therefore defaults to GETFUNCTIONS and returns both function names as a comma-separated list.
During report execution, OnRun loads the dataset-table action and its related action setup. The action's Param value selects the procedure to execute.
Resolving the report record
From an input field
PrintOrderConfirmation reads the value of the input field with code SALESORDER and evaluates it as a Business Central RecordId.
The input value must be a formatted RecordId, not only a sales order number such as SO-1001. A missing or invalid value causes Evaluate to raise an error. When adapting the sample, validate the value and provide a user-friendly error message before calling PrintReport.
From the dataset table
PrintOrderConfirmationFromTable passes Dataset Table Record directly to PrintReport. Use this function when the report's main record is the same record represented by the dataset row.
Creating the PDF
The shared PrintReport procedure performs the following steps:
- Opens the supplied record ID as a
RecordRef. - Uses
SIM_DPS Order Function Library.FilterRecRefByKeyto create a record reference filtered by the record's primary key. - Loads the
SIM_DPS Actionidentified byAction Code. - Reads the optional request-page XML from
Report Settings. - Clears the existing
File Bloband creates an output stream for it. - Calls
Report.SaveAswith the configuredReport ID, PDF format, request-page settings, and filtered record.
After the codeunit finishes, Business Portals reads File Blob, converts the PDF to Base64, and returns it to the frontend as the action's downloadable file. The dataset-table action controls the download filename and can use configured filename placeholders.
Configuration
To configure this sample pattern:
- Open the actions for the required dataset table.
- Create an action and set Type to Report over Codeunit.
- Set ID to codeunit
5138176or to the ID of the customer-specific copy. - Select
PrintOrderConfirmationorPrintOrderConfirmationFromTablein Param. - Select the Business Central report in Report Caption or set its Report ID.
- Optionally use Set Report Settings to save filters and request-page options for the report.
- Configure the report filename and any filename placeholders on the dataset-table action.
- When using
PrintOrderConfirmation, configure an action input field with codeSALESORDERwhose value resolves to a valid Business CentralRecordId.
The selected report must support the table represented by the resolved record and must be suitable for unattended Report.SaveAs execution.
Implementation considerations
Before using this code in a production extension, consider the following points:
- Keep
TableNo = "SIM_DPS OL"; Business Portals supplies the action context throughRec. - Add each selectable function to both the
GETFUNCTIONSlist and the dispatchcasestatement. - Validate input-field values before converting them to
RecordId. - Confirm that
Action Code,Report ID, and the resolved record are present and valid. - The sample retries the same
Report.SaveAscall up to three times. If all attempts fail, it exits without a dedicated error. Replace this with error handling and logging appropriate to the solution. - The code clears
File Blobbefore rendering. A failed render therefore leaves no previous file available. - Test saved report settings after report layouts, request pages, or Business Central versions change.
- Apply permission checks appropriate to the records and reports exposed through the portal.
- Avoid interactive report logic because portal execution does not provide a report request page to the end user.
Complete sample code
codeunit 5138176 "SIM_DPS Sample Report over CU"
{
TableNo = "SIM_DPS OL";
trigger OnRun()
var
LocalRecordSIMDPSDTAction: Record "SIM_DPS DT Action";
LocalRecordSIMDPSAction: Record "SIM_DPS Action";
LocalParamText: Text;
begin
LocalParamText := 'GETFUNCTIONS';
if Rec."Action Code" <> '' then begin
if not LocalRecordSIMDPSDTAction.Get(Rec."Dataset Code", Rec."Dataset Table Code", Rec."Dataset Table Type", Rec."Action Code") then exit;
if not LocalRecordSIMDPSAction.Get(LocalRecordSIMDPSDTAction."Action Code") then exit;
LocalParamText := LocalRecordSIMDPSAction.Param;
end;
case LocalParamText of
'PrintOrderConfirmation':
PrintOrderConfirmation(Rec);
'PrintOrderConfirmationFromTable':
PrintOrderConfirmationFromTable(Rec);
'GETFUNCTIONS':
Rec."Function List" := 'PrintOrderConfirmation,PrintOrderConfirmationFromTable';
end;
end;
var
GlobalCodeunitSIMDPSOrderFunctionLibrary: Codeunit "SIM_DPS Order Function Library";
GlobalRecIdLabelErr: Label 'You have not selected a valid record.';
procedure PrintOrderConfirmation(var ParamRecordSIMDPSOL: Record "SIM_DPS OL")
var
LocalRecordId: RecordId;
begin
Evaluate(LocalRecordId, Format(GlobalCodeunitSIMDPSOrderFunctionLibrary.GetDPSOrderLineInputFieldByCode(ParamRecordSIMDPSOL, 'SALESORDER')));
PrintReport(ParamRecordSIMDPSOL, LocalRecordId);
end;
procedure PrintOrderConfirmationFromTable(var ParamRecordSIMDPSOL: Record "SIM_DPS OL")
var
LocalRecordId: RecordId;
begin
LocalRecordId := ParamRecordSIMDPSOL."Dataset Table Record";
PrintReport(ParamRecordSIMDPSOL, LocalRecordId);
end;
procedure PrintReport(var ParamRecordSIMDPSOL: Record "SIM_DPS OL"; ParamRecordId: RecordId)
var
LocalRecordSIMDPSAction: Record "SIM_DPS Action";
LocalRecordRef: RecordRef;
Local2RecordRef: RecordRef;
LocalVariant: Variant;
LocalReportRequestPageText: Text;
LocalInStream: InStream;
LocalOutStream: OutStream;
begin
if LocalRecordRef.Get(ParamRecordId) then begin
GlobalCodeunitSIMDPSOrderFunctionLibrary.FilterRecRefByKey(LocalRecordRef, Local2RecordRef);
LocalVariant := Local2RecordRef;
if LocalRecordSIMDPSAction.Get(ParamRecordSIMDPSOL."Action Code") then begin
LocalReportRequestPageText := '';
if LocalRecordSIMDPSAction."Report Settings".HasValue() then begin
LocalRecordSIMDPSAction.CalcFields("Report Settings");
LocalRecordSIMDPSAction."Report Settings".CreateInStream(LocalInStream);
LocalInStream.Read(LocalReportRequestPageText);
end;
Clear(LocalOutStream);
Clear(ParamRecordSIMDPSOL."File Blob");
ParamRecordSIMDPSOL."File Blob".CreateOutStream(LocalOutStream);
if not Report.SaveAs(LocalRecordSIMDPSAction."Report ID", LocalReportRequestPageText, ReportFormat::Pdf, LocalOutStream, LocalVariant) then
if not Report.SaveAs(LocalRecordSIMDPSAction."Report ID", LocalReportRequestPageText, ReportFormat::Pdf, LocalOutStream, LocalVariant) then
if not Report.SaveAs(LocalRecordSIMDPSAction."Report ID", LocalReportRequestPageText, ReportFormat::Pdf, LocalOutStream, LocalVariant) then
exit;
end;
end else
Error(GlobalRecIdLabelErr);
end;
}