import { Callout } from "zudoku/ui/Callout";

# Extensibility Events

`365 business PDF` hooks into the standard report processing of Business Central: whenever a report that is listed in `Report Selections` has been rendered to PDF, the app takes the document, applies the configured actions to it, and hands it back.

The events on this page let your extension take part in that. They are published by two codeunits:

| Codeunit | Event | Raised |
| --- | --- | --- |
| `bdev.PDF` (`5523675`) | [`OnBeforePerformAdditionalActionsOnPdf`](#onbeforeperformadditionalactionsonpdf---event) | Before the app does anything to the rendered document. |
| `bdev.Doc. Attachment Impl.` (`5523596`) | [`OnAttachDocumentsForSource`](#onattachdocumentsforsource---event) | While document attachments are collected, for source tables the app does not handle itself. |

## OnBeforePerformAdditionalActionsOnPdf - Event

The `OnBeforePerformAdditionalActionsOnPdf` event is raised after the report has been rendered and the print context has been determined, and before stationery, concatenation, attachments, signing and ZUGFeRD conversion are applied. Use this event to act on the document yourself, to choose a different report selection, or to stop the processing of the app entirely.

```pascal
    /// <summary>
    /// Event publisher to overwrite report selection or skip further processing.
    /// </summary>
    /// <param name="document">Base document to perform additional actions on.</param>
    /// <param name="reportSelections">Report selection, containing settings for the report.</param>
    /// <param name="baseRecord">Base record (e.g. Sales Invoice Header) for which the report is being processed.</param>
    /// <param name="languageCode">Language code of the report being processed.</param>
    /// <param name="documentPrintIntent">Print intent of the document being processed.</param>
    /// <param name="objectPayload">JSON object payload containing metadata about the document.</param>
    /// <param name="skipProcessing">Set to true to skip further processing of the PDF document.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforePerformAdditionalActionsOnPdf(var document: Codeunit "Temp Blob"; var reportSelections: Record "Report Selections"; baseRecord: Variant; languageCode: Code[10]; documentPrintIntent: Enum "bdev.Pdf Document Print Intent"; objectPayload: JsonObject; var skipProcessing: Boolean)
    begin
    end;
```

| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `document` | Codeunit "Temp Blob" | The rendered PDF document. Changes are used for the processing that follows and end up in the document the user receives. |
| `reportSelections` | Record "Report Selections" | The report selection the app resolved for the report. Its configuration code fields decide which actions run; changing them here changes what the app does. |
| `baseRecord` | Variant | A `RecordRef` on the record the report was printed for, positioned on the first record of the report filter. It is empty when the base record could not be resolved from the payload. |
| `languageCode` | Code[10] | The language of the document. Set from the document on a single print, empty on a bulk print. |
| `documentPrintIntent` | Enum "bdev.Pdf Document Print Intent" | Whether the user printed, previewed, downloaded or saved the document. |
| `objectPayload` | JsonObject | The payload Business Central passes to `OnDocumentReady`, carrying `documenttype`, `intent` and `filterviews` among others. |
| `skipProcessing` | Boolean | Set to `true` to stop the app from applying any of its actions. |

<Callout type="tip" title="Good to know">
`OnBeforePerformAdditionalActionsOnPdf` has been introduced with 365 business PDF version 20.5, the `objectPayload` parameter shortly after it.<br/>
    <br/>
    Please consider update 365 business PDF to get access to the event.
</Callout>

### When the event is raised

The event is raised for **every** report that is listed in `Report Selections` and renders to PDF - not only for reports that have a `365 business PDF` configuration attached. A subscriber therefore has to decide for itself whether the document is one it cares about.

It is not raised when:

- the rendered document is not a PDF;
- the report is not listed in `Report Selections`;
- the report prints a sales invoice or credit memo and `365 business E-Invoice` is installed with its export enabled - the e-document pipeline takes over in that case.

### Skipping the processing

Setting `skipProcessing` to `true` stops everything the app would do to the document: stationery, concatenation, document attachments, signing, security and ZUGFeRD conversion. The document you leave in `document` is what is handed back to Business Central.

<Callout type="caution" title="A document of unchanged size is discarded">
The app only writes the document back when its **size** differs from the rendered one. A subscriber that changes the document without changing its length - overwriting a value in place, for example - leaves the user with the original report. If a change of that kind has to survive, make sure the document you produce differs in size, or write it through a path of your own.
</Callout>

### Errors in a subscriber are swallowed

The whole processing, including this event, runs inside a `[TryFunction]`. An error a subscriber raises does not reach the user: it silently aborts the processing and the user receives the unprocessed report. Do not use `Error` to communicate with the user from a subscriber - send a `Notification` or write to the telemetry instead.

### Example

The following example archives a copy of the rendered document in a table of its own before the app applies anything to it, and leaves the processing to the app:

```pascal
codeunit 50000 "My Pdf Subscriber"
{
    [EventSubscriber(ObjectType::Codeunit, Codeunit::"bdev.PDF", 'OnBeforePerformAdditionalActionsOnPdf', '', true, true)]
    local procedure ArchiveDocumentOnBeforePerformAdditionalActionsOnPdf(var document: Codeunit "Temp Blob"; var reportSelections: Record "Report Selections"; baseRecord: Variant; languageCode: Code[10]; documentPrintIntent: Enum "bdev.Pdf Document Print Intent"; objectPayload: JsonObject; var skipProcessing: Boolean)
    var
        salesInvoiceHeader: Record "Sales Invoice Header";
        recRef: RecordRef;
    begin
        if (documentPrintIntent <> Enum::"bdev.Pdf Document Print Intent"::Print) then
            exit;

        if (not baseRecord.IsRecordRef()) then
            exit;

        recRef := baseRecord;
        if (recRef.Number() <> Database::"Sales Invoice Header") then
            exit;

        recRef.SetTable(salesInvoiceHeader);
        ArchiveDocument(salesInvoiceHeader, document);
    end;
}
```

The next example takes a report out of the hands of the app altogether, for example because another extension already applied a stationery of its own:

```pascal
    [EventSubscriber(ObjectType::Codeunit, Codeunit::"bdev.PDF", 'OnBeforePerformAdditionalActionsOnPdf', '', true, true)]
    local procedure SkipOwnReportOnBeforePerformAdditionalActionsOnPdf(var document: Codeunit "Temp Blob"; var reportSelections: Record "Report Selections"; baseRecord: Variant; languageCode: Code[10]; documentPrintIntent: Enum "bdev.Pdf Document Print Intent"; objectPayload: JsonObject; var skipProcessing: Boolean)
    begin
        skipProcessing := (reportSelections."Report ID" = Report::"My Own Layouted Report");
    end;
```

## OnAttachDocumentsForSource - Event

Document attachments let a user place further PDF documents before or after a printed document. The app resolves the attachments of `Sales Header` and `Purchase Header` itself. For every other source table it raises `OnAttachDocumentsForSource`, so that an extension can state which `bdev.Document Attachment` records belong to the record being printed.

```pascal
    [IntegrationEvent(false, false)]
    local procedure OnAttachDocumentsForSource(var docAttachments: Record "bdev.Document Attachment"; var document: Codeunit "Temp Blob"; reportSelections: Record "Report Selections"; var recRef: RecordRef; isHandled: Boolean)
    begin
    end;
```

| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `docAttachments` | Record "bdev.Document Attachment" | The attachment record the app iterates afterwards. Set the filters that select the attachments of your record. |
| `document` | Codeunit "Temp Blob" | The document being printed. |
| `reportSelections` | Record "Report Selections" | The report selection of the report being printed. |
| `recRef` | RecordRef | The record the report is printed for. Its table id is what makes the app raise this event rather than handle the source itself. |
| `isHandled` | Boolean | Intended to state that the subscriber has filtered the attachments. |

The event is only raised on a **single print**; on a bulk print no attachments are added at all.

<Callout type="caution" title="isHandled cannot be returned as of 20.5">
`isHandled` is declared **without** `var`, so a subscriber receives a copy and cannot hand its value back to the publisher. The app therefore always treats a source of its own as unhandled and skips the attachments. Filtering `docAttachments` from a subscriber has no effect until the parameter is passed by reference. To attach documents to a source of your own today, subscribe to [OnBeforePerformAdditionalActionsOnPdf](#onbeforeperformadditionalactionsonpdf---event) and merge the documents yourself with [Pdf - Concatenate](pdf-concatenate.mdx).
</Callout>

## See also

- [365 business PDF - Overview](readme.mdx)
- [Pdf - Concatenate](pdf-concatenate.mdx)
- [Document Attachment API](document-attachment-api.mdx)
- [Documentation - Document Attachments](../../en-us/365-business-pdf/document-attachments.md)
