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
- Anhang Download als Zip
- Freeze Integration in benutzerdefinierte Seiten
- Anhang Download
- Archivierten Beleg per Suche finden
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.
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:
DownloadAttachmentsAsZipWithPagination- Lädt Anhänge aus Suchanfrage-Ergebnissen mit Paginierung herunterDownloadAttachmentsAsZipFromRecord- 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
Boolean:truewenn Anhänge gefunden und heruntergeladen wurden;falseandernfalls
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
Boolean:truewenn Anhänge gefunden und heruntergeladen wurden;falseandernfalls
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
falsezurü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 modifizierenOnBeforeProcessAttachmentForZip: Einzelne Anhänge überspringen oder modifizierenOnAfterGetAttachmentBase64: Anhanginhalt nach Abruf modifizierenOnAfterAddAttachmentToZip: Aktionen nach Hinzufügung zum ZIP durchführenOnNoAttachmentsFound: 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:
- Drag-and-Drop Datei-Upload-Funktionalität
- Anzeige archivierter Datensätze bezogen auf den aktuellen Datensatz
- Schnellzugriff auf Freeze-Operationen
2. Management Codeunits
- "DXP Extension Mgt." - Verwaltet Freeze-Warteschlangen-Operationen und Hintergrundverarbeitung
- "DXP Freeze Search Mgt." - Verwaltet Suchfunktionalität für archivierte Datensätze
- "DXP FRZ Result Mgt." - Verwaltet Ergebnisanzeige und Archiv-Ansicht
3. Wichtige Tabellen
- "DXP Frz Recent Record" - Speichert aktuelle Freeze-Einträge
- "DXP Freeze Setup" - Konfigurationseinstellungen
- "DXP Freeze Record Type" - Verfügbare Datensatztypen für die Archivierung
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:
- Ersetzen Sie
Database::"Ihr Tabellenname"mit der entsprechenden Tabellenkonstante
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
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:
GetAttachments- Lädt Metadaten aller Anhänge eines DatensatzesGetSpecificRecordAttachment- Lädt den Inhalt eines spezifischen Anhangs als Base64- 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
Text: Base64-kodierter Inhalt der Datei- Leerer String wenn Anhang nicht gefunden oder Fehler auftritt
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
- Datensatz identifizieren: SystemId und TableNo des BC-Datensatzes ermitteln
- Suchanfrage erstellen:
FrzSearchMgt.GetSearchCombinationForTable() - Freeze durchsuchen:
FrzApiMgt.SubmitSearch() - Ergebnisse verarbeiten:
FrzResultMgt.CreateResult() - Anhänge filtern: Filter auf
TempFrzAttachmentResultsetzen - Anhänge herunterladen:
FrzApiMgt.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
- Suchbegriff (Lucene-Query) zusammenstellen
- Suche an Freeze absetzen
- Treffer + Anhänge in temporäre Tabellen übernehmen
- 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:
GetSearchCombinationForTableist hier nicht nutzbar – die Prozedur benötigt dieSystemIddes Originalbelegs und hängt automatisch die eigeneCompanyName()an. Für die Suche in einem anderen Mandanten daher eine eigene Query bauen und die Partner-Company explizit als FeldCompanysetzen.
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.