Archiving documents from other extensions Overview Freeze archives documents automatically at fixed points in Business Central: when a document is posted, when a document is deleted, and when an email is sent through the BC email module. Many customers also use other extensions that print documents, send them by email or issue them as e-invoices. These extensions often use their own mechanisms, which Freeze does not pick up automatically. This page shows partners how to archive such documents in Freeze anyway. Often a setting in Freeze is all it takes. If not, you write a small extension of your own that hands the documents over to Freeze. Freeze itself never needs to change. The idea is simple: every archiving operation in Freeze starts with an entry in the Freeze Queue Entries . The Freeze job then takes care of the upload to the archive, retries and verification. Your extension only has to create such an entry. Check first: is additional archiving needed? If Archive after Posting and Archive Outgoing are enabled in the Freeze Setup , Freeze already archives a PDF of the document when it is posted. Freeze creates this PDF from the report selection. So first clarify with the customer what exactly is missing. Typical gaps are: The document looks different from the Freeze PDF, for example because of its own layout or letterhead. The email text or additional attachments of the sent email are missing. The documents are not posted at all, for example reminders, statements or quotes. The simplest option: attachments of the document Many extensions add the issued document as an attachment to the document itself, for example to the order or to the posted invoice. Technically this is a record in the Document Attachment table. Users see it among the attachments of the document. In this case you do not need an extension of your own. Turn on Archive Attachments in the Freeze Setup . Freeze then includes all attachments of a document whenever it archives that document. This happens: at posting, if Archive after Posting is enabled for purchases or sales, when an unposted purchase or sales document is deleted, if Archive on Delete is enabled, when a user runs the Freeze now action on the document, when a user runs the Freeze new record action in the archive search. This applies to purchase and sales documents, both open and posted (for example invoices, credit memos, shipments and returns), and to customers, vendors and items. Timing is what matters. Freeze only includes the attachments that are already there at that moment. Attachments added later are not archived automatically. The other extension attaches its document to … Result … the order or invoice before it is posted Business Central moves the attachments to the posted document during posting, and Freeze archives them automatically along with it. Nothing else to do. … the posted document, but only later, for example when it is sent Freeze does not archive the document automatically. Users can then run Freeze now on the posted document. Freeze creates a new version of the existing record, with the document PDF and all current attachments. If this has to happen automatically, use option 1 or 2 below. How to test it: Post a test document to which the other extension has attached its document. Wait for the Freeze job, then open the archive search for the posted document. The document from the other extension must be listed among the attachments of the record. Further options at a glance If attachments are not enough, for example because the other extension does not attach its document at all or only after posting, these are your options: Option How it works A good fit when … 1. Archive when the document is issued Your extension subscribes to an event of the other extension, for example "document sent", and passes the file to Freeze right away. the other extension offers a suitable event that gives access to the file and the document. 2. Archive later via the job queue A job queue entry regularly reads the log of the other extension and passes new entries to Freeze. the other extension has no suitable event but logs sent documents including the file. Also works for documents that were sent in the past. 3. Archive a different file at posting Your extension uses a Freeze event to replace the PDF that Freeze archives at posting. only the layout differs and the posting date is good enough. 4. Archive manually Users drag the file into the Quick Freeze FactBox. only single cases are affected. See Quick Freeze . Option 3 archives the state at posting, not the document that was actually sent later. If the sent document must be archived, use option 1 or 2. Building blocks All options need only a few public procedures from Freeze: Codeunit Procedure Purpose DXP FRZ Attachment Mgt. AddFileFromStream(var TempFrzAttachmentBuffer; var InStr; FileName; LinkedRecordRef) Puts one file into the buffer. Call it once per file. DXP Freeze Queue Buffer Mgt. InsertFREEZEQueueEntryWithAttachments(LinkedRecordRef; var TempFrzAttachmentBuffer; FrzRecordType; ReferenceDate) Creates the queue entry with all files. From here on, Freeze takes over. DXP Freeze Queue Buffer Mgt. InsertQueueBufferEntry(RecSystemID; RecTableNo; var TempFrzAttachmentBuffer; FrzRecordType; ReferenceDate) followed by GetEntryNo() Same as above. In addition, GetEntryNo() then returns the Entry No. of the new entry, for example for a log of your own (example 2). DXP Freeze Helper GetFreezeRecordTypeFromReportSelectionUsage(ReportSelectionUsage) Determines the matching record type, exactly as Freeze does itself. DXP Freeze Setup Mgt. IsModuleActivated() , ArchiveEmailAttachments() Checks that Freeze is active and that the customer has enabled archiving of sent documents. Example 1: Archive when the document is issued In this example the other extension publishes an event OnAfterDocumentSent . It provides the document, the report usage and the PDF. Replace the codeunit, event name and parameters with the event of the extension your customer uses. codeunit 50100 "PTE Archive Sent Documents" { // Placeholder: codeunit and event of the other extension [EventSubscriber(ObjectType::Codeunit, Codeunit::"Other Ext. Send Mgt.", 'OnAfterDocumentSent', '', false, false)] local procedure ArchiveInFreeze(DocumentRecordId: RecordId; ReportUsage: Enum "Report Selection Usage"; var PdfTempBlob: Codeunit "Temp Blob"; PdfFileName: Text) var TempFrzAttachmentBuffer: Record "DXP FRZ Attachment Buffer" temporary; FrzAttachmentMgt: Codeunit "DXP FRZ Attachment Mgt."; FrzQueueBufferMgt: Codeunit "DXP Freeze Queue Buffer Mgt."; FrzHelper: Codeunit "DXP Freeze Helper"; FrzSetupMgt: Codeunit "DXP Freeze Setup Mgt."; LinkedRecRef: RecordRef; PdfInStr: InStream; DocumentNotFoundErr: Label 'The document %1 could not be found and was not archived in Freeze.', Comment = '%1 = Record ID'; begin // 1. Respect the customer's setup if not FrzSetupMgt.IsModuleActivated() then exit; if not FrzSetupMgt.ArchiveEmailAttachments() then exit; // 2. Open the posted document, for example the sales invoice if not LinkedRecRef.Get(DocumentRecordId) then Error(DocumentNotFoundErr, Format(DocumentRecordId)); // 3. Put the file into the buffer PdfTempBlob.CreateInStream(PdfInStr); FrzAttachmentMgt.AddFileFromStream(TempFrzAttachmentBuffer, PdfInStr, PdfFileName, LinkedRecRef); // 4. Create the queue entry: record type as Freeze does it, reference date = document date FrzQueueBufferMgt.InsertFREEZEQueueEntryWithAttachments( LinkedRecRef, TempFrzAttachmentBuffer, Format(FrzHelper.GetFreezeRecordTypeFromReportSelectionUsage(ReportUsage)), GetDocumentDate(LinkedRecRef)); end; local procedure GetDocumentDate(LinkedRecRef: RecordRef): Date var SalesInvoiceHeader: Record "Sales Invoice Header"; SalesCrMemoHeader: Record "Sales Cr.Memo Header"; begin case LinkedRecRef.Number() of Database::"Sales Invoice Header": begin LinkedRecRef.SetTable(SalesInvoiceHeader); exit(SalesInvoiceHeader."Document Date"); end; Database::"Sales Cr.Memo Header": begin LinkedRecRef.SetTable(SalesCrMemoHeader); exit(SalesCrMemoHeader."Document Date"); end; end; exit(WorkDate()); end; } Important notes: To archive the email text as well, add it to the buffer as a second file named body.html . Freeze does the same for the BC email module. Freeze saves the queue entry immediately with a Commit . If the event of the other extension fires in the middle of its own processing, this can interfere with it. In that case, use option 2. Example 2: Archive later via the job queue In this example the other extension keeps a log Other Ext. Sent Log . Each entry contains the document, the report usage and the PDF. Your extension uses a small table of its own to remember which entries have already been passed to Freeze. Step 1: a table for entries that were already passed on. table 50100 "PTE Freeze Transfer Log" { DataClassification = SystemMetadata; fields { field(1; "Source Entry No."; Integer) { } field(2; "Freeze Queue Entry No."; Integer) { } field(3; "Transferred At"; DateTime) { } } keys { key(PK; "Source Entry No.") { Clustered = true; } } } Step 2: a codeunit that runs as a job queue entry. codeunit 50101 "PTE Transfer Sent Log" { trigger OnRun() var SentLog: Record "Other Ext. Sent Log"; // Placeholder: log table of the other extension TransferLog: Record "PTE Freeze Transfer Log"; FrzSetupMgt: Codeunit "DXP Freeze Setup Mgt."; begin if not FrzSetupMgt.IsModuleActivated() then exit; if not FrzSetupMgt.ArchiveEmailAttachments() then exit; SentLog.SetRange(Status, SentLog.Status::Sent); if SentLog.FindSet() then repeat if not TransferLog.Get(SentLog."Entry No.") then TransferEntry(SentLog); until SentLog.Next() = 0; end; local procedure TransferEntry(var SentLog: Record "Other Ext. Sent Log") var TempFrzAttachmentBuffer: Record "DXP FRZ Attachment Buffer" temporary; TransferLog: Record "PTE Freeze Transfer Log"; FrzAttachmentMgt: Codeunit "DXP FRZ Attachment Mgt."; FrzQueueBufferMgt: Codeunit "DXP Freeze Queue Buffer Mgt."; FrzHelper: Codeunit "DXP Freeze Helper"; LinkedRecRef: RecordRef; SystemIdFieldRef: FieldRef; PdfInStr: InStream; DocumentNotFoundErr: Label 'Log entry %1: the document %2 could not be found.', Comment = '%1 = Entry No., %2 = Record ID'; begin if not LinkedRecRef.Get(SentLog."Document Record ID") then Error(DocumentNotFoundErr, SentLog."Entry No.", Format(SentLog."Document Record ID")); SentLog.CalcFields("PDF File"); SentLog."PDF File".CreateInStream(PdfInStr); FrzAttachmentMgt.AddFileFromStream(TempFrzAttachmentBuffer, PdfInStr, SentLog."File Name", LinkedRecRef); // InsertQueueBufferEntry instead of InsertFREEZEQueueEntryWithAttachments, // so that GetEntryNo() returns the Entry No. of the new entry afterwards SystemIdFieldRef := LinkedRecRef.Field(LinkedRecRef.SystemIdNo()); FrzQueueBufferMgt.InsertQueueBufferEntry( SystemIdFieldRef.Value(), LinkedRecRef.Number(), TempFrzAttachmentBuffer, Format(FrzHelper.GetFreezeRecordTypeFromReportSelectionUsage(SentLog."Report Usage")), SentLog."Document Date"); TransferLog.Init(); TransferLog."Source Entry No." := SentLog."Entry No."; TransferLog."Freeze Queue Entry No." := FrzQueueBufferMgt.GetEntryNo(); TransferLog."Transferred At" := CurrentDateTime(); TransferLog.Insert(); Commit(); end; } Important notes: If an entry cannot be passed on, the run stops with an error message. The error is shown on the job queue entry, and the log entry is retried on the next run. That way no document gets lost unnoticed. This only works if the other extension stores the file in the database. If the file only lives in external storage, you have to load it from there. Tables of other extensions are not a guaranteed interface. Check your code after updates of the other extension. Example 3: Archive a different file at posting At posting, Freeze raises the event OnBeforeSaveReportAsPDFInTempBlob for each line of the report selection before it creates the PDF. Your extension can supply its own PDF here. codeunit 50102 "PTE Freeze Posting PDF" { [EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP FRZ Attachment Mgt.", 'OnBeforeSaveReportAsPDFInTempBlob', '', false, false)] local procedure UseOtherPdf(var ReportSelectionUsage: Enum "Report Selection Usage"; var ReportID: Integer; var TempBlobList: Codeunit "Temp Blob List"; var RecVarToPrint: Variant; var CustNoOrBillToNo: Code[20]; var IsHandled: Boolean) var TempBlob: Codeunit "Temp Blob"; begin if IsHandled then exit; if ReportSelectionUsage <> ReportSelectionUsage::"S.Invoice" then exit; // Placeholder: create the PDF with the other extension if not CreatePdfWithOtherExtension(RecVarToPrint, TempBlob) then exit; // Freeze then creates the PDF from the report selection as usual TempBlobList.Add(TempBlob); IsHandled := true; end; local procedure CreatePdfWithOtherExtension(RecVarToPrint: Variant; var TempBlob: Codeunit "Temp Blob"): Boolean begin // Call the function of the other extension here end; } Important notes: If the report selection has several lines for the same usage, the event is raised several times. The check if IsHandled then exit; prevents your PDF from being archived more than once. To keep the Freeze PDF and only add a file, use OnAfterPrintOutgoingSalesDocToPDF or OnAfterPrintOutgoingPurchaseDocToPDF instead and add your file to the TempBlobList . Important rules Link the right record. Freeze writes all fields of the linked record to the archive as search fields. So always link the Business Central document, for example the posted sales invoice, and not a log entry of the other extension. Only then can users find the document later by document number or customer. Set record type and reference date on purpose. Both control how the archive files and retains the document. Determine the record type with GetFreezeRecordTypeFromReportSelectionUsage and use the document date as reference date. See Record Types . Never swallow errors. If your extension cannot pass a document on, this must be visible, for example as an error message. Do not wrap the Freeze call in if ... then; . Same document again: new version. If the archive already contains a record for the same document, Freeze does not create a second record but a new version of the existing one. This happens, for example, when Freeze already archived the invoice at posting. Permissions. The user in whose session the queue entry is created needs the permission set DXP Freeze User . See Permission sets . More integration events These Freeze events are useful when archiving additional documents: Codeunit Event When Purpose DXP FRZ Attachment Mgt. OnBeforeSaveReportAsPDFInTempBlob At posting, before each PDF is created Supply your own PDF instead of the standard report (example 3). DXP FRZ Attachment Mgt. OnAfterPrintOutgoingSalesDocToPDF , OnAfterPrintOutgoingPurchaseDocToPDF At posting, after the PDFs were created Add or replace files. DXP Freeze Helper OnBeforeGetFreezeRecordTypeFromReportSelectionUsage When the record type is determined Use your own record type for specific report usages. DXP Freeze Queue Buffer Mgt. OnBeforeDoUpdate When the queue entry is created Decide whether another entry for the same record becomes a new version or a new archive record. DXP Freeze Queue Processing OnAfterPerformFREEZE , OnAfterProcessWaitingQueueBufferEntry In the Freeze job, after the upload Follow-up work, for example reporting the archiving status back to the other extension. Only set IsHandled in the event OnBeforePerformFREEZE if your extension fully takes over the upload to the archive. In that case, fill the fields "Archive Record ID" and "API Path" on the queue entry. From Freeze 1.30 on, Freeze otherwise sets the entry to status Error . Testing your implementation Check that Freeze is active and that Archive Email & Attachments is enabled in the Freeze Setup . Issue a test document through the other extension. Open the Freeze Queue Entries . A new entry with the right record type and reference date should appear. Wait for the Freeze job (every minute). The status moves via Waiting and Processed to Archived . Open the document in Business Central and check in the archive search that the file appears there. Related pages Archiving of documents Freeze Queue Entries Record Types Freeze integration into custom pages