Extension Entwicklung

In diesem Kapitel werden Anwendungsfälle aufgeführt, die bei der Entwicklung einer Extension für Freeze for BC helfen. Dies sind in der Regel Beispiele, die einer Notwendigkeit im Produktivbetrieb eines Partners oder Kunden entwachsen sind.

Wichtig:
Anpassungen dürfen nur durch entsprechend geschulte Berater/Entwickler durchgeführt werden. Diese befinden sich außerdem immer außerhalb des Standard-Supports.

Archivieren von Dateien

Freeze archiviert, abhängig von der Einrichtung, in verschiedenen Situationen automatisch (z.B. nach dem Buchen von Verkaufs- oder Einkaufsbelegen). Im Folgenden wird erläutert, welche Prozeduren ein Entwickler aufrufen kann, um das Archivieren einer Mappe inkl. Dateianhänge zu initiieren. 

Angenommen, Sie möchten eine neue Archivmappe mit einer oder mehreren Datei(en) archivieren.

Zunächst fügen Sie dem temporären Record des Typs "DXP FRZ Attachment Buffer" die entsprechenden Werte hinzu, die ihren Dateien entsprechend.

Zur Vereinfachung empfehlen wir die Nutzung der folgenden Prozedur:

codeunit 70954897 "DXP FRZ Attachment Mgt.":

procedure AddFileFromStream(var TempFRZAttachmentBuffer: Record "DXP FRZ Attachment Buffer" temporary, var InStr: InStream, FileName: Text)

Anschließend rufen sie die folgende Prozedur auf, um einen Eintrag in der Freeze Verarbeitungswarteschlange zu erstellen, der Mappendetails und Anhänge enthält, die zur Erstellung des Archiveintrags verwendet werden.

codeunit 70954899 "DXP Freeze Queue Buffer Mgt.":
procedure InsertQueueBufferEntry(RecSystemID: Guid, RecTableNo: Integer, var FRZAttachmentBufferTemp: Record "DXP FRZ Attachment Buffer" temporary, ArchiveRecordID: Guid, FrzRecordType: Text[35], ReferenceDate: Date, RecordTitle: Text[250]): Boolean

Die Prozeduren sind überladen - wählen Sie die passende Variante für ihr Szenario aus.

Sofern Sie eine eigene Logik implementieren möchten, um nach der Buchung eines Verkaufs- oder Einkaufsbelegs eine Archivierung zu initiieren, deaktivieren Sie die automatische Archivierung in der Freeze Einrichtung, um unerwünschte Redundanzen zu vermeiden.

 

Anhang Download als Zip

Diese Dokumentation bietet umfassende Anleitung für externe AL-Entwickler zur effektiven Nutzung der DXP Freeze Anhang-Download-Funktionalität in ihren Business Central Erweiterungen.

Überblick

Die DXP Freeze Result Management Codeunit bietet zwei Hauptmethoden zum Herunterladen archivierter Anhänge als ZIP-Dateien:

  1. DownloadAttachmentsAsZipWithPagination - Lädt Anhänge aus Suchanfrage-Ergebnissen mit Paginierung herunter
  2. DownloadAttachmentsAsZipFromRecord - Lädt Anhänge von spezifischen Business Central Datensätzen herunter

Beide Methoden organisieren Anhänge in strukturierte ZIP-Archive mit umfassenden Metadaten für Audit- und Nachverfolgungszwecke.

Methode 1: DownloadAttachmentsAsZipWithPagination

Zweck

Lädt alle Anhänge aus einem Suchanfrage-Ergebnissatz herunter und verarbeitet Ergebnisse seitenweise, um große Datensätze effizient zu handhaben. Die Anhänge jedes Datensatzes werden in individuelle ZIP-Dateien innerhalb eines Haupt-ZIP-Archivs organisiert.

Verfügbare Überladungen

1. Grundlegende Verwendung

procedure DownloadAttachmentsAsZipWithPagination(SearchQuery: Text; var ZipArchive: Codeunit "Data Compression"; var AttachmentCount: Integer): Boolean

2. Mit Filtern

procedure DownloadAttachmentsAsZipWithPagination(SearchQuery: Text; var ZipArchive: Codeunit "Data Compression"; var AttachmentCount: Integer; FilenameFilter: Text; FileExtensionFilter: Text): Boolean

3. Vollständige Kontrolle

procedure DownloadAttachmentsAsZipWithPagination(SearchQuery: Text; var ZipArchive: Codeunit "Data Compression"; var AttachmentCount: Integer; FilenameFilter: Text; FileExtensionFilter: Text; StoreApiLink: Text; RecordsPerPage: Integer; SuppressDialog: Boolean): Boolean

4. Vorab gefüllte Datensätze (Erweitert)

procedure DownloadAttachmentsAsZipWithPagination(var TempFrzResultQueryHeader: Record "DXP FRZ Query Result Header" temporary; var TempFrzResultRecordHeader: Record "DXP FRZ Record Result Header" temporary; var TempFrzResultRecordField: Record "DXP FRZ Result Record-Field" temporary; var TempFrzAttachmentResult: Record "DXP FRZ Attachment Result" temporary; SearchQuery: Text; var ZipArchive: Codeunit "Data Compression"; var AttachmentCount: Integer; FilenameFilter: Text; FileExtensionFilter: Text; SuppressDialog: Boolean): Boolean

Parameter

Parameter Typ Beschreibung
SearchQuery Text Freeze-Suchanfrage-String. Wenn leer in vorab gefüllter Überladung, verwendet vorhandene Anfrage oder führt Suche erneut aus
ZipArchive Codeunit “Data Compression” ZIP-Archiv-Objekt, das die heruntergeladenen Dateien enthalten wird
AttachmentCount Integer (var) Gibt die Gesamtanzahl der heruntergeladenen Anhänge zurück
FilenameFilter Text Filter für Anhangsdateinamen (z.B. ‘.pdf’, 'rechnung’)
FileExtensionFilter Text Filter für Dateierweiterungen (z.B. ‘pdf’, ‘docx’)
StoreApiLink Text Optional spezifischer Store-API-Link
RecordsPerPage Integer Anzahl Datensätze pro Seite (Standard: 100)
SuppressDialog Boolean Ob Fortschrittsdialog unterdrückt werden soll

Rückgabewert

  • Booleantrue wenn Anhänge gefunden und heruntergeladen wurden; false andernfalls

Verwendungsbeispiele

Grundlegender Download

procedure DownloadSearchResults()
var
    ResultMgt: Codeunit "DXP FRZ Result Mgt.";
    ZipArchive: Codeunit "Data Compression";
    FileMgt: Codeunit "File Management";
    TempBlob: Codeunit "Temp Blob";
    AttachmentCount: Integer;
    InStr: InStream;
    OutStr: OutStream;
    SearchQuery: Text;
begin
    SearchQuery := 'rechnung AND 2024';
    
    if ResultMgt.DownloadAttachmentsAsZipWithPagination(SearchQuery, ZipArchive, AttachmentCount) then begin
        // ZIP in Datei speichern
        TempBlob.CreateOutStream(OutStr);
        ZipArchive.SaveZipArchive(OutStr);
        TempBlob.CreateInStream(InStr);
        
        FileMgt.DownloadFromStreamHandler(InStr, '', '', '', 'Suchergebnisse.zip');
        Message('Erfolgreich %1 Anhänge heruntergeladen.', AttachmentCount);
    end else
        Message('Keine Anhänge für die Suchanfrage gefunden.');
end;

Mit Filtern

procedure DownloadPDFInvoices()
var
    ResultMgt: Codeunit "DXP FRZ Result Mgt.";
    ZipArchive: Codeunit "Data Compression";
    AttachmentCount: Integer;
    SearchQuery: Text;
begin
    SearchQuery := 'typ:rechnung';
    
    if ResultMgt.DownloadAttachmentsAsZipWithPagination(
        SearchQuery, 
        ZipArchive, 
        AttachmentCount, 
        '*.pdf', // Nur PDF-Dateien
        'pdf'    // Dateierweiterungsfilter
    ) then begin
        // ZIP-Archiv verarbeiten
        ProcessDownloadedFiles(ZipArchive, AttachmentCount);
    end;
end;

Verwendung vorab gefüllter Datensätze

procedure DownloadFromExistingResults()
var
    ResultMgt: Codeunit "DXP FRZ Result Mgt.";
    TempFrzResultQueryHeader: Record "DXP FRZ Query Result Header" temporary;
    TempFrzResultRecordHeader: Record "DXP FRZ Record Result Header" temporary;
    TempFrzResultRecordField: Record "DXP FRZ Result Record-Field" temporary;
    TempFrzAttachmentResult: Record "DXP FRZ Attachment Result" temporary;
    ZipArchive: Codeunit "Data Compression";
    AttachmentCount: Integer;
begin
    // Angenommen, diese Datensätze sind bereits aus einer vorherigen Suche gefüllt
    PopulateSearchResults(TempFrzResultQueryHeader, TempFrzResultRecordHeader, TempFrzResultRecordField, TempFrzAttachmentResult);
    
    // Download mit vorhandenen Ergebnissen ohne erneute Suchausführung
    if ResultMgt.DownloadAttachmentsAsZipWithPagination(
        TempFrzResultQueryHeader,
        TempFrzResultRecordHeader,
        TempFrzResultRecordField,
        TempFrzAttachmentResult,
        'rechnungssuche', // SearchQuery - wenn leer, wird Suche erneut ausgeführt
        ZipArchive,
        AttachmentCount,
        '',     // Kein Dateinamenfilter
        '',     // Kein Erweiterungsfilter
        true    // Dialog unterdrücken
    ) then begin
        ProcessDownloadedFiles(ZipArchive, AttachmentCount);
    end;
end;

ZIP-Struktur (Paginierung)

Suchergebnisse.zip
├── export-metadata.json
├── Rechnung_001_V1_20241201_1430.zip
│   ├── {GUID}_rechnung.pdf
│   └── {GUID}_begleitdokument.docx
├── Bestellung_002_V2_20241202_0900.zip
│   └── {GUID}_bestelldokument.pdf
└── Vertrag_003_V1_20241203_1200.zip
    ├── {GUID}_vertrag.pdf
    └── {GUID}_nachtrag.pdf

Methode 2: DownloadAttachmentsAsZipFromRecord

Zweck

Lädt Anhänge von spezifischen Business Central Datensätzen herunter. Die Anhänge jedes ausgewählten Datensatzes werden in individuelle ZIP-Dateien innerhalb eines Haupt-ZIP-Archivs organisiert.

Verfügbare Überladungen

1. Grundlegende Verwendung

procedure DownloadAttachmentsAsZipFromRecord(var SelectedRecord: RecordRef; var ZipArchive: Codeunit "Data Compression"): Boolean

2. Mit Filtern

procedure DownloadAttachmentsAsZipFromRecord(var SelectedRecord: RecordRef; var ZipArchive: Codeunit "Data Compression"; FilenameFilter: Text; FileExtensionFilter: Text): Boolean

Parameter

Parameter Typ Beschreibung
SelectedRecord RecordRef (var) RecordRef mit den ausgewählten Business Central Datensätzen
ZipArchive Codeunit “Data Compression” ZIP-Archiv-Objekt, das die heruntergeladenen Dateien enthalten wird
FilenameFilter Text Filter für Anhangsdateinamen
FileExtensionFilter Text Filter für Dateierweiterungen

Rückgabewert

  • Booleantrue wenn Anhänge gefunden und heruntergeladen wurden; false andernfalls

Verwendungsbeispiele

Download von Verkaufsrechnungen

procedure DownloadInvoiceAttachments()
var
    SalesInvoiceHeader: Record "Sales Invoice Header";
    ResultMgt: Codeunit "DXP FRZ Result Mgt.";
    ZipArchive: Codeunit "Data Compression";
    RecordRef: RecordRef;
    HasAttachments: Boolean;
begin
    // Spezifische Rechnungen auswählen
    SalesInvoiceHeader.SetRange("Posting Date", DMY2Date(1, 1, 2024), DMY2Date(31, 12, 2024));
    SalesInvoiceHeader.SetFilter("Sell-to Customer No.", '10000|20000');
    
    if SalesInvoiceHeader.FindSet() then begin
        RecordRef.GetTable(SalesInvoiceHeader);
        
        HasAttachments := ResultMgt.DownloadAttachmentsAsZipFromRecord(RecordRef, ZipArchive);
        
        if HasAttachments then
            SaveZipFile(ZipArchive, 'Rechnungsanhänge.zip')
        else
            Message('Keine Anhänge für die ausgewählten Rechnungen gefunden.');
    end;
end;

Download mit Filtern von Seite

// In einer Seitenerweiterung
action(DownloadAttachmentsFiltered)
{
    Caption = 'Gefilterte Anhänge herunterladen';
    Image = ExportFile;
    
    trigger OnAction()
    var
        ResultMgt: Codeunit "DXP FRZ Result Mgt.";
        ZipArchive: Codeunit "Data Compression";
        RecordRef: RecordRef;
        FilenameFilter: Text;
        FileExtensionFilter: Text;
    begin
        // Filterdialog anzeigen
        if ShowFilterDialog(FilenameFilter, FileExtensionFilter) then begin
            CurrPage.SetSelectionFilter(Rec);
            RecordRef.GetTable(Rec);
            
            if ResultMgt.DownloadAttachmentsAsZipFromRecord(
                RecordRef, 
                ZipArchive, 
                FilenameFilter, 
                FileExtensionFilter
            ) then
                DownloadZipFile(ZipArchive, 'GefilterteAnhänge.zip');
        end;
    end;
}

ZIP-Struktur (Datensätze)

Datensatzanhänge.zip
├── export-metadata.json
├── Sales_Invoice_Header_Unternehmen_SI-001.zip
│   ├── {GUID}_rechnung.pdf
│   └── {GUID}_bedingungen.pdf
├── Sales_Invoice_Header_Unternehmen_SI-002.zip
│   └── {GUID}_rechnung.pdf
└── Sales_Invoice_Header_Unternehmen_SI-003.zip
    ├── {GUID}_rechnung.pdf
    ├── {GUID}_lieferschein.pdf
    └── {GUID}_quittung.jpg

Metadaten-Struktur

Beide Methoden generieren umfassende Metadaten in export-metadata.json:

Paginierungs-Export-Metadaten

{
  "exportInfo": {
    "exportTimestamp": "2024-12-19T10:13:52.248Z",
    "exportedBy": "USER001",
    "searchQuery": "typ:rechnung AND jahr:2024",
    "totalRecordsFound": 150,
    "totalPages": 15,
    "exportType": "paginated-search",
    "description": "Freeze Suchanfrage Export"
  },
  "appliedFilters": {
    "filenameFilter": "*.pdf",
    "fileExtensionFilter": "pdf"
  },
  "statistics": {
    "pagesProcessed": 15,
    "totalRecordsProcessed": 150,
    "recordsWithAttachments": 120,
    "recordsWithoutAttachments": 30,
    "totalAttachments": 245,
    "exportCompletedAt": "2024-12-19T10:15:33.021Z"
  },
  "records": [
    {
      "recordId": "{GUID}",
      "title": "Rechnung REG-2024-001",
      "version": 1,
      "archivedAt": "2024-12-01T09:30:00Z",
      "archivedBy": "SYSTEM",
      "type": "Verkaufsrechnung",
      "masterId": "{GUID}",
      "attachmentCount": 3,
      "hasAttachments": true,
      "zipFile": "Rechnung_REG-2024-001_V1_20241201_0930.zip"
    }
  ]
}

Datensatz-Export-Metadaten

{
  "exportTimestamp": "2024-12-19T14:30:00Z",
  "exportedBy": "USER001",
  "totalRecordsProcessed": 25,
  "description": "DXP Freeze Anhänge Export",
  "sourceTable": {
    "tableNumber": 112,
    "tableName": "Sales Invoice Header",
    "tableCaption": "Geb. Verkaufsrechnung"
  },
  "appliedFilters": {
    "filenameFilter": "*.pdf",
    "fileExtensionFilter": "pdf"
  },
  "statistics": {
    "totalAttachments": 45,
    "recordsWithAttachments": 20,
    "recordsWithoutAttachments": 5,
    "totalZipFiles": 20
  },
  "records": [
    {
      "recordId": "Sales Invoice Header: Unternehmen, SI-001",
      "systemId": "{GUID}",
      "primaryKey": {
        "fields": [
          {
            "fieldName": "Nr.",
            "fieldValue": "SI-001",
            "fieldType": "Code"
          }
        ]
      },
      "hasAttachments": true,
      "attachmentCount": 2,
      "zipFile": "Sales_Invoice_Header_Unternehmen_SI-001.zip"
    }
  ]
}

Leistungsüberlegungen

Paginierungsmethode

  • Große Ergebnismengen: Automatische Handhabung der Paginierung zur effizienten Verarbeitung großer Datensätze
  • Speicherverwaltung: Verarbeitet eine Seite nach der anderen, bereinigt Speicher zwischen Seiten
  • Fortschrittsverfolgung: Zeigt Echtzeit-Fortschritt für länger dauernde Operationen
  • Empfohlen für: Suchanfragen, die Hunderte oder Tausende von Datensätzen zurückgeben können

Datensatzmethode

  • Ausgewählte Datensätze: Verarbeitet nur die spezifisch ausgewählten Datensätze
  • Direkte Verarbeitung: Kein Paginierungs-Overhead für kleinere Datensätze
  • Stapelverarbeitung: Effizient für die Verarbeitung spezifischer Datensatzmengen
  • Empfohlen für: Gezielte Downloads von spezifischen Business Central Datensätzen

Fehlerbehandlung

Beide Methoden beinhalten umfassende Fehlerbehandlung:

Häufige Szenarien

  • Keine Ergebnisse gefunden: Gibt false zurück, wenn keine Datensätze oder Anhänge gefunden werden
  • Berechtigungsprobleme: Schließt automatisch Datensätze aus, auf die der Benutzer nicht zugreifen kann
  • API-Fehler: Zuverlässiger Umgang mit API-Kommunikationsfehlern
  • Leere Filter: Behandelt leere oder ungültige Filterparameter

Best Practices

// Rückgabewert immer prüfen
if not ResultMgt.DownloadAttachmentsAsZipWithPagination(SearchQuery, ZipArchive, AttachmentCount) then begin
    Message('Keine Anhänge gefunden oder Download fehlgeschlagen.');
    exit;
end;

// Anhanganzahl validieren
if AttachmentCount = 0 then begin
    Message('Suche abgeschlossen, aber keine Anhänge entsprechen den Kriterien.');
    exit;
end;

// Große Downloads handhaben
if AttachmentCount > 1000 then
    if not Confirm('Dies wird %1 Anhänge herunterladen. Fortfahren?', false, AttachmentCount) then
        exit;

Integrationsereignisse

Beide Methoden unterstützen Integrationsereignisse zur Anpassung:

Verfügbare Ereignisse

  • OnBeforeDownloadAttachmentsAsZip: Verhalten vor Download-Start modifizieren
  • OnBeforeProcessAttachmentForZip: Einzelne Anhänge überspringen oder modifizieren
  • OnAfterGetAttachmentBase64: Anhanginhalt nach Abruf modifizieren
  • OnAfterAddAttachmentToZip: Aktionen nach Hinzufügung zum ZIP durchführen
  • OnNoAttachmentsFound: Szenario ohne Anhänge behandeln

Integrationsbeispiel

[EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP FRZ Result Mgt.", 'OnBeforeProcessAttachmentForZip', '', false, false)]
local procedure OnBeforeProcessAttachmentForZip(var TempFrzAttachmentResult: Record "DXP FRZ Attachment Result" temporary; var IsHandled: Boolean)
begin
    // Anhänge größer als 10MB überspringen
    if TempFrzAttachmentResult.Filesize > 10485760 then
        IsHandled := true;
end;

Dateinamen-Konventionen

Automatische Bereinigung

Alle Dateinamen werden automatisch mit der SanitizeFileName-Methode bereinigt:

  • Ungültige Zeichen (< > : " / \ | ? *) werden durch Unterstriche ersetzt
  • Leerzeichen werden durch Unterstriche ersetzt
  • Maximale Dateinamenlängen werden erzwungen

Eindeutige Benennung

  • Einzelne Dateien: Enthalten Anhang-GUID-Präfix zur Eindeutigkeit
  • ZIP-Dateien: Enthalten Datensatzinformationen und Zeitstempel
  • Keine Konflikte: Garantiert eindeutige Namen innerhalb jedes ZIP-Archivs

 

 

Freeze Integration in benutzerdefinierte Seiten

Überblick

Die DEXPRO Freeze Extension bietet Dokumentenarchivierungs-Funktionalität, die es Benutzern ermöglicht, Dokumente, Anhänge und zugehörige Daten für jeden Business Central-Datensatz zu speichern und abzurufen. Dieser Leitfaden erklärt, wie Sie die Freeze-Funktionalität in benutzerdefinierte Seiten integrieren.

Kernkomponenten

1. Quick Freeze Infobox

Die zentrale Komponente ist die "DXP FRZ Quick Freeze FB" Seite, die folgendes bietet:

2. Management Codeunits

3. Wichtige Tabellen

Implementierungsschritte

Schritt 1: Infobox hinzufügen

Fügen Sie die Quick Freeze Infobox zum Layout-Bereich Ihrer Seite hinzu:

layout
{
    addfirst(factboxes)
    {
        part("DXP Quick Archive"; "DXP FRZ Quick Freeze FB")
        {
            ApplicationArea = All;
            SubPageLink = "Record System ID" = field(SystemId),
                         "Record Table No." = const(Database::"Ihr Tabellenname");
            SubPageView = sorting("Entry No.") order(descending);
        }
    }
}

Wichtige Hinweise:

Schritt 2: Navigationsaktionen hinzufügen

Fügen Sie Freeze-bezogene Aktionen zum Aktionsbereich der Seite hinzu:

actions
{
    addfirst(navigation)
    {
        action("DXP Freeze")
        {
            Caption = 'Freeze öffnen';
            ApplicationArea = All;
            Image = Archive;
            ToolTip = 'Öffnet das Freeze-Archiv und zeigt mit dem ausgewählten Datensatz verknüpfte Anhänge an.';
            ShortcutKey = 'Ctrl+Alt+F';
            trigger OnAction()
            var
                ResultMgt: Codeunit "DXP FRZ Result Mgt.";
            begin
                ResultMgt.SubmitSearchAndShowResults(Rec.RecordId(), FrzSearchMgt.GetSearchCombinationForTable(Rec.SystemId, Rec.RecordId.TableNo()));
            end;
        }
    }
}

Schritt 3: Hervorgehobene Aktionen hinzufügen

actions
{
    addfirst(Category_Category16)
    {
        actionref("DXP Freeze_Promoted"; "DXP Freeze")
        {
        }
    }
    modify(Category_Category16)
    {
        Caption = 'DEXPRO Freeze', Locked = true;
    }
}

Schritt 4: Seiten-Trigger hinzufügen

trigger OnAfterGetCurrRecord()
begin
    CurrPage."DXP Quick Archive".Page.SetSourceRecord(Rec.RecordId());
end;

Schritt 5: Variablen deklarieren

var
    ExtMgt: Codeunit "DXP Extension Mgt.";
    FrzSearchMgt: Codeunit "DXP Freeze Search Mgt.";

Vollständiges Beispiel

Hier ist ein vollständiges Beispiel für eine benutzerdefinierte Belegseite:

pageextension 50000 "Mein benutzerdef. Beleg Erw." extends "Mein benutzerdefinierter Beleg"
{
    layout
    {
        addfirst(factboxes)
        {
            part("DXP Quick Archive"; "DXP FRZ Quick Freeze FB")
            {
                ApplicationArea = All;
                SubPageLink = "Record System ID" = field(SystemId),
                         "Record Table No." = const(Database::"Ihr Tabellenname");
                SubPageView = sorting("Entry No.") order(descending);
            }
        }
    }
    
    actions
    {
        addfirst(navigation)
        {
            action("DXP Freeze")
            {
                Caption = 'Freeze öffnen';
                ApplicationArea = All;
                Image = Archive;
                ToolTip = 'Öffnet das Freeze-Archiv und zeigt mit dem ausgewählten Datensatz verknüpfte Anhänge an.';
                ShortcutKey = 'Ctrl+Alt+F';
                trigger OnAction()
                var
                    ResultMgt: Codeunit "DXP FRZ Result Mgt.";
                begin
                    ResultMgt.SubmitSearchAndShowResults(Rec.RecordId(), FrzSearchMgt.GetSearchCombinationForTable(Rec.SystemId, Rec.RecordId.TableNo()));
                end;
            }
        }
        
        addfirst(Category_Category16)
        {
            actionref("DXP Freeze_Promoted"; "DXP Freeze")
            {
            }
        }
        modify(Category_Category16)
        {
            Caption = 'DEXPRO Freeze', Locked = true;
        }
    }

    trigger OnAfterGetCurrRecord()
    begin
        CurrPage."DXP Quick Archive".Page.SetSourceRecord(Rec.RecordId());
    end;

    var
        ExtMgt: Codeunit "DXP Extension Mgt.";
        FrzSearchMgt: Codeunit "DXP Freeze Search Mgt.";
}

Zusätzliche Suchabfragen einrichten

Um auch Archiveinträge anhand von nicht direkt über die System-ID/Tabellennummer verbundenen Datensätzen (via BC archiviert) anzeigen zu können, können zusätzliche Suchabfragen eingerichtet werden. Dies ist hier dokumentiert: https://docs.squeeze.one/books/freeze-for-dynamics-365-bc-de-de/page/zusatzliche-suchabfragen

Testen Ihrer Implementierung

  1. Navigieren Sie zu Ihrer erweiterten Seite
  2. Überprüfen Sie, ob die Quick Freeze Infobox erscheint
  3. Testen Sie die Aktion "Freeze öffnen" (Strg+Alt+F)
  4. Laden Sie Testdateien per Drag-and-Drop hoch
  5. Überprüfen Sie, ob archivierte Datensätze in der Infobox erscheinen

Dieses Integrationsmuster gewährleistet konsistente Freeze-Funktionalität über alle Business Central-Seiten hinweg und behält dabei die von der DEXPRO Freeze Extension etablierte Benutzererfahrung bei.

Anhang Download

Diese Dokumentation bietet umfassende Anleitung für externe AL-Entwickler zum Abrufen und Herunterladen einzelner Anhänge aus DXP Freeze für verknüpfte Business Central Datensätze.

Überblick

Die DXP Freeze Result Management Codeunit bietet Methoden zum direkten Zugriff auf einzelne Anhänge archivierter Datensätze:

  1. GetAttachments - Lädt Metadaten aller Anhänge eines Datensatzes
  2. GetSpecificRecordAttachment - Lädt den Inhalt eines spezifischen Anhangs als Base64
  3. Kombinierte Verwendung ermöglicht gezielte Filterung und Download einzelner Dateien

Diese Methoden ermöglichen präzise Kontrolle über welche Anhänge abgerufen werden und wie sie verarbeitet werden, ohne ZIP-Archive zu erstellen.


Methode 1: GetAttachments

Zweck

Ruft Metadaten aller Anhänge eines archivierten Datensatzes ab. Diese Methode füllt einen temporären DXP FRZ Attachment Result Record mit Informationen über verfügbare Anhänge ohne den eigentlichen Dateiinhalt herunterzuladen.

Verfügbare Überladungen

1. Standard-Verwendung

procedure GetAttachments(RecordHeaderID: Guid; RecordHeaderFileLink: Text[2048]; var TempFrzAttachmentResult: Record "DXP FRZ Attachment Result" temporary)

2. Mit Kontrolle über Datenlöschung

procedure GetAttachments(RecordHeaderID: Guid; RecordHeaderFileLink: Text[2048]; var TempFrzAttachmentResult: Record "DXP FRZ Attachment Result" temporary; ClearAttachmentTable: Boolean)

Parameter

Parameter Typ Beschreibung
RecordHeaderID Guid Eindeutige ID des Freeze-Datensatzes
RecordHeaderFileLink Text[2048] API-Link zum Datensatz (aus DXP FRZ Record Result Header."File Link")
TempFrzAttachmentResult Record (temporary, var) Temporärer Record, der mit Anhang-Metadaten gefüllt wird
ClearAttachmentTable Boolean true = Record vor Befüllung löschen; false = zu bestehenden Einträgen hinzufügen

Rückgabewert

Keine - Die Methode füllt den übergebenen temporären Record mit Anhang-Metadaten.

Anhang-Metadaten-Felder

Jeder Eintrag in TempFrzAttachmentResult enthält:

Feld Beschreibung
ID Eindeutige Anhang-ID (Guid)
Record Header Id Verknüpfung zum übergeordneten Datensatz
Filename Dateiname mit Erweiterung
File Extension Dateierweiterung (automatisch aus Filename extrahiert)
Filesize Größe in Bytes
Author Ersteller des Anhangs
Archived at Archivierungszeitpunkt
File Link API-Link zum Datensatz

Verwendungsbeispiel

procedure ListAttachmentsForSalesInvoice(SalesInvoiceNo: Code[20])
var
    SalesInvoiceHeader: Record "Sales Invoice Header";
    TempFrzResultQueryHeader: Record "DXP FRZ Query Result Header" temporary;
    TempFrzResultRecordHeader: Record "DXP FRZ Record Result Header" temporary;
    TempFrzResultRecordField: Record "DXP FRZ Result Record-Field" temporary;
    TempFrzAttachmentResult: Record "DXP FRZ Attachment Result" temporary;
    FrzSearchMgt: Codeunit "DXP Freeze Search Mgt.";
    FrzResultMgt: Codeunit "DXP FRZ Result Mgt.";
    FrzApiMgt: Codeunit "DXP Freeze API Mgt.";
    QueryResult: JsonObject;
    SearchQuery: Text;
begin
    if not SalesInvoiceHeader.Get(SalesInvoiceNo) then
        Error('Rechnung %1 nicht gefunden.', SalesInvoiceNo);

    // Suchanfrage für diesen Datensatz erstellen
    SearchQuery := FrzSearchMgt.GetSearchCombinationForTable(
        SalesInvoiceHeader.SystemId, 
        Database::"Sales Invoice Header"
    );

    // Freeze durchsuchen
    QueryResult := FrzApiMgt.SubmitSearch(SearchQuery);

    // Ergebnisse verarbeiten - dies füllt den Record Header
    FrzResultMgt.CreateResult(
        QueryResult,
        TempFrzResultQueryHeader,
        TempFrzResultRecordHeader,
        TempFrzResultRecordField,
        TempFrzAttachmentResult,
        SearchQuery
    );

    // Prüfen ob Datensatz gefunden wurde
    if TempFrzResultRecordHeader.FindFirst() then begin
        // Anhänge für diesen Record Header abrufen
        FrzResultMgt.GetAttachments(
            TempFrzResultRecordHeader.ID, 
            TempFrzResultRecordHeader."File Link", 
            TempFrzAttachmentResult
        );

        // Anhänge durchlaufen
        if TempFrzAttachmentResult.FindSet() then
            repeat
                //Verarbeitung der Anhänge
            until TempFrzAttachmentResult.Next() = 0;
    end else
        Message('Keine archivierten Anhänge für Rechnung %1 gefunden.', SalesInvoiceNo);
end;

Methode 2: GetSpecificRecordAttachment (Freeze API Mgt.)

Zweck

Lädt den tatsächlichen Inhalt eines spezifischen Anhangs als Base64-kodierten String herunter. Diese Methode wird typischerweise nach GetAttachments verwendet, um ausgewählte Anhänge herunterzuladen.

Verfügbare Überladungen

1. Mit automatischem Browser-Download

procedure GetSpecificRecordAttachment(FileLink: Text; AttachmentID: Guid; pFileName: Text): Text

2. Mit Kontrolle über Browser-Download

procedure GetSpecificRecordAttachment(FileLink: Text; AttachmentID: Guid; pFileName: Text; DownloadFromStream: Boolean): Text

Parameter

Parameter Typ Beschreibung
FileLink Text API-Link zum Datensatz (aus TempFrzAttachmentResult."File Link")
AttachmentID Guid Eindeutige Anhang-ID (aus TempFrzAttachmentResult.ID)
pFileName Text Dateiname für Download (aus TempFrzAttachmentResult.Filename)
DownloadFromStream Boolean true = Sofortiger Browser-Download; false = Nur Base64 zurückgeben

Rückgabewert

Verwendungsbeispiele

Beispiel 1: Einzelnen Anhang sofort herunterladen

procedure DownloadFirstPDFAttachment(SalesInvoiceNo: Code[20])
var
    SalesInvoiceHeader: Record "Sales Invoice Header";
    TempFrzResultRecordHeader: Record "DXP FRZ Record Result Header" temporary;
    TempFrzResultRecordField: Record "DXP FRZ Result Record-Field" temporary;
    TempFrzResultQueryHeader: Record "DXP FRZ Query Result Header" temporary;
    TempFrzAttachmentResult: Record "DXP FRZ Attachment Result" temporary;
    FrzSearchMgt: Codeunit "DXP Freeze Search Mgt.";
    FrzResultMgt: Codeunit "DXP FRZ Result Mgt.";
    FrzApiMgt: Codeunit "DXP Freeze API Mgt.";
    QueryResult: JsonObject;
    SearchQuery: Text;
begin
    if not SalesInvoiceHeader.Get(SalesInvoiceNo) then
        Error('Rechnung %1 nicht gefunden.', SalesInvoiceNo);

    // Suchanfrage erstellen
    SearchQuery := FrzSearchMgt.GetSearchCombinationForTable(
        SalesInvoiceHeader.SystemId, 
        Database::"Sales Invoice Header"
    );

    // Freeze durchsuchen
    QueryResult := FrzApiMgt.SubmitSearch(SearchQuery);

    // Ergebnisse verarbeiten
    FrzResultMgt.CreateResult(
        QueryResult,
        TempFrzResultQueryHeader,
        TempFrzResultRecordHeader,
        TempFrzResultRecordField,
        TempFrzAttachmentResult,
        SearchQuery
    );

    // Nach PDF-Anhängen filtern
    TempFrzAttachmentResult.SetRange("File Extension", 'pdf');
    
    if TempFrzAttachmentResult.FindFirst() then begin
        // Direkter Download im Browser
        FrzApiMgt.GetSpecificRecordAttachment(
            TempFrzAttachmentResult."File Link",
            TempFrzAttachmentResult.ID,
            TempFrzAttachmentResult.Filename
        );
        
        Message('PDF-Anhang %1 wird heruntergeladen.', TempFrzAttachmentResult.Filename);
    end else
        Message('Keine PDF-Anhänge für Rechnung %1 gefunden.', SalesInvoiceNo);
end;

Beispiel 2: Base64-Inhalt für weitere Verarbeitung

procedure GetAttachmentAsBase64(RecordRef: RecordRef; var AttachmentBase64: Text; var AttachmentFilename: Text): Boolean
var
    TempFrzResultRecordHeader: Record "DXP FRZ Record Result Header" temporary;
    TempFrzResultRecordField: Record "DXP FRZ Result Record-Field" temporary;
    TempFrzResultQueryHeader: Record "DXP FRZ Query Result Header" temporary;
    TempFrzAttachmentResult: Record "DXP FRZ Attachment Result" temporary;
    FrzSearchMgt: Codeunit "DXP Freeze Search Mgt.";
    FrzResultMgt: Codeunit "DXP FRZ Result Mgt.";
    FrzApiMgt: Codeunit "DXP Freeze API Mgt.";
    SystemIdFieldRef: FieldRef;
    QueryResult: JsonObject;
    SystemId: Guid;
    SearchQuery: Text;
begin
    // SystemId aus RecordRef extrahieren
    SystemIdFieldRef := RecordRef.Field(RecordRef.SystemIdNo());
    SystemId := SystemIdFieldRef.Value();

    // Suchanfrage erstellen
    SearchQuery := FrzSearchMgt.GetSearchCombinationForTable(SystemId, RecordRef.Number());

    // Freeze durchsuchen
    QueryResult := FrzApiMgt.SubmitSearch(SearchQuery);

    // Ergebnisse verarbeiten
    FrzResultMgt.CreateResult(
        QueryResult,
        TempFrzResultQueryHeader,
        TempFrzResultRecordHeader,
        TempFrzResultRecordField,
        TempFrzAttachmentResult,
        SearchQuery
    );

    if TempFrzAttachmentResult.FindFirst() then begin
        // Base64-Inhalt abrufen OHNE Browser-Download
        AttachmentBase64 := FrzApiMgt.GetSpecificRecordAttachment(
            TempFrzAttachmentResult."File Link",
            TempFrzAttachmentResult.ID,
            TempFrzAttachmentResult.Filename,
            false  // Kein automatischer Download
        );

        AttachmentFilename := TempFrzAttachmentResult.Filename;
        exit(AttachmentBase64 <> '');
    end;

    exit(false);
end;

Spezialszenario: Ältesten PDF-Anhang herunterladen

Dieses Beispiel zeigt, wie Sie den ältesten (zuerst archivierten) PDF-Anhang eines Datensatzes finden und herunterladen.

Vollständiges Beispiel

procedure DownloadOldestPDFAttachment(RecordVariant: Variant): Boolean
var
    TempFrzResultRecordHeader: Record "DXP FRZ Record Result Header" temporary;
    TempFrzResultRecordField: Record "DXP FRZ Result Record-Field" temporary;
    TempFrzResultQueryHeader: Record "DXP FRZ Query Result Header" temporary;
    TempFrzAttachmentResult: Record "DXP FRZ Attachment Result" temporary;
    FrzSearchMgt: Codeunit "DXP Freeze Search Mgt.";
    FrzResultMgt: Codeunit "DXP FRZ Result Mgt.";
    FrzApiMgt: Codeunit "DXP Freeze API Mgt.";
    RecordRef: RecordRef;
    SystemIdFieldRef: FieldRef;
    QueryResult: JsonObject;
    SystemId: Guid;
    SearchQuery: Text;
    OldestArchiveDate: DateTime;
    AttachmentBase64: Text;
begin
    // RecordRef aus Variant erstellen
    RecordRef.GetTable(RecordVariant);

    // SystemId extrahieren
    SystemIdFieldRef := RecordRef.Field(RecordRef.SystemIdNo());
    SystemId := SystemIdFieldRef.Value();

    // Suchanfrage für diesen Datensatz erstellen
    SearchQuery := FrzSearchMgt.GetSearchCombinationForTable(SystemId, RecordRef.Number());

    // Freeze durchsuchen
    QueryResult := FrzApiMgt.SubmitSearch(SearchQuery);

    // Ergebnisse mit Anhängen abrufen
    FrzResultMgt.CreateResult(
        QueryResult,
        TempFrzResultQueryHeader,
        TempFrzResultRecordHeader,
        TempFrzResultRecordField,
        TempFrzAttachmentResult,
        SearchQuery
    );

    // Nach PDF-Anhängen filtern
    TempFrzAttachmentResult.SetRange("File Extension", 'pdf');

    if TempFrzAttachmentResult.IsEmpty() then begin
        Message('Keine PDF-Anhänge für diesen Datensatz gefunden.');
        exit(false);
    end;

    // Nach Archivierungsdatum aufsteigend sortieren (älteste zuerst)
    TempFrzAttachmentResult.SetCurrentKey("Archived at");
    TempFrzAttachmentResult.Ascending(true);

    // Ersten (ältesten) Anhang abrufen
    if TempFrzAttachmentResult.FindFirst() then begin
        OldestArchiveDate := TempFrzAttachmentResult."Archived at";

        Message('Ältester PDF-Anhang gefunden:\Datei: %1\Größe: %2 KB\Archiviert am: %3\Autor: %4',
            TempFrzAttachmentResult.Filename,
            Round(TempFrzAttachmentResult.Filesize / 1024, 1),
            OldestArchiveDate,
            TempFrzAttachmentResult.Author);

        // Anhang herunterladen
        AttachmentBase64 := FrzApiMgt.GetSpecificRecordAttachment(
            TempFrzAttachmentResult."File Link",
            TempFrzAttachmentResult.ID,
            TempFrzAttachmentResult.Filename,
            true  // Sofortiger Browser-Download
        );

        exit(AttachmentBase64 <> '');
    end;

    exit(false);
end;

Verwendung des Beispiels

// Von einer Verkaufsrechnung aus
var
    SalesInvoiceHeader: Record "Sales Invoice Header";
begin
    SalesInvoiceHeader.Get('SI-001');
    DownloadOldestPDFAttachment(SalesInvoiceHeader);
end;

// Von einer gebuchten Einkaufsrechnung aus
var
    PurchInvHeader: Record "Purch. Inv. Header";
begin
    PurchInvHeader.Get('PI-001');
    DownloadOldestPDFAttachment(PurchInvHeader);
end;

Zusammenfassung

Typischer Workflow

  1. Datensatz identifizieren: SystemId und TableNo des BC-Datensatzes ermitteln
  2. Suchanfrage erstellenFrzSearchMgt.GetSearchCombinationForTable()
  3. Freeze durchsuchenFrzApiMgt.SubmitSearch()
  4. Ergebnisse verarbeitenFrzResultMgt.CreateResult()
  5. Anhänge filtern: Filter auf TempFrzAttachmentResult setzen
  6. Anhänge herunterladenFrzApiMgt.GetSpecificRecordAttachment()

Archivierten Beleg per Suche finden

Die Seite Anhang Download beschreibt, wie Anhänge eines bereits gefundenen Archiv-Datensatzes heruntergeladen werden. Diese Seite beschreibt den vorgelagerten Schritt: Wie findet man den passenden Archiv-Datensatz – auch mandantenübergreifend, z. B. um im Intercompany-Szenario aus einer EK-Rechnung den zugehörigen VK-Beleg des Partnermandanten zu ermitteln.

Ablauf

  1. Suchbegriff (Lucene-Query) zusammenstellen
  2. Suche an Freeze absetzen
  3. Treffer + Anhänge in temporäre Tabellen übernehmen
  4. Anhänge herunterladen (siehe Seite „Anhang Download")

Verwendete öffentliche Prozeduren

DXP Freeze Search Mgt. (Codeunit 70954893)

Prozedur Zweck
GetSearchCombinationForTable(SystemId: Guid; TableNo: Integer): Text Fertige Query für einen lokalen Beleg (SysLink + Kunde/Lieferant + aktuelle Company).
ToSafeSearchTerm(OldSearchTerm: Text): Text Escaping (Lucene) + URL-Encoding eines Suchwertes. Für jeden dynamischen Wert verwenden.
SystemLinkSearchQuery(SystemId: Guid; TableNo: Integer): Text Baustein SysLink:{GUID}-{TableNo}.

Mandantenübergreifend: GetSearchCombinationForTable ist hier nicht nutzbar – die Prozedur benötigt die SystemId des Originalbelegs und hängt automatisch die eigene CompanyName() an. Für die Suche in einem anderen Mandanten daher eine eigene Query bauen und die Partner-Company explizit als Feld Company setzen.

DXP Freeze API Mgt. (Codeunit 70954891)

Prozedur Zweck
SubmitSearch(SearchQuery: Text): JsonObject Suche absetzen (1 Treffer/Seite).
SubmitSearch(SearchQuery: Text; ItemsPerPage: Integer): JsonObject Suche mit Seitengröße.
SubmitSearch(SearchQuery: Text; StoreName: Text[30]): JsonObject Suche in einem bestimmten Store.

DXP FRZ Result Mgt. (Codeunit 70954894)

Prozedur Zweck
CreateResultForAttachments(QueryResult: JsonObject; var TempFrzResultRecordHeader; var TempFrzAttachmentResult; SearchQueryTxt: Text) Übernimmt Treffer + Anhang-Metadaten aus dem JSON in temporäre Tabellen.
HasRecord(SearchString: Text): Boolean Prüft, ob es überhaupt einen Treffer gibt.
GetEffectiveResultsCount(SearchString: Text): Integer Liefert die Trefferanzahl.

Query-Aufbau

Eine Query besteht aus Feldname:"Wert"-Paaren, verknüpft mit AND / OR. Feldnamen verwenden _ statt Leerzeichen/Sonderzeichen (z. B. Sell_to_Customer_No). Werte immer über ToSafeSearchTerm escapen.

Beispiel – VK-Rechnung anhand Belegnummer, Belegtyp und Mandant:

(No:"VK-2026-001" AND Document_Type:"2" AND Company:"VERKAUF AG")

Document_Type:"2" entspricht dem Belegtyp Invoice.

Codebeispiel

procedure FindSalesDocInArchive(SalesDocNo: Code[20]; PartnerCompany: Text): Boolean
var
    TempResultHeader: Record "DXP FRZ Record Result Header" temporary;
    TempAttachment: Record "DXP FRZ Attachment Result" temporary;
    FrzApiMgt: Codeunit "DXP Freeze API Mgt.";
    FrzResultMgt: Codeunit "DXP FRZ Result Mgt.";
    FrzSearchMgt: Codeunit "DXP Freeze Search Mgt.";
    QueryResult: JsonObject;
    SearchQuery: Text;
    Base64: Text;
begin
    // 1) Query bauen (mandantenübergreifend)
    SearchQuery := StrSubstNo(
        '(No:"%1" AND Document_Type:"2" AND Company:"%2")',
        FrzSearchMgt.ToSafeSearchTerm(SalesDocNo),
        FrzSearchMgt.ToSafeSearchTerm(PartnerCompany));

    // 2) Suche absetzen
    QueryResult := FrzApiMgt.SubmitSearch(SearchQuery, 50);

    // 3) Treffer + Anhänge übernehmen
    FrzResultMgt.CreateResultForAttachments(
        QueryResult, TempResultHeader, TempAttachment, SearchQuery);
    if TempAttachment.IsEmpty() then
        exit(false);

    // 4) Anhang als Base64 abrufen (siehe Seite „Anhang Download")
    TempAttachment.FindSet();
    repeat
        Base64 := FrzApiMgt.GetSpecificRecordAttachment(
            TempAttachment."File Link",
            TempAttachment.ID,
            TempAttachment.Filename,
            false); // false = kein direkter Browser-Download
        // ... Base64 weiterverarbeiten (z. B. in Beleganhang speichern)
    until TempAttachment.Next() = 0;

    exit(true);
end;

Hinweis: Das Speichern in den Beleganhang (Tabelle „Document Attachment", 1173) ist Standard-Business-Central-Funktionalität und nicht Teil des Freeze-Moduls.