# Extension Entwicklung

In diesem Kapitel werden wichtige Integration Events und Beispiele aufgeführt, die bei der Entwicklung einer Extension für SQUEEZE for BC helfen.  
  
Wichtig:  
Anpassungen dürfen nur durch entsprechend geschulte Berater/Entwickler durchgeführt werden. Diese befinden sich außerdem immer außerhalb des Standard-Supports.

# Squeeze-Ursprungsanhang anpassen

## Übersicht

Das `OnAfterSetOriginFileNameOnBeforeModifyDocumentAttachment` Integration Event ermöglicht es Drittanbieter-Entwicklern, Dokumentenanhang-Datensätze anzupassen, bevor sie während des SQUEEZE-Anhang-Verarbeitungsworkflows modifiziert werden. Ein Ursprungsanhang ist der relevante Anhang, der zur Erstellung eines Belegs führt (z.B. die Rechnung des Lieferanten).

## Event-Deklaration

```PASCAL
[IntegrationEvent(false, false)]
local procedure OnAfterSetOriginFileNameOnBeforeModifyDocumentAttachment(var DocumentAttachment: Record "DXP Document Attachment")
begin
end;
```

## Event-Parameter

<div id="bkmrk-"><div><div><div><svg class="transition-all opacity-100 scale-100" fill="currentColor" height="14" viewbox="0 0 256 256" width="14" xmlns="http://www.w3.org/2000/svg"></svg></div></div></div></div><table class="bg-bg-100 min-w-full border-separate border-spacing-0 text-sm leading-[1.88888] whitespace-normal" id="bkmrk-parameter-typ-beschr" style="width: 815px;"><thead class="border-b-border-100/50 border-b-[0.5px] text-left"><tr class="[tbody>&]:odd:bg-bg-500/10"><th class="text-text-000 [&:not(:first-child)]:-x-[hsla(var(--border-100) / 0.5)] font-400 px-2 [&:not(:first-child)]:border-l-[0.5px]" style="width: 158px;">Parameter</th><th class="text-text-000 [&:not(:first-child)]:-x-[hsla(var(--border-100) / 0.5)] font-400 px-2 [&:not(:first-child)]:border-l-[0.5px]" style="width: 289px;">Typ</th><th class="text-text-000 [&:not(:first-child)]:-x-[hsla(var(--border-100) / 0.5)] font-400 px-2 [&:not(:first-child)]:border-l-[0.5px]" style="width: 368px;">Beschreibung</th></tr></thead><tbody><tr class="[tbody>&]:odd:bg-bg-500/10"><td class="border-t-border-100/50 [&:not(:first-child)]:-x-[hsla(var(--border-100) / 0.5)] border-t-[0.5px] px-2 [&:not(:first-child)]:border-l-[0.5px]" style="width: 158px;">DocumentAttachment</td><td class="border-t-border-100/50 [&:not(:first-child)]:-x-[hsla(var(--border-100) / 0.5)] border-t-[0.5px] px-2 [&:not(:first-child)]:border-l-[0.5px]" style="width: 289px;">`Record "DXP Document Attachment"` (var)</td><td class="border-t-border-100/50 [&:not(:first-child)]:-x-[hsla(var(--border-100) / 0.5)] border-t-[0.5px] px-2 [&:not(:first-child)]:border-l-[0.5px]" style="width: 368px;">Der Dokumentenanhang-Datensatz, der modifiziert werden soll. Als Referenz übergeben, wodurch Änderungen möglich sind.</td></tr></tbody></table>

## Wann wird dieses Event ausgelöst?

Dieses Event wird während der `SaveAttachments`-Prozedur ausgelöst, wenn:

- Ein Anhang von SQUEEZE verarbeitet wird
- Der Anhang als Ursprungsdatei identifiziert wird (`IsOriginFile = true`)
- Das System den Ursprungsdateinamen und das `DXP Is Origin File` Flag gesetzt hat
- Kurz vor dem`DocumentAttachment.Modify(true)`-Aufruf

## Anwendungsfälle

Dieses Integration Event ist nützlich für:

- **Benutzerdefiniertes Füllen von Feldern**: Setzen zusätzlicher benutzerdefinierter Felder im Dokumentenanhang
- **Dateinamen-Transformation**: Anwenden benutzerdefinierter Namenskonventionen oder Formatierung
- **Metadaten-Erweiterung**: Hinzufügen benutzerdefinierter Metadaten oder Tags zu Anhängen
- **Validierungslogik**: Implementierung benutzerdefinierter Validierung vor dem Speichern des Datensatzes
- **Integrationsanforderungen**: Vorbereitung von Daten für externe Systemintegrationen

## Implementierungsbeispiel

### Dateinamen vor Modifikation ändern

```PASCAL
[EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP SQZ API Mgt.", 'OnAfterSetOriginFileNameOnBeforeModifyDocumentAttachment', '', false, false)]
local procedure OnAfterSetOriginFileNameOnBeforeModifyDocumentAttachment(var DocumentAttachment: Record "DXP Document Attachment")
var
    CoreDocument: Record "DXP Document";
    SQZDocumentHeader: Record "DXP SQZ Document Header";
    Vendor: Record Vendor;
    NewFileName: Text[250];
    VendorPrefix: Text[50];
begin
    // Get the core document using the document attachment's Document No.
    if not CoreDocument.Get(DocumentAttachment."Document No.") then
        exit;

    // Check if the core document has a linked SQUEEZE document
    if IsNullGuid(CoreDocument."Linked-to Record Id") then
        exit;

    // Get the SQZ Document Header using the SystemId from Linked-to Record Id
    if not GetSQZDocumentHeaderFromRecordId(CoreDocument."Linked-to Record Id", SQZDocumentHeader) then
        exit;

    // Check if Buy-from Vendor No. is populated
    if SQZDocumentHeader."Buy-from Vendor No." = '' then
        exit;

    // Get the vendor record
    if not Vendor.Get(SQZDocumentHeader."Buy-from Vendor No.") then
        exit;

    // Create vendor prefix from Search Name (fallback to Name if Search Name is empty)
    if Vendor."Search Name" <> '' then
        VendorPrefix := Vendor."Search Name"
    else
        VendorPrefix := Vendor.Name;

    // Clean the vendor prefix (remove invalid filename characters and limit length)
    VendorPrefix := CleanFilenameText(VendorPrefix, 30);

    // Check if vendor prefix already exists in filename to avoid duplicates
    if DocumentAttachment."File Name".StartsWith(VendorPrefix + '_') then
        exit;

    // Create new filename with vendor prefix
    NewFileName := VendorPrefix + '_' + DocumentAttachment."File Name";

    // Update the attachment filename
    DocumentAttachment."File Name" := NewFileName;
end;

local procedure GetSQZDocumentHeaderFromRecordId(LinkedRecordId: Guid; var SQZDocumentHeader: Record "DXP SQZ Document Header"): Boolean
var
    RecRef: RecordRef;
    SystemIdFieldRef: FieldRef;
begin
    // Method 1: Try to get the record directly if LinkedRecordId is actually a SystemId
    if SQZDocumentHeader.GetBySystemId(LinkedRecordId) then
        exit(true);

    // Method 2: If that fails, we need to find the record another way
    // This assumes the Linked-to Record Id might be stored differently
    SQZDocumentHeader.Reset();
    SQZDocumentHeader.SetRange(SystemId, LinkedRecordId);
    exit(SQZDocumentHeader.FindFirst());
end;

local procedure CleanFilenameText(InputText: Text; MaxLength: Integer): Text
var
    CleanText: Text;
begin
    // Remove invalid filename characters
    CleanText := DelChr(InputText, '=', '<>|"/\:*?');
    
    // Replace spaces with underscores for better filename compatibility
    CleanText := CleanText.Replace(' ', '_');
    
    // Limit length
    CleanText := CopyStr(CleanText, 1, MaxLength);
    
    exit(CleanText);
end;
```

## Wichtige Überlegungen

### Datenintegrität

- Der `DocumentAttachment`-Datensatz wird als Referenz übergeben, daher werden alle Änderungen gespeichert
- Berücksichtigen Sie Feldlängenbegrenzungen beim Ändern von Textfeldern

### Performance

- Halten Sie die Verarbeitung schlank, da dieses Event für jeden Ursprungsdatei-Anhang aufgerufen wird
- Erwägen Sie das Zwischenspeichern häufig verwendeter Daten

### Fehlerbehandlung

- Implementieren Sie ordnungsgemäße Fehlerbehandlung, um zu verhindern, dass der Anhang-Speichervorgang fehlschlägt
- Verwenden Sie try-Funktionen für riskante Operationen

## Verwandte Events

- `OnAfterSaveAttachment`: Wird ausgelöst, nachdem jeder Anhang vollständig gespeichert wurde.
- Erwägen Sie die Verwendung dieses alternativen Events, wenn Sie Aktionen nach dem Speichern des Datensatzes durchführen müssen.

## Fehlerbehebung

Wenn Ihr Event Subscriber nicht ausgelöst wird:

- Testen Sie speziell mit Ursprungsdateien (Nicht-Ursprungsdateien lösen dieses Event nicht aus)

# Abweichende Verwendung der SQUEEZE Anhänge

Im Folgenden wird kurz erläutert, wie die durch SQUEEZE for BC heruntergeladenen Anhänge für ein Szenario verwendet werden können, das von der vorgesehenen Verwendung abweicht.

Nach der Erstellung des Einkaufsbeleges aus dem validierten SQUEEZE Beleg haben Sie die Möglichkeit, die Anhänge abzugreifen (sofern diese im Vorfeld heruntergeladen wurden).

```C#
    // Set IsHandled to true and implement your code
    // Do not forget to deactivate "Transfer attachments to target document" in the Document Class Setup
    [EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP Document Mgt.", 'OnBeforeTransferCoreAttachmentsToStandardDocument', '', false, false)]
    local procedure DXPDocMgtOnBeforeTransferCoreAttachmentsToStandardDocument(Document: Record "DXP Document"; var IsHandled: Boolean)
    begin
    	IsHandled := true;
        HandleCoreAttachmentsAccordingToYourNeeds(Document, IsHandled);
    end;

    procedure HandleCoreAttachmentsAccordingToYourNeeds(Document: Record "DXP Document"; var IsHandled: Boolean)
    var
        CoreAttachments: Record "DXP Document Attachment";
        InStr: InStream;
    begin
        CoreAttachments.SetRange("Document No.", Document."No.");
        CoreAttachments.IsEmpty() then
       		exit;
        CoreAttachments.FindSet();
        repeat
            CoreAttachments.Calcfields(Attachment);
            CoreAttachments.Attachment.CreateInStream(InStr);
            // Here you can handle the file stream according to your needs
         	[...]
        until CoreAttachments.Next() = 0;
    end;
```

# Anpassung der Dateinamen von Anhängen nach der Validierung

## <a id="bkmrk-"></a>Überblick

Diese Anleitung zeigt, wie Entwickler die Dateinamen von Anhängen in DEXPRO SQUEEZE anpassen können. Das Event `OnBeforeAddLineToDocumentJObj` wird **nach der Plausibilitätsprüfung** ausgeführt, sodass alle Belegdaten vollständig validiert und verfügbar sind.

## <a id="bkmrk--0"></a>Verfügbares Event

### <a id="bkmrk--1"></a>OnBeforeAddLineToDocumentJObj

**Für Rechnungen/Gutschriften:** `Codeunit 70954657 "DXP SQZ P. Inv/Crdt Memo Impl."`  
**Für Bestellbestätigungen:** `Codeunit 70954658 "DXP SQZ P. Order Conf. Impl."`

**Wann aufgerufen:** Nach Header-Verarbeitung, vor Zeilen-Verarbeitung, **nach Plausibilitätsprüfung**

## <a id="bkmrk--3"></a>Implementierungsbeispiele

### <a id="bkmrk--4"></a>Beispiel 1: Einfacher Kreditorenprefix für Rechnungen/Gutschriften

```PASCAL
codeunit 50100 "Custom Invoice Filename"
{
        [EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP SQZ P. Inv/Crdt Memo Impl.", 'OnBeforeAddLineToDocumentJObj', '', false, false)]
    local procedure OnBeforeAddLineToDocumentJObjInvoice(var DocumentJObj: JsonObject; DocHeader: Record "DXP SQZ Document Header")
    begin
        CustomizeAttachmentFilenames(DocHeader);
    end;

    local procedure CustomizeAttachmentFilenames(DocHeader: Record "DXP SQZ Document Header")
    var
        DocumentAttachment: Record "DXP Document Attachment";
        VendorPrefix: Text;
        NewFileName: Text[1024];
    begin
        // Beenden, wenn keine Kreditorinformationen vorhanden
        if DocHeader."Buy-from Vendor No." = '' then
            exit;

        // Kreditorenprefix erstellen
        VendorPrefix := DocHeader."Buy-from Vendor No." + '_';

        // Alle Anhänge für dieses Dokument bearbeiten
        DocumentAttachment.Reset();
        DocumentAttachment.SetRange("Document No.", DocHeader."Core Document No.");

        if DocumentAttachment.FindSet(true) then
            repeat
                // Überspringen, wenn Prefix bereits vorhanden
                if not DocumentAttachment."File Name".Contains(VendorPrefix) then begin
                    // Neuen Dateinamen mit Prefix erstellen
                    NewFileName := VendorPrefix + DocumentAttachment."File Name";

                    // Aktualisieren
                    DocumentAttachment."File Name" := CopyStr(NewFileName, 1, MaxStrLen(DocumentAttachment."File Name"));
                    DocumentAttachment.Modify(true);
                end;
            until DocumentAttachment.Next() = 0;
    end;
}


```

### <a id="bkmrk--5"></a>Beispiel 2: Bestellbestätigungen - Kreditorenprefix

```PASCAL
codeunit 50101 "Custom Order Conf Filename"
{
    [EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP SQZ P. Order Conf. Impl.", 'OnBeforeAddLineToDocumentJObj', '', false, false)]
    local procedure OnBeforeAddLineToDocumentJObjOrderConf(var DocumentJObj: JsonObject; DocHeader: Record "DXP SQZ Document Header")
    begin
        CustomizeAttachmentFilenames(DocHeader);
    end;

    local procedure CustomizeAttachmentFilenames(DocHeader: Record "DXP SQZ Document Header")
    var
        DocumentAttachment: Record "DXP Document Attachment";
        VendorPrefix: Text;
        NewFileName: Text[1024];
    begin
        // Beenden, wenn keine Kreditorinformationen vorhanden
        if DocHeader."Buy-from Vendor No." = '' then
            exit;

        // Kreditorenprefix erstellen
        VendorPrefix := DocHeader."Buy-from Vendor No." + '_';

        // Alle Anhänge für dieses Dokument bearbeiten
        DocumentAttachment.Reset();
        DocumentAttachment.SetRange("Document No.", DocHeader."Core Document No.");

        if DocumentAttachment.FindSet(true) then
            repeat
                // Überspringen, wenn Prefix bereits vorhanden
                if not DocumentAttachment."File Name".Contains(VendorPrefix) then begin
                    // Neuen Dateinamen mit Prefix erstellen
                    NewFileName := VendorPrefix + DocumentAttachment."File Name";

                    // Aktualisieren
                    DocumentAttachment."File Name" := CopyStr(NewFileName, 1, MaxStrLen(DocumentAttachment."File Name"));
                    DocumentAttachment.Modify(true);
                end;
            until DocumentAttachment.Next() = 0;
    end;
}


```

### <a id="bkmrk--6"></a>Beispiel 3: Beide Dokumenttypen - Verschiedene Prefixe für Quelldateien

```PASCAL
codeunit 50102 "Advanced Filename Handling"
{
    [EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP SQZ P. Inv/Crdt Memo Impl.", 'OnBeforeAddLineToDocumentJObj', '', false, false)]
    local procedure OnBeforeAddLineToDocumentJObjInvoice(var DocumentJObj: JsonObject; DocHeader: Record "DXP SQZ Document Header")
    begin
        CustomizeFilenamesWithSourceHandling(DocHeader);
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP SQZ P. Order Conf. Impl.", 'OnBeforeAddLineToDocumentJObj', '', false, false)]
    local procedure OnBeforeAddLineToDocumentJObjOrderConf(var DocumentJObj: JsonObject; DocHeader: Record "DXP SQZ Document Header")
    begin
        CustomizeFilenamesWithSourceHandling(DocHeader);
    end;

    local procedure CustomizeAttachmentFilenames(DocHeader: Record "DXP SQZ Document Header")
    var
        DocumentAttachment: Record "DXP Document Attachment";
        VendorPrefix: Text;
        NewFileName: Text[1024];
    begin
        // Beenden, wenn keine Kreditorinformationen vorhanden
        if DocHeader."Buy-from Vendor No." = '' then
            exit;

        // Kreditorenprefix erstellen
        VendorPrefix := DocHeader."Buy-from Vendor No." + '_';

        // Alle Anhänge für dieses Dokument bearbeiten
        DocumentAttachment.Reset();
        DocumentAttachment.SetRange("Document No.", DocHeader."Core Document No.");

        if DocumentAttachment.FindSet(true) then
            repeat
                // Überspringen, wenn Prefix bereits vorhanden
                if not DocumentAttachment."File Name".Contains(VendorPrefix) then begin
                    // Neuen Dateinamen mit Prefix erstellen
                    NewFileName := VendorPrefix + DocumentAttachment."File Name";

                    // Aktualisieren
                    DocumentAttachment."File Name" := CopyStr(NewFileName, 1, MaxStrLen(DocumentAttachment."File Name"));
                    DocumentAttachment.Modify(true);
                end;
            until DocumentAttachment.Next() = 0;
    end;
}


```

## <a id="bkmrk--7"></a>Praktische Hinweise

### <a id="bkmrk--8"></a>Verfügbare Daten zur Event-Zeit:

- `DocHeader."Buy-from Vendor No."` - Kreditorennummer (validiert)
- `DocHeader."Document Date"` - Belegdatum
- `DocHeader."Document Reference"` - Belegnummer
- `DocHeader."Core Document No."` - Verknüpfung zu Anhängen
- Alle anderen Belegfelder sind verfügbar

### <a id="bkmrk--9"></a>Beispielresultate:

- **Original**: `"Rechnung_2024_001.pdf"`
- **Mit Kreditorenprefix**: `"VEND001_Rechnung_2024_001.pdf"`
- **Quelldatei**: `"ORIGINAL_VEND001_Rechnung_2024_001.pdf"`

### <a id="bkmrk--10"></a>Best Practices:

- **Duplikatprüfung**: Immer prüfen, ob Prefix bereits vorhanden
- **Längenvalidierung**: Neue Dateinamen dürfen max. 1024 Zeichen haben

# Hinzufügen eines Feldes in der Validierung (ab Version 2.10)

Im Folgenden wird anhand einer Beispielextension dargestellt, wie man als Entwickler Felder in der Validierung hinzufügen kann (hier: Dokumentenklasse Rechnung/Gutschrift).

#### Optionale Integration Events inkl. beispielhafter Prozeduren:

<div id="bkmrk-"></div>```Powershell
codeunit 50100 EventSubs
{
    //
    // [If you want to perform plausibility checks on the newly added header field, this is the place]
    //
    [EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP SQZ P. Inv/Crdt Memo Impl.", 'OnAfterDoHeaderPlausibilityChecks', '', false, false)]
    local procedure SQZPInvCrdtMemoImplOnBeforeDoHeaderPlausibilityChecks(DocHeader: Record "DXP SQZ Document Header"; var PlausibilityCheck: Codeunit "DXP Plausiblity Check Mgt.")
    begin
        CheckCustomer(PlausibilityCheck, DocHeader."Customer No.");
    end;

    // 
    // [If you want to perform plausibility checks on the newly added line field, this is the place]
    //
    [EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP SQZ P. Inv/Crdt Memo Impl.", 'OnAfterDoLinePlausibilityChecks', '', false, false)]
    local procedure SQZPInvCrdtMemoImplOnAfterDoLinePlausibilityChecks(DocHeader: Record "DXP SQZ Document Header"; DocLine: Record "DXP SQZ Document Line"; var PlausibilityCheck: Codeunit "DXP Plausiblity Check Mgt.")
    begin
        CheckCustomer(PlausibilityCheck, DocLine."Customer No.");
    end;

    [TryFunction]
    local procedure CustomerExists(CustomerNo: Code[20])
    var
        Customer: Record Customer;
    begin
        Customer.Get(CustomerNo);
    end;

    local procedure CheckCustomer(var PlausibilityCheck: Codeunit "DXP Plausiblity Check Mgt."; CustomerNo: Code[20]): Boolean
    begin
        if CustomerNo = '' then
            exit(false);

        if not CustomerExists(CustomerNo) then begin
            PlausibilityCheck.AddPlausibilityCheckEntry(GetLastErrorText(), Page::"Customer List");
            exit(false);
        end;

        exit(true);
    end;
}
```

#### Table Extensions:

##### SQUEEZE 4 BC

```Python
tableextension 50100 "SQZ Doc. Header Ext." extends "DXP SQZ Document Header"
{
    fields
    {
        field(50100; "Customer No."; Code[20])
        {
            TableRelation = Customer;
            ValidateTableRelation = false;
            Caption = 'Customer No.';
        }
    }
}
```

```Python
tableextension 50101 "SQZ Doc. Line Ext." extends "DXP SQZ Document Line"
{
    fields
    {
        field(50100; "Customer No."; Code[20])
        {
            TableRelation = Customer;
            ValidateTableRelation = false;
            Caption = 'Customer No.';
        }
    }
}
```

##### Zielbeleg (hier: Einkaufsbeleg)

```Python
tableextension 50102 "Purchase Header Ext." extends "Purchase Header"
{
    fields
    {
        field(50100; "DXP Customer No."; Code[20])
        {
            DataClassification = CustomerContent;
            TableRelation = Customer;
            Caption = 'DEXPRO Customer No.';
        }
    }
}
```

#### Page Extensions:

##### SQUEEZE 4 BC

```Python
pageextension 50100 "SQZ Document Ext." extends "DXP SQZ Document v2"
{
    layout
    {
        addafter(BuyFromVendorInternal)
        {
            field("Customer No. Internal"; Rec."Customer No.")
            {
                ApplicationArea = All;
                ToolTip = 'Specifies the value of the Customer No. field.';
              	//The following code is optional. It is used to highlight a recognized value in the Squeeze Viewer
                trigger OnAssistEdit()
                begin
                    MarkField(Rec.FieldNo("Customer No."), Rec."Customer No.", Rec);
                end;

                trigger OnValidate()
                begin
                    CheckPlausibility();
                end;
            }
        }
        addafter(BuyFromVendorExternal)
        {
            field("Customer No."; Rec."Customer No.")
            {
                ApplicationArea = All;
                ToolTip = 'Specifies the value of the Customer No. field.';
                //The following code is optional. It is used to highlight a recognized value in the Squeeze Viewer
                trigger OnAssistEdit()
                var
                    ApiMgt: Codeunit "DXP SQZ API Mgt.";
                    ViewerResident: Codeunit "DXP SQUEEZE Viewer Resident";
                begin
                    MarkField(Rec.FieldNo("Customer No."), Rec."Customer No.", Rec);
                end;

                trigger OnValidate()
                begin
                    CheckPlausibility();
                end;
            }
        }
    }

    local procedure MarkField(AppFldNo: Integer; FieldVal: Variant; DocHeader: Record "DXP SQZ Document Header")
    var
        ApiMgt: Codeunit "DXP SQZ API Mgt.";
        ViewerResident: Codeunit "DXP SQUEEZE Viewer Resident";
    begin
        ViewerResident := GetViewerResident();
        ApiMgt.MarkField(AppFldNo, FieldVal, ViewerResident, DocHeader);
    end;
}

```

```Python
pageextension 50101 "SQZ Document Sf. Ext." extends "DXP SQZ Document Subform"
{
    layout
    {
        addafter("No.")
        {
            field("Customer No."; Rec."Customer No.")
            {
                ApplicationArea = All;
                ToolTip = 'Specifies the value of the Customer No. field.';
            }
        }
    }
}

```

##### Zielbeleg (hier: Einkaufsbeleg)

```Python
pageextension 50102 "Purchase Invoice Ext." extends "Purchase Invoice"
{
    layout
    {
        addafter("Buy-from Vendor No.")
        {
            field("DXP Customer No."; Rec."DXP Customer No.")
            {
                ToolTip = 'Specifies the value of the DEXPRO Customer No. field.';
                ApplicationArea = all;
            }
        }
    }
}

```

```Python
pageextension 50103 "Purchase Inv. Sf Ext." extends "Purch. Invoice Subform"
{
    layout
    {
        addafter("No.")
        {
            field("DXP Customer No."; Rec."DXP Customer No.")
            {
                ToolTip = 'Specifies the value of the DEXPRO Customer No. field.';
                ApplicationArea = all;
            }
        }
    }
}

```

# Implementierung eines benutzerdefinierten, automatischen Bestellabgleichs

## Einführung

Der automatische Bestellabgleich ist ein wichtiger Bestandteil der Dokumentenverarbeitung in Squeeze for Business Central. Im Standard vergleicht das System eingehende Belege mit vorhandenen Bestellungen und Wareneingängen. Diese Dokumentation zeigt Ihnen, wie Sie diesen Abgleichsprozess um Ihre eigenen Belegarten erweitern können.

Die Standardimplementierung verwendet einen dreistufigen Prozess:

1. Sammeln relevanter Belegnummern basierend auf Geschäftsregeln
2. Für jede gefundene Belegnummer wird ein detaillierter Abgleich durchgeführt
3. Im Anschluss werden die von Squeeze ausgelesenen Positionen um die gefundenen Daten angereichert

Diesen bewährten Ansatz werden wir in der folgenden, beispielhaften Implementierung beibehalten.

## Integrationspunkt

Der zentrale Integrationspunkt ist das `OnBeforePerformAutomaticOrdermatch`-Event in Codeunit 70954632 "DXP SQZ Document Mgt.". Dieses Event wird nach der Erstellung von Kopf- und Positionsdaten aufgerufen.

```PASCAL
[IntegrationEvent(false, false)]
local procedure OnBeforePerformAutomaticOrdermatch(
    DocHeader: Record "DXP SQZ Document Header"; 
    var OrderNoList: List of [Code[20]]; 
    var IsHandled: Boolean)

```

### Parameter

- `DocHeader`: Der Dokumentenkopf mit den zu vergleichenden Daten
- `OrderNoList`: Eine Liste von Belegnummern für den Abgleich
- `IsHandled`: Steuert, ob die Standardverarbeitung übersprungen werden soll

## Implementierungsbeispiel

## Technische Umsetzung

### 1. Erweiterung des Dokumententyp-Enums

Zunächst erweitern wir die möglichen Dokumententypen um unseren eigenen Typ:

```PASCAL
enumextension 50100 "Custom Order Match Doc. Type" extends "DXP Order Match Document Type"
{
    value(50000; "Custom")
    {
        Caption = 'Custom Document';
    }
}
```

### 2. Implementierung der Abgleichslogik

Der zentrale Punkt unserer Implementierung ist eine Codeunit, die den Abgleichsprozess steuert. Integrationspunkt ist :

```PASCAL
//codeunit 50100 "Custom Document Matching"
//{
    [EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP SQZ Document Mgt.", 'OnBeforePerformAutomaticOrdermatch', '', false, false)]
    local procedure OnBeforePerformAutomaticOrdermatch(
        DocHeader: Record "DXP SQZ Document Header";
        var OrderNoList: List of [Code[20]];
        var IsHandled: Boolean)
    var
        CustomSourceDoc: Record "Custom Source Document";
        CustomDocNo: Code[20];
    begin
        // Take control of the matching process and prevent standard processing
        IsHandled := true;
        
        // Find potential matching documents
        CustomSourceDoc.SetRange("Vendor No.", DocHeader."Buy-from Vendor No.");
        // Add your specific document status or type filters
        CustomSourceDoc.SetRange("Document Type", CustomSourceDoc."Document Type"::Order);
        CustomSourceDoc.SetRange(Status, CustomSourceDoc.Status::Released);
        
        // Add matching document numbers to the list
        if CustomSourceDoc.FindSet() then
            repeat
            	CustomDocNo := CustomSourceDoc."No.";
                if IsDocumentEligibleForMatching(CustomSourceDoc, DocHeader) then
                	if not OrderNoList.Contains(CustomDocNo) then
                    	OrderNoList.Add(CustomDocNo);
            until CustomSourceDoc.Next() = 0;

        // Process all collected documents
        ProcessMatchingDocuments(DocHeader, OrderNoList);
    end;

    local procedure IsDocumentEligibleForMatching(
        CustomSourceDoc: Record "Custom Source Document";
        DocHeader: Record "DXP SQZ Document Header"): Boolean
    begin
        // Implement business rules for document selection
        // For example:
        if CustomSourceDoc."Document Date" > DocHeader."Document Date" then
            exit(false);

        if CustomSourceDoc."Currency Code" <> DocHeader."Currency Code" then
            exit(false);

        // Check for open lines that can be matched
        if not HasOpenLinesToMatch(CustomSourceDoc) then
            exit(false);

        exit(true);
    end;

    local procedure HasOpenLinesToMatch(CustomSourceDoc: Record "Custom Source Document"): Boolean
    var
        CustomSourceLine: Record "Custom Source Line";
    begin
        CustomSourceLine.SetRange("Document No.", CustomSourceDoc."No.");
        CustomSourceLine.SetFilter("Outstanding Quantity", '>0');
        exit(not CustomSourceLine.IsEmpty());
    end;
//}
```

### 3. Verarbeitung der gefundenen Belege

Nach dem Sammeln der relevanten Belegnummern erfolgt der eigentliche Abgleich:

```PASCAL
local procedure ProcessMatchingDocuments(
    DocHeader: Record "DXP SQZ Document Header";
    OrderNoList: List of [Code[20]])
var
    TempMatchEntry: Record "DXP SQZ Order Match Entry" temporary;
    OrderNo: Code[20];
begin
    foreach OrderNo in OrderNoList do begin
        // Get matching entries for current document
        GetMatchEntries(OrderNo, TempMatchEntry, DocHeader);
        
        if not TempMatchEntry.IsEmpty() then
            // Try to find and assign matching lines
            TryMatchDocumentLines(TempMatchEntry, DocHeader);
    end;
end;

local procedure GetMatchEntries(
    DocumentNo: Code[20];
    var TempMatchEntry: Record "DXP SQZ Order Match Entry" temporary;
    DocHeader: Record "DXP SQZ Document Header")
var
    CustomSourceLine: Record "Custom Source Line";
begin
    TempMatchEntry.Reset();
    TempMatchEntry.DeleteAll();

    // Get lines from custom document
    CustomSourceLine.SetRange("Document No.", DocumentNo);
    
    if CustomSourceLine.FindSet() then
        repeat
            // Create match entry for each relevant line
            CreateMatchEntry(CustomSourceLine, TempMatchEntry);
        until CustomSourceLine.Next() = 0;
end;

local procedure CreateMatchEntry(
    CustomSourceLine: Record "Custom Source Line";
    var TempMatchEntry: Record "DXP SQZ Order Match Entry" temporary)
begin
    TempMatchEntry.Init();
    TempMatchEntry."Document Type" := "DXP Order Match Document Type"::Custom;
    TempMatchEntry."Document No." := CustomSourceLine."Document No.";
    TempMatchEntry."Document Line No." := CustomSourceLine."Line No.";
    TempMatchEntry."No." := CustomSourceLine."Item No.";
    TempMatchEntry.Quantity := CustomSourceLine.Quantity;
    TempMatchEntry."Direct Unit Cost" := CustomSourceLine."Unit Price";
    TempMatchEntry."Line Amount" := CustomSourceLine.Amount;
    TempMatchEntry.Insert();
end;

```

## Die Matching-Logik im Detail

Der eigentliche Abgleich der Belegzeilen erfolgt nach definierten Geschäftsregeln:

```PASCAL
local procedure TryMatchDocumentLines(
    var TempMatchEntry: Record "DXP SQZ Order Match Entry" temporary;
    DocHeader: Record "DXP SQZ Document Header")
var
    DocLine: Record "DXP SQZ Document Line";
    MatchSetup: Record "Custom Match Setup";
    HasCustomSetup: Boolean;
begin
    // Get configuration
    MatchSetup.Get();
    
    // Process each potential match entry
    if TempMatchEntry.FindSet() then
        repeat
            DocLine.Reset();
            DocLine.SetRange("Document No.", DocHeader."No.");
            DocLine.SetRange("Buy-from Vendor No.", TempMatchEntry."Buy-from Vendor No.");
            DocLine.SetFilter("Allocated Document Line No.", '%1', 0); // Only unallocated lines
            
            // Try matching strategies in order of precision
            if TryExactMatch(DocLine, TempMatchEntry) then
                CheckTolerancesAndAllocate(TempMatchEntry, DocLine, MatchSetup)
            else
                if TryItemReferenceMatch(DocLine, TempMatchEntry) then
                    CheckTolerancesAndAllocate(TempMatchEntry, DocLine, MatchSetup)
                else
                    if TryDescriptionMatch(DocLine, TempMatchEntry) then
                        CheckTolerancesAndAllocate(TempMatchEntry, DocLine, MatchSetup)
                    else
                        if TryBasicValuesMatch(DocLine, TempMatchEntry) then
                            CheckTolerancesAndAllocate(TempMatchEntry, DocLine, MatchSetup);
        until TempMatchEntry.Next() = 0;
end;

local procedure TryExactMatch(
    var DocLine: Record "DXP SQZ Document Line";
    TempMatchEntry: Record "DXP SQZ Order Match Entry" temporary): Boolean
begin
    DocLine.SetRange(Type, TempMatchEntry.Type);
    DocLine.SetFilter("No.", '%1|%2', TempMatchEntry."No.", TempMatchEntry."Item Reference No.");
    DocLine.SetRange("SQZ Quantity", TempMatchEntry.Quantity);
    
    exit(not DocLine.IsEmpty());
end;

local procedure TryItemReferenceMatch(
    var DocLine: Record "DXP SQZ Document Line";
    TempMatchEntry: Record "DXP SQZ Order Match Entry" temporary): Boolean
begin
    DocLine.SetRange(Type);  // Clear previous filters
    DocLine.SetRange("Item Reference No.", TempMatchEntry."Item Reference No.");
    
    exit(not DocLine.IsEmpty());
end;

local procedure TryDescriptionMatch(
    var DocLine: Record "DXP SQZ Document Line";
    TempMatchEntry: Record "DXP SQZ Order Match Entry" temporary): Boolean
begin
    DocLine.SetRange("Item Reference No."); // Clear previous filter
    DocLine.SetFilter("SQZ Description", '@*' + TempMatchEntry.Description + '*');
    
    exit(not DocLine.IsEmpty());
end;

local procedure TryBasicValuesMatch(
    var DocLine: Record "DXP SQZ Document Line";
    TempMatchEntry: Record "DXP SQZ Order Match Entry" temporary): Boolean
begin
    DocLine.SetRange("SQZ Description");  // Clear previous filter
    DocLine.SetRange("SQZ Quantity", TempMatchEntry.Quantity);
    DocLine.SetRange("SQZ Unit Price", TempMatchEntry."Direct Unit Cost");
    
    exit(not DocLine.IsEmpty());
end;

local procedure CheckTolerancesAndAllocate(
    TempMatchEntry: Record "DXP SQZ Order Match Entry" temporary;
    var DocLine: Record "DXP SQZ Document Line";
    MatchSetup: Record "Custom Match Setup")
begin
    DocLine.FindFirst();  // We know there is at least one line
    
    if IsMatchWithinTolerances(DocLine, TempMatchEntry, MatchSetup) then
        AssignMatchData(DocLine, TempMatchEntry);
end;

local procedure IsMatchWithinTolerances(
    DocLine: Record "DXP SQZ Document Line";
    TempMatchEntry: Record "DXP SQZ Order Match Entry" temporary;
    MatchSetup: Record "Custom Match Setup"): Boolean
var
    QtyTolerance: Decimal;
    AmountTolerance: Decimal;
begin
    QtyTolerance := MatchSetup."Quantity Tolerance";
    AmountTolerance := MatchSetup."Amount Tolerance";
    
    if Abs(DocLine."SQZ Quantity" - TempMatchEntry.Quantity) > QtyTolerance then
        exit(false);
        
    if Abs(DocLine."SQZ Unit Price" - TempMatchEntry."Direct Unit Cost") > AmountTolerance then
        exit(false);
        
    exit(true);
end;

local procedure AssignMatchData(
    var DocLine: Record "DXP SQZ Document Line";
    TempMatchEntry: Record "DXP SQZ Order Match Entry" temporary)
begin
    // Assign the matched data to the document line
    DocLine.Validate("Allocated Document Type", "DXP Order Match Document Type"::Custom);
    DocLine.Validate("Allocated Document No.", TempMatchEntry."Document No.");
    DocLine.Validate("Allocated Document Line No.", TempMatchEntry."Document Line No.");
    DocLine.Validate("Allocated Quantity", TempMatchEntry.Quantity);
    DocLine.Validate("Allocated Unit Price", TempMatchEntry."Direct Unit Cost");
    DocLine.Validate("Allocated Line Amount", TempMatchEntry."Line Amount");
    DocLine.Validate("Allocated Line Discount %", TempMatchEntry."Line Discount %");
    DocLine.Modify(true);
end;

```

Bei Fragen zur Implementierung stehen wir Ihnen gerne zur Verfügung.

# Implementierung einer neuen, generischen Dokumentenklasse

## SQUEEZE → Core → Ihre Verarbeitung

Dieser Leitfaden ist bewusst fokussiert und pipeline‑orientiert: Er zeigt, wie Belegdaten aus **SQUEEZE** heruntergeladen, in Warteschlangen verarbeitet und in **DEXPRO Core** persistiert werden – und an welcher Stelle sich eine Drittanbieter‑Extension einklinkt.

Begriff: In diesem Leitfaden meint „SQUEEZE‑App“ die Business‑Central‑Erweiterung, die mit der SQUEEZE API verbunden ist und den Import nach DEXPRO Core steuert.

Annahmen:

- Drittanbieter‑Entwickler arbeiten typischerweise mit **veröffentlichten Symbolen**, nicht mit dem gesamten Quellcode.
- Die SQUEEZE‑App übernimmt **Herunterladen + Importwarteschlange + Erstellung des Core‑Belegs sowie die rudimentäre Erstellung des SQUEEZE‑Belegs unter Berücksichtigung der Feldzuordnung**, sobald die Einrichtung abgeschlossen ist.
- Ihre Extension übernimmt die Fachlogik, nachdem das Core‑Dokument existiert: Quell‑Datensätze erstellen bzw. erweitern, validieren, „Processed JSON“ erzeugen und optional ein eigenes Zielobjekt oder einen eigenen Beleg erstellen.

---

## 1) Der Ende‑zu‑Ende‑Ablauf SQUEEZE → Core

Sobald Dokumentklasse und Einrichtung konfiguriert sind, läuft die Importpipeline der SQUEEZE‑App typischerweise so ab:

1. **`DXP Document Class` erweitern** und Ihre Implementierung binden: `DXP ISource Document`.
2. **Dokumentenklassen‑Einrichtung** für diese Klasse konfigurieren. 
    - Beim Validieren von `DXP SQZ Document Class ID` holt die SQUEEZE‑App automatisch die benötigten Einrichtungswerte aus SQUEEZE (z. B. `DXP SQZ Export Interface ID` und `DXP SQZ Line Table ID`) und füllt sie.
3. Der Job der SQUEEZE‑App ruft `DXP SQZ API Mgt.DownloadDocumentData()` auf. Auch via "Belege herunterladen" aufrufbar 
    - Dabei werden neue Einträge in `DXP SQZ Import Queue` angelegt (Dokument‑ID + Zielmandant).
4. Die SQUEEZE‑App ruft `DXP SQZ Import Queue Mgt.TransferImportQueueEntries()` auf. 
    - Dabei wird die Orchestrierungs‑Codeunit `DXP Sqz Create Document` für die Einträge in der Importwarteschlange ausgeführt.
5. `DXP SQZ API Mgt.CreateCoreAndSQUEEZEDocument(DocClass, DocumentId)` lädt das vollständige JSON. 
    - Es wird `DXP Document Mgt.AddDocument(DocClass, DocumentId, RawJson, …)` aufgerufen.
    - Ergebnis: Ein `DXP Document`‑Datensatz existiert. Status = `Imported`, `JSON Raw` ist gefüllt.
6. Die SQUEEZE‑App ruft Ihre Implementierung `ISourceDocument.CreateSource(Document)` auf. 
    - Ab hier beginnt Ihre Implementierung.

Nach Schritt 6 haben Sie:

- Ein Core‑Container‑Dokument (`DXP Document`).
- Ein SQUEEZE‑Dokument auf Seite der SQUEEZE‑App.
- Ihre Source‑Datensätze sollten existieren, sobald `CreateSource` erfolgreich abgeschlossen ist.

---

## 2) Was Sie implementieren (Interfaces)

### Quell‑Vertrag, also Squeeze Seite: `DXP ISource Document`

Das implementieren Sie, um:

- Ihre editierbaren Source‑Datensätze aus `DXP Document.JSON Raw` zu erstellen
- Source‑Datensätze zu validieren
- „Processed JSON“ zu erzeugen und an Core‑Processing zu übergeben

### Ziel‑Vertrag, also Zielbeleg: `DXP IDocument Processing`

Das implementieren Sie, wenn Core aus dem „Processed JSON“ **einen eigenen Ziel‑Datensatz** (Ihre eigene Tabelle / Ihr eigenes Dokument) erstellen/aktualisieren soll.

---

## 3) Schritt 1 — Dokumentklasse definieren und Quell‑Implementierung zuweisen

Erstellen Sie eine Enum‑Extension und binden Sie Ihre Source‑Implementierung:

```al
enumextension 50100 "MY Document Class Ext." extends "DXP Document Class"
{
    value(50100; "MY Generic Document")
    {
        Caption = 'My Generic Document';
        Implementation = "DXP ISource Document" = "MY Source Document Impl.";
    }
}

```

---

## 4) Schritt 2 — Dokumentenklassen‑Einrichtung konfigurieren (damit die Importwarteschlange gefüllt wird)

Konfigurieren Sie in der **Dokumentenklassen‑Einrichtung** für Ihre neue Dokumentklasse die SQUEEZE‑spezifischen Felder.

Wichtig:

- Das Validieren von `DXP SQZ Document Class ID` triggert das automatische Laden der erforderlichen Konfiguration aus SQUEEZE (z. B. `DXP SQZ Export Interface ID` und `DXP SQZ Line Table ID`).
- Falls der Abruf fehlschlägt, werden diese API‑abgeleiteten Felder wieder geleert.

Ergebnis: Die SQUEEZE‑App kann `DownloadDocumentData()` ausführen und Einträge in `DXP SQZ Import Queue` für Ihre Klasse anlegen.

---

## 5) Schritt 3 — Feldzuordnung sicherstellen (heruntergeladene Zuordnung + Ihre Standardwerte)

Die SQUEEZE‑App kann den Feldkatalog des angeschlossenen Squeeze Mandanten herunterladen und in Core‑Mapping‑Tabellen persistieren (pro Dokumentklasse).

Ihre Rolle als Dokumentklassen‑Implementierer:

- Standard‑Feldzuordnung „Squeeze Field Name → AL Field No.“ via `InitFieldMapping(...)` bereitstellen.
- Alternativ Felder über die Zuordnungs‑Pages mappen (Benutzerdefinierte Feldzuordnung).

### 5.1 `InitFieldMapping` implementieren (eigenständiges Beispiel)

```al
codeunit 50101 "MY Source Document Impl." implements "DXP ISource Document"
{
    procedure InitFieldMapping(var HeaderMapping: Dictionary of [Text, Integer]; var LineMapping: Dictionary of [Text, Integer])
    var
        Header: Record "DXP SQZ Document Header";
        Line: Record "DXP SQZ Document Line";
    begin
        Clear(HeaderMapping);
        Clear(LineMapping);

        // Header fields
        HeaderMapping.Add('vendorNo', Header.FieldNo("Buy-from Vendor No."));
        HeaderMapping.Add('documentDate', Header.FieldNo("Document Date"));
        HeaderMapping.Add('postingDate', Header.FieldNo("Posting Date"));
        HeaderMapping.Add('reference', Header.FieldNo("Document Reference"));

        // Line fields
        LineMapping.Add('description', Line.FieldNo(Description));
        LineMapping.Add('quantity', Line.FieldNo(Quantity));
        LineMapping.Add('unitPrice', Line.FieldNo("Direct Unit Cost"));
    end;

    // other interface methods omitted here...
}

```

### 5.2 Validierungs‑Schalter beim Zuweisen von Feldern: App‑Feld validieren

Wenn die SQUEEZE‑App oder Ihre Quell‑Erstelllogik JSON‑Werte in „App-/Quellfelder“ schreibt, können Sie steuern, ob der **OnValidate‑Trigger** ausgeführt wird.

In `DXP Custom Field Mapping`:

- `Validate App Field` = App‑Feld während des Assignments validieren.
- `Validate` (Ziel) = Validierung beim Transfer in das Ziel‑Feld ausführen.

Nutzen Sie das z. B. wenn Erweiterungsfelder Business‑Logik in `OnValidate` haben oder wenn Sie bewusst trigger‑frei (performanter) zuweisen wollen.

---

## 6) Schritt 4 — Source‑Datensätze in `CreateSource` erstellen

`CreateSource(Document)` wird von der SQUEEZE‑App unmittelbar nach `DXP Document Mgt.AddDocument(...)` aufgerufen, sobald der Core‑Container‑Datensatz existiert.

Minimale Verantwortlichkeiten:

1. `DXP Document.JSON Raw` lesen.
2. SQUEEZE‑Source‑Header/Lines erstellen (`DXP SQZ Document Header` / `DXP SQZ Document Line`).
3. Core‑Dokument mit dem SQUEEZE‑Source‑Header via `UpdateDocumentAfterTransfer` verknüpfen.

Optional (empfohlen, wenn Sie Gleichwertigkeit der Funktionen zu den Standard‑SQUEEZE‑Dokumentklassen möchten): 4) Anhänge herunterladen, wenn `DXP Download Attachments` aktiv ist. 5) Automatische Validierung ausführen, wenn für das Dokument aktiviert.

### 6.1 Beispiel: SQUEEZE‑Header + Lines mit den generischen Helpern erstellen

Was die Helper machen (damit Sie es nicht doppelt implementieren):

- `SqzDocumentMgt.CreateSQUEEZEDocHeader(Document, DocHeader, RawJson)`
    
    
    - Erstellt genau einen Datensatz in `DXP SQZ Document Header` für das Core‑Dokument (`DXP Document`).
    - Setzt Identifikationsfelder wie `Core Document No.` und die SQUEEZE‑`API Document ID`.
    - Weist Header‑Felder aus JSON anhand der konfigurierten Zuordnungen zu: 
        - `DXP Field Mapping` (Feldzuordnung)
        - `DXP Custom Field Mapping` (Benutzerdefinierte Feldzuordnung)
    - Respektiert `DXP Custom Field Mapping.Validate App Field` und entscheidet damit, ob beim Zuweisen der **OnValidate‑Trigger** ausgeführt wird.
    - Führt das Integration‑Event `OnBeforeModifySQUEEZEDocumentHeader(...)` als Extension‑Hook aus.
    - Für die eingebauten Standard‑SQUEEZE‑Klassen wird zusätzlich Standard‑Post‑Processing ausgeführt (z. B. alternative IBAN/USt‑ID‑Ermittlung, Lieferantenaktualisierung, Standard‑Buchungsdatum, Duplikatsprüfung).
- `SqzDocumentMgt.CreateSQUEEZEDocLine(DocHeader, RawJson)`
    
    
    - Erstellt die `DXP SQZ Document Line`‑Datensätze basierend auf den JSON‑Rows.
    - Weist Line‑Felder nach dem gleichen Mapping‑Prinzip zu (Standard + Custom; optional mit Validierungstriggern).
    - Befüllt `Core Document No.` auf den Lines.
    - Für Standard‑SQUEEZE‑Klassen wird zusätzlich Standard‑Line‑Post‑Processing angewendet (z. B. Übertragung erkannter Werte).
    - Persistiert line‑bezogene Hilfsdaten (z. B. Koordinaten/Metadaten, die UI und Matching‑Logik nutzen).

```al
procedure CreateSource(Document: Record "DXP Document"): Boolean
var
    DocClassSetup: Record "DXP Document Class Setup";
    AutoValidationMgt: Codeunit "DXP Automatic Validation Mgt.";
    JsonHelper: Codeunit "DXP Json Helper";
    DocMgt: Codeunit "DXP Document Mgt.";
    SqzDocumentMgt: Codeunit "DXP SQZ Document Mgt.";
    SqzApiMgt: Codeunit "DXP SQZ API Mgt.";
    RawJson: JsonObject;
    DocHeader: Record "DXP SQZ Document Header";
    AutomaticValidation: Boolean;
begin
    if (not Document."JSON Raw".HasValue()) or (Document.Status <> Document.Status::Imported) then
        exit(false);

    // 1) Unverarbeitetes JSON lesen
    RawJson := JsonHelper.JObjectFromBlob(Document.RecordId(), Document.FieldNo("JSON Raw"));

    // 2) SQUEEZE‑Source‑Dokument mit den generischen Hilfsfunktionen erstellen
    //    (Feldzuordnung + optionales Validierungs‑Trigger‑Verhalten wird im Helper behandelt)
    SqzDocumentMgt.CreateSQUEEZEDocHeader(Document, DocHeader, RawJson);
    SqzDocumentMgt.CreateSQUEEZEDocLine(DocHeader, RawJson);

    // 3) Core → SQUEEZE‑Source‑Header verknüpfen
    DocMgt.UpdateDocumentAfterTransfer(Document."No.", Document.Status::Transferred, DocHeader."API Document ID", DocHeader.RecordId());

    // 4) Optional: Anhänge herunterladen und speichern
    //    (gesteuert über die Dokumentenklassen‑Einrichtung)
    if DocClassSetup.Get(DocHeader."Document Class") then
        if DocClassSetup."DXP Download Attachments" then
            SqzApiMgt.DownloadAndSaveAttachments(DocHeader);

    // 5) Optional: Automatische Validierung
    //    Wenn aktiviert, führt die Validierung die Plausibilitätsprüfungen aus und erstellt das „Processed JSON“.
    AutomaticValidation := AutoValidationMgt.AutomaticValidationActivated(DocHeader."No.");
    if AutomaticValidation then
        SqzDocumentMgt.ValidateSQUEEZEDocument(DocHeader, AutomaticValidation);

    exit(true);
end;

```

---

## 7) Schritt 5 — Validierung + Übergabe „Processed JSON“

### 7.1 Validierung: `IsSourceDataPlausible`

Nutzen Sie das Core‑Plausibility‑Framework, damit Fehler konsistent angezeigt werden:

```al
procedure IsSourceDataPlausible(Source: Variant; var TempPlausibilityCheckEntry: Record "DXP Plausibility Check Entry" temporary): Boolean
var
    PlausibilityCheck: Codeunit "DXP Plausiblity Check Mgt.";
    Header: Record "DXP SQZ Document Header";
begin
    if not Source.IsRecord then
        exit(false);

    Header := Source;
    TempPlausibilityCheckEntry.DeleteAll();

    PlausibilityCheck.FieldIsEmpty(Header, Header.FieldNo("Buy-from Vendor No."), 0, false, false);
    PlausibilityCheck.CheckPostingDate(Header."Posting Date", Header.FieldNo("Posting Date"));
    PlausibilityCheck.GetPlausibilityCheckEntries(TempPlausibilityCheckEntry);

    exit(TempPlausibilityCheckEntry.IsEmpty());
end;

```

### 7.2 „Processed JSON“ bauen und an Core übergeben: `CreateProcessedJsonFromSource`

„Processed JSON“ wird von Core in `DXP Document.JSON Processed` gespeichert und an Ihre Zielverarbeitung übergeben.

```al
procedure CreateProcessedJsonFromSource(Source: Variant)
var
    CoreTokenMgt: Codeunit "DXP Core Token Mgt.";
    DocMgt: Codeunit "DXP Document Mgt.";
    JsonHelper: Codeunit "DXP Json Helper";
    DocTransferMgt: Codeunit "DXP Document Transfer Mgt.";
    Header: Record "DXP SQZ Document Header";
    Line: Record "DXP SQZ Document Line";
    HeaderJObj: JsonObject;
    LinesJArr: JsonArray;
    LineJObj: JsonObject;
begin
    Header := Source;

    HeaderJObj.Add(CoreTokenMgt.GetVendorNoTok(), Header."Buy-from Vendor No.");
    HeaderJObj.Add(CoreTokenMgt.GetDocDateTok(), Header."Document Date");
    HeaderJObj.Add(CoreTokenMgt.GetDocReferenceTok(), Header."Document Reference");

    // Benutzerdefinierte Felder (Erweiterungsfelder) aus den Source‑Tabellen hinzufügen
    HeaderJObj.Add(CoreTokenMgt.GetCustomFieldsTok(), JsonHelper.Rec2JsonFieldArray(Header, true, true, Header."Document Class", Enum::"DXP Field Type"::Header));

    Line.SetRange("Document No.", Header."No.");
    if Line.FindSet() then
        repeat
            Clear(LineJObj);
            LineJObj.Add(CoreTokenMgt.GetDescriptionTok(), Line.Description);
            LineJObj.Add(CoreTokenMgt.GetQtyTok(), Line.Quantity);
            LineJObj.Add(CoreTokenMgt.GetCustomFieldsTok(), JsonHelper.Rec2JsonFieldArray(Line, true, true, Header."Document Class", Enum::"DXP Field Type"::Line));
            LinesJArr.Add(LineJObj);
        until Line.Next() = 0;

    DocTransferMgt.AddDocLinesToHeaderJson(HeaderJObj, LinesJArr);

    // An Core‑Zielverarbeitung übergeben
    DocMgt.UpdateDocument(
        Header."Core Document No.",
        Enum::"DXP Document Status"::Transferred,
        Enum::"DXP Target Document Process"::"MY Create Custom Document",
        Enum::"DXP Next process step"::"Standard document",
        HeaderJObj);
end;

```

---

## 8) Schritt 6 — Eigenes Ziel‑Dokument erstellen

Wenn Core Ihren eigenen Ziel‑Datensatz erstellen soll, erweitern Sie `DXP Target Document Process` und implementieren Sie `DXP IDocument Processing`:

```al
enumextension 50102 "MY Target Doc Process Ext." extends "DXP Target Document Process"
{
    value(50102; "MY Create Custom Document")
    {
        Caption = 'Create My Custom Document';
        Implementation = "DXP IDocument Processing" = "MY Custom Doc Creation";
    }
}

codeunit 50103 "MY Custom Doc Creation" implements "DXP IDocument Processing"
{
    procedure ProcessStandardDocument(JObject: JsonObject): RecordId
    var
        CoreTokenMgt: Codeunit "DXP Core Token Mgt.";
        DocTransferMgt: Codeunit "DXP Document Transfer Mgt.";
        JsonHelper: Codeunit "DXP Json Helper";
        MyDoc: Record "MY Custom Document";
        TargetVariant: Variant;
    begin
        MyDoc.Init();
        MyDoc.Validate("Vendor No.", JsonHelper.ValAsTxt(JObject, CoreTokenMgt.GetVendorNoTok(), false));
        MyDoc.Validate("Document Date", JsonHelper.ValAsDate(JObject, CoreTokenMgt.GetDocDateTok(), false));
        MyDoc.Insert(true);

        // Überträgt Metadaten sowie benutzerdefinierte/zusätzliche Felder anhand der Core‑Zuordnungen in den Ziel‑Datensatz.
        TargetVariant := MyDoc;
        DocTransferMgt.ProcessAdditionalInformation(JObject, TargetVariant);
        MyDoc := TargetVariant;

        exit(MyDoc.RecordId());
    end;
}

```

Hinweis: Die Übertragung von Metadaten sowie benutzerdefinierten/zusätzlichen Feldern ist im obigen Beispiel bereits enthalten und basiert auf den Core‑Zuordnungstabellen.

---

## 9) Fehlerbehebung (schnelle Prüfungen)

- Keine Einträge in `DXP SQZ Import Queue` → Dokumentenklassen‑Einrichtung für SQUEEZE ist unvollständig.
- Core‑`DXP Document` existiert, aber kein Source‑Datensatz → Ihr `CreateSource(Document)` ist fehlgeschlagen oder gibt `false` zurück.
- Source existiert, aber Ziel wird nicht erstellt → `CreateProcessedJsonFromSource` wird nicht aufgerufen oder `DXP Target Document Process` ist nicht konfiguriert.
- Falsches Trigger‑Verhalten beim Zuweisen → `DXP Custom Field Mapping.Validate App Field` und `Validate` prüfen. 
    - `Validate` steuert, ob die Validierung des Ziel‑Feldes (OnValidate) ausgeführt wird.

---

## 10) Was Core automatisch macht

Wenn Sie `DXP Document Mgt.UpdateDocument(...)` aufrufen:

- Core speichert das „Processed JSON“ in `DXP Document`.`JSON Processed`.
- Core setzt `DXP Document`.`Status` auf den übergebenen Status.
- Core verknüpft/aktualisiert `DXP Document`.`Linked-to Record Id` mit dem Datensatz, den Ihre `DXP IDocument Processing`‑Implementierung zurückgibt.
- Core überträgt Core‑Attachments auf den zurückgegebenen Datensatz (und hängt typischerweise das „Processed JSON“ als `.json`‑Attachment an).

# Erstellung benutzerdefinierter Dokumente (Individuelle Verarbeitung)

## Inhaltsverzeichnis

1. [Überblick](#%C3%BCberblick)
2. [Verstehen des Dokumentenablaufs](#bkmrk-verstehen-des-dokume)
3. [JSON-Datenstruktur](#bkmrk-json-datenstruktur)
4. [Schnellstart: Einfache benutzerdefinierte Verarbeitung](#bkmrk-schnellstart%3A-einfac)

---

## Überblick

Dieses Handbuch erklärt, wie Sie benutzerdefinierte Business Central-Dokumente aus JSON-Daten erstellen, die von DEXPRO Core bereitgestellt werden. Das System empfängt Dokumente mit dem Status **"Custom Processing"** (Individuelle Verarbeitung), die Drittanbieter-Entwickler in jeden BC-Dokumententyp umwandeln können (Bestellungen, Verkaufsaufträge, Fibu-Buchblätter, Serviceaufträge usw.).

### Was Sie lernen werden

- Wie Sie Dokumente abfangen, die für die individuelle Verarbeitung markiert sind
- Die JSON-Struktur mit Dokumentenkopf, Zeilen, Metadaten und benutzerdefinierten Feldern
- Wie Sie Ihr benutzerdefiniertes Dokument erstellen

---

## Verstehen des Dokumentenablaufs

### Kernworkflow

```
┌─────────────────────────────────────────────────────────────────┐
│ 1. Dokumentenimport                                             │
│    JSON-Rohdaten → DXP Document (Status: Imported)              │
└────────────────────────┬────────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────────┐
│ 2. Erstellung des Quelldokuments                                │
│    ISourceDocument.CreateSource() → Ihre Quelltabellen          │
│    (z.B. SQZ Document Header/Lines)  │
└────────────────────────┬────────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────────┐
│ 3. Validierung & Plausibilitätsprüfungen                        │
│    ISourceDocument.IsSourceDataPlausible()                      │
│    → Validiert Datenqualität, prüft auf Fehler                  │
└────────────────────────┬────────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────────┐
│ 4. Verarbeitetes JSON erstellen                                 │
│    ISourceDocument.CreateProcessedJsonFromSource()              │
│    → Erstellt JSON-Struktur für Zieldokument                    │
└────────────────────────┬────────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────────┐
│ 5. Core-Dokument mit "Custom Processing" Status aktualisieren   │
│    UpdateDocument() → Setzt Status auf "Custom Processing"      │
│    → Speichert JSON Processed in DXP Document                   │
└────────────────────────┬────────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────────┐
│ 6. Ihre benutzerdefinierte Verarbeitung (HIER SETZEN SIE AN!)   │
│    Ereignisse abonnieren oder Interface implementieren          │
│    → JSON Processed aus DXP Document lesen                      │
│    → Ihr BC-Zieldokument erstellen                              │
└────────────────────────┬────────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────────┐
│ 7. Finale Statusaktualisierung                                  │
│    Dokumentstatus → Transferred oder Finished                   │
│    Linked-to Record Id → Ihr erstelltes BC-Dokument             │
└─────────────────────────────────────────────────────────────────┘

```

### Wichtige Statuswerte

<table id="bkmrk-status-beschreibung-"><thead><tr><th>Status</th><th>Beschreibung</th></tr></thead><tbody><tr><td>**Imported**</td><td>JSON-Rohdaten empfangen, noch nicht verarbeitet</td></tr><tr><td>**Transferred**</td><td>Quelldokument erstellt, bereit zur Verarbeitung</td></tr><tr><td>**Custom Processing**</td><td>**Ihr Zielstatus** - Dokument bereit für benutzerdefinierte Verarbeitung</td></tr><tr><td>**Closed**</td><td>Zieldokument erfolgreich verbucht oder ggfs. bezahlt</td></tr><tr><td>**Deleted**</td><td>Verarbeitung abgebrochen, Dokument gelöscht</td></tr></tbody></table>

---

## JSON-Datenstruktur

### Überblick

Das `"JSON Processed"` Blob in der `DXP Document`-Tabelle enthält ein strukturiertes JSON-Objekt mit allen Informationen, die zum Erstellen Ihres Zieldokuments benötigt werden.

### JSON-Struktur (Auszug)

```lang-json
{
  // ═══════════════════════════════════════════════════════════
  // KOPFBEREICH - Dokumentenebene Informationen
  // ═══════════════════════════════════════════════════════════

  "Type": "Invoice",                    // Dokumenttyp: Invoice, Credit Memo, Order, usw.
  "VendorNo": "VENDOR001",              // Kreditoren-/Kundennummer
  "DocumentDate": "2025-10-15",         // Belegdatum
  "PostingDate": "2025-10-16",          // Buchungsdatum
  "DocumentReference": "INV-2025-001",  // Externe Belegnummer
  "OrderNo": "PO-12345",                // Referenz zur Bestellung (falls vorhanden)
  "PostingDescription": "Invoice Q4",   // Buchungsbeschreibung
  "NetAmount": 1000.00,                 // Nettobetrag (ohne MwSt.)
  "TotalAmount": 1190.00,               // Bruttobetrag (inkl. MwSt.)
  "TaxAmount": 190.00,                  // Gesamter Steuerbetrag
  "Currency": "EUR",                    // Währungscode

  // ───────────────────────────────────────────────────────────
  // DIMENSIONEN - Kopfebene Dimensionen als Schlüssel-Wert-Paare
  // ───────────────────────────────────────────────────────────

  "Dimensions": {
    "DEPARTMENT": "SALES",
    "PROJECT": "PROJ001",
    "COSTCENTER": "CC-100"
  },

  // ───────────────────────────────────────────────────────────
  // BENUTZERDEFINIERTE FELDER - Zusätzliche Kopffelder (siehe unten)
  // ───────────────────────────────────────────────────────────

  "CustomFields": [
    {
      "Id": 50100,
      "Name": "CustomerNo",
      "Value": "CUST001",
      "Caption": "Customer Number"
    },
    {
      "Id": 50101,
      "Name": "DeliveryTerms",
      "Value": "EXW",
      "Caption": "Delivery Terms"
    }
  ],

  // ───────────────────────────────────────────────────────────
  // METADATEN - Systemgenerierte Feldzuordnungen
  // ───────────────────────────────────────────────────────────

  "Metadata": [
    {
      "DocumentClass": "DXP Invoice / Credit Memo",
      "FieldType": "Header",
      "FieldId": 101,
      "Value": "Additional Info"
    }
  ],

  // ═══════════════════════════════════════════════════════════
  // ZEILEN-BEREICH - Dokumentzeilen
  // ═══════════════════════════════════════════════════════════

  "Lines": [
    {
      // ─────────────────────────────────────────────────────────
      // Zeilen-Grundinformationen
      // ─────────────────────────────────────────────────────────

      "Type": "Item",                   // Item, G/L Account, Charge (Item), Fixed Asset
      "No": "ITEM001",                  // Artikel-/Kontonummer
      "Description": "Product A",       // Zeilenbeschreibung
      "VendorItemNo": "VEND-SKU-123",   // Kreditorenartikelnummer
      "Quantity": 10.0,                 // Menge
      "UnitOfMeasure": "PCS",          // Einheitencode
      "DirectUnitCost": 100.00,        // EK-Preis
      "LineDiscount": 5.0,             // Zeilenrabatt in Prozent

      // ─────────────────────────────────────────────────────────
      // Zeilen-Beträge & Steuern
      // ─────────────────────────────────────────────────────────

      "NetAmount": 950.00,             // Zeilennettobetrag
      "TotalAmount": 1130.50,          // Zeilenbruttobetrag
      "TaxRate": 19.0,                 // Steuersatz in Prozent
      "VATBusPostingGroup": "DOMESTIC",
      "VATProdPostingGroup": "STANDARD",
      "GenBusPostingGroup": "DOMESTIC",
      "GenProdPostingGroup": "RETAIL",

      // ─────────────────────────────────────────────────────────
      // Bestellreferenz (für Zuordnung)
      // ─────────────────────────────────────────────────────────

      "OrderNo": "PO-12345",           // Referenzierte Bestellnummer
      "OrderLineNo": 10000,            // Referenzierte Bestellzeilennummer
      "ReceiptNo": "RCP-001",          // Referenzierte Wareneingangsnummer
      "ReceiptLineNo": 10000,          // Referenzierte Wareneingangszeile

      // ─────────────────────────────────────────────────────────
      // Zeilen-Dimensionen
      // ─────────────────────────────────────────────────────────

      "Dimensions": {
        "DEPARTMENT": "PROD",
        "PROJECT": "PROJ001"
      },

      // ─────────────────────────────────────────────────────────
      // Zeilen-Benutzerdefinierte Felder
      // ─────────────────────────────────────────────────────────

      "CustomFields": [
        {
          "Id": 50200,
          "Name": "SerialNo",
          "Value": "SN-12345",
          "Caption": "Serial Number"
        }
      ],

      // ─────────────────────────────────────────────────────────
      // Zeilen-Metadaten
      // ─────────────────────────────────────────────────────────

      "Metadata": [
        {
          "DocumentClass": "DXP Invoice / Credit Memo",
          "FieldType": "Line",
          "FieldId": 201,
          "Value": "Line-specific metadata"
        }
      ]
    }
    // ... weitere Zeilen
  ]
}

```

### Benutzerdefinierte Felder vs. Metadaten

**Benutzerdefinierte Felder (Custom Fields):**

- Benutzerdefinierte Felder aus Ihrer App
- Ggfs. zugeordnet über Custom Field Mapping
- Können jedem Feld in der Squeeze-Validierung zugeordnet werden
- Beispiel: Hinzufügen einer "Kundennr." zur Squeeze-Validierung

**Metadaten:**

- Systemgenerierte Felder
- Vom Squeeze-System extrahiert, aber keinem Feld in BC zugeordnet

---

## Schnellstart: Einfache benutzerdefinierte Verarbeitung

### Szenario

Sie möchten einen benutzerdefinierten Dokumenttyp (z.B. einen Warenausgang) aus Dokumenten erstellen, die mit dem Status "Custom Processing" markiert sind.

### Schritt 1: Integration Event abonnieren

Erstellen Sie eine Codeunit, um Dokumente mit dem Status "Custom Processing" abzufangen:

```lang-al
codeunit 50100 "My Custom Document Handler"
{
    [EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP Document Mgt.", 
        'OnAfterWriteProcessedJsonToBlob', '', true, true)]
    local procedure OnAfterWriteProcessedJsonToBlob(
        var Document: Record "DXP Document"; 
        var ProcessedJSONObj: JsonObject)
    begin
        // Nur Custom Processing Status behandeln
        if Document.Status <> Document.Status::"Custom Processing" then
            exit;

        // Ihr benutzerdefiniertes Dokument erstellen
        CreateMyCustomDocument(Document, ProcessedJSONObj);
    end;

    local procedure CreateMyCustomDocument(
        var Document: Record "DXP Document"; 
        ProcessedJSON: JsonObject)
    var
        CoreTokenMgt: Codeunit "DXP Core Token Mgt.";
        JsonHelper: Codeunit "DXP Json Helper";
        MyCustomHeader: Record "My Custom Document Header";
        MyCustomLine: Record "My Custom Document Line";
        LineJArray: JsonArray;
        LineJToken: JsonToken;
        LineJObj: JsonObject;
    begin
        // ═══════════════════════════════════════════════════════
        // 1. KOPF ERSTELLEN
        // ═══════════════════════════════════════════════════════

        MyCustomHeader.Init();
        MyCustomHeader."No." := '';  // Wird durch Nummernserie zugewiesen

        // Standard-Kopffelder lesen
        MyCustomHeader."Vendor No." := 
            JsonHelper.ValAsTxt(ProcessedJSON, CoreTokenMgt.GetVendorNoTok(), false);
        MyCustomHeader."Document Date" := 
            JsonHelper.ValAsDate(ProcessedJSON, CoreTokenMgt.GetDocDateTok(), false);
        MyCustomHeader."Document Reference" := 
            JsonHelper.ValAsTxt(ProcessedJSON, CoreTokenMgt.GetDocReferenceTok(), false);

        MyCustomHeader.Insert(true);

        // ═══════════════════════════════════════════════════════
        // 2. DIMENSIONEN VERARBEITEN (wenn Ihr Dokument diese unterstützt)
        // ═══════════════════════════════════════════════════════

        ProcessHeaderDimensions(ProcessedJSON, MyCustomHeader);

        // ═══════════════════════════════════════════════════════
        // 3. BENUTZERDEFINIERTE FELDER VERARBEITEN
        // ═══════════════════════════════════════════════════════

        ProcessCustomFields(ProcessedJSON, MyCustomHeader);

        // ═══════════════════════════════════════════════════════
        // 4. ZEILEN ERSTELLEN
        // ═══════════════════════════════════════════════════════

        LineJArray := JsonHelper.ReadJArrayFromObj(ProcessedJSON, CoreTokenMgt.GetLinesTok());

        foreach LineJToken in LineJArray do begin
            LineJObj := LineJToken.AsObject();

            MyCustomLine.Init();
            MyCustomLine."Document No." := MyCustomHeader."No.";
            MyCustomLine."Line No." := GetNextLineNo(MyCustomHeader."No.");

            // Zeilenfelder lesen
            MyCustomLine."Item No." := 
                JsonHelper.ValAsTxt(LineJObj, CoreTokenMgt.GetNoTok(), false);
            MyCustomLine.Description := 
                JsonHelper.ValAsTxt(LineJObj, CoreTokenMgt.GetDescriptionTok(), false);
            MyCustomLine.Quantity := 
                JsonHelper.ValAsDec(LineJObj, CoreTokenMgt.GetQtyTok(), false);
            MyCustomLine."Unit of Measure" := 
                JsonHelper.ValAsTxt(LineJObj, CoreTokenMgt.GetUoMTok(), false);

            MyCustomLine.Insert(true);

            // Zeilen-Dimensionen und benutzerdefinierte Felder verarbeiten, falls erforderlich
            ProcessLineDimensions(LineJObj, MyCustomLine);
            ProcessLineCustomFields(LineJObj, MyCustomLine);
        end;

        // ═══════════════════════════════════════════════════════
        // 5. CORE-DOKUMENTSTATUS AKTUALISIEREN
        // ═══════════════════════════════════════════════════════

        UpdateCoreDocument(Document, MyCustomHeader);
    end;

    local procedure ProcessHeaderDimensions(ProcessedJSON: JsonObject; var MyCustomHeader: Record "My Custom Document Header")
    var
        CoreTokenMgt: Codeunit "DXP Core Token Mgt.";
        DocTransferMgt: Codeunit "DXP Document Transfer Mgt.";
        DimensionsJObj: JsonObject;
        DimSetID: Integer;
    begin
        // Dimensionen aus JSON lesen
        if ProcessedJSON.Contains(CoreTokenMgt.GetDimensionsTok()) then begin
            ProcessedJSON.Get(CoreTokenMgt.GetDimensionsTok(), DimensionsJObj);

            // In Dimensionssatz-ID konvertieren
            if DocTransferMgt.GetDimSetIdFromJsonObj(DimensionsJObj, DimSetID) then begin
                MyCustomHeader."Dimension Set ID" := DimSetID;
                MyCustomHeader.Modify(true);
            end;
        end;
    end;

    local procedure ProcessCustomFields(ProcessedJSON: JsonObject; var MyCustomHeader: Record "My Custom Document Header")
    var
        CoreTokenMgt: Codeunit "DXP Core Token Mgt.";
        JsonHelper: Codeunit "DXP Json Helper";
        CustomFieldsJArray: JsonArray;
        CustomFieldJToken: JsonToken;
        CustomFieldJObj: JsonObject;
        FieldName: Text;
        FieldValue: Text;
    begin
        // Benutzerdefinierte Felder-Array lesen
        if not ProcessedJSON.Contains(CoreTokenMgt.GetCustomFieldsTok()) then
            exit;

        CustomFieldsJArray := JsonHelper.ReadJArrayFromObj(ProcessedJSON, CoreTokenMgt.GetCustomFieldsTok());

        foreach CustomFieldJToken in CustomFieldsJArray do begin
            CustomFieldJObj := CustomFieldJToken.AsObject();
            FieldName := JsonHelper.ValAsTxt(CustomFieldJObj, CoreTokenMgt.GetNameTok(), false);
            FieldValue := JsonHelper.ValAsTxt(CustomFieldJObj, CoreTokenMgt.GetValueTok(), false);

            // Zu Ihren benutzerdefinierten Feldern zuordnen
            case FieldName of
                'CustomerNo':
                    MyCustomHeader."Customer No." := CopyStr(FieldValue, 1, 20);
                'DeliveryTerms':
                    MyCustomHeader."Delivery Terms" := CopyStr(FieldValue, 1, 10);
                // Weitere Feldzuordnungen nach Bedarf hinzufügen
            end;
        end;

        MyCustomHeader.Modify(true);
    end;

    local procedure ProcessLineDimensions(LineJObj: JsonObject; var MyCustomLine: Record "My Custom Document Line")
    var
        CoreTokenMgt: Codeunit "DXP Core Token Mgt.";
        DocTransferMgt: Codeunit "DXP Document Transfer Mgt.";
        DimensionsJObj: JsonObject;
        DimSetID: Integer;
    begin
        if LineJObj.Contains(CoreTokenMgt.GetDimensionsTok()) then begin
            LineJObj.Get(CoreTokenMgt.GetDimensionsTok(), DimensionsJObj);

            if DocTransferMgt.GetDimSetIdFromJsonObj(DimensionsJObj, DimSetID) then begin
                MyCustomLine."Dimension Set ID" := DimSetID;
                MyCustomLine.Modify(true);
            end;
        end;
    end;

    local procedure ProcessLineCustomFields(LineJObj: JsonObject; var MyCustomLine: Record "My Custom Document Line")
    var
        CoreTokenMgt: Codeunit "DXP Core Token Mgt.";
        JsonHelper: Codeunit "DXP Json Helper";
        CustomFieldsJArray: JsonArray;
        CustomFieldJToken: JsonToken;
        CustomFieldJObj: JsonObject;
        FieldName: Text;
        FieldValue: Text;
    begin
        if not LineJObj.Contains(CoreTokenMgt.GetCustomFieldsTok()) then
            exit;

        CustomFieldsJArray := JsonHelper.ReadJArrayFromObj(LineJObj, CoreTokenMgt.GetCustomFieldsTok());

        foreach CustomFieldJToken in CustomFieldsJArray do begin
            CustomFieldJObj := CustomFieldJToken.AsObject();
            FieldName := JsonHelper.ValAsTxt(CustomFieldJObj, CoreTokenMgt.GetNameTok(), false);
            FieldValue := JsonHelper.ValAsTxt(CustomFieldJObj, CoreTokenMgt.GetValueTok(), false);

            // Zu Ihren benutzerdefinierten Zeilenfeldern zuordnen
            case FieldName of
                'SerialNo':
                    MyCustomLine."Serial No." := CopyStr(FieldValue, 1, 50);
                // Weitere Feldzuordnungen hinzufügen
            end;
        end;

        MyCustomLine.Modify(true);
    end;

    local procedure UpdateCoreDocument(var Document: Record "DXP Document"; MyCustomHeader: Record "My Custom Document Header")
    var
        DocumentMgt: Codeunit "DXP Document Mgt.";
    begin
        // Core-Dokument aktualisieren, um es mit Ihrem erstellten Dokument zu verknüpfen
        Document.Status := Document.Status::Transferred;
        Document."Linked-to Record Id" := MyCustomHeader.RecordId;
        Document.Modify(true);

        // Optional: Anhänge von Core zu Ihrem Dokument übertragen
        TransferAttachments(Document, MyCustomHeader);
    end;

    local procedure TransferAttachments(Document: Record "DXP Document"; MyCustomHeader: Record "My Custom Document Header")
    var
        DocAttachment: Record "DXP Document Attachment";
        MyDocAttachment: Record "Document Attachment";
        InStr: InStream;
    begin
        DocAttachment.SetRange("Document No.", Document."No.");
        if DocAttachment.FindSet() then
            repeat
                MyDocAttachment.Init();
                MyDocAttachment.ID := 0;

                DocAttachment."File Content".CreateInStream(InStr);
                MyDocAttachment.SaveAttachmentFromStream(
                    InStr, 
                    MyCustomHeader.RecordId, 
                    DocAttachment."File Name");
            until DocAttachment.Next() = 0;
    end;

    local procedure GetNextLineNo(DocumentNo: Code[20]): Integer
    var
        MyCustomLine: Record "My Custom Document Line";
    begin
        MyCustomLine.SetRange("Document No.", DocumentNo);
        if MyCustomLine.FindLast() then
            exit(MyCustomLine."Line No." + 10000);
        exit(10000);
    end;
}

```

### Wichtige Hilfs-Codeunits

<table id="bkmrk-codeunit-zweck-dxp-c"><thead><tr><th>Codeunit</th><th>Zweck</th></tr></thead><tbody><tr><td>**DXP Core Token Mgt.**</td><td>Stellt Token-Namen für JSON-Felder bereit (GetVendorNoTok(), GetDocDateTok(), usw.)</td></tr><tr><td>**DXP Json Helper**</td><td>JSON-Parsing-Hilfsfunktionen (ValAsTxt(), ValAsDate(), ReadJArrayFromObj(), usw.)</td></tr><tr><td>**DXP Document Transfer Mgt.**</td><td>Dimensionsverarbeitung, Metadatenübertragung, Behandlung benutzerdefinierter Felder</td></tr><tr><td>**DXP Document Mgt.**</td><td>Kern-Dokumentenverwaltung (UpdateDocument(), UpdateDocumentStatus(), usw.)</td></tr></tbody></table>

---

*Bei Fragen oder Unklarheiten wenden Sie sich bitte an die DEXPRO Solutions GmbH.*

# Einbetten des SQUEEZE Viewer‑Parts in eigene Pages

Diese Anleitung beschreibt, wie Sie den **SQUEEZE Viewer** in *beliebige* Seiten einbetten (eigene Pages oder Page Extensions) – über den wiederverwendbaren Viewer‑Part:

- Page: **"DXP SQZ Viewer Part"** (PageType = `CardPart`)
- Source table: **"DXP SQZ Document Header"**

Der Viewer‑Part hostet die SQUEEZE Control Add-ins und lädt die Preview‑URL anhand des aktuellen Datensatzes **DXP SQZ Document Header**.

Referenz‑Implementierung, die in dieser App bereits enthalten ist:

- Page **"DXP SQZ Document (Generic)"** (Card, SourceTable **DXP SQZ Document Header**) bettet den Viewer‑Part als FactBox ein und verdrahtet das Resident‑Sharing zur generischen Lines‑Subform.
- Page **"DXP SQZ Doc. Generic Subform"** (ListPart, SourceTable **DXP SQZ Document Line**) enthält eine bekannte, funktionierende Minimal‑Implementierung von `MarkRow()`.

Die Pages können Sie benutzen und mit ihren Feldern erweitern. Die Page **"DXP SQZ Document (Generic)"** verfügt über einen Viewer in der Factbox und die wichtigsten Actions.

---

## 1) Voraussetzungen

1. Ihre Extension muss von der DEXPRO SQUEEZE App abhängig sein (damit Page/Control Add-ins als Symbole verfügbar sind).
2. Der Host‑Datensatz muss eine Möglichkeit bieten, einen **DXP SQZ Document Header** Datensatz zu referenzieren.
    
    
    - Die `SourceTable` des Viewer‑Parts ist fest auf **DXP SQZ Document Header**.
    - Deshalb muss Ihre Seite ein Feld haben (oder bereitstellen), das sich in `SubPageLink` verwenden lässt, um den Header zu finden. Alternativ kann der Record in dem Viewer-Part gesetzt werden - Beispiele folgen weiter unten.
3. SQUEEZE muss aktiviert und konfiguriert sein (Setup + API Key). Der Viewer‑Part lädt absichtlich **nicht**, wenn das Modul nicht aktiviert ist.

---

## 2) Empfohlenes Einbettungsmuster

### 2.1 Als FactBox einbetten (empfohlen)

Für die meisten Szenarien (Card Pages, List Pages) platzieren Sie den Viewer in `area(FactBoxes)`.

#### Wenn die `SourceTable` Ihrer Seite **DXP SQZ Document Header** ist

```al
layout
{
    area(FactBoxes)
    {
        part(SQZViewer; "DXP SQZ Viewer Part")
        {
            ApplicationArea = All;
            Caption = 'Viewer';

            // Link the FactBox part to the current SQZ header
            SubPageLink = "No." = field("No.");
        }
    }
}

```

#### Wenn die `SourceTable` Ihrer Seite NICHT **DXP SQZ Document Header** ist

Sie können den Viewer‑Part trotzdem einbetten, sofern Ihr Datensatz ein Feld enthält, das die SQUEEZE Header‑Nummer speichert.

Beispiel: Ihr Datensatz hat ein Feld `"SQZ Document No." : Code[20]`

```al
layout
{
    area(FactBoxes)
    {
        part(SQZViewer; "DXP SQZ Viewer Part")
        {
            ApplicationArea = All;
            Caption = 'Viewer';

            // Link the viewer part’s "No." to your field
            SubPageLink = "No." = field("SQZ Document No.");
        }
    }
}

```

Wichtig:

- Setze am Part nicht `Enabled = false`. Wenn eine FactBox‑Part deaktiviert ist, kann das verhindern, dass das Control Add-in „ready“ wird – dadurch bleibt der Viewer ggf. dauerhaft leer.

### 2.2 Viewer dynamisch binden (ohne `SubPageLink`)

Wenn Ihre Host‑Seite keinen einfachen `SubPageLink` anbieten kann (z. B. weil es sich um eine Zielbeleg‑Seite wie **Purchase Invoice** handelt und dort die SQUEEZE Belegnummer nicht gespeichert ist), können Sie den Viewer‑Part trotzdem einbetten und im Code binden.

Konzept:

- Viewer‑Part als FactBox hinzufügen – **ohne** `SubPageLink`.
- In `OnAfterGetCurrRecord()` den relevanten `Record "DXP SQZ Document Header"` auflösen.
- Den Datensatz per `CurrPage.<PartName>.Page.SetSqzRecord(SqzHeader);` in den Part pushen.

Warum `SetSqzRecord`?

- Im DEXPRO SQUEEZE Viewer‑Part läuft das Laden der URL in `OnAfterGetCurrRecord()`.
- Beim Navigieren innerhalb einer Seite (z. B. Wechsel zwischen Purchase Invoices) kann `SetRecord(...)` allein dazu führen, dass die FactBox nicht sofort neu lädt.
- `SetSqzRecord(...)` ist dafür gedacht, den Header sauber neu zu binden und einen Refresh auszulösen, sodass die korrekte Preview ohne Schließen/Öffnen der Seite geladen wird.

Beispiel‑Gerüst:

```al
layout
{
    area(FactBoxes)
    {
        part(SQZViewer; "DXP SQZ Viewer Part")
        {
            ApplicationArea = All;
            Caption = 'Viewer';
        }
    }
}

trigger OnAfterGetCurrRecord()
var
    SqzHeader: Record "DXP SQZ Document Header";
begin
    if TryResolveSqzHeaderForCurrentRecord(SqzHeader) then
        CurrPage.SQZViewer.Page.SetSqzRecord(SqzHeader);
end;

```

Dieses Pattern ist besonders für Third‑party Pages hilfreich, weil es vermeidet, „SQUEEZE Document No.“ als Tabellenfeld persistieren zu müssen.

---

## 3) Viewer‑Modus (Belegübersicht / Positionsfokus / Abgedockt)

Wenn der Viewer als FactBox eingebettet ist, sind Felder typischerweise nicht editierbar. Deshalb bietet der Viewer‑Part **Actions**, um den Viewer‑Modus zu ändern.

Was beim Wechsel des Modus passiert:

- **Belegübersicht**: Zeigt eine Übersichtliche Darstellung des Viewers an.
- **Positionsfokus**: Zeigt eine schmalere Darstellung des Viewers an.
- **Abgedockt**: Öffnet den Viewer in einem externen Fenster

Für Third‑party Entwickler:

- Sie müssen keine eigene Mode‑Logik implementieren.
- Nutzen Sie die Actions des Viewer‑Parts (erscheinen im FactBox Action‑Menü).

---

## 4) Optional, aber wichtig: Zeilen‑Highlighting aktivieren (MarkRow)

Wenn Sie eine Lines‑Subform haben und die Benutzererfahrung „Zeile klicken → Viewer markiert Koordinaten“ wünschen, muss Ihre Lines‑Page Messages über **dieselbe Viewer‑Resident‑Instanz** posten, die der Viewer‑Part initialisiert.

Warum das wichtig ist:

- Die Viewer‑UI ist eine Control Add‑in Instanz, die in einer Codeunit `DXP SQUEEZE Viewer Resident` gespeichert wird.
- Wenn Ihre Lines‑Page eine *eigene* Variable `DXP SQUEEZE Viewer Resident` verwendet (die nicht vom Viewer‑Part initialisiert wurde), hat `ApiMgt.PostMessage(...)` keine Control‑Add‑in Instanz zum Ansprechen – es wird nichts markiert.

### 4.1 Pattern (Host‑Page verdrahtet Viewer‑Resident in Lines)

Annahmen:

- Ihre Header‑Page hat: 
    - `part(SQZViewer; "DXP SQZ Viewer Part")`
    - `part(DocumentLines; <Ihre Lines ListPart>)`
- Ihre Lines‑ListPart stellt eine Procedure bereit: 
    - `SetViewerResident(var NewResident: Codeunit "DXP SQUEEZE Viewer Resident")`

Das ist ein expliziter Vertrag/Annahme für interaktives Line‑Highlighting.

Ergänzen Sie das auf Ihrer Header‑Page:

```al
trigger OnAfterGetCurrRecord()
var
    SharedResident: Codeunit "DXP SQUEEZE Viewer Resident";
begin
    CurrPage.SQZViewer.Page.GetViewerResident(SharedResident);
    CurrPage.DocumentLines.Page.SetViewerResident(SharedResident);
end;

```

Hinweise:

- Das gehört in `OnAfterGetCurrRecord()` der Host‑Page (dort ist der aktuelle Header stabil, und die Parts existieren).
- Wenn Ihre Host‑Page andere Part‑Namen verwendet, passen Sie `CurrPage.<PartName>.Page...` entsprechend an.

### 4.2 Was die Lines‑Subform tun muss

Ihre Lines‑Page sollte `MarkRow()` in `OnAfterGetCurrRecord()` aufrufen und `MarkRow()` ähnlich zur Standard‑Seite implementieren:

- Wenn der externe Viewer aktiv ist (Abgedockt), nur die State‑Tabelle aktualisieren.
- Ansonsten `ApiMgt.PostMessage(ApiMgt.MarkFieldByCoordinatesObj(Rec.RecordId), Resident)` aufrufen.

Das ist die Mindestanforderung für interaktives Highlighting.

### 4.3 Referenz‑Implementierung: minimale Lines‑Subform mit `MarkRow`

Das ist ein bekannt funktionierendes Minimal‑Pattern (basierend auf der Standard SQUEEZE Lines‑Subform). Third‑party Entwickler können es kopieren und Felder/Repeater‑Spalten nach Bedarf anpassen:

```al
page 70954672 "DXP SQZ Doc. Generic Subform"
{
    Caption = 'Lines';
    PageType = ListPart;
    AutoSplitKey = true;
    DelayedInsert = true;
    SourceTable = "DXP SQZ Document Line";
    RefreshOnActivate = true;

    layout
    {
        area(content)
        {
            repeater(General)
            {
                field("Line No."; Rec."Line No.")
                {
                    ApplicationArea = All;
                    Visible = false;
                }
                // Add your visible SQZ fields here...
            }
        }
    }

    trigger OnAfterGetCurrRecord()
    begin
        MarkRow();
    end;

    var
        ApiMgt: Codeunit "DXP SQZ API Mgt.";
        Resident: Codeunit "DXP SQUEEZE Viewer Resident";
        MarkedLineNo: Integer;

    procedure SetViewerResident(var NewResident: Codeunit "DXP SQUEEZE Viewer Resident")
    begin
        Resident := NewResident;
        MarkedLineNo := 0;
    end;

    local procedure MarkRow()
    var
        DocHeader: Record "DXP SQZ Document Header";
        LineCoordMgt: Codeunit "DXP SQZ Coordinates Mgt.";
        ViewerState: Codeunit "DXP Viewer State";
    begin
        if MarkedLineNo = Rec."Line No." then
            exit;

        if not LineCoordMgt.CoordinateExists(Rec.RecordId) then
            exit;

        // Detached viewer: update the state table only
        if IsExternalViewerActive() then begin
            if DocHeader.Get(Rec."Document No.") then
                ViewerState.UpdateViewerStateWithCoordinates(DocHeader."API Document ID", Rec.RecordId);
        end else
            // Internal viewer: post message to the control add-in instance via the shared resident
            ApiMgt.PostMessage(ApiMgt.MarkFieldByCoordinatesObj(Rec.RecordId), Resident);

        MarkedLineNo := Rec."Line No.";
    end;

    local procedure IsExternalViewerActive(): Boolean
    var
        ViewerState: Codeunit "DXP Viewer State";
    begin
        exit(ViewerState.GetViewerMode() = "DXP SQZ Viewer Mode"::Detached);
    end;
}

```

---

## 5) Fehlersuche (Viewer bleibt leer)

### 5.1 Der Viewer lädt nie eine Preview

Prüfe zuerst:

- Ist das SQUEEZE Modul aktiviert und das Setup vollständig?
- Hat der verknüpfte **DXP SQZ Document Header** Datensatz eine gültige `"API Document ID"`?
- Ist der Viewer‑Modus aktuell **Abgedockt** und ein externer Viewer offen? 
    - Der interne Viewer lädt nicht, solange der externe Viewer geöffnet ist.

### 5.2 FactBox ist sichtbar, aber Control Add-in initialisiert nicht

- Deaktiviere den Viewer‑FactBox‑Part nicht über `Enabled = false`.
- Stelle sicher, dass die FactBox tatsächlich angezeigt wird (FactBoxes können vom Benutzer eingeklappt/ausgeblendet werden).

### 5.3 MarkRow hebt nichts hervor

- Stelle sicher, dass die Lines‑Page dieselbe Viewer‑Resident‑Instanz wie der Viewer‑Part nutzt (siehe Abschnitt 4).
- Stelle sicher, dass Koordinaten für den Zeilen‑Datensatz existieren (`DXP SQZ Coordinates Mgt.` muss Koordinaten zu `Rec.RecordId` finden).

---

## 6) Minimales Beispiel: Viewer in eine eigene Page Extension einbauen

```al
pageextension 50100 "MY SQZ Doc Ext." extends "DXP SQZ Document (Generic)"
{
    layout
    {
        addfirst(FactBoxes)
        {
            part(MySQZViewer; "DXP SQZ Viewer Part")
            {
                ApplicationArea = All;
                Caption = 'Viewer';
                SubPageLink = "No." = field("No.");
            }
        }
    }
}

```

---

## 7) Hinweise / Einschränkungen

- Der Viewer‑State ist session‑getrieben (via `DXP Viewer State`). Wenn mehrere Seiten mit Viewer gleichzeitig offen sind, können Modus/External‑Viewer‑State alle beeinflussen.
- Der Viewer‑Part ist für Datensätze mit SQUEEZE‑Bezug gedacht; er versucht nicht, automatisch „irgendeinen“ Header zu suchen.

---

## 8) Beispiel: Viewer auf einem Zielbeleg anzeigen (Purchase Invoice)

Dieser Abschnitt adressiert ein häufiges Third‑party Szenario:

Annahmen:

- Der Zielbeleg (z. B. **Purchase Invoice**) wurde aus einem Core‑Beleg erstellt.
- Der Core‑Beleg ist weiterhin in der Tabelle **"DXP Document"** verfügbar.
- Der Core‑Beleg speichert eine Rückverknüpfung zum Ziel‑Datensatz im Feld **"Linked-to Record Id"**.
- Der SQUEEZE Quell‑Header existiert noch und kann aus dem Core‑Beleg aufgelöst werden (typischerweise über **Core Document No.**).

### 8.1 Core‑Beleg aus dem Ziel‑Datensatz auflösen

```al
local procedure TryGetCoreDocumentForTarget(TargetRecId: RecordId; var CoreDoc: Record "DXP Document"): Boolean
begin
    CoreDoc.Reset();
    CoreDoc.SetRange("Linked-to Record Id", TargetRecId);
    exit(CoreDoc.FindFirst());
end;

```

### 8.2 SQUEEZE Header aus dem Core‑Beleg auflösen

```al
local procedure TryGetSqzHeaderForCore(CoreDocNo: Code[20]; var SqzHeader: Record "DXP SQZ Document Header"): Boolean
begin
    SqzHeader.Reset();
    SqzHeader.SetRange("Core Document No.", CoreDocNo);
    exit(SqzHeader.FindFirst());
end;

```

### 8.3 Viewer einbetten (dynamisches Binden)

Beispiel: **Purchase Invoice** erweitern (konzeptionell; Basis‑Page‑Name ggf. an Ihre Umgebung anpassen).

Diese Implementierung verwendet:

- Einen FactBox‑Part
- Ein `ShowViewer`‑Flag, um den Part auszublenden, wenn kein SQZ Header aufgelöst werden kann.
- `SetSqzRecord(...)`, damit die Preview beim Wechsel des Belegs sofort korrekt neu lädt.
- Ein `SingleInstance` Context‑Codeunit, um Viewer‑Resident und SQZ‑Kontext mit der **Purch. Invoice Subform** zu teilen (FactBoxes und Subforms sind separate Pages).

```al
pageextension 50100 "MY Purch. Invoice Viewer" extends "Purchase Invoice"
{
    layout
    {
        addfirst(FactBoxes)
        {
            part(DXPSQZViewer; "DXP SQZ Viewer Part")
            {
                ApplicationArea = All;
                Caption = 'SQUEEZE Viewer';
                Visible = ShowViewer;
            }
        }
    }

    var
        ShowViewer: Boolean;

    trigger OnAfterGetCurrRecord()
    var
        CoreDoc: Record "DXP Document";
        SqzHeader: Record "DXP SQZ Document Header";
        SharedResident: Codeunit "DXP SQUEEZE Viewer Resident";
        ViewerCtx: Codeunit "DXP SQZ Purch. Inv. Viewer Ctx";
    begin
        ShowViewer := false;
        if not TryGetCoreDocumentForTarget(Rec.RecordId, CoreDoc) then
            exit;

        if not TryGetSqzHeaderForCore(CoreDoc."No.", SqzHeader) then
            exit;

        ShowViewer := true;

        CurrPage.DXPSQZViewer.Page.SetSqzRecord(SqzHeader);

        CurrPage.DXPSQZViewer.Page.GetViewerResident(SharedResident);
        ViewerCtx.SetResident(SharedResident);
        ViewerCtx.SetSqzContextForPurchInv(Rec."No.", SqzHeader."No.", SqzHeader."API Document ID");
    end;

    local procedure TryGetCoreDocumentForTarget(TargetRecId: RecordId; var CoreDoc: Record "DXP Document"): Boolean
    begin
        CoreDoc.Reset();
        CoreDoc.SetRange("Linked-to Record Id", TargetRecId);
        exit(CoreDoc.FindFirst());
    end;

    local procedure TryGetSqzHeaderForCore(CoreDocNo: Code[20]; var SqzHeader: Record "DXP SQZ Document Header"): Boolean
    begin
        SqzHeader.Reset();
        SqzHeader.SetRange("Core Document No.", CoreDocNo);
        exit(SqzHeader.FindFirst());
    end;
}

```

Zugehöriges Context‑Codeunit (speichert Resident + SQZ‑Kontext, damit die Subform korrekt highlighten kann):

```al
codeunit 50101 "MY SQZ Purch. Inv. Viewer Ctx"
{
    SingleInstance = true;

    procedure SetResident(var NewResident: Codeunit "DXP SQUEEZE Viewer Resident")
    begin
        Resident := NewResident;
        HasResident := true;
    end;

    procedure TryGetResident(var OutResident: Codeunit "DXP SQUEEZE Viewer Resident"): Boolean
    begin
        if not HasResident then
            exit(false);

        OutResident := Resident;
        exit(true);
    end;

    procedure SetSqzContextForPurchInv(PurchInvNo: Code[20]; SqzDocNo: Code[20]; ApiDocumentId: Text[50])
    begin
        PurchInvToSqzDocNo.Set(PurchInvNo, SqzDocNo);
        PurchInvToApiDocumentId.Set(PurchInvNo, ApiDocumentId);
    end;

    procedure TryGetSqzContextForPurchInv(PurchInvNo: Code[20]; var SqzDocNo: Code[20]; var ApiDocumentId: Text[50]): Boolean
    begin
        if not PurchInvToSqzDocNo.Get(PurchInvNo, SqzDocNo) then
            exit(false);

        PurchInvToApiDocumentId.Get(PurchInvNo, ApiDocumentId);
        exit(true);
    end;

    var
        Resident: Codeunit "DXP SQUEEZE Viewer Resident";
        HasResident: Boolean;
        PurchInvToSqzDocNo: Dictionary of [Code[20], Code[20]];
        PurchInvToApiDocumentId: Dictionary of [Code[20], Text[50]];
}

```

### 8.4 Wichtiger Hinweis: SQUEEZE Header‑Status nach Verarbeitung

Sie haben ein häufiges Setup beschrieben, in dem der SQUEEZE Header nach dem Erstellen des Zielbelegs noch existiert, aber `Status = Deleted` ist.

Der Viewer‑Part kann die Preview trotzdem laden, solange `"API Document ID"` vorhanden ist und SQUEEZE aktiviert ist.

Optionen:

- Wenn Sie die Preview nach Zielbeleg‑Erstellung **nicht** anzeigen möchten, bauen Sie eine Guard‑Logik auf der Host‑Page ein (Viewer‑Part dann nicht binden, wenn der SQZ Header „Deleted“ ist).

Welche Option korrekt ist, hängt von Ihren Prozessanforderungen ab.

---

## 9) Sonderfall: Hervorheben aus der Purchase Invoice Subform (Purchase Line → SQZ line)

Manchmal mächten Sie diese Benutzererfahrung auf einer Zielbeleg‑Seite:

- User navigiert durch **Purchase Lines**.
- Der eingebettete SQUEEZE Viewer hebt den entsprechenden Bereich (Koordinaten) des originalen Belegs, bzw. der Viewer-Abbildung hervor.

In dieser Situation basiert Ihre Lines‑Page **nicht** auf `"DXP SQZ Document Line"`, daher können Sie `MarkFieldByCoordinatesObj(Rec.RecordId)` nicht direkt aufrufen.

Stattdessen müssen Sie die passende SQUEEZE Line auflösen und danach mit der `RecordId` dieser SQUEEZE Line highlighten.

### 9.1 Zwei mögliche Verknüpfungsstrategien

Es gibt zwei realistische Wege, eine Einkaufszeile wieder auf eine SQUEEZE Line zu mappen:

1. **Expliziter Allocation‑Link** (nur in manchen Szenarien verfügbar)
    
    
    - Verwendet Felder auf `"DXP SQZ Document Line"`: 
        - `"Allocated Document Type"` (enum `"DXP Order Match Document Type"`)
        - `"Allocated Document No."`
        - `"Allocated Document Line No."`
2. **Heuristischer Match nach Inhalt** (empfohlen als Fallback)
    
    
    - Verwendet Felder, die typischerweise auf beiden Seiten existieren (Type/No/Quantity/Unit Cost/Line Amount/Description).
    - Das funktioniert auch dann, wenn keine Allocation‑Beziehung gepflegt wird.
    - Es ist nicht mathematisch perfekt (Duplikate sind möglich), daher sollten Matching‑Regeln auf Ihren Use Case angepasst werden.

### 9.2 Empfohlenes Wiring (Host‑Page übergibt SQZ‑Kontext + Resident)

Sie benötigen in der Purchase‑Lines‑Subpage zwei Dinge:

1. Den Viewer‑Resident (damit `PostMessage(...)` die richtige Control‑Add‑in Instanz trifft)
2. Den aktuellen SQZ Header‑Kontext (damit Sie SQZ Lines auf das richtige Dokument filtern)

Empfohlenes Pattern auf der Host‑Page (nachdem Sie `SqzHeader` aufgelöst haben):

```al
trigger OnAfterGetCurrRecord()
var
    SharedResident: Codeunit "DXP SQUEEZE Viewer Resident";
    SqzHeader: Record "DXP SQZ Document Header";
begin
    if not TryResolveSqzHeaderForCurrentRecord(SqzHeader) then
        exit;

    CurrPage.DXPSQZViewer.Page.SetSqzRecord(SqzHeader);

    CurrPage.DXPSQZViewer.Page.GetViewerResident(SharedResident);
    ViewerCtx.SetResident(SharedResident);
    ViewerCtx.SetSqzContextForPurchInv(Rec."No.", SqzHeader."No.", SqzHeader."API Document ID");
end;

```

### 9.3 Minimales Beispiel: Purch. Invoice Subform triggert Highlighting im Viewer

Dieses Beispiel erweitert **Purch. Invoice Subform**. Beachten Sie: der Datensatz der Basis‑Subform ist ein `Record "Purchase Line"`, daher müssen die Resolver‑Prozeduren `Record "Purchase Line"` akzeptieren.

Das hier gezeigte Allocation‑Mapping ist die funktionierende Implementierung für Purchase‑Invoice‑Szenarien: es verknüpft über **Receipt** (`"Receipt No."` / `"Receipt Line No."`) statt über Belegnummer/Zeilennummer der Rechnung.

```al
pageextension 50101 "MY Purch. Inv. Sf Mark" extends "Purch. Invoice Subform"
{
    trigger OnAfterGetCurrRecord()
    begin
        MarkSqzLineForPurchInvLine();
    end;

    var
        ApiMgt: Codeunit "DXP SQZ API Mgt.";
        Resident: Codeunit "DXP SQUEEZE Viewer Resident";
        ViewerState: Codeunit "DXP Viewer State";
        LineCoordMgt: Codeunit "DXP SQZ Coordinates Mgt.";
        ViewerCtx: Codeunit "DXP SQZ Purch. Inv. Viewer Ctx";
        SqzDocNo: Code[20];
        ApiDocumentId: Text[50];

    local procedure MarkSqzLineForPurchInvLine()
    var
        SqzLine: Record "DXP SQZ Document Line";
    begin
        if not ViewerCtx.TryGetSqzContextForPurchInv(Rec."Document No.", SqzDocNo, ApiDocumentId) then
            exit;

        if not ViewerCtx.TryGetResident(Resident) then
            exit;

        if not TryResolveSqzLineForPurchLine(SqzDocNo, Rec, SqzLine) then
            exit;

        if not LineCoordMgt.CoordinateExists(SqzLine.RecordId) then
            exit;

        if ViewerState.GetViewerMode() = "DXP SQZ Viewer Mode"::Detached then
            ViewerState.UpdateViewerStateWithCoordinates(ApiDocumentId, SqzLine.RecordId)
        else
            ApiMgt.PostMessage(ApiMgt.MarkFieldByCoordinatesObj(SqzLine.RecordId), Resident);
    end;

    local procedure TryResolveSqzLineForPurchLine(SqzDocNo: Code[20]; PurchLine: Record "Purchase Line"; var SqzLine: Record "DXP SQZ Document Line"): Boolean
    begin
        // 1) Allocation link via receipt (Purchase Invoice Szenario)
        SqzLine.Reset();
        SqzLine.SetRange("Document No.", SqzDocNo);
        SqzLine.SetFilter("Allocated Document No.", '%1&<>%2', PurchLine."Receipt No.", '');
        SqzLine.SetFilter("Allocated Document Line No.", '%1&<>%2', PurchLine."Receipt Line No.", 0);
        if SqzLine.FindFirst() then
            exit(true);

        // 2) Fallback: content-based match (Type/No/Amounts)
        exit(TryResolveSqzLineByContent(SqzDocNo, PurchLine, SqzLine));
    end;

    local procedure TryResolveSqzLineByContent(SqzDocNo: Code[20]; PurchLine: Record "Purchase Line"; var BestSqzLine: Record "DXP SQZ Document Line"): Boolean
    var
        Candidate: Record "DXP SQZ Document Line";
        BestScore: Integer;
        Score: Integer;
        QtyTol: Decimal;
        AmtTol: Decimal;
    begin
        QtyTol := 0.00001;
        AmtTol := 0.01;

        BestScore := 0;
        Candidate.Reset();
        Candidate.SetRange("Document No.", SqzDocNo);

        // Prefer narrowing early on stable fields
        Candidate.SetRange(Type, PurchLine.Type);
        if PurchLine."No." <> '' then
            Candidate.SetRange("No.", PurchLine."No.");

        if not Candidate.FindSet() then
            exit(false);

        repeat
            Score := 0;

            // Strong signals
            if Candidate.Type = PurchLine.Type then
                Score += 20;
            if (PurchLine."No." <> '') and (Candidate."No." = PurchLine."No.") then
                Score += 40;

            // Weak/medium signals (tolerant)
            if (Candidate.Quantity <> 0) and (Abs(Candidate.Quantity - PurchLine.Quantity) <= QtyTol) then
                Score += 10;
            if (Candidate."Direct Unit Cost" <> 0) and (Abs(Candidate."Direct Unit Cost" - PurchLine."Direct Unit Cost") <= AmtTol) then
                Score += 10;
            if (Candidate."Line Amount" <> 0) and (Abs(Candidate."Line Amount" - PurchLine."Line Amount") <= AmtTol) then
                Score += 10;
            if (Candidate.Description <> '') and (PurchLine.Description <> '') and (Candidate.Description = PurchLine.Description) then
                Score += 5;

            if Score > BestScore then begin
                BestScore := Score;
                BestSqzLine := Candidate;
            end;
        until Candidate.Next() = 0;

        // Guard: avoid picking a random line when nothing matches well.
        exit(BestScore >= 30);
    end;
}
```

# Hinzufügen eines Feldes in der Validierung (bis Squeeze BC APP Version 1.*)

Im Folgenden wird anhand einer Beispielextension dargestellt, wie man als Entwickler Felder in der Validierung hinzufügen kann.

#### Benötigte Integration Events inkl. beispielhafter Prozeduren:

<div id="bkmrk-"></div>```Powershell
codeunit 50100 EventSubs
{
    //
    //Any header field that you want to transfer to the resulting document, has to be added to the source JSON Object
    //
    [EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP SQZ Document Mgt.", 'OnBeforeAddLineToDocumentJObj', '', false, false)]
    local procedure SQZDocMgtOnBeforeAddLineToDocumentJObj(var DocumentJObj: JsonObject; DocHeader: Record "DXP SQZ Document Header")
    begin
        AddCustomHeaderFieldsToJson(DocumentJObj, DocHeader);
    end;

    //
    // Any line field that you want to transfer to the resulting document, has to be added to the source JSON Object
    //
    [EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP SQZ Document Mgt.", 'OnBeforeAddLineJObjToLineJArray', '', false, false)]
    local procedure SQZDocMgtOnBeforeAddLineJObjToLineJArray(var LineJObj: JsonObject; DocLine: Record "DXP SQZ Document Line")
    begin
        AddCustomLineFieldsToJson(LineJObj, DocLine);
    end;

    //
    // [If you want to perform plausibility checks on the newly added header field, this is the place]
    //
    [EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP SQZ Document Mgt.", 'OnAfterDoHeaderPlausibilityChecks', '', false, false)]
    local procedure SQZDocMgtOnBeforeDoHeaderPlausibilityChecks(DocHeader: Record "DXP SQZ Document Header"; var PlausibilityCheck: Codeunit "DXP Plausiblity Check Mgt.")
    begin
        CheckCustomer(PlausibilityCheck, DocHeader."Customer No.");
    end;

    //
    // [If you want to perform plausibility checks on the newly added line field, this is the place]
    //
    [EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP SQZ Document Mgt.", 'OnAfterDoLinePlausibilityChecks', '', false, false)]
    local procedure SQZDocMgtOnAfterDoLinePlausibilityChecks(DocHeader: Record "DXP SQZ Document Header"; DocLine: Record "DXP SQZ Document Line"; var PlausibilityCheck: Codeunit "DXP Plausiblity Check Mgt.")
    begin
        CheckCustomer(PlausibilityCheck, DocLine."Customer No.");
    end;

    //
    // The value of the previously extended JSON Object now has to saved to the corresponding field in the SQUEEZE Document Line
    //
    [EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP SQZ Document Mgt.", 'OnBeforeModifySQUEEZEDocumentHeader', '', false, false)]
    local procedure SqzDocMgtOnBeforeModifySQUEEZEDocumentHeader(var DocHeader: Record "DXP SQZ Document Header"; RawJson: JsonObject)
    var
        JSONHelper: Codeunit "DXP Json Helper";
        SQZApiTokenMgt: Codeunit "DXP SQZ API Token Mgt.";
        TokenMgt: Codeunit TokenMgt;
    begin
        DocHeader."Customer No." := COPYSTR(JsonHelper.ValAsTxt(RawJson, SQZApiTokenMgt.GetHeaderValueByNameTok(TokenMgt.GetCustomerNoTok()), false), 1, MaxStrLen(DocHeader."Customer No."));
    end;

    //
    // The value of the previously extended JSON Object now has to saved to the corresponding field in the SQUEEZE Document Header
    //
    [EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP SQZ Document Mgt.", 'OnBeforeTransferRecognizedValuesOnAfterAssignRecognizedValues', '', false, false)]
    local procedure SQZDocMgtOnBeforeTransferRecognizedValuesOnAfterAssignRecognizedValues(RowJTok: JsonToken; var DocLine: Record "DXP SQZ Document Line")
    var
        JSONHelper: Codeunit "DXP Json Helper";
        SQZApiTokenMgt: Codeunit "DXP SQZ API Token Mgt.";
        TokenMgt: Codeunit TokenMgt;
    begin
        DocLine."Customer No." := CopyStr(JsonHelper.ValAsTxt(RowJTok.AsObject(), SQZApiTokenMgt.GetCellValueTok(TokenMgt.GetCustomerNoTok()), false), 1, MaxStrLen(DocLine."Customer No."));
    end;

    //
    // The newly added fields now have to be added to the field mapping
    // Otherwise the recognized values will be saved as meta data
    // This step is necessary to make sure that the fields can be highlighted in the SQUEEZE Viewer
    //
    [EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP SQZ API Mgt.", 'OnAfterInitMapping', '', false, false)]
    local procedure SQZApiMgtOnAfterInitMapping(var HeaderMapping: Dictionary of [Text, Integer]; var LineMapping: Dictionary of [Text, Integer]; DocClass: Enum "DXP Document Class")
    var
        SQZDocHeader: Record "DXP SQZ Document Header";
        SQZDocLine: Record "DXP SQZ Document Line";
        TokenMgt: Codeunit TokenMgt;
    begin
        // The standard (without installed extensions) supports one Document Class
        case DocClass of
            DocClass::"DXP Invoice / Credit Memo":
                begin
                    HeaderMapping.Add(TokenMgt.GetCustomerNoTok(), SQZDocHeader.FieldNo("Customer No."));
                    LineMapping.Add(TokenMgt.GetCustomerNoTok(), SQZDocLine.FieldNo("Customer No."));
                end;
        end;
    end;

    //
    // The value in the JSON Object now has to be transferred to the Purchase Header
    //
    [EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP Document Transfer Mgt.", 'OnAfterCreatePurchaseHeader', '', false, false)]
    local procedure DocTransferMgtOnAfterCreatePurchaseHeader(var JObject: JsonObject; PurchaseHeader: Record "Purchase Header")
    begin
        TransferCustomHeaderFieldFromJsonToPurchaseHeader(JObject, PurchaseHeader);
    end;

    //
    // The value in the JSON Object now has to be transferred to the Purchase Line
    //
    [EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP Document Transfer Mgt.", 'OnBeforeInsertPurchaseLine', '', false, false)]
    local procedure DocTransferMgtOnAfterInsertPurchaseLineOnBeforeAddDimensions(var JObjectLine: JsonObject; var PurchaseLine: Record "Purchase Line")
    begin
        TransferCustomHeaderFieldFromJsonToPurchaseLine(JObjectLine, PurchaseLine);
    end;
    
    local procedure TransferCustomHeaderFieldFromJsonToPurchaseHeader(var JObject: JsonObject; PurchaseHeader: Record "Purchase Header")
    var
        JsonHelper: Codeunit "DXP Json Helper";
        TokenMgt: Codeunit TokenMgt;
    begin
        PurchaseHeader.Validate("DXP Customer No.", JsonHelper.ValAsTxt(JObject, TokenMgt.GetCustomerNoTok(), false));
        PurchaseHeader.Modify();
    end;

    local procedure TransferCustomHeaderFieldFromJsonToPurchaseLine(var JObjectLine: JsonObject; var PurchaseLine: Record "Purchase Line")
    var
        JsonHelper: Codeunit "DXP Json Helper";
        TokenMgt: Codeunit TokenMgt;
    begin
        PurchaseLine.Validate("DXP Customer No.", JsonHelper.ValAsTxt(JObjectLine, TokenMgt.GetCustomerNoTok(), false));
    end;

    local procedure AddCustomHeaderFieldsToJson(var DocumentJObj: JsonObject; SQZDocumentHeader: Record "DXP SQZ Document Header")
    var
        TokenMgt: Codeunit TokenMgt;
    begin
        DocumentJObj.Add(TokenMgt.GetCustomerNoTok(), SQZDocumentHeader."Customer No.");
    end;

    local procedure AddCustomLineFieldsToJson(var LineJObj: JsonObject; SQZDocumentLine: Record "DXP SQZ Document Line")
    var
        TokenMgt: Codeunit TokenMgt;
    begin
        LineJObj.Add(TokenMgt.GetCustomerNoTok(), SQZDocumentLine."Customer No.");
    end;

    [TryFunction]
    local procedure CustomerExists(CustomerNo: Code[20])
    var
        Customer: Record Customer;
    begin
        Customer.Get(CustomerNo);
    end;

    local procedure CheckCustomer(var PlausibilityCheck: Codeunit "DXP Plausiblity Check Mgt."; CustomerNo: Code[20]): Boolean
    begin
        if CustomerNo = '' then
            exit(false);

        if not CustomerExists(CustomerNo) then begin
            PlausibilityCheck.AddPlausibilityCheckEntry(GetLastErrorText(), Page::"Customer List");
            exit(false);
        end;

        exit(true);
    end;
}
```

#### Table Extensions:

##### SQUEEZE 4 BC

```Python
tableextension 50100 "SQZ Doc. Header Ext." extends "DXP SQZ Document Header"
{
    fields
    {
        field(50100; "Customer No."; Code[20])
        {
            TableRelation = Customer;
            ValidateTableRelation = false;
            Caption = 'Customer No.';
        }
    }
}
```

```Python
tableextension 50101 "SQZ Doc. Line Ext." extends "DXP SQZ Document Line"
{
    fields
    {
        field(50100; "Customer No."; Code[20])
        {
            TableRelation = Customer;
            ValidateTableRelation = false;
            Caption = 'Customer No.';
        }
    }
}
```

##### Zielbeleg (hier: Einkaufsbeleg)

```Python
tableextension 50102 "Purchase Header Ext." extends "Purchase Header"
{
    fields
    {
        field(50100; "DXP Customer No."; Code[20])
        {
            DataClassification = CustomerContent;
            TableRelation = Customer;
            Caption = 'DEXPRO Customer No.';
        }
    }
}
```

#### Page Extensions:

##### SQUEEZE 4 BC

```Python
pageextension 50100 "SQZ Document Ext." extends "DXP SQZ Document"
{
    layout
    {
        addafter(BuyFromVendorInternal)
        {
            field("Customer No. Internal"; Rec."Customer No.")
            {
                ApplicationArea = All;
                ToolTip = 'Specifies the value of the Customer No. field.';
                trigger OnAssistEdit()
                begin
                    MarkField(Rec.FieldNo("Customer No."), Rec."Customer No.", Rec);
                end;

                trigger OnValidate()
                begin
                    CheckPlausibility();
                end;
            }
        }
        addafter(BuyFromVendor)
        {
            field("Customer No."; Rec."Customer No.")
            {
                ApplicationArea = All;
                ToolTip = 'Specifies the value of the Customer No. field.';
                trigger OnAssistEdit()
                var
                    ApiMgt: Codeunit "DXP SQZ API Mgt.";
                    ViewerResident: Codeunit "DXP SQUEEZE Viewer Resident";
                begin
                    MarkField(Rec.FieldNo("Customer No."), Rec."Customer No.", Rec);
                end;

                trigger OnValidate()
                begin
                    CheckPlausibility();
                end;
            }
        }
    }

    local procedure MarkField(AppFldNo: Integer; FieldVal: Variant; DocHeader: Record "DXP SQZ Document Header")
    var
        ApiMgt: Codeunit "DXP SQZ API Mgt.";
        ViewerResident: Codeunit "DXP SQUEEZE Viewer Resident";
    begin
        ViewerResident := GetViewerResident();
        ApiMgt.MarkField(AppFldNo, FieldVal, ViewerResident, DocHeader);
    end;
}

```

```Python
pageextension 50101 "SQZ Document Sf. Ext." extends "DXP SQZ Document Subform"
{
    layout
    {
        addafter("No.")
        {
            field("Customer No."; Rec."Customer No.")
            {
                ApplicationArea = All;
                ToolTip = 'Specifies the value of the Customer No. field.';
            }
        }
    }
}

```

##### Zielbeleg (hier: Einkaufsbeleg)

```Python
pageextension 50102 "Purchase Invoice Ext." extends "Purchase Invoice"
{
    layout
    {
        addafter("Buy-from Vendor No.")
        {
            field("DXP Customer No."; Rec."DXP Customer No.")
            {
                ToolTip = 'Specifies the value of the DEXPRO Customer No. field.';
                ApplicationArea = all;
            }
        }
    }
}

```

```Python
pageextension 50103 "Purchase Inv. Sf Ext." extends "Purch. Invoice Subform"
{
    layout
    {
        addafter("No.")
        {
            field("DXP Customer No."; Rec."DXP Customer No.")
            {
                ToolTip = 'Specifies the value of the DEXPRO Customer No. field.';
                ApplicationArea = all;
            }
        }
    }
}

```

##### Optionale Hilfsobjekte:

```Python
codeunit 50101 TokenMgt
{
    procedure GetCustomerNoTok(): Text
    begin
        exit(CustomerNoTok);
    end;

    var
        CustomerNoTok: Label 'customerNo', Locked = true;
}
```

# Hinzufügen eines Feldes in der Validierung (ab Version 2.0)

Im Folgenden wird anhand einer Beispielextension dargestellt, wie man als Entwickler Felder in der Validierung hinzufügen kann (hier: Dokumentenklasse Rechnung/Gutschrift).

#### Benötigte Integration Events inkl. beispielhafter Prozeduren:

<div id="bkmrk-"></div>```Powershell
codeunit 50100 EventSubs
{
    //
    //Any header field that you want to transfer to the resulting document, has to be added to the source JSON Object
    //
    [EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP SQZ P. Inv/Crdt Memo Impl.", 'OnBeforeAddLineToDocumentJObj', '', false, false)]
    local procedure SQZPInvCrdtMemoImplOnBeforeAddLineToDocumentJObj(var DocumentJObj: JsonObject; DocHeader: Record "DXP SQZ Document Header")
    begin
        AddCustomHeaderFieldsToJson(DocumentJObj, DocHeader);
    end;

    //
    // Any line field that you want to transfer to the resulting document, has to be added to the source JSON Object
    //
    [EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP SQZ P. Inv/Crdt Memo Impl.", 'OnBeforeAddLineJObjToLineJArray', '', false, false)]
    local procedure SQZPInvCrdtMemoImplOnBeforeAddLineJObjToLineJArray(var LineJObj: JsonObject; DocLine: Record "DXP SQZ Document Line")
    begin
        AddCustomLineFieldsToJson(LineJObj, DocLine);
    end;

    //
    // [If you want to perform plausibility checks on the newly added header field, this is the place]
    //
    [EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP SQZ P. Inv/Crdt Memo Impl.", 'OnAfterDoHeaderPlausibilityChecks', '', false, false)]
    local procedure SQZPInvCrdtMemoImplOnBeforeDoHeaderPlausibilityChecks(DocHeader: Record "DXP SQZ Document Header"; var PlausibilityCheck: Codeunit "DXP Plausiblity Check Mgt.")
    begin
        CheckCustomer(PlausibilityCheck, DocHeader."Customer No.");
    end;

    //
    // [If you want to perform plausibility checks on the newly added line field, this is the place]
    //
    [EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP SQZ P. Inv/Crdt Memo Impl.", 'OnAfterDoLinePlausibilityChecks', '', false, false)]
    local procedure SQZPInvCrdtMemoImplOnAfterDoLinePlausibilityChecks(DocHeader: Record "DXP SQZ Document Header"; DocLine: Record "DXP SQZ Document Line"; var PlausibilityCheck: Codeunit "DXP Plausiblity Check Mgt.")
    begin
        CheckCustomer(PlausibilityCheck, DocLine."Customer No.");
    end;

    //
    // The value of the previously extended JSON Object now has to saved to the corresponding field in the SQUEEZE Document Line
    //
    [EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP SQZ Document Mgt.", 'OnBeforeModifySQUEEZEDocumentHeader', '', false, false)]
    local procedure SqzDocMgtOnBeforeModifySQUEEZEDocumentHeader(var DocHeader: Record "DXP SQZ Document Header"; RawJson: JsonObject)
    var
        JSONHelper: Codeunit "DXP Json Helper";
        SQZApiTokenMgt: Codeunit "DXP SQZ API Token Mgt.";
        TokenMgt: Codeunit TokenMgt;
    begin
        DocHeader."Customer No." := COPYSTR(JsonHelper.ValAsTxt(RawJson, SQZApiTokenMgt.GetHeaderValueByNameTok(TokenMgt.GetCustomerNoTok()), false), 1, MaxStrLen(DocHeader."Customer No."));
    end;

    //
    // The value of the previously extended JSON Object now has to saved to the corresponding field in the SQUEEZE Document Header
    //
    [EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP SQZ Document Mgt.", 'OnBeforeTransferRecognizedValuesOnAfterAssignRecognizedValues', '', false, false)]
    local procedure SQZDocMgtOnBeforeTransferRecognizedValuesOnAfterAssignRecognizedValues(RowJTok: JsonToken; var DocLine: Record "DXP SQZ Document Line")
    var
        JSONHelper: Codeunit "DXP Json Helper";
        SQZApiTokenMgt: Codeunit "DXP SQZ API Token Mgt.";
        TokenMgt: Codeunit TokenMgt;
    begin
        DocLine."Customer No." := CopyStr(JsonHelper.ValAsTxt(RowJTok.AsObject(), SQZApiTokenMgt.GetCellValueTok(TokenMgt.GetCustomerNoTok()), false), 1, MaxStrLen(DocLine."Customer No."));
    end;

    //
    // The newly added fields now have to be added to the field mapping
    // Otherwise the recognized values will be saved as meta data
    // This step is also necessary to make sure that the fields can be highlighted in the SQUEEZE Viewer
    //
    [EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP SQZ API Mgt.", 'OnAfterInitMapping', '', false, false)]
    local procedure SQZApiMgtOnAfterInitMapping(var HeaderMapping: Dictionary of [Text, Integer]; var LineMapping: Dictionary of [Text, Integer]; DocClass: Enum "DXP Document Class")
    var
        SQZDocHeader: Record "DXP SQZ Document Header";
        SQZDocLine: Record "DXP SQZ Document Line";
        TokenMgt: Codeunit TokenMgt;
    begin
        case DocClass of
            DocClass::"DXP Invoice / Credit Memo":
                begin
                    HeaderMapping.Add(TokenMgt.GetCustomerNoTok(), SQZDocHeader.FieldNo("Customer No."));
                    LineMapping.Add(TokenMgt.GetCustomerNoTok(), SQZDocLine.FieldNo("Customer No."));
                end;
        end;
    end;

    //
    // The value in the JSON Object now has to be transferred to the Purchase Header
    //
    [EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP Document Transfer Mgt.", 'OnAfterCreatePurchaseHeader', '', false, false)]
    local procedure DocTransferMgtOnAfterCreatePurchaseHeader(var JObject: JsonObject; PurchaseHeader: Record "Purchase Header")
    begin
        TransferCustomHeaderFieldFromJsonToPurchaseHeader(JObject, PurchaseHeader);
    end;

    //
    // The value in the JSON Object now has to be transferred to the Purchase Line
    //
    [EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP Document Transfer Mgt.", 'OnBeforeInsertPurchaseLine', '', false, false)]
    local procedure DocTransferMgtOnAfterInsertPurchaseLineOnBeforeAddDimensions(var JObjectLine: JsonObject; var PurchaseLine: Record "Purchase Line")
    begin
        TransferCustomHeaderFieldFromJsonToPurchaseLine(JObjectLine, PurchaseLine);
    end;
    
    local procedure TransferCustomHeaderFieldFromJsonToPurchaseHeader(var JObject: JsonObject; PurchaseHeader: Record "Purchase Header")
    var
        JsonHelper: Codeunit "DXP Json Helper";
        TokenMgt: Codeunit TokenMgt;
    begin
        PurchaseHeader.Validate("DXP Customer No.", JsonHelper.ValAsTxt(JObject, TokenMgt.GetCustomerNoTok(), false));
        PurchaseHeader.Modify();
    end;

    local procedure TransferCustomHeaderFieldFromJsonToPurchaseLine(var JObjectLine: JsonObject; var PurchaseLine: Record "Purchase Line")
    var
        JsonHelper: Codeunit "DXP Json Helper";
        TokenMgt: Codeunit TokenMgt;
    begin
        PurchaseLine.Validate("DXP Customer No.", JsonHelper.ValAsTxt(JObjectLine, TokenMgt.GetCustomerNoTok(), false));
    end;

    local procedure AddCustomHeaderFieldsToJson(var DocumentJObj: JsonObject; SQZDocumentHeader: Record "DXP SQZ Document Header")
    var
        TokenMgt: Codeunit TokenMgt;
    begin
        DocumentJObj.Add(TokenMgt.GetCustomerNoTok(), SQZDocumentHeader."Customer No.");
    end;

    local procedure AddCustomLineFieldsToJson(var LineJObj: JsonObject; SQZDocumentLine: Record "DXP SQZ Document Line")
    var
        TokenMgt: Codeunit TokenMgt;
    begin
        LineJObj.Add(TokenMgt.GetCustomerNoTok(), SQZDocumentLine."Customer No.");
    end;

    [TryFunction]
    local procedure CustomerExists(CustomerNo: Code[20])
    var
        Customer: Record Customer;
    begin
        Customer.Get(CustomerNo);
    end;

    local procedure CheckCustomer(var PlausibilityCheck: Codeunit "DXP Plausiblity Check Mgt."; CustomerNo: Code[20]): Boolean
    begin
        if CustomerNo = '' then
            exit(false);

        if not CustomerExists(CustomerNo) then begin
            PlausibilityCheck.AddPlausibilityCheckEntry(GetLastErrorText(), Page::"Customer List");
            exit(false);
        end;

        exit(true);
    end;
}
```

#### Table Extensions:

##### SQUEEZE 4 BC

```Python
tableextension 50100 "SQZ Doc. Header Ext." extends "DXP SQZ Document Header"
{
    fields
    {
        field(50100; "Customer No."; Code[20])
        {
            TableRelation = Customer;
            ValidateTableRelation = false;
            Caption = 'Customer No.';
        }
    }
}
```

```Python
tableextension 50101 "SQZ Doc. Line Ext." extends "DXP SQZ Document Line"
{
    fields
    {
        field(50100; "Customer No."; Code[20])
        {
            TableRelation = Customer;
            ValidateTableRelation = false;
            Caption = 'Customer No.';
        }
    }
}
```

##### Zielbeleg (hier: Einkaufsbeleg)

```Python
tableextension 50102 "Purchase Header Ext." extends "Purchase Header"
{
    fields
    {
        field(50100; "DXP Customer No."; Code[20])
        {
            DataClassification = CustomerContent;
            TableRelation = Customer;
            Caption = 'DEXPRO Customer No.';
        }
    }
}
```

#### Page Extensions:

##### SQUEEZE 4 BC

```Python
pageextension 50100 "SQZ Document Ext." extends "DXP SQZ Document v2"
{
    layout
    {
        addafter(BuyFromVendorInternal)
        {
            field("Customer No. Internal"; Rec."Customer No.")
            {
                ApplicationArea = All;
                ToolTip = 'Specifies the value of the Customer No. field.';
                trigger OnAssistEdit()
                begin
                    MarkField(Rec.FieldNo("Customer No."), Rec."Customer No.", Rec);
                end;

                trigger OnValidate()
                begin
                    CheckPlausibility();
                end;
            }
        }
        addafter(BuyFromVendorExternal)
        {
            field("Customer No."; Rec."Customer No.")
            {
                ApplicationArea = All;
                ToolTip = 'Specifies the value of the Customer No. field.';
                trigger OnAssistEdit()
                var
                    ApiMgt: Codeunit "DXP SQZ API Mgt.";
                    ViewerResident: Codeunit "DXP SQUEEZE Viewer Resident";
                begin
                    MarkField(Rec.FieldNo("Customer No."), Rec."Customer No.", Rec);
                end;

                trigger OnValidate()
                begin
                    CheckPlausibility();
                end;
            }
        }
    }

    local procedure MarkField(AppFldNo: Integer; FieldVal: Variant; DocHeader: Record "DXP SQZ Document Header")
    var
        ApiMgt: Codeunit "DXP SQZ API Mgt.";
        ViewerResident: Codeunit "DXP SQUEEZE Viewer Resident";
    begin
        ViewerResident := GetViewerResident();
        ApiMgt.MarkField(AppFldNo, FieldVal, ViewerResident, DocHeader);
    end;
}

```

```Python
pageextension 50101 "SQZ Document Sf. Ext." extends "DXP SQZ Document Subform"
{
    layout
    {
        addafter("No.")
        {
            field("Customer No."; Rec."Customer No.")
            {
                ApplicationArea = All;
                ToolTip = 'Specifies the value of the Customer No. field.';
            }
        }
    }
}

```

##### Zielbeleg (hier: Einkaufsbeleg)

```Python
pageextension 50102 "Purchase Invoice Ext." extends "Purchase Invoice"
{
    layout
    {
        addafter("Buy-from Vendor No.")
        {
            field("DXP Customer No."; Rec."DXP Customer No.")
            {
                ToolTip = 'Specifies the value of the DEXPRO Customer No. field.';
                ApplicationArea = all;
            }
        }
    }
}

```

```Python
pageextension 50103 "Purchase Inv. Sf Ext." extends "Purch. Invoice Subform"
{
    layout
    {
        addafter("No.")
        {
            field("DXP Customer No."; Rec."DXP Customer No.")
            {
                ToolTip = 'Specifies the value of the DEXPRO Customer No. field.';
                ApplicationArea = all;
            }
        }
    }
}

```

##### Optionale Hilfsobjekte:

```Python
codeunit 50101 TokenMgt
{
    procedure GetCustomerNoTok(): Text
    begin
        exit(CustomerNoTok);
    end;

    var
        CustomerNoTok: Label 'customerNo', Locked = true;
}
```

# Regelbasierter nächster Prozessschritt: Belege in „Individuelle Verarbeitung“ steuern

## Zielgruppe

Diese Anleitung richtet sich an Entwickler und Implementierer, die DEXPRO SQUEEZE in Business Central einsetzen und **vor** der eigentlichen „[Individuelle Verarbeitung](https://docs.squeeze.one/books/squeeze-for-dynamics-365-bc-de-de/page/erstellung-benutzerdefinierter-dokumente-individuelle-verarbeitung "Erstellung benutzerdefinierter Dokumente (Individuelle Verarbeitung)")“-Implementierung steuern möchten, **welche** Belege nach der Validierung in den Status **„Individuelle Verarbeitung“** wechseln sollen.

## Einordnung (kommt *vor* der bestehenden Custom-Processing-Anleitung)

Mit dieser Konfiguration sorgst du dafür, dass ein Beleg (abhängig von Regeln) den Status **„Individuelle Verarbeitung“** erhält. **Ab diesem Zeitpunkt** greift dann der in der bestehenden Dokumentation beschriebene Ablauf zur benutzerdefinierten Verarbeitung ([Erstellen von Zieldokumenten aus „JSON Processed“](https://docs.squeeze.one/link/862#bkmrk-%C3%9Cberblick-0 "Erstellung benutzerdefinierter Dokumente (Individuelle Verarbeitung)")).

Weiterführend (nächster Schritt, nach dieser Dokumentation):

- [Erstellung benutzerdefinierter Dokumente (Individuelle Verarbeitung)](https://docs.squeeze.one/books/squeeze-for-dynamics-365-bc-de-de/page/erstellung-benutzerdefinierter-dokumente-individuelle-verarbeitung "Erstellung benutzerdefinierter Dokumente (Individuelle Verarbeitung)")

## Verwendete (UI-)Begriffe

Die nachfolgenden Begriffe entsprechen den deutschen Beschriftungen aus den de-DE Übersetzungen:

- **„Individuelle Verarbeitung“** = `DXP Document Status` / `Custom Processing`
- **„Nächster Prozessschritt“** = `Next Process Step`
- **„Standardbeleg“** = Enum-Wert `Standard document`
- **„Regelsatz“**, **„Regelsatzcode“** = `Rule Set`, `Rule Set Code`
- **„Regelgruppe“**, **„Regelgruppencode“** = `Rule Group`, `Rule Group Code`

## Technischer Hintergrund

- Pro **Dokumentenklasse** gibt es eine **Dokumentenklasseneinrichtung** (`DXP Document Class Setup`).
- Für jede Dokumentenklasseneinrichtung wird automatisch ein SQUEEZE-Regelsatz angelegt (intern: Feld **„DXP Sqz Rule Set Code“**).
- In der Dokumentenklasseneinrichtung wird ein **Regelgruppencode** hinterlegt (intern: Feld **„DXP Sqz Next Step Rule Group“**).
- Beim Ermitteln des nächsten Prozessschritts werden die Regeln dieser Regelgruppe ausgewertet.
- Wenn eine Regel zutrifft und 
    - **Nächster Prozessschritt = „Standardbeleg“** und
    - **Individuelle Verarbeitung = Ja** dann wird der Dokumentstatus auf **„Individuelle Verarbeitung“** gesetzt.

Wichtig:

- Ist **keine** Regelgruppe hinterlegt oder **keine** Regel zutreffend, wird auf den Standardwert „Nächster Prozessschritt“ aus der Dokumentenklasseneinrichtung zurückgefallen.

## Voraussetzungen

- DEXPRO Core + DEXPRO SQUEEZE sind installiert.
- Du hast Zugriff auf die Einrichtung **Dokumentenklasseneinrichtung** (DXP Document Class Setup) sowie auf die Regel-Engine-Seiten (Regelgruppen/Regeln).

## Schritt-für-Schritt: Regelgruppe „DXP Sqz Next Step Rule Group“ anlegen und verknüpfen

### 1) Dokumentenklasseneinrichtung öffnen

1. Öffne die Seite **Dokumentenklasseneinrichtung**.
2. Wähle die Dokumentenklasse aus, für die du das Routing auf **„Individuelle Verarbeitung“** aktivieren willst.
3. Scrolle in den Bereich **Squeeze** → Gruppe **Regeln**.

[![image-1770121643866.png](https://docs.squeeze.one/uploads/images/gallery/2026-02/scaled-1680-/image-1770121643866.png)](https://docs.squeeze.one/uploads/images/gallery/2026-02/image-1770121643866.png)

### 2) Regelgruppe anlegen

1. In **Squeeze → Rules** wähle das Feld **„Nächster Prozessschritt“** (technisch: *DXP Sqz Next Step Rule Group*).
2. Öffne die **Auswahl/Lookup** (Regelgruppenliste).
3. Lege eine neue **Regelgruppe** an: 
    - **Regelgruppencode**: z. B. `Next Step Rules`
    - **Beschreibung**: z. B. „Routing nach Individuelle Verarbeitung“

Hinweis: Der Lookup ist durch die Dokumentenklasse bereits auf den passenden **Regelsatzcode** gefiltert. Du musst den Regelsatz nicht manuell suchen.

[![image-1770121713883.png](https://docs.squeeze.one/uploads/images/gallery/2026-02/scaled-1680-/image-1770121713883.png)](https://docs.squeeze.one/uploads/images/gallery/2026-02/image-1770121713883.png)

### 3) Regelgruppe in der Dokumentenklasseneinrichtung speichern

1. Stelle sicher, dass in der Dokumentenklasseneinrichtung im Feld **„Nächster Prozessschritt“** jetzt der Regelgruppencode `DXP Sqz Next Step Rule Group` steht.

[![image-1770121741544.png](https://docs.squeeze.one/uploads/images/gallery/2026-02/scaled-1680-/image-1770121741544.png)](https://docs.squeeze.one/uploads/images/gallery/2026-02/image-1770121741544.png)

## Schritt-für-Schritt: Regel erstellen, die auf „Individuelle Verarbeitung“ routet

### 4) Regeln der Regelgruppe öffnen

1. Öffne die soeben angelegte **Regelgruppe** `DXP Sqz Next Step Rule Group`.
2. Wähle die Aktion **„Regeln anzeigen“** (öffnet die Regeln dieser Regelgruppe).

[![image-1770121790623.png](https://docs.squeeze.one/uploads/images/gallery/2026-02/scaled-1680-/image-1770121790623.png)](https://docs.squeeze.one/uploads/images/gallery/2026-02/image-1770121790623.png)

### 5) Neue Regel anlegen

1. Lege eine neue **Regel** an.
2. Lege eine sinnvolle **Priorität** fest (niedriger Wert = höhere Priorität, falls eure Umgebung so konfiguriert ist).
3. Definiere die **Bedingungen** (Rule Conditions), die zutreffen müssen.

\[Best Practice\] Starte mit einer sehr klaren, testbaren Bedingung (z. B. anhand von Belegtyp, Lieferant, Betragsschwellen, Mandant/Company, etc.), bevor du komplexe Regelketten aufbaust.

[![image-1770121891420.png](https://docs.squeeze.one/uploads/images/gallery/2026-02/scaled-1680-/image-1770121891420.png)](https://docs.squeeze.one/uploads/images/gallery/2026-02/image-1770121891420.png)

### 6) SQUEEZE-spezifische Felder setzen (entscheidend für „Individuelle Verarbeitung“)

Auf der Regelkarte gibt es den Bereich **„DEXPRO Squeeze“** (CardPart). Setze dort:

1. **Nächster Prozessschritt** = **„Standardbeleg“**
2. **Individuelle Verarbeitung** = **Ja**

Ergebnis:

- Wenn diese Regel zutrifft, wird der Belegstatus auf **„Individuelle Verarbeitung“** gesetzt.

[![image-1770121986406.png](https://docs.squeeze.one/uploads/images/gallery/2026-02/scaled-1680-/image-1770121986406.png)](https://docs.squeeze.one/uploads/images/gallery/2026-02/image-1770121986406.png)

Optional:

- **Kontierungscode** \[*Account Assignment Code*\] (falls du zusätzlich eine Kontierungslogik über Regeln steuern möchtest; unabhängig vom Custom-Processing-Routing).

## Prüfen / Troubleshooting

### Beleg landet *nicht* in „Individuelle Verarbeitung“

Gehe diese Punkte durch:

1. **Regelgruppe hinterlegt?** In der Dokumentenklasseneinrichtung muss der Regelgruppencode gesetzt sein (sonst fällt das System auf Standard „**Nächster Prozessschritt**“ zurück).
2. **Trifft eine Regel zu?** Wenn keine Regel zutrifft, wird der nächste Prozessschritt nicht überschrieben.
3. **Ist „Individuelle Verarbeitung“ gesetzt UND Next Step = „Standardbeleg“?**
    - Wenn „Individuelle Verarbeitung“ = Ja, aber Next Step ≠ „Standardbeleg“, wird der Status nicht auf „Individuelle Verarbeitung“ gesetzt.
4. **Siehst du den Bereich „DEXPRO Squeeze“ auf der Regelkarte nicht?**
    - Dann befindest du dich sehr wahrscheinlich in einem Regelsatz, der nicht zur Dokumentenklasseneinrichtung (Quelltabelle) gehört.
    - Lege die Regelgruppe im besten Fall über den Lookup aus der Dokumentenklasseneinrichtung an. Damit sparst du dir den Weg über die allgemeine Regelverwaltng

### Beleg landet in „Fertig“ statt „Individuelle Verarbeitung“

Wenn eine Regel zutrifft und **Nächster Prozessschritt** auf einen Wert ungleich „Standardbeleg“ gesetzt wird, kann der Status auf **„Fertig“** gesetzt werden (damit wird weitere Verarbeitung übersprungen). Prüfe daher die Next-Step-Auswahl in der Regel. Das ist zum Beispiel der Fall, wenn die App [Breeze Interface](https://docs.squeeze.one/books/dexpro-microsoft-dynamics-365-business-central-apps/chapter/dexpro-breeze-interface "DEXPRO Breeze Interface") benutzt wird.

### Danach (Übergang in die bestehende [Doku, zur individuellen Belegerstellung](https://docs.squeeze.one/books/squeeze-for-dynamics-365-bc-de-de/page/erstellung-benutzerdefinierter-dokumente-individuelle-verarbeitung "Erstellung benutzerdefinierter Dokumente (Individuelle Verarbeitung)"))

Sobald ein Beleg im Status **„Individuelle Verarbeitung“** ist, ist es bereit für die Verarbeitung:

- „JSON Processed“ aus `DXP Document` lesen
- Zieldokumente erstellen
- finalen Status setzen

# Events nach der Erstellung eines SQUEEZE-Belegs (CreateSource)

## Zielgruppe

Diese Dokumentation richtet sich an Drittanbieter-Entwickler, die den **Erstellungsprozess eines SQUEEZE-Belegs** (Tabellen `DXP SQZ Document Header`/`DXP SQZ Document Line`) erweitern möchten.

Im Fokus stehen die **Integration Events**, die *nach* bzw. *während* `ISourceDocument.CreateSource(...)` ausgelöst werden.

## Einordnung: Wo wird `CreateSource` aufgerufen?

Der Einstiegspunkt für die SQUEEZE-Integration ist in der Codeunit **`DXP SQZ API Mgt.`**:

- Es wird ein Core-Beleg (`DXP Document`) angelegt (über `DXP Document Mgt.`).
- Anschließend wird über die Dokumentenklasse das Interface **`DXP ISource Document`** aufgelöst und `CreateSource(Document)` ausgeführt.

Vereinfachter Ablauf (aus `CreateCoreAndSQUEEZEDocument`):

1. `DocumentMgt.AddDocument(DocClass, ...)` (Core-Beleg anlegen)
2. `ISourceDocument.CreateSource(Document)` (SQUEEZE-Beleg erzeugen)
3. Import-Queue verarbeiten / SQUEEZE-Dokument „finishen“

## Dokumentenklassen &amp; Default-Verhalten

Welche `CreateSource`-Implementierung ausgeführt wird, ist in der Enum-Erweiterung **`DXP Document Class Ext.`** hinterlegt.

- **`DXP Invoice / Credit Memo`** → Codeunit **`DXP SQZ P. Inv/Crdt Memo Impl.`**
- **`DXP Order Confirmation`** → Codeunit **`DXP SQZ P. Order Conf. Impl.`**

**Default (wenn keine spezifische Implementierung gesetzt ist):** Die Enum `DXP Document Class` definiert eine `DefaultImplementation` auf **`DXP Source Document Def. Impl.`**. Diese Default-Implementierung:

- erzeugt **keinen** SQUEEZE-Beleg,
- `CreateSource(...)` gibt **`false`** zurück.

## Gemeinsame Pipeline innerhalb `CreateSource`

Beide SQUEEZE-Implementierungen folgen (verkürzt) diesem Muster:

1. Guard: Core-Dokument muss `Status = Imported` haben und `JSON Raw` muss existieren.
2. `DXP SQZ Document Mgt.`: 
    - `CreateSQUEEZEDocHeader(Document, DocHeader, RawJson)`
    - `CreateSQUEEZEDocLine(DocHeader, RawJson)`
3. `DXP Document Mgt.`: `UpdateDocumentAfterTransfer(...)` (Core-Dokument auf „Transferred“ setzen und mit SQZ-Header verknüpfen)
4. Ordermatch / Autocomplete / Attachments / Validierung (abhängig von Dokumentenklasse und Setup)

Die wichtigsten Events hängen daher an:

- der Header-/Line-Erzeugung in `DXP SQZ Document Mgt.`
- dem Abschluss der `CreateSource`-Prozedur in den jeweiligen Implementierungen
- optionalen Schritten wie Ordermatch oder Validierung

## Event-Matrix (Kurzüberblick)

<table id="bkmrk-bereich-event-zeitpu"><thead><tr><th>Bereich</th><th>Event</th><th>Zeitpunkt</th><th>Steuerung</th><th>Typische Use-Cases</th></tr></thead><tbody><tr><td>`DXP SQZ API Mgt.`</td><td>[`OnAfterInitMapping(...)`](https://docs.squeeze.one/link/939#bkmrk-onafterinitmapping)</td><td>nach Initialisierung Mapping/Token-Kontext</td><td>–</td><td>Mapping erweitern/prüfen</td></tr><tr><td>`DXP SQZ API Mgt.`</td><td>[`OnAfterSaveAttachment(...)`](https://docs.squeeze.one/link/939#bkmrk-onaftersaveattachmen)</td><td>nach erfolgreichem Speichern eines Attachments</td><td>–</td><td>Nachverarbeitung/Indexierung</td></tr><tr><td>`DXP SQZ API Mgt.`</td><td>[`OnAfterSetOriginFileNameOnBeforeModifyDocumentAttachment(...)`](https://docs.squeeze.one/link/939#bkmrk-onaftersetoriginfile)</td><td>vor dem Modify des Attachment-Datensatzes</td><td>–</td><td>Dateiname/Origin beeinflussen</td></tr><tr><td>`DXP SQZ P. Inv/Crdt Memo Impl.`</td><td>[`OnAfterCreateDocLines`](https://docs.squeeze.one/link/939#bkmrk-onaftercreatedocline)</td><td>direkt nach `CreateSQUEEZEDocLine(...)`</td><td>–</td><td>Lines/RawJson nach Anlage auswerten</td></tr><tr><td>`DXP SQZ P. Inv/Crdt Memo Impl.`</td><td>[`OnAfterCreateSource`](https://docs.squeeze.one/link/939#bkmrk-onaftercreatesource)</td><td>am Ende von `CreateSource(...)`</td><td>–</td><td>Post-Aktionen nach Autocomplete/Ordermatch/Validierung</td></tr><tr><td>`DXP SQZ P. Inv/Crdt Memo Impl.`</td><td>[`OnBeforeDoPlausibilityChecks`](https://docs.squeeze.one/link/939#bkmrk-plausibilit%C3%A4tspr%C3%BCfun)</td><td>vor Plausibilitätschecks</td><td>`IsHandled`</td><td>Checks ersetzen/erweitern</td></tr><tr><td>`DXP SQZ P. Inv/Crdt Memo Impl.`</td><td>[`OnAfterDoPlausibilityChecks`](https://docs.squeeze.one/link/939#bkmrk-plausibilit%C3%A4tspr%C3%BCfun)</td><td>nach Plausibilitätschecks</td><td>–</td><td>Ergebnis weiterverwenden (Entries)</td></tr><tr><td>`DXP SQZ P. Order Conf. Impl.`</td><td>[`OnBeforeDoPlausibilityChecks`](https://docs.squeeze.one/link/939#bkmrk-plausibilit%C3%A4tspr%C3%BCfun-0)</td><td>vor Plausibilitätschecks</td><td>`IsHandled`</td><td>Checks ersetzen/erweitern</td></tr><tr><td>`DXP SQZ Document Mgt.`</td><td>[`OnBeforeModifySQUEEZEDocumentHeader`](https://docs.squeeze.one/link/939#bkmrk-onbeforemodifysqueez)</td><td>in `CreateSQUEEZEDocHeader(...)` vor finalem Modify</td><td>–</td><td>Headerwerte aus `RawJson` ergänzen/normalisieren</td></tr><tr><td>`DXP SQZ Document Mgt.`</td><td>[`OnBeforeTransferRecognizedValuesOnAfterAssignRecognizedValues`](https://docs.squeeze.one/link/939#bkmrk-onbeforetransferreco)</td><td>im Standard-Line-Post-Processing vor `TransferRecognizedValues`</td><td>–</td><td>erkannte Werte pro Line korrigieren</td></tr><tr><td>`DXP SQZ Document Mgt.`</td><td>[`OnAfterTransferRecognizedValuesOnBeforeInsertDocLine`](https://docs.squeeze.one/link/939#bkmrk-onaftertransferrecog)</td><td>im Standard-Line-Post-Processing nach `TransferRecognizedValues`</td><td>–</td><td>line-basierte Ableitungen (Kontierung/Dimensionen)</td></tr><tr><td>`DXP SQZ Document Mgt.`</td><td>[`OnBeforePerformAutomaticOrdermatch`](https://docs.squeeze.one/link/939#bkmrk-onbeforeperformautom)</td><td>vor automatischem Ordermatch</td><td>`IsHandled`</td><td>Ordermatch komplett übernehmen/OrderNoList anpassen</td></tr><tr><td>JSON-Erzeugung</td><td>[`OnBeforeAddLineToDocumentJObj`](https://docs.squeeze.one/link/939#bkmrk-onbeforeaddlinetodoc)</td><td>vor dem Aufbau der Line-Struktur</td><td>–</td><td>Header-JSON erweitern</td></tr><tr><td>JSON-Erzeugung</td><td>[`OnBeforeAddLineJObjToLineJArray`](https://docs.squeeze.one/link/939#bkmrk-onbeforeaddlinejobjt)</td><td>vor `LineJArray.Add(LineJObj)`</td><td>–</td><td>Line-JSON erweitern</td></tr></tbody></table>

---

## Events in `DXP SQZ P. Inv/Crdt Memo Impl.` (Dokumentenklasse: Invoice / Credit Memo)

Diese Codeunit bietet die **stärksten Erweiterungspunkte direkt im `CreateSource`-Ablauf**.

### `OnAfterCreateDocLines`

Wird direkt nach `CreateSQUEEZEDocLine(...)` ausgelöst (also nachdem Header &amp; Lines existieren).

Signatur:

```al
[IntegrationEvent(false, false)]
local procedure OnAfterCreateDocLines(DocHeader: Record "DXP SQZ Document Header"; RawJson: JsonObject)

```

Typische Use-Cases:

- zusätzliche Initialisierung am Header (z. B. eigene Flags/Felder)
- zusätzliche line-basierte Auswertungen direkt nach Anlage
- eigene Metadaten aus `RawJson` extrahieren

### `OnAfterCreateSource`

Wird am Ende von `CreateSource(...)` ausgelöst (nach Autocomplete/Ordermatch/optionalem Attachment-Download/optionalem Auto-Validate).

Signatur:

```al
[IntegrationEvent(false, false)]
local procedure OnAfterCreateSource(DocHeader: Record "DXP SQZ Document Header"; RawJson: JsonObject)

```

Typische Use-Cases:

- nachgelagerte Automatisierungen starten (Workflows, Benachrichtigungen)
- zusätzliche Datenvalidierung oder eigene Statuslogik

### Plausibilitätsprüfung-Events

Diese Events betreffen die Methode `IsSourceDataPlausible(...)` und sind relevant, wenn ihr Plausibilitätschecks erweitert/ersetzt.

Signaturen (Auszug):

```al
[IntegrationEvent(false, false)]
local procedure OnBeforeDoPlausibilityChecks(DocHeader: Record "DXP SQZ Document Header"; var TempPlausibilityCheckEntry: Record "DXP Plausibility Check Entry" temporary; var IsHandled: Boolean)

[IntegrationEvent(false, false)]
local procedure OnBeforeDoHeaderPlausibilityChecks(DocHeader: Record "DXP SQZ Document Header"; var PlausibilityCheck: Codeunit "DXP Plausiblity Check Mgt.")

[IntegrationEvent(false, false)]
local procedure OnBeforeDoLinePlausibilityChecks(DocHeader: Record "DXP SQZ Document Header"; DocLine: Record "DXP SQZ Document Line"; var PlausibilityCheck: Codeunit "DXP Plausiblity Check Mgt.")

[IntegrationEvent(false, false)]
local procedure OnAfterDoPlausibilityChecks(DocHeader: Record "DXP SQZ Document Header"; var TempPlausibilityCheckEntry: Record "DXP Plausibility Check Entry" temporary)

```

### JSON-Erzeugung (Processed JSON aus dem SQZ-Beleg)

Die Implementierung enthält Events, bevor Line-JSON zur Header-Struktur hinzugefügt wird:

#### `OnBeforeAddLineToDocumentJObj`

```al
[IntegrationEvent(false, false)]
local procedure OnBeforeAddLineToDocumentJObj(var DocumentJObj: JsonObject; DocHeader: Record "DXP SQZ Document Header")

```

#### `OnBeforeAddLineJObjToLineJArray`

```al

[IntegrationEvent(false, false)]
local procedure OnBeforeAddLineJObjToLineJArray(var LineJObj: JsonObject; DocLine: Record "DXP SQZ Document Line")

```

Use-Cases:

- zusätzliche Felder ins Header-JSON schreiben
- zusätzliche Felder pro Line ergänzen (Custom Tokens)

---

## Events in `DXP SQZ P. Order Conf. Impl.` (Dokumentenklasse: Order Confirmation)

Diese Codeunit hat **keine** dedizierten `OnAfterCreateSource`-Events in `CreateSource(...)`. Erweiterungen passieren hier typischerweise über:

- `DXP SQZ Document Mgt.` (Header/Line-Erzeugung, Ordermatch)
- die JSON-Erzeugung (`CreateProcessedJsonFromSource`)
- Plausibilitätsprüfung-Events

### JSON-Erzeugung

```al
[IntegrationEvent(false, false)]
local procedure OnBeforeAddLineToDocumentJObj(var DocumentJObj: JsonObject; DocHeader: Record "DXP SQZ Document Header")

[IntegrationEvent(false, false)]
local procedure OnBeforeAddLineJObjToLineJArray(var LineJObj: JsonObject; DocLine: Record "DXP SQZ Document Line")

```

### Plausibilitätsprüfung-Events

Analog zur Invoice/Credit-Memo-Implementierung:

- `OnBeforeDoPlausibilityChecks`
- `OnBeforeDoHeaderPlausibilityChecks`
- `OnBeforeDoLinePlausibilityChecks`
- `OnAfterDoPlausibilityChecks`

---

## Events in `DXP SQZ Document Mgt.` (gemeinsam für alle Dokumentenklassen)

Diese Events sind besonders relevant, weil sie **innerhalb** der Header/Line-Erzeugung und Ordermatch-Pipeline ausgelöst werden.

### `OnBeforeModifySQUEEZEDocumentHeader`

Wird in `CreateSQUEEZEDocHeader(...)` ausgelöst, nachdem Werte über Field Mapping zugewiesen wurden und **bevor** der Header final modifiziert wird.

Signatur:

```al
[IntegrationEvent(false, false)]
local procedure OnBeforeModifySQUEEZEDocumentHeader(var DocHeader: Record "DXP SQZ Document Header"; RawJson: JsonObject)

```

Use-Cases:

- Header-Felder anpassen, bevor Standard-Post-Processing läuft
- zusätzliche Werte aus `RawJson` in den Header übernehmen

### `OnBeforeTransferRecognizedValuesOnAfterAssignRecognizedValues`

### `OnAfterTransferRecognizedValuesOnBeforeInsertDocLine`

Werden im Standard-Line-Post-Processing (für Standard-Dokumentenklassen) um `TransferRecognizedValues(DocLine)` herum ausgelöst.

Signaturen:

```al
[IntegrationEvent(false, false)]
local procedure OnBeforeTransferRecognizedValuesOnAfterAssignRecognizedValues(var DocLine: Record "DXP SQZ Document Line"; RowJTok: JsonToken)

[IntegrationEvent(false, false)]
local procedure OnAfterTransferRecognizedValuesOnBeforeInsertDocLine(var DocLine: Record "DXP SQZ Document Line")

```

Use-Cases:

- erkannte Werte pro Line korrigieren/normalisieren
- zusätzliche Ableitungen (z. B. Kontierung, Dimensionen) vorbereiten

### `OnBeforePerformAutomaticOrdermatch`

Wird in `PerformAutomaticOrdermatch(...)` ausgelöst, nachdem die Order-Nummern-Liste (`OrderNoList`) gefüllt wurde und **bevor** der automatische Ordermatch läuft.

Signatur:

```al
[IntegrationEvent(false, false)]
local procedure OnBeforePerformAutomaticOrdermatch(DocHeader: Record "DXP SQZ Document Header"; var OrderNoList: List of [Code[20]]; var IsHandled: Boolean)

```

Use-Cases:

- automatische Ordermatch-Logik komplett übernehmen (`IsHandled := true`)
- OrderNoList anpassen (z. B. filtern/ergänzen)

---

## Events in `DXP SQZ API Mgt.` (kontextnah zur Anlage/Attachments)

Diese Events liegen „um“ die API-Kommunikation herum (z. B. Attachments).

### `OnAfterInitMapping`

Wird in `DownloadFieldMapping(...)` ausgelöst, **nachdem** die Dokumentenklasse über das Interface `DXP ISource Document` das Default-Mapping initialisiert hat (`ISourceDocument.InitFieldMapping(...)`) und **bevor** die Feldliste aus SQUEEZE in die BC-Field-Mapping-Strukturen synchronisiert wird.

Signatur:

```al
[IntegrationEvent(false, false)]
local procedure OnAfterInitMapping(var HeaderMapping: Dictionary of [Text, Integer]; var LineMapping: Dictionary of [Text, Integer]; DocClass: Enum "DXP Document Class")

```

Parameter:

- `HeaderMapping` / `LineMapping`: Dictionaries mit Default-Zuordnung **SQZ-Feldname → BC-Feldnr.** (über `var` veränderbar)
- `DocClass`: Dokumentenklasse, für die das Mapping geladen wird

Typische Use-Cases:

- zusätzliche (kundenspezifische) Feldnamen als Default-Mapping ergänzen
- Default-Mapping korrigieren (z. B. wenn sich SQZ-Feldnamen geändert haben)
- Mapping-Validierung/Logging je Dokumentenklasse

### `OnAfterSaveAttachment`

Wird in `SaveAttachments(...)` **pro Attachment** ausgelöst, nachdem das Attachment über `DXP Document Attachment Mgt.` angelegt wurde und – falls das Attachment als Origin-Datei markiert ist – nach dem `Modify(true)` des Datensatzes.

Signatur:

```al
[IntegrationEvent(false, false)]
local procedure OnAfterSaveAttachment(DocumentAttachment: Record "DXP Document Attachment")

```

Hinweis:

- `DocumentAttachment` wird **nicht** als `var` übergeben. Wenn ihr Felder am Datensatz persistent ändern möchtet, müsst ihr im Subscriber den Datensatz erneut laden und explizit `Modify(...)` ausführen.

Typische Use-Cases:

- Attachment extern indexieren/archivieren
- Metadaten ableiten (z. B. Klassifizierung nach MIME-Type/Dateiendung)
- Folgeprozesse triggern (z. B. OCR/Preview-Generierung in Drittsystem)

### `OnAfterSetOriginFileNameOnBeforeModifyDocumentAttachment`

Wird in `SaveAttachments(...)` ausgelöst, **wenn** das Attachment laut Payload als Origin-Datei markiert ist und nachdem `Is Source File` sowie `File Name` (Origin-Name) gesetzt wurden – aber **bevor** `DocumentAttachment.Modify(true)` aufgerufen wird.

Signatur:

```al
[IntegrationEvent(false, false)]
local procedure OnAfterSetOriginFileNameOnBeforeModifyDocumentAttachment(var DocumentAttachment: Record "DXP Document Attachment")

```

Typische Use-Cases:

- Origin-Dateinamen normalisieren (z. B. verbotene Zeichen entfernen, Namensschema erzwingen)
- zusätzliche Felder am Attachment setzen, bevor gespeichert wird

## Minimalbeispiele: EventSubscriber für `DXP SQZ API Mgt.`

Die folgenden Beispiele zeigen typische, **kleine** Erweiterungen. Passt Objekt-IDs/Namen an euren Namespace an.

```al
codeunit 50101 "My SQZ API Mgt. Subs"
{
    [EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP SQZ API Mgt.", 'OnAfterInitMapping', '', false, false)]
    local procedure OnAfterInitMapping(var HeaderMapping: Dictionary of [Text, Integer]; var LineMapping: Dictionary of [Text, Integer]; DocClass: Enum "DXP Document Class")
    var
        SqzDocHeader: Record "DXP SQZ Document Header";
        SqzDocLine: Record "DXP SQZ Document Line";
    begin
        // Beispiel: neues SQZ-Feld als Default-Mapping ergänzen.
        // Wichtig: Der Integer-Wert ist die BC-Feldnr. des Ziel-Feldes (abhängig von eurem Mapping-Konzept).
        if not HeaderMapping.ContainsKey('my_custom_header_token') then
            HeaderMapping.Add('my_custom_header_token', SqzDocHeader.FieldNo("Vendor No."));

        if not LineMapping.ContainsKey('my_custom_line_token') then
            LineMapping.Add('my_custom_line_token', SqzDocLine.FieldNo(Description));
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP SQZ API Mgt.", 'OnAfterSetOriginFileNameOnBeforeModifyDocumentAttachment', '', false, false)]
    local procedure OnAfterSetOriginFileNameOnBeforeModifyDocumentAttachment(var DocumentAttachment: Record "DXP Document Attachment")
    begin
        // Beispiel: Origin-Dateiname normalisieren (Whitespace entfernen).
        DocumentAttachment."File Name" := DelChr(DocumentAttachment."File Name", '=', ' ');
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP SQZ API Mgt.", 'OnAfterSaveAttachment', '', false, false)]
    local procedure OnAfterSaveAttachment(DocumentAttachment: Record "DXP Document Attachment")
    begin
        // Beispiel: Nachverarbeitung anstoßen.
        // Falls ihr persistent am Attachment ändern wollt, könnt ihr den Parameter-Datensatz auch direkt modifizieren.
        // DocumentAttachment.Validate("Your Field", ...);
        // DocumentAttachment.Modify(true);
    end;
}

```

# Events nach der Erstellung eines Core-Belegs (DXP Document)

## Zielgruppe

Diese Dokumentation richtet sich an Drittanbieter-Entwickler, die den Lebenszyklus des Core-Belegs **`DXP Document`** erweitern möchten – insbesondere an den Punkten, an denen:

- Status / „Nächster Prozessschritt“ bewertet wird,
- `JSON Raw` / `JSON Processed` aktualisiert wird,
- Standardbelege erzeugt werden (z. B. Einkaufsbelege),
- Attachments übertragen werden.

## Einordnung: Core-Beleg-Erstellung vs. Weiterverarbeitung

### 1) Core-Beleg wird angelegt (`AddDocument`)

Die Anlage eines `DXP Document` erfolgt in **`DXP Document Mgt.`** über `AddDocument(...)`:

- Status wird auf **Imported** gesetzt.
- `JSON Raw` wird (falls vorhanden) in ein BLOB-Feld geschrieben.

### 2) Danach: Transfer/Verknüpfung und Verarbeitung

Typische nächste Schritte (abhängig vom App-Flow):

- Verknüpfung mit einem Quellbeleg (z. B. SQUEEZE Header) über `UpdateDocumentAfterTransfer(...)`
- Erzeugung eines Standardbelegs und Schreiben des `JSON Processed` über `UpdateDocument(...)`

Die meisten Erweiterungspunkte liegen daher **nicht direkt beim Insert**, sondern im Update-/Processing-Flow.

## Event-Matrix (Kurzüberblick)

<table id="bkmrk-bereich-event-zeitpu"><thead><tr><th>Bereich</th><th>Event</th><th>Zeitpunkt</th><th>Steuerung</th><th>Typische Use-Cases</th></tr></thead><tbody><tr><td>`DXP Document Mgt.`</td><td>[`OnAfterSetLinkedToRecIdOnBeforeModify`](https://docs.squeeze.one/link/940#bkmrk-onaftersetlinkedtore)</td><td>in `UpdateDocumentAfterTransfer(...)` vor `Modify(true)`</td><td>–</td><td>Zusatzinfos setzen, sobald `Linked-to Record Id` steht</td></tr><tr><td>`DXP Document Mgt.`</td><td>[`OnBeforeGetDocumentOnBeforeCheckStatusAndNextProcessStep`](https://docs.squeeze.one/link/940#bkmrk-onbeforegetdocumento)</td><td>früh in `UpdateDocument(...)` vor `Document.Get(DocNo)`</td><td>`IsHandled`</td><td>Status/NextStep überschreiben, Processing übernehmen</td></tr><tr><td>`DXP Document Mgt.`</td><td>[`OnAfterGetDocumentOnBeforeCheckStatusAndNextProcessStep`](https://docs.squeeze.one/link/940#bkmrk-onaftergetdocumenton)</td><td>in `UpdateDocument(...)` nach `Get(...)` vor Standardbeleg-Erzeugung</td><td>`Handled`</td><td>abhängig vom Dokument entscheiden, JSON/Status anpassen</td></tr><tr><td>`DXP Document Mgt.`</td><td>[`OnAfterUpdateRawJson`](https://docs.squeeze.one/link/940#bkmrk-onafterupdaterawjson)</td><td>nach Schreiben von `JSON Raw` in `UpdateRawJsonJson(...)`</td><td>–</td><td>Raw JSON normalisieren/zusätzliche Ableitungen</td></tr><tr><td>`DXP Document Mgt.`</td><td>[`OnAfterWriteProcessedJsonToBlob`](https://docs.squeeze.one/link/940#bkmrk-onafterwriteprocesse)</td><td>nach Schreiben von `JSON Processed` in `UpdateStatusAndProcessedJson(...)`</td><td>–</td><td>Processed JSON finalisieren, Tokens ergänzen</td></tr><tr><td>`DXP Document Mgt.`</td><td>[`OnBeforeTransferCoreAttachmentsToStandardDocument`](https://docs.squeeze.one/link/940#bkmrk-onbeforetransfercore)</td><td>vor Attachment-Transfer</td><td>`IsHandled`</td><td>Transfer ersetzen/selektiv steuern</td></tr><tr><td>`DXP Document Transfer Mgt.`</td><td>[`OnBeforeExitProcessedJsonDocHeader`](https://docs.squeeze.one/link/940#bkmrk-onbeforeexitprocesse)</td><td>beim Erzeugen von Header-JSON (vor `exit(JObject)`)</td><td>–</td><td>Standard-Tokens ergänzen/normalisieren</td></tr><tr><td>`DXP Document Transfer Mgt.`</td><td>[`OnBeforeExitProcessedJsonDocLine`](https://docs.squeeze.one/link/940#bkmrk-onbeforeexitprocesse-0)</td><td>beim Erzeugen von Line-JSON (vor `exit(JObject)`)</td><td>–</td><td>Line-Tokens ergänzen/normalisieren</td></tr><tr><td>`DXP Document Transfer Mgt.`</td><td>[`OnAfterCreatePurchaseHeader`](https://docs.squeeze.one/link/940#bkmrk-onaftercreatepurchas)</td><td>nach Anlage/Validierung des Purchase Headers</td><td>–</td><td>Header-Felder ergänzen, Post-Processing</td></tr><tr><td>`DXP Document Transfer Mgt.`</td><td>[`OnBeforeInsertPurchaseLine`](https://docs.squeeze.one/link/940#bkmrk-onbeforeinsertpurcha)</td><td>vor Insert einer Purchase Line</td><td>–</td><td>Purchase Line anpassen (z. B. Kontierung, Typ/No)</td></tr><tr><td>`DXP Document Transfer Mgt.`</td><td>[`OnAfterInsertPurchaseLineOnBeforeAddDimensions`](https://docs.squeeze.one/link/940#bkmrk-onafterinsertpurchas)</td><td>nach Insert, vor Dimensionen</td><td>–</td><td>Dimensions/Additional-Info vorbereiten</td></tr><tr><td>`DXP Document Transfer Mgt.`</td><td>[`OnAfterOnBeforeAddLineDimensions`](https://docs.squeeze.one/link/940#bkmrk-onafteronbeforeaddli)</td><td>bei Receipt-Lines: vor Dimensionen des zu aktualisierenden Lines</td><td>–</td><td>Update-Fälle (Receipt) anpassen</td></tr><tr><td>`DXP Document Transfer Mgt.`</td><td>[`OnAfterProcessAdditionalInformation`](https://docs.squeeze.one/link/940#bkmrk-onafterprocessadditi)</td><td>nach Transfer zusätzlicher Informationen</td><td>–</td><td>Add-on-Felder pro Ziel-RecordRef ergänzen</td></tr><tr><td>`DXP Document Transfer Mgt.`</td><td>[`OnAfterCreatePurchaseDoc`](https://docs.squeeze.one/link/940#bkmrk-onaftercreatepurchas-0)</td><td>nach kompletter Erstellung des Purchase Dokuments</td><td>–</td><td>finaler Hook (Logging, Folgeaktionen)</td></tr></tbody></table>

---

## Events in `DXP Document Mgt.` (Core-Lebenszyklus)

### `OnAfterSetLinkedToRecIdOnBeforeModify`

Wird in `UpdateDocumentAfterTransfer(...)` ausgelöst – nachdem Status/`Linked-to Record Id` gesetzt wurden, aber **bevor** das `DXP Document` gespeichert wird.

Signatur:

```al
[IntegrationEvent(false, false)]
local procedure OnAfterSetLinkedToRecIdOnBeforeModify(var Document: Record "DXP Document")

```

Use-Cases:

- Zusatzinformationen am Core-Beleg setzen, sobald er auf einen Quellbeleg verlinkt ist
- eigene Logging-/Audit-Informationen ergänzen

### `OnBeforeGetDocumentOnBeforeCheckStatusAndNextProcessStep`

Wird in `UpdateDocument(...)` als **frühester Hook** aufgerufen – noch bevor das `DXP Document` per `Get(DocNo)` geladen wird.

Signatur:

```al
[IntegrationEvent(false, false)]
local procedure OnBeforeGetDocumentOnBeforeCheckStatusAndNextProcessStep(
    DocNo: Code[20];
    var Status: Enum "DXP Document Status";
    var NextStep: Enum "DXP Next Process Step";
    JObject: JsonObject;
    var IsHandled: Boolean)

```

Use-Cases:

- Status/NextStep vorab überschreiben (z. B. „Individuelle Verarbeitung“)
- komplette Verarbeitung übernehmen (`IsHandled := true`)

### `OnAfterGetDocumentOnBeforeCheckStatusAndNextProcessStep`

Wird in `UpdateDocument(...)` nach `Document.Get(DocNo)` ausgelöst, bevor Standardbelege erzeugt werden.

Signatur:

```al
[IntegrationEvent(false, false)]
local procedure OnAfterGetDocumentOnBeforeCheckStatusAndNextProcessStep(
    var Document: Record "DXP Document";
    NewStatus: Enum "DXP Document Status";
    NextStep: Enum "DXP Next process step";
    var ProcessedJSONObj: JsonObject;
    var Handled: Boolean)

```

Use-Cases:

- abhängig vom aktuellen Core-Dokument entscheiden, ob Processing übernommen werden soll
- Anpassungen am `ProcessedJSONObj` vor der weiteren Verarbeitung

### `OnAfterUpdateRawJson`

Wird in `UpdateRawJsonJson(...)` nach dem Schreiben von `JSON Raw` ausgelöst.

Signatur:

```al
[IntegrationEvent(false, false)]
local procedure OnAfterUpdateRawJson(var Document: Record "DXP Document"; var RawJSONObj: JsonObject)

```

Use-Cases:

- Validierungs-/Normalisierungsschritte auf `RawJSONObj`
- zusätzliche Datenextraktion direkt nach Raw-Update

### `OnAfterWriteProcessedJsonToBlob`

Wird in `UpdateStatusAndProcessedJson(...)` ausgelöst, nachdem `JSON Processed` in das BLOB geschrieben wurde.

Signatur:

```al
[IntegrationEvent(false, false)]
local procedure OnAfterWriteProcessedJsonToBlob(var Document: Record "DXP Document"; var ProcessedJSONObj: JsonObject)

```

Use-Cases:

- zusätzliche Tokens ergänzen
- Datenstruktur finalisieren, bevor nachgelagerte Prozesse darauf zugreifen

### `OnBeforeTransferCoreAttachmentsToStandardDocument`

Wird in `TransferCoreAttachmentsToStandardDocument(...)` vor dem Transfer ausgelöst. Über `IsHandled` kann die Standardlogik ersetzt werden.

Signatur:

```al
[IntegrationEvent(false, false)]
local procedure OnBeforeTransferCoreAttachmentsToStandardDocument(Document: Record "DXP Document"; var IsHandled: Boolean)

```

Use-Cases:

- Attachment-Transfer komplett übernehmen (z. B. andere Ablage/Benennung)
- Transfer selektiv einschränken

---

## Events in `DXP Document Transfer Mgt.` (Standardbelege aus `JSON Processed`)

Diese Codeunit ist relevant, sobald aus `JSON Processed` Standardbelege (z. B. Einkaufsbelege) erzeugt werden.

### `OnBeforeExitProcessedJsonDocHeader`

Wird in `CreateDocHeaderJson(...)` unmittelbar vor `exit(JObject)` ausgelöst.

Signatur:

```al
[IntegrationEvent(false, false)]
local procedure OnBeforeExitProcessedJsonDocHeader(var JObject: JsonObject)

```

Use-Cases:

- zusätzliche Standard-Tokens ergänzen
- Token-Werte normalisieren (z. B. Typ-/Textdarstellung)

### `OnBeforeExitProcessedJsonDocLine`

Wird in `CreateDocLineJson(...)` unmittelbar vor `exit(JObject)` ausgelöst.

Signatur:

```al
[IntegrationEvent(false, false)]
local procedure OnBeforeExitProcessedJsonDocLine(var JObject: JsonObject)

```

Use-Cases:

- Line-Tokens ergänzen/normalisieren

### `OnAfterCreatePurchaseHeader`

Wird nach Anlage/Validierung des Purchase Headers ausgelöst.

Signatur:

```al
[IntegrationEvent(false, false)]
local procedure OnAfterCreatePurchaseHeader(var JObject: JsonObject; PurchaseHeader: Record "Purchase Header")

```

Use-Cases:

- zusätzliche Felder am `Purchase Header` setzen (z. B. eigene Referenzen)
- Post-Processing/Logging

### `OnBeforeInsertPurchaseLine`

Wird unmittelbar vor dem Insert einer Purchase Line ausgelöst.

Signatur:

```al
[IntegrationEvent(false, false)]
local procedure OnBeforeInsertPurchaseLine(var JObjectLine: JsonObject; var PurchaseLine: Record "Purchase Line")

```

Use-Cases:

- Purchase Line anpassen (z. B. Kontierung, Typ/No)

### `OnAfterInsertPurchaseLineOnBeforeAddDimensions`

Wird nach Insert der Purchase Line, aber vor Dimensions-Validierung ausgelöst.

Signatur:

```al
[IntegrationEvent(false, false)]
local procedure OnAfterInsertPurchaseLineOnBeforeAddDimensions(var JObjectLine: JsonObject; PurchaseLine: Record "Purchase Line")

```

Use-Cases:

- Dimensions/Additional-Info vorbereiten

### `OnAfterOnBeforeAddLineDimensions`

Wird im Receipt-Line-Update-Fall ausgelöst, bevor Dimensionen für die zu aktualisierende Purchase Line gesetzt werden.

Signatur:

```al
[IntegrationEvent(false, false)]
local procedure OnAfterOnBeforeAddLineDimensions(var JObjectLine: JsonObject; PurchaseLineToUpdate: Record "Purchase Line")

```

Use-Cases:

- Update-Fälle (Receipt) anpassen

### `OnAfterProcessAdditionalInformation`

Wird nach dem Transfer zusätzlicher Informationen (`ProcessAdditionalInformation`) ausgelöst.

Signatur:

```al
[IntegrationEvent(false, false)]
local procedure OnAfterProcessAdditionalInformation(var RecRef: RecordRef; SourceJsonObject: JsonObject)

```

Use-Cases:

- Add-on Felder für unterschiedliche Zieltabellen (Header/Line) ergänzen

### `OnAfterCreatePurchaseDoc`

Wird nach kompletter Erstellung des Purchase Dokuments ausgelöst.

Signatur:

```al
[IntegrationEvent(false, false)]
local procedure OnAfterCreatePurchaseDoc(var JObject: JsonObject; PurchaseHeader: Record "Purchase Header")

```

Use-Cases:

- finaler Hook (Post-Processing, Logging, Folgeaktionen)

---

## Minimalbeispiel: Status/NextStep in Richtung „Individuelle Verarbeitung“ beeinflussen

Beispiel: Vor dem Standard-Processing `Status` auf „Custom Processing“ setzen:

```al
codeunit 50101 "My Core Document Hooks"
{
    [EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP Document Mgt.", 'OnBeforeGetDocumentOnBeforeCheckStatusAndNextProcessStep', '', false, false)]
    local procedure OnBeforeGetDoc(
        DocNo: Code[20];
        var Status: Enum "DXP Document Status";
        var NextStep: Enum "DXP Next Process Step";
        JObject: JsonObject;
        var IsHandled: Boolean)
    begin
        Status := Status::"Custom Processing";
        // NextStep optional anpassen
    end;
}

```

\[Best Practice\] Nutze `IsHandled := true` nur, wenn du die Standardlogik vollständig ersetzt – sonst kann es zu inkonsistenten Status-/JSON-Zuständen kommen.