# Extension Development

This chapter lists important integration events and examples that help in developing an extension for SQUEEZE for BC.  
  
Important:  
Customizations may only be performed by appropriately trained consultants/developers. Furthermore, these are always outside of the standard support.

# Modify Squeeze origin attachment

## Overview

The `OnAfterSetOriginFileNameOnBeforeModifyDocumentAttachment` integration event allows third-party developers to customize document attachment records before they are modified during the SQUEEZE attachment processing workflow. An origin attachment is the relevant attachment that leads to the creation of a document (e.g. the vendor´s invoice).

## Event Declaration

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

## Event Parameters

<div id="bkmrk-parameter-type-descr"><div><table><thead><tr><th style="width: 173px;">Parameter</th><th style="width: 322px;">Type</th><th style="width: 314px;">Description</th></tr></thead><tbody><tr><td style="width: 173px;">`DocumentAttachment`</td><td style="width: 322px;">`Record "DXP Document Attachment"` (var)</td><td style="width: 314px;">The document attachment record that is about to be modified. Passed by reference, allowing modifications.</td></tr></tbody></table>

</div></div>## When This Event is Triggered

This event is fired during the `SaveAttachments` procedure when:

<div id="bkmrk-an-attachment-is-bei">1. An attachment is being processed from SQUEEZE
2. The attachment is identified as an origin file (`IsOriginFile = true`)
3. The system has set the origin file name and `DXP Is Origin File` flag
4. Just before the `DocumentAttachment.Modify(true)` call

</div>## Use Cases

This integration event is useful for:

<div id="bkmrk-custom-field-populat">- **Custom Field Population**: Setting additional custom fields on the document attachment
- **File Name Transformation**: Applying custom naming conventions or formatting
- **Metadata Enhancement**: Adding custom metadata or tags to attachments
- **Validation Logic**: Implementing custom validation before the record is saved
- **Integration Requirements**: Preparing data for external system integrations

</div>## Implementation Example

### Change The Filename Before Modify

```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;
```

## Important Considerations

### Data Integrity

<div id="bkmrk-the%C2%A0documentattachme">- The `DocumentAttachment` record is passed by reference, so any changes will be persisted
- Consider field length limitations when modifying text fields

</div>### Performance

<div id="bkmrk-keep-processing-ligh">- Keep processing lightweight as this event is called for each origin file attachment
- Consider caching frequently accessed data

</div>### Error Handling

<div id="bkmrk-implement-proper-err">- Implement proper error handling to prevent the attachment save process from failing
- Use try-functions for risky operations

</div><div id="bkmrk-"></div>## Related Events

<div id="bkmrk-onaftersaveattachmen">- `OnAfterSaveAttachment`: Triggered after each attachment is completely saved
- Consider using this alternative event if you need to perform actions after the record is saved

</div><div id="bkmrk--0"></div>## Troubleshooting

If your event subscriber isn’t being triggered:

<div id="bkmrk-test-with-origin-fil">1. Test with origin files specifically (non-origin files won’t trigger this event)

</div>

# Alternative use of the SQUEEZE attachments

The following briefly explains how the attachments downloaded by SQUEEZE for BC can be used for a scenario that differs from the intended use.

After creating the purchase document from the validated SQUEEZE document, you have the option of accessing the attachments ( assuming they have been downloaded in advance).

```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;
```

# Adjustment of attachment file names after validation

## Overview

This guide shows how developers can customize the file names of attachments in DEXPRO SQUEEZE. The `OnBeforeAddLineToDocumentJObj` event is executed **after the plausibility check**, ensuring that all document data is fully validated and available.

## Available Event

### OnBeforeAddLineToDocumentJObj

**For invoices/credit notes:** `Codeunit 70954657 "DXP SQZ P. Inv/Crdt Memo Impl."`  
**For orderconfirmations:** `Codeunit 70954658 "DXP SQZ P. Order Conf. Impl."`

**When accessed:** After header processing, before line processing, **after plausibility check**

## Implementation examples

### Example 1: Simple vendor prefix for invoices/credit notes

```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;
}


```

### Example 2: Order confirmations - vendor prefix

```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;
}


```

### Example 3: Both document types - Different prefixes for source files

```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;
}


```

## Practical information

### Available data at the time of the event:

- `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

### Example results:

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

### Best Practices:

- **Duplicate check**: Always check whether prefix already exists
- **Length validation**: New file names may have a maximum of 1024 characters.

# Adding a field in the validation (from version 2.10)

The following example extension shows how developers can add fields in the validation (here: document class invoice/credit note).

#### Optional integration events incl. sample procedures:

<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.';
        }
    }
}
```

##### Target document (here: Purchase Document)

```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.';
            }
        }
    }
}

```

##### Target document (here: Purchase document)

```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;
            }
        }
    }
}

```

# Implementation of a user-defined, automatic order reconciliation

## Introduction

Automatic order matching is an important part of document processing in Squeeze for Business Central. In the standard system, the system compares incoming documents with existing orders and goods receipts. This documentation shows you how you can extend this matching process to include your own document types.

The standard implementation uses a three-step process:

1. Collect relevant document numbers based on business rules
2. A detailed comparison is performed for each document number found.
3. The positions read by Squeeze are then enriched with the data found.

We will retain this proven approach in the following example implementation.

## Integration point

The central integration point is the `OnBeforePerformAutomaticOrdermatch` event in codeunit 70954632 “DXP SQZ Document Mgt.”. This event is called after header and line data has been created.

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

```

### Parameter

- `DocHeader`: The document header with the data to be compared
- `OrderNoList`: A list of document numbers for reconciliation
- `IsHandled`: Controls whether standard processing should be skipped

## Implementation example

## Technical implementation

### 1. Extension of the document type enum

First, we extend the possible document types to include our own type:

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

### 2. Implementation of the matching logic

The central point of our implementation is a code unit that controls the synchronization process. The integration point is:

```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. Processing of the documents found

After collecting the relevant document numbers, the actual comparison takes place:

```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;

```

## The matching logic in detail

The actual reconciliation of document lines is performed according to defined business rules:

```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;

```

# Implementation of a new, generic document class

## SQUEEZE → Core → Your processing

This guide is deliberately focused and pipeline-oriented: It shows how document data is downloaded from SQUEEZE, processed in queues, and persisted in DEXPRO Core—and where a third-party extension comes into play.

Term: In this guide, “*SQUEEZE app*” refers to the Business Central extension that is connected to the SQUEEZE API and controls imports to DEXPRO Core.

**Assumptions**:

- Third-party developers typically work with **published icons**, not with the entire source code.
- The SQUEEZE app handles **downloading + import queue + creation of the core document as well as the rudimentary creation of the SQUEEZE document, taking field mapping into account**, as soon as setup is complete.
- Your extension takes over the business logic once the core document exists: create or extend source records, validate them, generate “Processed JSON,” and optionally create your own target object or document.

---

## 1) The end-to-end process SQUEEZE → Core

Once the document class and setup are configured, the SQUEEZE app's import pipeline typically runs as follows:

1. **`DXP Document Class` extend** and bind your implementation: `DXP ISource Document`.
2. Configure settings for this **Document class‑Setup**. 
    - When validating `DXP SQZ Document Class ID`, the SQUEEZE app automatically retrieves the required setup values from SQUEEZE (e.g., `DXP SQZ Export Interface ID` and `DXP SQZ Line Table ID`) and fills them in.
3. The SQUEEZE app job calls `DXP SQZ API Mgt.DownloadDocumentData()`. Can also be called via “Download documents”. 
    - New entries are created in the DXP SQZ import queue (document ID + target client).
4. The SQUEEZE app calls `DXP SQZ Import Queue Mgt.TransferImportQueueEntries()`. 
    - The orchestration code unit `DXP Sqz Create Document` is executed for the entries in the import queue.
5. `DXP SQZ API Mgt.CreateCoreAndSQUEEZEDocument(DocClass, DocumentId)` loads the complete JSON. 
    - `DXP Document Mgt.AddDocument(DocClass, DocumentId, RawJson, …)` is called up.
    - Result: A `DXP Document`‑Record exists. Status = `Imported`, `JSON Raw` is filled.
6. The SQUEEZE app calls your implementation `ISourceDocument.CreateSource(Document)`. 
    - Your implementation begins here.

After step 6, you will have:

- A Core‑Container‑Document (`DXP Document`).
- A SQUEEZE document in the SQUEEZE app.
- Your source records should exist once `CreateSource` has completed successfully.

---

## 2) What you implement (Interfaces)

### Source contract, i.e., squeeze page: `DXP ISource Document`

You implement this to:

- Create your editable source data sets from `DXP Document.JSON Raw`
- Validate source data records
- Generate “Processed JSON” and transfer it to core processing

### Target contract, i.e., target document: `DXP IDocument Processing`

You implement this if Core is to create/update its **own target data set** (your own table/document) from the “Processed JSON.”

---

## 3) Step 1 — Define document class and assign source implementation

Create an enum extension and bind your source implementation:

```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) Step 2 — Configure document class setup (so that the import queue is filled)

Configure the SQUEEZE-specific fields in the document class setup for your new document class.

Important:

- Validating the `DXP SQZ Document Class ID` triggers the automatic loading of the required configuration from SQUEEZE (e.g., `DXP SQZ Export Interface ID` and `DXP SQZ Line Table ID`).
- If the call fails, these API-derived fields are cleared again.

Result: The SQUEEZE app can execute `DownloadDocumentData()` and create entries in the DXP SQZ Import Queue for your class.

---

## 5) Step 3 — Ensure field mapping (downloaded mapping + your default values)

The SQUEEZE app can download the field catalog of the connected Squeeze client and persist it in Core Mapping tables (per document class).

Your role as a document class implementer:

- Provide standard field mapping “Squeeze Field Name → AL Field No.” via `InitFieldMapping(...)`.
- Alternatively, map fields via the assignment pages (custom field assignment).

### 5.1 Implement `InitFieldMapping` (independent example)

```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 Validation switch when assigning fields: Validate app field

When the SQUEEZE app or your source creation logic writes JSON values to “app/source fields,” you can control whether the **OnValidate trigger** is executed.

In `DXP Custom Field Mapping`:

- `Validate App Field` = Validate the app field during the assignment.
- `Validate` (Target) = Perform validation when transferring to the target field.

Use this, for example, if extension fields have business logic in `OnValidate` or if you want to assign deliberately trigger-free (higher performance).

---

## 6) Step 4 — Create Source data sets in `CreateSource`

`CreateSource(Document)` is called by the SQUEEZE app immediately after `DXP Document Mgt.AddDocument(...)` as soon as the core container data record exists.

Minimum responsibilities:

1. Read `DXP Document.JSON Raw`.
2. Create SQUEEZE‑Source‑Header/Lines (`DXP SQZ Document Header` / `DXP SQZ Document Line`).
3. Connect Core‑document with SQUEEZE‑Source‑Header via `UpdateDocumentAfterTransfer`.

Optional (recommended if you want functionality equivalent to the standard SQUEEZE document classes): 4) Download attachments if `DXP Download Attachments` is active. 5) Perform automatic validation if enabled for the document.

### 6.1 Example: Create SQUEEZE‑Header + Lines with generic helpers

What the helpers do (so you don't have to implement it twice):

- `SqzDocumentMgt.CreateSQUEEZEDocHeader(Document, DocHeader, RawJson)`
    
    
    - Creates exactly one data record in `DXP SQZ Document Header` for the Core‑document (`DXP Document`).
    - Sets identification fields as`Core Document No.` and the SQUEEZE‑`API Document ID`.
    - Assigns header fields from JSON based on the configured mappings:  
        
        - `DXP Field Mapping` (field mapping)
        - `DXP Custom Field Mapping` (Custom field mapping)
    - Respects `DXP Custom Field Mapping.Validate App Field` and thus decides whether the **OnValidate trigger** is executed when assigning.
    - Executes the integration event `OnBeforeModifySQUEEZEDocumentHeader(...)` as an extension hook.
    - Standard post-processing is also performed for the built-in standard SQUEEZE classes (e.g., alternative IBAN/VAT ID determination, supplier update, standard posting date, duplicate check).
- `SqzDocumentMgt.CreateSQUEEZEDocLine(DocHeader, RawJson)`
    
    
    - Creates the `DXP SQZ Document Line`‑data sets based on the JSON‑Rows.
    - Assign line fields using the same mapping principle (standard + custom; optionally with validation triggers).
    - Fill the `Core Document No.` in the lines.
    - For standard SQUEEZE classes, standard line post-processing is also applied (e.g., transfer of recognized values).
    - Persists line-related auxiliary data (e.g., coordinates/metadata that use the UI and matching logic).

```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) Step 5 — Validation + Handover „Processed JSON“

### 7.1 Validation: `IsSourceDataPlausible`

Use the Core Plausibility Framework to ensure that errors are displayed consistently:

```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 Build „Processed JSON“ and pass it to the core: `CreateProcessedJsonFromSource`

Core stores “Processed JSON” in `DXP Document.JSON Processed` and transfers it to your target processing.

```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) Step 6 — Create your own target document

If you want Core to create your own target data set, extend `DXP Target Document Process` and implement `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;
}

```

Note: The transfer of metadata and user-defined/additional fields is already included in the above example and is based on the core mapping tables.

---

## 9) Troubleshooting (quick checks)

- No entries in `DXP SQZ Import Queue` → Document class setup for SQUEEZE is incomplete.
- Core‑`DXP Document` exists, but no source record → Your `CreateSource(Document)` has failed or returns `false`.
- Source exists, but target is not created → `CreateProcessedJsonFromSource` is not called or `DXP Target Document Process` is not configured.
- Incorrect trigger behavior when assigning → Check `DXP Custom Field Mapping.Validate App Field` and `Validate`. 
    - `Validate` controls whether validation of the target field (OnValidate) is performed.

---

## 10) What Core does automatically

When you call up `DXP Document Mgt.UpdateDocument(...)`:

- Core stores the “Processed JSON” in `DXP Document`.`JSON Processed`.
- Core sets `DXP Document`.`Status` to the transferred status.
- Core linked/updated `DXP Document`.`Linked-to Record Id` with the record, which your `DXP IDocument Processing`‑implementation returns.
- Core transfers Core attachments to the returned data set (and typically appends the “Processed JSON” as a `.json` attachment).

# Custom Document Creation (Custom Processing)

## Table of Contents

1. [Overview](#bkmrk-overview)
2. [Understanding the Document Flow](#bkmrk-understanding-the-do)
3. [JSON Data Structure](#bkmrk-json-data-structure)
4. [Quick Start: Simple Custom Processing](#bkmrk-quick-start%3A-simple-)

---

## Overview

This guide explains how to create custom Business Central documents from JSON data provided by DEXPRO Core. The system receives documents with status **"Custom Processing"**, which third-party developers can transform into any BC document type (Purchase Orders, Sales Orders, G/L Journals, Service Orders, etc.).

### What You'll Learn

- How to intercept documents marked for custom processing
- The JSON structure containing document header, lines, metadata, and custom fields
- How to create your custom document

---

## Understanding the Document Flow

### Core Workflow

```
┌─────────────────────────────────────────────────────────────────┐
│ 1. Document Import                                              │
│    JSON Raw Data → DXP Document (Status: Imported)              │
└────────────────────────┬────────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────────┐
│ 2. Source Document Creation                                     │
│    ISourceDocument.CreateSource() → Your Source Tables          │
│    (e.g., SQZ Document Header/Lines or Your Custom Tables)      │
└────────────────────────┬────────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────────┐
│ 3. Validation & Plausibility Checks                             │
│    ISourceDocument.IsSourceDataPlausible()                      │
│    → Validates data quality, checks for errors                  │
└────────────────────────┬────────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────────┐
│ 4. <span class="hljs-keyword">Create</span> Processed <span class="hljs-keyword">JSON</span>                                        │
│    ISourceDocument.CreateProcessedJsonFromSource()              │
│    → Builds <span class="hljs-keyword">JSON</span> structure <span class="hljs-keyword">for</span> target <span class="hljs-keyword">document</span>                  │
└────────────────────────┬────────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────────┐
│ <span class="hljs-number">5.</span> <span class="hljs-keyword">Update</span> Core <span class="hljs-keyword">Document</span> <span class="hljs-keyword">with</span> <span class="hljs-string">"Custom Processing"</span> <span class="hljs-keyword">Status</span>         │
│    UpdateDocument() → <span class="hljs-keyword">Sets</span> <span class="hljs-keyword">Status</span> <span class="hljs-keyword">to</span> <span class="hljs-string">"Custom Processing"</span>        │
│    → Stores <span class="hljs-keyword">JSON</span> Processed <span class="hljs-keyword">in</span> DXP <span class="hljs-keyword">Document</span>                      │
└────────────────────────┬────────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────────┐
│ <span class="hljs-number">6.</span> Your Custom Processing (THIS <span class="hljs-keyword">IS</span> <span class="hljs-keyword">WHERE</span> YOU HOOK <span class="hljs-keyword">IN</span>!)          │
│    Subscribe <span class="hljs-keyword">to</span> <span class="hljs-keyword">events</span> <span class="hljs-keyword">or</span> implement <span class="hljs-keyword">interface</span>                   │
│    → <span class="hljs-keyword">Read</span> <span class="hljs-keyword">JSON</span> Processed <span class="hljs-keyword">from</span> DXP <span class="hljs-keyword">Document</span>                      │
│    → <span class="hljs-keyword">Create</span> your target BC <span class="hljs-keyword">document</span>                             │
└────────────────────────┬────────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────────┐
│ <span class="hljs-number">7.</span> <span class="hljs-keyword">Final</span> <span class="hljs-keyword">Status</span> <span class="hljs-keyword">Update</span>                                          │
│    <span class="hljs-keyword">Document</span> <span class="hljs-keyword">Status</span> → Transferred <span class="hljs-keyword">or</span> Finished                    │
│    Linked-<span class="hljs-keyword">to</span> <span class="hljs-built_in">Record</span> <span class="hljs-keyword">Id</span> → Your created BC <span class="hljs-keyword">document</span>               │
└─────────────────────────────────────────────────────────────────┘

```

### Key Status Values

<table id="bkmrk-status-description-i"><thead><tr><th>Status</th><th>Description</th></tr></thead><tbody><tr><td>**Imported**</td><td>Raw JSON received, not yet processed</td></tr><tr><td>**Transferred**</td><td>Source document created, ready for processing</td></tr><tr><td>**Custom Processing**</td><td>**Your target status** - Document ready for custom handling</td></tr><tr><td>**Finished**</td><td>Target document successfully created</td></tr><tr><td>**Deleted**</td><td>Processing complete, document archived</td></tr></tbody></table>

---

## JSON Data Structure

### Overview

The `"JSON Processed"` blob in the `DXP Document` table contains a structured JSON object with all information needed to create your target document.

### Complete JSON Structure

```
{
  <span class="hljs-comment">// ═══════════════════════════════════════════════════════════</span>
  <span class="hljs-comment">// HEADER SECTION - Document-level information</span>
  <span class="hljs-comment">// ═══════════════════════════════════════════════════════════</span>

  <span class="hljs-string">"Type"</span>: <span class="hljs-string">"Invoice"</span>,                    <span class="hljs-comment">// Document type: Invoice, Credit Memo, Order, etc.</span>
  <span class="hljs-string">"VendorNo"</span>: <span class="hljs-string">"VENDOR001"</span>,              <span class="hljs-comment">// Vendor/Customer number</span>
  <span class="hljs-string">"DocumentDate"</span>: <span class="hljs-string">"2025-10-15"</span>,         <span class="hljs-comment">// Document date</span>
  <span class="hljs-string">"PostingDate"</span>: <span class="hljs-string">"2025-10-16"</span>,          <span class="hljs-comment">// Posting date</span>
  <span class="hljs-string">"DocumentReference"</span>: <span class="hljs-string">"INV-2025-001"</span>,  <span class="hljs-comment">// External document number</span>
  <span class="hljs-string">"OrderNo"</span>: <span class="hljs-string">"PO-12345"</span>,                <span class="hljs-comment">// Reference to order (if applicable)</span>
  <span class="hljs-string">"PostingDescription"</span>: <span class="hljs-string">"Invoice Q4"</span>,   <span class="hljs-comment">// Posting description</span>
  <span class="hljs-string">"NetAmount"</span>: <span class="hljs-number">1000.00</span>,                 <span class="hljs-comment">// Net amount (excluding tax)</span>
  <span class="hljs-string">"TotalAmount"</span>: <span class="hljs-number">1190.00</span>,               <span class="hljs-comment">// Gross amount (including tax)</span>
  <span class="hljs-string">"TaxAmount"</span>: <span class="hljs-number">190.00</span>,                  <span class="hljs-comment">// Total tax amount</span>
  <span class="hljs-string">"Currency"</span>: <span class="hljs-string">"EUR"</span>,                    <span class="hljs-comment">// Currency code</span>
  <span class="hljs-string">"ResponsibilityCenter"</span>: <span class="hljs-string">"BERLIN"</span>,     <span class="hljs-comment">// Responsibility center (blank: default from user setup / vendor)</span>
  <span class="hljs-string">"VATBusPostingGroup"</span>: <span class="hljs-string">"EU"</span>,           <span class="hljs-comment">// VAT business posting group (blank: vendor default) - the lines' default</span>

  <span class="hljs-comment">// ───────────────────────────────────────────────────────────</span>
  <span class="hljs-comment">// DIMENSIONS - Header-level dimensions as key-value pairs</span>
  <span class="hljs-comment">// ───────────────────────────────────────────────────────────</span>

  <span class="hljs-string">"Dimensions"</span>: {
    <span class="hljs-string">"DEPARTMENT"</span>: <span class="hljs-string">"SALES"</span>,
    <span class="hljs-string">"PROJECT"</span>: <span class="hljs-string">"PROJ001"</span>,
    <span class="hljs-string">"COSTCENTER"</span>: <span class="hljs-string">"CC-100"</span>
  },

  <span class="hljs-comment">// ───────────────────────────────────────────────────────────</span>
  <span class="hljs-comment">// CUSTOM FIELDS - Additional header fields (see below)</span>
  <span class="hljs-comment">// ───────────────────────────────────────────────────────────</span>

  <span class="hljs-string">"CustomFields"</span>: [
    {
      <span class="hljs-string">"Id"</span>: <span class="hljs-number">50100</span>,
      <span class="hljs-string">"Name"</span>: <span class="hljs-string">"CustomerNo"</span>,
      <span class="hljs-string">"Value"</span>: <span class="hljs-string">"CUST001"</span>,
      <span class="hljs-string">"Caption"</span>: <span class="hljs-string">"Customer Number"</span>
    },
    {
      <span class="hljs-string">"Id"</span>: <span class="hljs-number">50101</span>,
      <span class="hljs-string">"Name"</span>: <span class="hljs-string">"DeliveryTerms"</span>,
      <span class="hljs-string">"Value"</span>: <span class="hljs-string">"EXW"</span>,
      <span class="hljs-string">"Caption"</span>: <span class="hljs-string">"Delivery Terms"</span>
    }
  ],

  <span class="hljs-comment">// ───────────────────────────────────────────────────────────</span>
  <span class="hljs-comment">// METADATA - System-generated field mappings</span>
  <span class="hljs-comment">// ───────────────────────────────────────────────────────────</span>

  <span class="hljs-string">"Metadata"</span>: [
    {
      <span class="hljs-string">"DocumentClass"</span>: <span class="hljs-string">"DXP Invoice / Credit Memo"</span>,
      <span class="hljs-string">"FieldType"</span>: <span class="hljs-string">"Header"</span>,
      <span class="hljs-string">"FieldId"</span>: <span class="hljs-number">101</span>,
      <span class="hljs-string">"Value"</span>: <span class="hljs-string">"Additional Info"</span>
    }
  ],

  <span class="hljs-comment">// ═══════════════════════════════════════════════════════════</span>
  <span class="hljs-comment">// LINES SECTION - Document line items</span>
  <span class="hljs-comment">// ═══════════════════════════════════════════════════════════</span>

  <span class="hljs-string">"Lines"</span>: [
    {
      <span class="hljs-comment">// ─────────────────────────────────────────────────────────</span>
      <span class="hljs-comment">// Line Basic Information</span>
      <span class="hljs-comment">// ─────────────────────────────────────────────────────────</span>

      <span class="hljs-string">"Type"</span>: <span class="hljs-string">"Item"</span>,                   <span class="hljs-comment">// Item, G/L Account, Charge (Item), Fixed Asset</span>
      <span class="hljs-string">"No"</span>: <span class="hljs-string">"ITEM001"</span>,                  <span class="hljs-comment">// Item/Account number</span>
      <span class="hljs-string">"Description"</span>: <span class="hljs-string">"Product A"</span>,       <span class="hljs-comment">// Line description</span>
      <span class="hljs-string">"VendorItemNo"</span>: <span class="hljs-string">"VEND-SKU-123"</span>,   <span class="hljs-comment">// Vendor's item number</span>
      <span class="hljs-string">"Quantity"</span>: <span class="hljs-number">10.0</span>,                 <span class="hljs-comment">// Quantity</span>
      <span class="hljs-string">"UnitOfMeasure"</span>: <span class="hljs-string">"PCS"</span>,          <span class="hljs-comment">// Unit of measure</span>
      <span class="hljs-string">"DirectUnitCost"</span>: <span class="hljs-number">100.00</span>,        <span class="hljs-comment">// Unit price</span>
      <span class="hljs-string">"LineDiscount"</span>: <span class="hljs-number">5.0</span>,             <span class="hljs-comment">// Line discount percentage</span>

      <span class="hljs-comment">// ─────────────────────────────────────────────────────────</span>
      <span class="hljs-comment">// Line Amounts & Tax</span>
      <span class="hljs-comment">// ─────────────────────────────────────────────────────────</span>

      <span class="hljs-string">"NetAmount"</span>: <span class="hljs-number">950.00</span>,             <span class="hljs-comment">// Line net amount</span>
      <span class="hljs-string">"TotalAmount"</span>: <span class="hljs-number">1130.50</span>,          <span class="hljs-comment">// Line gross amount</span>
      <span class="hljs-string">"TaxRate"</span>: <span class="hljs-number">19.0</span>,                 <span class="hljs-comment">// Tax percentage</span>
      <span class="hljs-string">"VATBusPostingGroup"</span>: <span class="hljs-string">"DOMESTIC"</span>,
      <span class="hljs-string">"VATProdPostingGroup"</span>: <span class="hljs-string">"STANDARD"</span>,
      <span class="hljs-string">"GenBusPostingGroup"</span>: <span class="hljs-string">"DOMESTIC"</span>,
      <span class="hljs-string">"GenProdPostingGroup"</span>: <span class="hljs-string">"RETAIL"</span>,

      <span class="hljs-comment">// ─────────────────────────────────────────────────────────</span>
      <span class="hljs-comment">// Order Reference (for matching)</span>
      <span class="hljs-comment">// ─────────────────────────────────────────────────────────</span>

      <span class="hljs-string">"OrderNo"</span>: <span class="hljs-string">"PO-12345"</span>,           <span class="hljs-comment">// Referenced order number</span>
      <span class="hljs-string">"OrderLineNo"</span>: <span class="hljs-number">10000</span>,            <span class="hljs-comment">// Referenced order line number</span>
      <span class="hljs-string">"ReceiptNo"</span>: <span class="hljs-string">"RCP-001"</span>,          <span class="hljs-comment">// Referenced receipt number</span>
      <span class="hljs-string">"ReceiptLineNo"</span>: <span class="hljs-number">10000</span>,          <span class="hljs-comment">// Referenced receipt line number</span>

      <span class="hljs-comment">// ─────────────────────────────────────────────────────────</span>
      <span class="hljs-comment">// Line Dimensions</span>
      <span class="hljs-comment">// ─────────────────────────────────────────────────────────</span>

      <span class="hljs-string">"Dimensions"</span>: {
        <span class="hljs-string">"DEPARTMENT"</span>: <span class="hljs-string">"PROD"</span>,
        <span class="hljs-string">"PROJECT"</span>: <span class="hljs-string">"PROJ001"</span>
      },

      <span class="hljs-comment">// ─────────────────────────────────────────────────────────</span>
      <span class="hljs-comment">// Line Custom Fields</span>
      <span class="hljs-comment">// ─────────────────────────────────────────────────────────</span>

      <span class="hljs-string">"CustomFields"</span>: [
        {
          <span class="hljs-string">"Id"</span>: <span class="hljs-number">50200</span>,
          <span class="hljs-string">"Name"</span>: <span class="hljs-string">"SerialNo"</span>,
          <span class="hljs-string">"Value"</span>: <span class="hljs-string">"SN-12345"</span>,
          <span class="hljs-string">"Caption"</span>: <span class="hljs-string">"Serial Number"</span>
        }
      ],

      <span class="hljs-comment">// ─────────────────────────────────────────────────────────</span>
      <span class="hljs-comment">// Line Metadata</span>
      <span class="hljs-comment">// ─────────────────────────────────────────────────────────</span>

      <span class="hljs-string">"Metadata"</span>: [
        {
          <span class="hljs-string">"DocumentClass"</span>: <span class="hljs-string">"DXP Invoice / Credit Memo"</span>,
          <span class="hljs-string">"FieldType"</span>: <span class="hljs-string">"Line"</span>,
          <span class="hljs-string">"FieldId"</span>: <span class="hljs-number">201</span>,
          <span class="hljs-string">"Value"</span>: <span class="hljs-string">"Line-specific metadata"</span>
        }
      ]
    }
    <span class="hljs-comment">// ... additional lines</span>
  ]
}

```

### Custom Fields vs. Metadata

**Custom Fields:**

- User-defined fields from your app
- Mapped via Custom Field Mapping
- Can be mapped to any field in the Squeeze Validation
- Example: Adding a "Customer No." to the Squeeze Validation

**Metadata:**

- System-generated fields
- Extracted by the Squeeze System but not mapped to any Field in BC

---

## Quick Start: Simple Custom Processing

### Scenario

You want to create a custom document type (e.g., a Warehouse Shipment) from documents marked with "Custom Processing" status.

### Step 1: Subscribe to the Integration Event

Create a codeunit to intercept documents with "Custom Processing" status:

```
codeunit <span class="hljs-number">50100</span> <span class="hljs-string">"My Custom Document Handler"</span>
{
    [EventSubscriber(ObjectType::Codeunit, Codeunit::<span class="hljs-string">"DXP Document Mgt."</span>, 
        <span class="hljs-string">'OnAfterWriteProcessedJsonToBlob'</span>, <span class="hljs-string">''</span>, <span class="hljs-literal">true</span>, <span class="hljs-literal">true</span>)]
    local <span class="hljs-function"><span class="hljs-keyword">procedure</span> <span class="hljs-title">OnAfterWriteProcessedJsonToBlob</span><span class="hljs-params">(
        <span class="hljs-keyword">var</span> Document: Record "DXP Document"; 
        <span class="hljs-keyword">var</span> ProcessedJSONObj: JsonObject)</span>
    <span class="hljs-title">begin</span>
        <span class="hljs-comment">// Only handle Custom Processing status</span>
        <span class="hljs-title">if</span> <span class="hljs-title">Document</span>.<span class="hljs-title">Status</span> <> <span class="hljs-title">Document</span>.<span class="hljs-title">Status</span>:</span>:<span class="hljs-string">"Custom Processing"</span> <span class="hljs-keyword">then</span>
            <span class="hljs-keyword">exit</span>;

        // Create your custom document
        CreateMyCustomDocument(Document, ProcessedJSONObj);
    <span class="hljs-keyword">end</span>;

    local <span class="hljs-function"><span class="hljs-keyword">procedure</span> <span class="hljs-title">CreateMyCustomDocument</span><span class="hljs-params">(
        <span class="hljs-keyword">var</span> Document: Record "DXP Document"; 
        ProcessedJSON: JsonObject)</span>
    <span class="hljs-title">var</span>
        <span class="hljs-title">CoreTokenMgt</span>:</span> Codeunit <span class="hljs-string">"DXP Core Token Mgt."</span>;
        JsonHelper: Codeunit <span class="hljs-string">"DXP Json Helper"</span>;
        MyCustomHeader: Record <span class="hljs-string">"My Custom Document Header"</span>;
        MyCustomLine: Record <span class="hljs-string">"My Custom Document Line"</span>;
        LineJArray: JsonArray;
        LineJToken: JsonToken;
        LineJObj: JsonObject;
    <span class="hljs-keyword">begin</span>
        // ═══════════════════════════════════════════════════════
        // <span class="hljs-number">1</span>. CREATE HEADER
        // ═══════════════════════════════════════════════════════

        MyCustomHeader.Init();
        MyCustomHeader.<span class="hljs-string">"No."</span> := <span class="hljs-string">''</span>;  // Will be assigned by number series

        // Read standard header fields
        MyCustomHeader.<span class="hljs-string">"Vendor No."</span> := 
            JsonHelper.ValAsTxt(ProcessedJSON, CoreTokenMgt.GetVendorNoTok(), <span class="hljs-literal">false</span>);
        MyCustomHeader.<span class="hljs-string">"Document Date"</span> := 
            JsonHelper.ValAsDate(ProcessedJSON, CoreTokenMgt.GetDocDateTok(), <span class="hljs-literal">false</span>);
        MyCustomHeader.<span class="hljs-string">"Document Reference"</span> := 
            JsonHelper.ValAsTxt(ProcessedJSON, CoreTokenMgt.GetDocReferenceTok(), <span class="hljs-literal">false</span>);

        MyCustomHeader.Insert(<span class="hljs-literal">true</span>);

        // ═══════════════════════════════════════════════════════
        // <span class="hljs-number">2</span>. PROCESS DIMENSIONS (<span class="hljs-keyword">if</span> your document supports them)
        // ═══════════════════════════════════════════════════════

        ProcessHeaderDimensions(ProcessedJSON, MyCustomHeader);

        // ═══════════════════════════════════════════════════════
        // <span class="hljs-number">3</span>. PROCESS CUSTOM FIELDS
        // ═══════════════════════════════════════════════════════

        ProcessCustomFields(ProcessedJSON, MyCustomHeader);

        // ═══════════════════════════════════════════════════════
        // <span class="hljs-number">4</span>. CREATE LINES
        // ═══════════════════════════════════════════════════════

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

        foreach LineJToken <span class="hljs-keyword">in</span> LineJArray <span class="hljs-keyword">do</span> <span class="hljs-keyword">begin</span>
            LineJObj := LineJToken.AsObject();

            MyCustomLine.Init();
            MyCustomLine.<span class="hljs-string">"Document No."</span> := MyCustomHeader.<span class="hljs-string">"No."</span>;
            MyCustomLine.<span class="hljs-string">"Line No."</span> := GetNextLineNo(MyCustomHeader.<span class="hljs-string">"No."</span>);

            // Read line fields
            MyCustomLine.<span class="hljs-string">"Item No."</span> := 
                JsonHelper.ValAsTxt(LineJObj, CoreTokenMgt.GetNoTok(), <span class="hljs-literal">false</span>);
            MyCustomLine.Description := 
                JsonHelper.ValAsTxt(LineJObj, CoreTokenMgt.GetDescriptionTok(), <span class="hljs-literal">false</span>);
            MyCustomLine.Quantity := 
                JsonHelper.ValAsDec(LineJObj, CoreTokenMgt.GetQtyTok(), <span class="hljs-literal">false</span>);
            MyCustomLine.<span class="hljs-string">"Unit of Measure"</span> := 
                JsonHelper.ValAsTxt(LineJObj, CoreTokenMgt.GetUoMTok(), <span class="hljs-literal">false</span>);

            MyCustomLine.Insert(<span class="hljs-literal">true</span>);

            // Process line dimensions <span class="hljs-keyword">and</span> custom fields <span class="hljs-keyword">if</span> needed
            ProcessLineDimensions(LineJObj, MyCustomLine);
            ProcessLineCustomFields(LineJObj, MyCustomLine);
        <span class="hljs-keyword">end</span>;

        // ═══════════════════════════════════════════════════════
        // <span class="hljs-number">5</span>. UPDATE CORE DOCUMENT STATUS
        // ═══════════════════════════════════════════════════════

        UpdateCoreDocument(Document, MyCustomHeader);
    <span class="hljs-keyword">end</span>;

    local <span class="hljs-function"><span class="hljs-keyword">procedure</span> <span class="hljs-title">ProcessHeaderDimensions</span><span class="hljs-params">(ProcessedJSON: JsonObject; <span class="hljs-keyword">var</span> MyCustomHeader: Record "My Custom Document Header")</span>
    <span class="hljs-title">var</span>
        <span class="hljs-title">CoreTokenMgt</span>:</span> Codeunit <span class="hljs-string">"DXP Core Token Mgt."</span>;
        DocTransferMgt: Codeunit <span class="hljs-string">"DXP Document Transfer Mgt."</span>;
        DimensionsJObj: JsonObject;
        DimSetID: Integer;
    <span class="hljs-keyword">begin</span>
        // Read dimensions from JSON
        <span class="hljs-keyword">if</span> ProcessedJSON.Contains(CoreTokenMgt.GetDimensionsTok()) <span class="hljs-keyword">then</span> <span class="hljs-keyword">begin</span>
            ProcessedJSON.Get(CoreTokenMgt.GetDimensionsTok(), DimensionsJObj);

            // Convert <span class="hljs-keyword">to</span> Dimension Set ID
            <span class="hljs-keyword">if</span> DocTransferMgt.GetDimSetIdFromJsonObj(DimensionsJObj, DimSetID) <span class="hljs-keyword">then</span> <span class="hljs-keyword">begin</span>
                MyCustomHeader.<span class="hljs-string">"Dimension Set ID"</span> := DimSetID;
                MyCustomHeader.Modify(<span class="hljs-literal">true</span>);
            <span class="hljs-keyword">end</span>;
        <span class="hljs-keyword">end</span>;
    <span class="hljs-keyword">end</span>;

    local <span class="hljs-function"><span class="hljs-keyword">procedure</span> <span class="hljs-title">ProcessCustomFields</span><span class="hljs-params">(ProcessedJSON: JsonObject; <span class="hljs-keyword">var</span> MyCustomHeader: Record "My Custom Document Header")</span>
    <span class="hljs-title">var</span>
        <span class="hljs-title">CoreTokenMgt</span>:</span> Codeunit <span class="hljs-string">"DXP Core Token Mgt."</span>;
        JsonHelper: Codeunit <span class="hljs-string">"DXP Json Helper"</span>;
        CustomFieldsJArray: JsonArray;
        CustomFieldJToken: JsonToken;
        CustomFieldJObj: JsonObject;
        FieldName: Text;
        FieldValue: Text;
    <span class="hljs-keyword">begin</span>
        // Read custom fields array
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> ProcessedJSON.Contains(CoreTokenMgt.GetCustomFieldsTok()) <span class="hljs-keyword">then</span>
            <span class="hljs-keyword">exit</span>;

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

        foreach CustomFieldJToken <span class="hljs-keyword">in</span> CustomFieldsJArray <span class="hljs-keyword">do</span> <span class="hljs-keyword">begin</span>
            CustomFieldJObj := CustomFieldJToken.AsObject();
            FieldName := JsonHelper.ValAsTxt(CustomFieldJObj, CoreTokenMgt.GetNameTok(), <span class="hljs-literal">false</span>);
            FieldValue := JsonHelper.ValAsTxt(CustomFieldJObj, CoreTokenMgt.GetValueTok(), <span class="hljs-literal">false</span>);

            // Map <span class="hljs-keyword">to</span> your custom fields
            <span class="hljs-keyword">case</span> FieldName <span class="hljs-keyword">of</span>
                <span class="hljs-string">'CustomerNo'</span>:
                    MyCustomHeader.<span class="hljs-string">"Customer No."</span> := CopyStr(FieldValue, <span class="hljs-number">1</span>, <span class="hljs-number">20</span>);
                <span class="hljs-string">'DeliveryTerms'</span>:
                    MyCustomHeader.<span class="hljs-string">"Delivery Terms"</span> := CopyStr(FieldValue, <span class="hljs-number">1</span>, <span class="hljs-number">10</span>);
                // Add more field mappings as needed
            <span class="hljs-keyword">end</span>;
        <span class="hljs-keyword">end</span>;

        MyCustomHeader.Modify(<span class="hljs-literal">true</span>);
    <span class="hljs-keyword">end</span>;

    local <span class="hljs-function"><span class="hljs-keyword">procedure</span> <span class="hljs-title">ProcessLineDimensions</span><span class="hljs-params">(LineJObj: JsonObject; <span class="hljs-keyword">var</span> MyCustomLine: Record "My Custom Document Line")</span>
    <span class="hljs-title">var</span>
        <span class="hljs-title">CoreTokenMgt</span>:</span> Codeunit <span class="hljs-string">"DXP Core Token Mgt."</span>;
        DocTransferMgt: Codeunit <span class="hljs-string">"DXP Document Transfer Mgt."</span>;
        DimensionsJObj: JsonObject;
        DimSetID: Integer;
    <span class="hljs-keyword">begin</span>
        <span class="hljs-keyword">if</span> LineJObj.Contains(CoreTokenMgt.GetDimensionsTok()) <span class="hljs-keyword">then</span> <span class="hljs-keyword">begin</span>
            LineJObj.Get(CoreTokenMgt.GetDimensionsTok(), DimensionsJObj);

            <span class="hljs-keyword">if</span> DocTransferMgt.GetDimSetIdFromJsonObj(DimensionsJObj, DimSetID) <span class="hljs-keyword">then</span> <span class="hljs-keyword">begin</span>
                MyCustomLine.<span class="hljs-string">"Dimension Set ID"</span> := DimSetID;
                MyCustomLine.Modify(<span class="hljs-literal">true</span>);
            <span class="hljs-keyword">end</span>;
        <span class="hljs-keyword">end</span>;
    <span class="hljs-keyword">end</span>;

    local <span class="hljs-function"><span class="hljs-keyword">procedure</span> <span class="hljs-title">ProcessLineCustomFields</span><span class="hljs-params">(LineJObj: JsonObject; <span class="hljs-keyword">var</span> MyCustomLine: Record "My Custom Document Line")</span>
    <span class="hljs-title">var</span>
        <span class="hljs-title">CoreTokenMgt</span>:</span> Codeunit <span class="hljs-string">"DXP Core Token Mgt."</span>;
        JsonHelper: Codeunit <span class="hljs-string">"DXP Json Helper"</span>;
        CustomFieldsJArray: JsonArray;
        CustomFieldJToken: JsonToken;
        CustomFieldJObj: JsonObject;
        FieldName: Text;
        FieldValue: Text;
    <span class="hljs-keyword">begin</span>
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> LineJObj.Contains(CoreTokenMgt.GetCustomFieldsTok()) <span class="hljs-keyword">then</span>
            <span class="hljs-keyword">exit</span>;

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

        foreach CustomFieldJToken <span class="hljs-keyword">in</span> CustomFieldsJArray <span class="hljs-keyword">do</span> <span class="hljs-keyword">begin</span>
            CustomFieldJObj := CustomFieldJToken.AsObject();
            FieldName := JsonHelper.ValAsTxt(CustomFieldJObj, CoreTokenMgt.GetNameTok(), <span class="hljs-literal">false</span>);
            FieldValue := JsonHelper.ValAsTxt(CustomFieldJObj, CoreTokenMgt.GetValueTok(), <span class="hljs-literal">false</span>);

            // Map <span class="hljs-keyword">to</span> your line custom fields
            <span class="hljs-keyword">case</span> FieldName <span class="hljs-keyword">of</span>
                <span class="hljs-string">'SerialNo'</span>:
                    MyCustomLine.<span class="hljs-string">"Serial No."</span> := CopyStr(FieldValue, <span class="hljs-number">1</span>, <span class="hljs-number">50</span>);
                // Add more field mappings
            <span class="hljs-keyword">end</span>;
        <span class="hljs-keyword">end</span>;

        MyCustomLine.Modify(<span class="hljs-literal">true</span>);
    <span class="hljs-keyword">end</span>;

    local <span class="hljs-function"><span class="hljs-keyword">procedure</span> <span class="hljs-title">UpdateCoreDocument</span><span class="hljs-params">(<span class="hljs-keyword">var</span> Document: Record "DXP Document"; MyCustomHeader: Record "My Custom Document Header")</span>
    <span class="hljs-title">var</span>
        <span class="hljs-title">DocumentMgt</span>:</span> Codeunit <span class="hljs-string">"DXP Document Mgt."</span>;
    <span class="hljs-keyword">begin</span>
        // Update the core document <span class="hljs-keyword">to</span> link it <span class="hljs-keyword">to</span> your created document
        Document.Status := Document.Status::Transferred;
        Document.<span class="hljs-string">"Linked-to Record Id"</span> := MyCustomHeader.RecordId;
        Document.Modify(<span class="hljs-literal">true</span>);

        // Optionally, transfer attachments from Core <span class="hljs-keyword">to</span> your document
        TransferAttachments(Document, MyCustomHeader);
    <span class="hljs-keyword">end</span>;

    local <span class="hljs-function"><span class="hljs-keyword">procedure</span> <span class="hljs-title">TransferAttachments</span><span class="hljs-params">(Document: Record "DXP Document"; MyCustomHeader: Record "My Custom Document Header")</span>
    <span class="hljs-title">var</span>
        <span class="hljs-title">DocAttachment</span>:</span> Record <span class="hljs-string">"DXP Document Attachment"</span>;
        MyDocAttachment: Record <span class="hljs-string">"Document Attachment"</span>;
        InStr: InStream;
    <span class="hljs-keyword">begin</span>
        DocAttachment.SetRange(<span class="hljs-string">"Document No."</span>, Document.<span class="hljs-string">"No."</span>);
        <span class="hljs-keyword">if</span> DocAttachment.FindSet() <span class="hljs-keyword">then</span>
            <span class="hljs-keyword">repeat</span>
                MyDocAttachment.Init();
                MyDocAttachment.ID := <span class="hljs-number">0</span>;

                DocAttachment.<span class="hljs-string">"File Content"</span>.CreateInStream(InStr);
                MyDocAttachment.SaveAttachmentFromStream(
                    InStr, 
                    MyCustomHeader.RecordId, 
                    DocAttachment.<span class="hljs-string">"File Name"</span>);
            <span class="hljs-keyword">until</span> DocAttachment.Next() = <span class="hljs-number">0</span>;
    <span class="hljs-keyword">end</span>;

    local <span class="hljs-function"><span class="hljs-keyword">procedure</span> <span class="hljs-title">GetNextLineNo</span><span class="hljs-params">(DocumentNo: Code[20])</span>:</span> Integer
    <span class="hljs-keyword">var</span>
        MyCustomLine: Record <span class="hljs-string">"My Custom Document Line"</span>;
    <span class="hljs-keyword">begin</span>
        MyCustomLine.SetRange(<span class="hljs-string">"Document No."</span>, DocumentNo);
        <span class="hljs-keyword">if</span> MyCustomLine.FindLast() <span class="hljs-keyword">then</span>
            <span class="hljs-keyword">exit</span>(MyCustomLine.<span class="hljs-string">"Line No."</span> + <span class="hljs-number">10000</span>);
        <span class="hljs-keyword">exit</span>(<span class="hljs-number">10000</span>);
    <span class="hljs-keyword">end</span>;
}

```

### Key Helper Codeunits

<table id="bkmrk-codeunit-purpose-dxp"><thead><tr><th>Codeunit</th><th>Purpose</th></tr></thead><tbody><tr><td>**DXP Core Token Mgt.**</td><td>Provides token names for JSON fields (GetVendorNoTok(), GetDocDateTok(), etc.)</td></tr><tr><td>**DXP Json Helper**</td><td>JSON parsing utilities (ValAsTxt(), ValAsDate(), ReadJArrayFromObj(), etc.)</td></tr><tr><td>**DXP Document Transfer Mgt.**</td><td>Dimension processing, metadata transfer, custom field handling</td></tr><tr><td>**DXP Document Mgt.**</td><td>Core document management (UpdateDocument(), UpdateDocumentStatus(), etc.)</td></tr></tbody></table>

---

*For questions or clarifications, please contact DEXPRO Solutions GmbH.*

# Rule-based next process step: routing documents to Custom Processing

## Audience

This guide is intended for developers and implementers who use DEXPRO SQUEEZE in Business Central and want to control, **before** the actual "[Custom Processing](https://docs.squeeze.one/books/squeeze-for-dynamics-365-bc-de-de/page/erstellung-benutzerdefinierter-dokumente-individuelle-verarbeitung)" implementation, **which** documents should switch to the **"Custom Processing"** status after validation.

## Context (comes *before* the existing Custom Processing guide)

With this configuration you ensure that a document (depending on rules) is given the **"Custom Processing"** status. **From that point on**, the flow for custom processing described in the existing documentation takes over ([creating target documents from "JSON Processed"](https://docs.squeeze.one/link/862#bkmrk-%C3%9Cberblick-0)).

Further reading (next step, after this documentation):

- [Creating custom documents (Custom Processing)](https://docs.squeeze.one/books/squeeze-for-dynamics-365-bc-de-de/page/erstellung-benutzerdefinierter-dokumente-individuelle-verarbeitung)

## UI terms used

The following terms correspond to the field captions used in the app:

- **"Custom Processing"** = `DXP Document Status` / `Custom Processing`
- **"Next Process Step"** = `Next Process Step`
- **"Standard document"** = enum value `Standard document`
- **"Rule Set", "Rule Set Code"** = `Rule Set`, `Rule Set Code`
- **"Rule Group", "Rule Group Code"** = `Rule Group`, `Rule Group Code`

## Technical background

- There is one **document class setup** (`DXP Document Class Setup`) per **document class**.
- A SQUEEZE rule set is created automatically for each document class setup (internally: field **"DXP Sqz Rule Set Code"**).
- A **rule group code** is stored in the document class setup (internally: field **"DXP Sqz Next Step Rule Group"**).
- When the next process step is determined, the rules of this rule group are evaluated.
- If a rule matches and 
    - **Next Process Step = "Standard document"** and
    - **Custom Processing = Yes**, then the document status is set to **"Custom Processing"**.

Important:

- If **no** rule group is stored or **no** rule matches, the system falls back to the default "Next Process Step" value from the document class setup.

## Prerequisites

- DEXPRO Core and DEXPRO SQUEEZE are installed.
- You have access to the **Document Class Setup** (DXP Document Class Setup) page as well as the rule engine pages (rule groups / rules).

## Step by step: creating and linking the "DXP Sqz Next Step Rule Group" rule group

### 1) Open the document class setup

1. Open the **Document Class Setup** page.
2. Select the document class for which you want to enable routing to **"Custom Processing"**.
3. Scroll to the **Squeeze** area → **Rules** group.

{add screenshot of Document Class Setup - Squeeze Rules here}

### 2) Create the rule group

1. In **Squeeze → Rules**, select the **"Next Process Step"** field (technically: *DXP Sqz Next Step Rule Group*).
2. Open the **lookup** (rule group list).
3. Create a new **rule group**: 
    - **Rule Group Code**: for example `Next Step Rules`
    - **Description**: for example "Routing to Custom Processing"

Note: The lookup is already filtered to the matching **rule set code** by the document class. You do not need to search for the rule set manually.

{add screenshot of rule group lookup here}

### 3) Save the rule group in the document class setup

1. Make sure that the **"Next Process Step"** field in the document class setup now contains the rule group code `DXP Sqz Next Step Rule Group`.

{add screenshot of document class setup with rule group code here}

## Step by step: creating a rule that routes to "Custom Processing"

### 4) Open the rules of the rule group

1. Open the **rule group** `DXP Sqz Next Step Rule Group` you just created.
2. Choose the **"Show Rules"** action (opens the rules of this rule group).

{add screenshot of Show Rules action here}

### 5) Create a new rule

1. Create a new **rule**.
2. Set a meaningful **priority** (lower value = higher priority, if your environment is configured that way).
3. Define the **conditions** (rule conditions) that must match.

<p class="callout info">\[Best Practice\] Start with a very clear, testable condition (for example based on document type, vendor, amount thresholds, company, etc.) before you build complex rule chains.</p>

{add screenshot of new rule here}

### 6) Set the SQUEEZE-specific fields (decisive for "Custom Processing")

On the rule card there is a **"DEXPRO Squeeze"** area (CardPart). Set the following there:

1. **Next Process Step** = **"Standard document"**
2. **Custom Processing** = **Yes**

Result:

- When this rule matches, the document status is set to **"Custom Processing"**.

{add screenshot of rule card DEXPRO Squeeze area here}

Optional:

- **Account Assignment Code** (if you additionally want to control account assignment logic via rules; independent of the Custom Processing routing).

## Checks / troubleshooting

### The document does *not* end up in "Custom Processing"

Work through these points:

1. **Is the rule group stored?** The rule group code must be set in the document class setup (otherwise the system falls back to the default "**Next Process Step**").
2. **Does a rule match?** If no rule matches, the next process step is not overwritten.
3. **Is "Custom Processing" set AND Next Step = "Standard document"?**
    - If "Custom Processing" = Yes but Next Step ≠ "Standard document", the status is not set to "Custom Processing".
4. **Can you not see the "DEXPRO Squeeze" area on the rule card?**
    - Then you are most likely in a rule set that does not belong to the document class setup (source table).
    - It is best to create the rule group via the lookup from the document class setup. This saves you the detour via the general rule management.

### The document ends up in "Done" instead of "Custom Processing"

If a rule matches and **Next Process Step** is set to a value other than "Standard document", the status may be set to **"Done"** (which skips further processing). Therefore check the Next Step selection in the rule. This is the case, for example, when the [Breeze Interface](https://docs.squeeze.one/books/dexpro-microsoft-dynamics-365-business-central-apps/chapter/dexpro-breeze-interface) app is used.

### Afterwards (transition into the existing [documentation on custom document creation](https://docs.squeeze.one/books/squeeze-for-dynamics-365-bc-de-de/page/erstellung-benutzerdefinierter-dokumente-individuelle-verarbeitung))

As soon as a document is in the **"Custom Processing"** status, it is ready for processing:

- Read "JSON Processed" from `DXP Document`
- Create target documents
- Set the final status

# Embedding the SQUEEZE Viewer part in your own pages WIP

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;
}
```

# Events after creating a SQUEEZE document (CreateSource)

## Audience

This documentation is intended for third-party developers who want to extend the **creation process of a SQUEEZE document** (tables `DXP SQZ Document Header`/`DXP SQZ Document Line`).

The focus is on the **integration events** that are raised *after* or *during* `ISourceDocument.CreateSource(...)`.

## Context: where is `CreateSource` called?

The entry point for the SQUEEZE integration is in the **`DXP SQZ API Mgt.`** codeunit:

- A Core document (`DXP Document`) is created (via `DXP Document Mgt.`).
- The **`DXP ISource Document`** interface is then resolved via the document class and `CreateSource(Document)` is executed.

Simplified flow (from `CreateCoreAndSQUEEZEDocument`):

1. `DocumentMgt.AddDocument(DocClass, ...)` (create the Core document)
2. `ISourceDocument.CreateSource(Document)` (create the SQUEEZE document)
3. Process the import queue / "finish" the SQUEEZE document

## Document classes &amp; default behavior

Which `CreateSource` implementation is executed is defined in the enum extension **`DXP Document Class Ext.`**.

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

**Default (when no specific implementation is set):** The `DXP Document Class` enum defines a `DefaultImplementation` on **`DXP Source Document Def. Impl.`**. This default implementation:

- does **not** create a SQUEEZE document,
- `CreateSource(...)` returns **`false`**.

## Common pipeline inside `CreateSource`

Both SQUEEZE implementations follow (in short) this pattern:

1. Guard: the Core document must have `Status = Imported` and `JSON Raw` must exist.
2. `DXP SQZ Document Mgt.`: 
    - `CreateSQUEEZEDocHeader(Document, DocHeader, RawJson)`
    - `CreateSQUEEZEDocLine(DocHeader, RawJson)`
3. `DXP Document Mgt.`: `UpdateDocumentAfterTransfer(...)` (set the Core document to "Transferred" and link it to the SQZ header)
4. Order match / autocomplete / attachments / validation (depending on document class and setup)

The most important events are therefore attached to:

- the header/line creation in `DXP SQZ Document Mgt.`
- the completion of the `CreateSource` procedure in the respective implementations
- optional steps such as order match or validation

## Event matrix (short overview)

<table id="bkmrk-area-event-timing-co"><thead><tr><th>Area</th><th>Event</th><th>Timing</th><th>Control</th><th>Typical 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>after initializing the mapping/token context</td><td>–</td><td>extend/check mapping</td></tr><tr><td>`DXP SQZ API Mgt.`</td><td>[`OnAfterSaveAttachment(...)`](https://docs.squeeze.one/link/939#bkmrk-onaftersaveattachmen)</td><td>after an attachment has been saved successfully</td><td>–</td><td>post-processing/indexing</td></tr><tr><td>`DXP SQZ API Mgt.`</td><td>[`OnAfterSetOriginFileNameOnBeforeModifyDocumentAttachment(...)`](https://docs.squeeze.one/link/939#bkmrk-onaftersetoriginfile)</td><td>before the attachment record is modified</td><td>–</td><td>influence file name/origin</td></tr><tr><td>`DXP SQZ P. Inv/Crdt Memo Impl.`</td><td>[`OnAfterCreateDocLines`](https://docs.squeeze.one/link/939#bkmrk-onaftercreatedocline)</td><td>directly after `CreateSQUEEZEDocLine(...)`</td><td>–</td><td>evaluate lines/RawJson after creation</td></tr><tr><td>`DXP SQZ P. Inv/Crdt Memo Impl.`</td><td>[`OnAfterCreateSource`](https://docs.squeeze.one/link/939#bkmrk-onaftercreatesource)</td><td>at the end of `CreateSource(...)`</td><td>–</td><td>post actions after autocomplete/order match/validation</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>before the plausibility checks</td><td>`IsHandled`</td><td>replace/extend checks</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>after the plausibility checks</td><td>–</td><td>reuse the result (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>before the plausibility checks</td><td>`IsHandled`</td><td>replace/extend checks</td></tr><tr><td>`DXP SQZ Document Mgt.`</td><td>[`OnBeforeModifySQUEEZEDocumentHeader`](https://docs.squeeze.one/link/939#bkmrk-onbeforemodifysqueez)</td><td>in `CreateSQUEEZEDocHeader(...)` before the final modify</td><td>–</td><td>add/normalize header values from `RawJson`</td></tr><tr><td>`DXP SQZ Document Mgt.`</td><td>[`OnBeforeTransferRecognizedValuesOnAfterAssignRecognizedValues`](https://docs.squeeze.one/link/939#bkmrk-onbeforetransferreco)</td><td>in standard line post-processing before `TransferRecognizedValues`</td><td>–</td><td>correct recognized values per line</td></tr><tr><td>`DXP SQZ Document Mgt.`</td><td>[`OnAfterTransferRecognizedValuesOnBeforeInsertDocLine`](https://docs.squeeze.one/link/939#bkmrk-onaftertransferrecog)</td><td>in standard line post-processing after `TransferRecognizedValues`</td><td>–</td><td>line-based derivations (account assignment/dimensions)</td></tr><tr><td>`DXP SQZ Document Mgt.`</td><td>[`OnBeforePerformAutomaticOrdermatch`](https://docs.squeeze.one/link/939#bkmrk-onbeforeperformautom)</td><td>before the automatic order match</td><td>`IsHandled`</td><td>take over order match completely/adjust OrderNoList</td></tr><tr><td>JSON creation</td><td>[`OnBeforeAddLineToDocumentJObj`](https://docs.squeeze.one/link/939#bkmrk-onbeforeaddlinetodoc)</td><td>before the line structure is built</td><td>–</td><td>extend the header JSON</td></tr><tr><td>JSON creation</td><td>[`OnBeforeAddLineJObjToLineJArray`](https://docs.squeeze.one/link/939#bkmrk-onbeforeaddlinejobjt)</td><td>before `LineJArray.Add(LineJObj)`</td><td>–</td><td>extend the line JSON</td></tr></tbody></table>

---

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

This codeunit provides the **strongest extension points directly in the `CreateSource` flow**.

### `OnAfterCreateDocLines`

Raised directly after `CreateSQUEEZEDocLine(...)` (that is, once the header &amp; lines exist).

Signature:

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

```

Typical use cases:

- additional header initialization (for example custom flags/fields)
- additional line-based evaluations directly after creation
- extract custom metadata from `RawJson`

### `OnAfterCreateSource`

Raised at the end of `CreateSource(...)` (after autocomplete/order match/optional attachment download/optional auto-validate).

Signature:

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

```

Typical use cases:

- start downstream automations (workflows, notifications)
- additional data validation or custom status logic

### Plausibility check events

These events relate to the `IsSourceDataPlausible(...)` method and are relevant if you extend/replace plausibility checks.

Signatures (excerpt):

```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 creation (Processed JSON from the SQZ document)

The implementation contains events before line JSON is added to the header structure:

#### `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:

- write additional fields into the header JSON
- add additional fields per line (custom tokens)

---

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

This codeunit has **no** dedicated `OnAfterCreateSource` events in `CreateSource(...)`. Extensions here typically happen via:

- `DXP SQZ Document Mgt.` (header/line creation, order match)
- the JSON creation (`CreateProcessedJsonFromSource`)
- plausibility check events

### JSON creation

```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")

```

### Plausibility check events

Analogous to the Invoice/Credit Memo implementation:

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

---

## Events in `DXP SQZ Document Mgt.` (common to all document classes)

These events are especially relevant because they are raised **within** the header/line creation and order match pipeline.

### `OnBeforeModifySQUEEZEDocumentHeader`

Raised in `CreateSQUEEZEDocHeader(...)`, after values have been assigned via field mapping and **before** the header is finally modified.

Signature:

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

```

Use cases:

- adjust header fields before the standard post-processing runs
- take additional values from `RawJson` into the header

### `OnBeforeTransferRecognizedValuesOnAfterAssignRecognizedValues`

### `OnAfterTransferRecognizedValuesOnBeforeInsertDocLine`

Raised in the standard line post-processing (for standard document classes) around `TransferRecognizedValues(DocLine)`.

Signatures:

```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:

- correct/normalize recognized values per line
- prepare additional derivations (for example account assignment, dimensions)

### `OnBeforePerformAutomaticOrdermatch`

Raised in `PerformAutomaticOrdermatch(...)`, after the order number list (`OrderNoList`) has been filled and **before** the automatic order match runs.

Signature:

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

```

Use cases:

- take over the automatic order match logic completely (`IsHandled := true`)
- adjust the OrderNoList (for example filter/extend)

---

## Events in `DXP SQZ API Mgt.` (close to creation/attachments)

These events sit "around" the API communication (for example attachments).

### `OnAfterInitMapping`

Raised in `DownloadFieldMapping(...)`, **after** the document class has initialized the default mapping via the `DXP ISource Document` interface (`ISourceDocument.InitFieldMapping(...)`) and **before** the field list from SQUEEZE is synchronized into the BC field mapping structures.

Signature:

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

```

Parameters:

- `HeaderMapping` / `LineMapping`: dictionaries with the default mapping **SQZ field name → BC field no.** (modifiable via `var`)
- `DocClass`: the document class for which the mapping is loaded

Typical use cases:

- add additional (customer-specific) field names as default mapping
- correct the default mapping (for example when SQZ field names have changed)
- mapping validation/logging per document class

### `OnAfterSaveAttachment`

Raised in `SaveAttachments(...)` **per attachment**, after the attachment has been created via `DXP Document Attachment Mgt.` and – if the attachment is marked as origin file – after the `Modify(true)` of the record.

Signature:

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

```

<p class="callout info">`DocumentAttachment` is **not** passed as `var`. If you want to change fields on the record persistently, you must reload the record in the subscriber and explicitly call `Modify(...)`.</p>

Typical use cases:

- index/archive the attachment externally
- derive metadata (for example classification by MIME type/file extension)
- trigger follow-up processes (for example OCR/preview generation in a third-party system)

### `OnAfterSetOriginFileNameOnBeforeModifyDocumentAttachment`

Raised in `SaveAttachments(...)`, **when** the attachment is marked as origin file according to the payload and after `Is Source File` and `File Name` (origin name) have been set – but **before** `DocumentAttachment.Modify(true)` is called.

Signature:

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

```

Typical use cases:

- normalize the origin file name (for example remove forbidden characters, enforce a naming scheme)
- set additional fields on the attachment before it is saved

## Minimal examples: EventSubscriber for `DXP SQZ API Mgt.`

The following examples show typical, **small** extensions. Adjust the object IDs/names to your namespace.

```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
        // Example: add a new SQZ field as default mapping.
        // Important: The integer value is the BC field no. of the target field (depends on your mapping concept).
        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
        // Example: normalize the origin file name (remove whitespace).
        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
        // Example: kick off post-processing.
        // If you want to change the attachment persistently, you can also modify the parameter record directly.
        // DocumentAttachment.Validate("Your Field", ...);
        // DocumentAttachment.Modify(true);
    end;
}

```

# Events after creating a Core document (DXP Document)

## Audience

This documentation is intended for third-party developers who want to extend the lifecycle of the Core document **`DXP Document`** – in particular at the points where:

- the status / "Next Process Step" is evaluated,
- `JSON Raw` / `JSON Processed` is updated,
- standard documents are created (for example purchase documents),
- attachments are transferred.

## Context: Core document creation vs. further processing

### 1) The Core document is created (`AddDocument`)

A `DXP Document` is created in **`DXP Document Mgt.`** via `AddDocument(...)`:

- The status is set to **Imported**.
- `JSON Raw` is written (if present) to a BLOB field.

### 2) Afterwards: transfer/linking and processing

Typical next steps (depending on the app flow):

- Linking to a source document (for example a SQUEEZE header) via `UpdateDocumentAfterTransfer(...)`
- Creating a standard document and writing `JSON Processed` via `UpdateDocument(...)`

Most extension points are therefore **not directly at the insert**, but in the update/processing flow.

## Event matrix (short overview)

<table id="bkmrk-area-event-timing-co"><thead><tr><th>Area</th><th>Event</th><th>Timing</th><th>Control</th><th>Typical 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(...)` before `Modify(true)`</td><td>–</td><td>set additional info as soon as `Linked-to Record Id` is set</td></tr><tr><td>`DXP Document Mgt.`</td><td>[`OnBeforeGetDocumentOnBeforeCheckStatusAndNextProcessStep`](https://docs.squeeze.one/link/940#bkmrk-onbeforegetdocumento)</td><td>early in `UpdateDocument(...)` before `Document.Get(DocNo)`</td><td>`IsHandled`</td><td>overwrite status/next step, take over processing</td></tr><tr><td>`DXP Document Mgt.`</td><td>[`OnAfterGetDocumentOnBeforeCheckStatusAndNextProcessStep`](https://docs.squeeze.one/link/940#bkmrk-onaftergetdocumenton)</td><td>in `UpdateDocument(...)` after `Get(...)` before standard document creation</td><td>`Handled`</td><td>decide depending on the document, adjust JSON/status</td></tr><tr><td>`DXP Document Mgt.`</td><td>[`OnAfterUpdateRawJson`](https://docs.squeeze.one/link/940#bkmrk-onafterupdaterawjson)</td><td>after writing `JSON Raw` in `UpdateRawJsonJson(...)`</td><td>–</td><td>normalize raw JSON/additional derivations</td></tr><tr><td>`DXP Document Mgt.`</td><td>[`OnAfterWriteProcessedJsonToBlob`](https://docs.squeeze.one/link/940#bkmrk-onafterwriteprocesse)</td><td>after writing `JSON Processed` in `UpdateStatusAndProcessedJson(...)`</td><td>–</td><td>finalize processed JSON, add tokens</td></tr><tr><td>`DXP Document Mgt.`</td><td>[`OnBeforeTransferCoreAttachmentsToStandardDocument`](https://docs.squeeze.one/link/940#bkmrk-onbeforetransfercore)</td><td>before the attachment transfer</td><td>`IsHandled`</td><td>replace/selectively control the transfer</td></tr><tr><td>`DXP Document Transfer Mgt.`</td><td>[`OnBeforeExitProcessedJsonDocHeader`](https://docs.squeeze.one/link/940#bkmrk-onbeforeexitprocesse)</td><td>when creating header JSON (before `exit(JObject)`)</td><td>–</td><td>add/normalize standard tokens</td></tr><tr><td>`DXP Document Transfer Mgt.`</td><td>[`OnBeforeExitProcessedJsonDocLine`](https://docs.squeeze.one/link/940#bkmrk-onbeforeexitprocesse-0)</td><td>when creating line JSON (before `exit(JObject)`)</td><td>–</td><td>add/normalize line tokens</td></tr><tr><td>`DXP Document Transfer Mgt.`</td><td>[`OnAfterCreatePurchaseHeader`](https://docs.squeeze.one/link/940#bkmrk-onaftercreatepurchas)</td><td>after creating/validating the purchase header</td><td>–</td><td>add header fields, post-processing</td></tr><tr><td>`DXP Document Transfer Mgt.`</td><td>[`OnBeforeInsertPurchaseLine`](https://docs.squeeze.one/link/940#bkmrk-onbeforeinsertpurcha)</td><td>before inserting a purchase line</td><td>–</td><td>adjust purchase line (for example account assignment, type/no.)</td></tr><tr><td>`DXP Document Transfer Mgt.`</td><td>[`OnAfterInsertPurchaseLineOnBeforeAddDimensions`](https://docs.squeeze.one/link/940#bkmrk-onafterinsertpurchas)</td><td>after insert, before dimensions</td><td>–</td><td>prepare dimensions/additional info</td></tr><tr><td>`DXP Document Transfer Mgt.`</td><td>[`OnAfterOnBeforeAddLineDimensions`](https://docs.squeeze.one/link/940#bkmrk-onafteronbeforeaddli)</td><td>for receipt lines: before the dimensions of the line to be updated</td><td>–</td><td>adjust update cases (receipt)</td></tr><tr><td>`DXP Document Transfer Mgt.`</td><td>[`OnAfterProcessAdditionalInformation`](https://docs.squeeze.one/link/940#bkmrk-onafterprocessadditi)</td><td>after transferring additional information</td><td>–</td><td>add add-on fields per target RecordRef</td></tr><tr><td>`DXP Document Transfer Mgt.`</td><td>[`OnAfterCreatePurchaseDoc`](https://docs.squeeze.one/link/940#bkmrk-onaftercreatepurchas-0)</td><td>after the complete creation of the purchase document</td><td>–</td><td>final hook (logging, follow-up actions)</td></tr></tbody></table>

---

## Events in `DXP Document Mgt.` (Core lifecycle)

### `OnAfterSetLinkedToRecIdOnBeforeModify`

Raised in `UpdateDocumentAfterTransfer(...)` – after the status/`Linked-to Record Id` have been set, but **before** the `DXP Document` is saved.

Signature:

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

```

Use cases:

- set additional information on the Core document as soon as it is linked to a source document
- add your own logging/audit information

### `OnBeforeGetDocumentOnBeforeCheckStatusAndNextProcessStep`

Called in `UpdateDocument(...)` as the **earliest hook** – even before the `DXP Document` is loaded via `Get(DocNo)`.

Signature:

```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:

- overwrite status/next step in advance (for example "Custom Processing")
- take over the complete processing (`IsHandled := true`)

### `OnAfterGetDocumentOnBeforeCheckStatusAndNextProcessStep`

Raised in `UpdateDocument(...)` after `Document.Get(DocNo)`, before standard documents are created.

Signature:

```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:

- decide, depending on the current Core document, whether processing should be taken over
- adjust the `ProcessedJSONObj` before further processing

### `OnAfterUpdateRawJson`

Raised in `UpdateRawJsonJson(...)` after writing `JSON Raw`.

Signature:

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

```

Use cases:

- validation/normalization steps on `RawJSONObj`
- additional data extraction directly after the raw update

### `OnAfterWriteProcessedJsonToBlob`

Raised in `UpdateStatusAndProcessedJson(...)` after `JSON Processed` has been written to the BLOB.

Signature:

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

```

Use cases:

- add additional tokens
- finalize the data structure before downstream processes access it

### `OnBeforeTransferCoreAttachmentsToStandardDocument`

Raised in `TransferCoreAttachmentsToStandardDocument(...)` before the transfer. The standard logic can be replaced via `IsHandled`.

Signature:

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

```

Use cases:

- take over the attachment transfer completely (for example different storage/naming)
- restrict the transfer selectively

---

## Events in `DXP Document Transfer Mgt.` (standard documents from `JSON Processed`)

This codeunit is relevant as soon as standard documents (for example purchase documents) are created from `JSON Processed`.

### `OnBeforeExitProcessedJsonDocHeader`

Raised in `CreateDocHeaderJson(...)` immediately before `exit(JObject)`.

Signature:

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

```

Use cases:

- add additional standard tokens
- normalize token values (for example type/text representation)

### `OnBeforeExitProcessedJsonDocLine`

Raised in `CreateDocLineJson(...)` immediately before `exit(JObject)`.

Signature:

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

```

Use cases:

- add/normalize line tokens

### `OnAfterCreatePurchaseHeader`

Raised after the purchase header is created/validated.

Signature:

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

```

Use cases:

- set additional fields on the `Purchase Header` (for example custom references)
- post-processing/logging

### `OnBeforeInsertPurchaseLine`

Raised immediately before a purchase line is inserted.

Signature:

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

```

Use cases:

- adjust the purchase line (for example account assignment, type/no.)

### `OnAfterInsertPurchaseLineOnBeforeAddDimensions`

Raised after the purchase line is inserted, but before dimension validation.

Signature:

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

```

Use cases:

- prepare dimensions/additional info

### `OnAfterOnBeforeAddLineDimensions`

Raised in the receipt line update case, before dimensions are set for the purchase line to be updated.

Signature:

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

```

Use cases:

- adjust update cases (receipt)

### `OnAfterProcessAdditionalInformation`

Raised after the transfer of additional information (`ProcessAdditionalInformation`).

Signature:

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

```

Use cases:

- add add-on fields for different target tables (header/line)

### `OnAfterCreatePurchaseDoc`

Raised after the complete creation of the purchase document.

Signature:

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

```

Use cases:

- final hook (post-processing, logging, follow-up actions)

---

## Minimal example: influencing status/next step toward "Custom Processing"

Example: set `Status` to "Custom Processing" before the standard processing:

```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";
        // optionally adjust NextStep
    end;
}

```

<p class="callout info">\[Best Practice\] Use `IsHandled := true` only when you fully replace the standard logic – otherwise it can lead to inconsistent status/JSON states.</p>

# Adding a field in the validation (up to Squeeze BC App version 1.*)

The following uses an example extension to show how, as a developer, you can add fields in the validation.

#### Required integration events including example procedures:

```al
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

```al
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.';
        }
    }
}
```

```al
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.';
        }
    }
}
```

##### Target document (here: purchase document)

```al
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

```al
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;
}

```

```al
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.';
            }
        }
    }
}

```

##### Target document (here: purchase document)

```al
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;
            }
        }
    }
}

```

```al
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;
            }
        }
    }
}

```

##### Optional helper objects:

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

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

# Adding a field in the validation (from version 2.0)

The following uses an example extension to show how, as a developer, you can add fields in the validation (here: document class Invoice/Credit Memo).

#### Required integration events including example procedures:

```al
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

```al
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.';
        }
    }
}
```

```al
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.';
        }
    }
}
```

##### Target document (here: purchase document)

```al
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

```al
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;
}

```

```al
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.';
            }
        }
    }
}

```

##### Target document (here: purchase document)

```al
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;
            }
        }
    }
}

```

```al
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;
            }
        }
    }
}

```

##### Optional helper objects:

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

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

# Events when validating a SQUEEZE document (Validate action)

## Target audience

This documentation is aimed at third-party developers who want to run their own logic when a user triggers the **Validate** action on the **DXP SQZ Document v2** page (page `70954659`) – for example additional custom checks before validation, setting further values on the SQUEEZE document, or follow-up actions on the target document that was created.

The events described here are available from **SQUEEZE for Dynamics 365 BC version 2.50**.

## Context: what happens when you choose Validate?

The **Validate** action calls two routines of the document page in sequence:

### 1) `CheckPlausibility()`

Runs the **plausibility check** of the respective **document class** and updates the **plausibility entries** in the lower part of the page. This routine runs not only when you choose **Validate**, but after almost every field change. It is therefore **not** a suitable hook for "once, on validation".

To extend the check itself, use the already documented events `OnBeforeDoPlausibilityChecks`, `OnAfterDoHeaderPlausibilityChecks`, `OnAfterDoLinePlausibilityChecks` and `OnAfterDoPlausibilityChecks`.

### 2) `FinishValidation()`

Stops with an error message as long as plausibility entries are still open, handles the duplicate check where applicable, and then calls the `ValidateSQUEEZEDocument` method of the **`DXP SQZ Document Mgt.`** codeunit (`70954632`). That is where the processed JSON is created, the Core document (**`DXP Document`**) is processed further, the target document is created and the SQUEEZE document is completed.

**Both events described here are raised in `DXP SQZ Document Mgt.ValidateSQUEEZEDocument`.** They therefore apply uniformly to:

- the **Validate** action on **DXP SQZ Document v2** (`70954659`),
- the **Validate** action on **DXP SQZ Document (Generic)** (`70954670`) and the older document pages,
- the **automatic validation** (for example from the processing queue).

There is deliberately no event directly on the action. If you want to distinguish between a manual click and automatic validation, evaluate the `AutomaticValidation` parameter.

## Event matrix (overview)

<table class="code-line" data-line="46" dir="auto" id="bkmrk-area-event-timing-co"><thead class="code-line" data-line="46" dir="auto"><tr class="code-line" data-line="46" dir="auto"><th>Area</th><th>Event</th><th>Timing</th><th>Control</th><th>Typical use cases</th></tr></thead><tbody class="code-line" data-line="48" dir="auto"><tr class="code-line" data-line="48" dir="auto"><td>`DXP SQZ Document Mgt.`</td><td>`OnBeforeValidateSQUEEZEDocument`</td><td>After the plausibility and duplicate checks, before the processed JSON is created and the target document is generated</td><td>`var IsHandled: Boolean` cancels the validation; `var DocHeader` allows changes to the document</td><td>Custom additional checks with cancellation, filling fields, starting an approval workflow, querying external systems</td></tr><tr class="code-line" data-line="49" dir="auto"><td>`DXP SQZ Document Mgt.`</td><td>`OnAfterValidateSQUEEZEDocument`</td><td>After the Core document has been processed and the target document has been created, before the validation dialog</td><td>Cancellation not possible</td><td>Post-processing the target document, writing custom log tables, sending notifications, informing third-party systems</td></tr></tbody></table>

## Distinction from `OnAfterWriteProcessedJsonToBlob`

For logic that runs **after** validation, a Core event already exists. It is described on the page *Creating custom documents (custom processing)*:

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

```

It is raised in the **`DXP Document Mgt.`** codeunit (Core) after the processed JSON has been written to the Core document – within the validation process, so it fires when **Validate** is chosen as well.

<table class="code-line" data-line="65" dir="auto" id="bkmrk-%C2%A0-onafterwriteproces"><thead class="code-line" data-line="65" dir="auto"><tr class="code-line" data-line="65" dir="auto"><th> </th><th>`OnAfterWriteProcessedJsonToBlob` (Core)</th><th>`OnAfterValidateSQUEEZEDocument` (SQUEEZE)</th></tr></thead><tbody class="code-line" data-line="67" dir="auto"><tr class="code-line" data-line="67" dir="auto"><td>Raised in</td><td>`DXP Document Mgt.`</td><td>`DXP SQZ Document Mgt.`</td></tr><tr class="code-line" data-line="68" dir="auto"><td>Applies to</td><td>**all** Core documents – including those from other DEXPRO source apps. You have to filter by document class yourself.</td><td>SQUEEZE documents only</td></tr><tr class="code-line" data-line="69" dir="auto"><td>Context</td><td>`DXP Document` and the processed JSON</td><td>additionally the complete **`DXP SQZ Document Header`** with all recognised values, order matching and splitting information</td></tr><tr class="code-line" data-line="70" dir="auto"><td>Manual vs. automatic</td><td>cannot be distinguished</td><td>can be distinguished via `AutomaticValidation`</td></tr><tr class="code-line" data-line="71" dir="auto"><td>Timing</td><td>directly after the JSON is written, still inside the Core processing</td><td>at the end of the validation, after the SQUEEZE document has been completed</td></tr><tr class="code-line" data-line="72" dir="auto"><td>JSON modifiable</td><td>yes (`var ProcessedJSONObj`)</td><td>no</td></tr></tbody></table>

**Recommendation:**

- You want to modify the **processed JSON** or hand a document over to **custom processing** → `OnAfterWriteProcessedJsonToBlob`.
- You want to evaluate SQUEEZE-specific values or react to manual validation only → `OnAfterValidateSQUEEZEDocument`.
- You want to intervene **before** the target document is created, or to cancel the validation → `OnBeforeValidateSQUEEZEDocument`. There is no Core equivalent for this.

## `OnBeforeValidateSQUEEZEDocument`

```al
[IntegrationEvent(false, false)]
local procedure OnBeforeValidateSQUEEZEDocument(var DocHeader: Record "DXP SQZ Document Header"; AutomaticValidation: Boolean; var IsHandled: Boolean)

```

<table class="code-line" data-line="90" dir="auto" id="bkmrk-parameter-descriptio"><thead class="code-line" data-line="90" dir="auto"><tr class="code-line" data-line="90" dir="auto"><th>Parameter</th><th>Description</th></tr></thead><tbody class="code-line" data-line="92" dir="auto"><tr class="code-line" data-line="92" dir="auto"><td>`var DocHeader`</td><td>The SQUEEZE document to be validated. Changes to fields are written afterwards and flow into the target document.</td></tr><tr class="code-line" data-line="93" dir="auto"><td>`AutomaticValidation`</td><td>`false` when **Validate** is chosen, `true` for automatic validation.</td></tr><tr class="code-line" data-line="94" dir="auto"><td>`var IsHandled`</td><td>Set to `true` to cancel the validation. The SQUEEZE document stays open, unchanged.</td></tr></tbody></table>

The event is raised after:

- the document has been re-read and has neither the **Deleted** nor the **Rejected** status,
- the plausibility check succeeded, in the case of automatic validation,

and before the processed JSON is created. At this point the target document does not exist yet.

## `OnAfterValidateSQUEEZEDocument`

```al
[IntegrationEvent(false, false)]
local procedure OnAfterValidateSQUEEZEDocument(DocHeader: Record "DXP SQZ Document Header"; CoreDocument: Record "DXP Document"; AutomaticValidation: Boolean)

```

<table class="code-line" data-line="110" dir="auto" id="bkmrk-parameter-descriptio-1"><thead class="code-line" data-line="110" dir="auto"><tr class="code-line" data-line="110" dir="auto"><th>Parameter</th><th>Description</th></tr></thead><tbody class="code-line" data-line="112" dir="auto"><tr class="code-line" data-line="112" dir="auto"><td>`DocHeader`</td><td>The validated SQUEEZE document. At this point its status is already **Deleted** – this is the regular final state of a validated document and **not** an error.</td></tr><tr class="code-line" data-line="113" dir="auto"><td>`CoreDocument`</td><td>The corresponding Core document (**`DXP Document`**), including `Status` and `Linked-to Record Id` of the target document that was created.</td></tr><tr class="code-line" data-line="114" dir="auto"><td>`AutomaticValidation`</td><td>`false` when **Validate** is chosen, `true` for automatic validation.</td></tr></tbody></table>

Use the Core document to determine whether the target document was actually created:

- `CoreDocument.Status = CoreDocument.Status::Transferred` – the target document was created and `CoreDocument."Linked-to Record Id"` points to it.
- `CoreDocument.Status = CoreDocument.Status::"Custom Processing"` – the document was handed over to **custom processing**; no standard target document exists.

## Important: custom checks do **not** belong in this event

`OnBeforeValidateSQUEEZEDocument` is the wrong place for checks that are meant to prevent a validation. An `Error` raised here only appears as a dialog *after* the user chose **Validate** – so the user learns about the problem at the very end of their work. Use one of the two approaches below instead. Both make the problem appear as a **plausibility entry** and disable the **Validate** action.

### Approach 1 (no development): plausibility rule group

In the **document class setup**, define rules under **Plausibility Rule Group**. Every active and currently valid rule whose conditions **match** describes a problem and automatically creates a plausibility entry. Rules that reference the document lines are evaluated per line, so the entry points to the line concerned.

For checks such as "Shortcut Dimension 1 Code must not be empty" this is the preferred approach – no code, no extension, maintainable by the user.

### Approach 2 (with development): `OnAfterDoPlausibilityChecks`

This event is published by all document classes with `var TempPlausibilityCheckEntry` and is therefore the reliable route. You insert your entry into the temporary record directly:

```al
codeunit 50100 "My SQZ Plausibility Checks"
{
    [EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP SQZ P. Inv/Crdt Memo Impl.",
        'OnAfterDoPlausibilityChecks', '', false, false)]
    local procedure OnAfterChecks(DocHeader: Record "DXP SQZ Document Header"; var TempPlausibilityCheckEntry: Record "DXP Plausibility Check Entry" temporary)
    var
        CostCenterMissingLbl: Label 'Please enter a cost center.';
    begin
        if DocHeader."Shortcut Dimension 1 Code" <> '' then
            exit;

        AddEntry(TempPlausibilityCheckEntry, CostCenterMissingLbl, DocHeader.RecordId(), 0);
    end;

    local procedure AddEntry(var TempPlausibilityCheckEntry: Record "DXP Plausibility Check Entry" temporary; MessageText: Text; LinkedToRecId: RecordId; LookupPageId: Integer)
    var
        NewEntryNo: Integer;
    begin
        NewEntryNo := 1;
        TempPlausibilityCheckEntry.Reset();
        TempPlausibilityCheckEntry.SetCurrentKey("Entry No.");
        if TempPlausibilityCheckEntry.FindLast() then
            NewEntryNo := TempPlausibilityCheckEntry."Entry No." + 1;

        TempPlausibilityCheckEntry.Init();
        TempPlausibilityCheckEntry."Entry No." := NewEntryNo;
        TempPlausibilityCheckEntry.Text := CopyStr(MessageText, 1, MaxStrLen(TempPlausibilityCheckEntry.Text));
        TempPlausibilityCheckEntry."Linked to Record Id" := LinkedToRecId;
        TempPlausibilityCheckEntry."Lookup Page Id" := LookupPageId;
        if TempPlausibilityCheckEntry.Insert() then;
    end;
}

```

The publisher object depends on the **document class**:

<table class="code-line" data-line="183" dir="auto" id="bkmrk-document-class-codeu"><thead class="code-line" data-line="183" dir="auto"><tr class="code-line" data-line="183" dir="auto"><th>Document class</th><th>Codeunit</th></tr></thead><tbody class="code-line" data-line="185" dir="auto"><tr class="code-line" data-line="185" dir="auto"><td>Purchase invoice / credit memo</td><td>`DXP SQZ P. Inv/Crdt Memo Impl.`</td></tr><tr class="code-line" data-line="186" dir="auto"><td>Order confirmation</td><td>`DXP SQZ P. Order Conf. Impl.`</td></tr><tr class="code-line" data-line="187" dir="auto"><td>Generic document classes</td><td>`DXP SQZ Document Mgt.`</td></tr></tbody></table>

Set `Linked to Record Id` and `Lookup Page Id` (for lines: `Page::"DXP SQZ Document Subform"`) so that the plausibility entry becomes clickable and jumps to the record concerned.

### Alternative: `OnAfterDoHeaderPlausibilityChecks`

`PlausibilityCheck.AddPlausibilityCheckEntry(...)` offers a more convenient API that saves you the manual insert into the temporary record:

```al
[EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP SQZ P. Inv/Crdt Memo Impl.",
    'OnAfterDoHeaderPlausibilityChecks', '', false, false)]
local procedure OnAfterHeaderChecks(DocHeader: Record "DXP SQZ Document Header"; var PlausibilityCheck: Codeunit "DXP Plausiblity Check Mgt.")
var
    CostCenterMissingLbl: Label 'Please enter a cost center.';
begin
    if DocHeader."Shortcut Dimension 1 Code" = '' then
        PlausibilityCheck.AddPlausibilityCheckEntry(CostCenterMissingLbl, 0);
end;

```

`OnAfterDoLinePlausibilityChecks` works the same way for line checks; the `AddPlausibilityCheckEntry(Text; LinkedToRecId; LookupPageId)` overload makes the entry clickable.

> **Caution for the order confirmation document class:** in **`DXP SQZ P. Order Conf. Impl.`**, the events `OnBeforeDoHeaderPlausibilityChecks`, `OnBeforeDoLinePlausibilityChecks`, `OnAfterDoHeaderPlausibilityChecks` and `OnAfterDoLinePlausibilityChecks` were originally published without `var` on the `PlausibilityCheck` parameter. A subscriber receives a copy there, so entries it adds are lost. For this document class, use the variants with the **`V2`** suffix from **version 2.50** onwards – for example `OnAfterDoHeaderPlausibilityChecksV2`. Their signature is identical to the events of the other document classes. The old events remain available but are marked as obsolete.

## Example 1: adding values before the handover to the target document

```al
[EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP SQZ Document Mgt.",
    'OnBeforeValidateSQUEEZEDocument', '', false, false)]
local procedure OnBeforeValidateSetPostingDescription(var DocHeader: Record "DXP SQZ Document Header"; AutomaticValidation: Boolean; var IsHandled: Boolean)
begin
    if DocHeader."Posting Description" = '' then
        DocHeader."Posting Description" := CopyStr(DocHeader."Document Reference", 1, MaxStrLen(DocHeader."Posting Description"));
    // No Modify required – the document is written afterwards.
end;

```

## Modifying the target document: please use the Core events

If you want to change the **target document** that was created, neither event on this page is the right place. Core provides events that hand you the target document directly – most of them even as a typed `Purchase Header`, so you can skip the detour via `Linked-to Record Id` and a `RecordRef`. These events are described on the page *Events after the creation of a Core document (DXP Document)*:

<table class="code-line" data-line="241" dir="auto" id="bkmrk-target-document-code"><thead class="code-line" data-line="241" dir="auto"><tr class="code-line" data-line="241" dir="auto"><th>Target document</th><th>Codeunit</th><th>Event</th></tr></thead><tbody class="code-line" data-line="243" dir="auto"><tr class="code-line" data-line="243" dir="auto"><td>Purchase invoice / credit memo</td><td>`DXP P. Inv. CredMemo. Creation`</td><td>`OnAfterProcessStandardDocument(JObject; DocRecRef)`</td></tr><tr class="code-line" data-line="244" dir="auto"><td>Purchase document, general</td><td>`DXP Document Transfer Mgt.`</td><td>`OnAfterCreatePurchaseDoc(var JObject; PurchaseHeader)`</td></tr><tr class="code-line" data-line="245" dir="auto"><td>Purchase header</td><td>`DXP Document Transfer Mgt.`</td><td>`OnAfterCreatePurchaseHeader(var JObject; PurchaseHeader)`</td></tr><tr class="code-line" data-line="246" dir="auto"><td>Purchase order</td><td>`DXP P. Order Creation`</td><td>`OnAfterCreatePurchaseOrder(var JObject; var PurchaseHeader)`</td></tr><tr class="code-line" data-line="247" dir="auto"><td>Order confirmation</td><td>`DXP P. Order Confirmation`</td><td>`OnAfterProcessStandardDocument(JObject; var PurchaseHeader)`</td></tr></tbody></table>

`OnAfterValidateSQUEEZEDocument` is only the better choice if you additionally need values from the **`DXP SQZ Document Header`** – for example recognised values, order matching or splitting information that is not contained in the JSON of the Core document.

## Example 2: reacting to manual validation only

```al
[EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP SQZ Document Mgt.",
    'OnAfterValidateSQUEEZEDocument', '', false, false)]
local procedure OnAfterValidateNotifyUser(DocHeader: Record "DXP SQZ Document Header"; CoreDocument: Record "DXP Document"; AutomaticValidation: Boolean)
var
    ValidatedMsg: Label 'Document %1 has been forwarded.', Comment = '%1 = SQUEEZE document no.';
begin
    if AutomaticValidation then
        exit;
    if not GuiAllowed() then
        exit;

    Message(ValidatedMsg, DocHeader."No.");
end;

```

## Notes and limitations

- **No dialogs in automatic processing.** Always check `AutomaticValidation` and `GuiAllowed()` before `Message`, `Confirm` or `Page.RunModal`. Otherwise the processing queue runs into an error.
- **`OnBeforeValidateSQUEEZEDocument` runs after the plausibility check.** Custom checks that are supposed to appear in the page's **plausibility entries** and to disable the **Validate** action belong in `OnAfterDoHeaderPlausibilityChecks` or `OnAfterDoLinePlausibilityChecks` instead.
- **`IsHandled := true` cancels completely.** No target document is created, the Core document stays unchanged and the SQUEEZE document keeps its status. The user does not get a message – issue one yourself if required.
- **No event for deleted or rejected documents.** If the document has the **Deleted** or **Rejected** status, validation ends beforehand and neither event is raised.
- **`OnAfterValidateSQUEEZEDocument` runs after a `Commit`.** An `Error` in a subscriber does not roll the validation back; it leaves a validated document plus an error message for the user.

## Related pages

- Creating custom documents (custom processing)
- Events after the creation of a SQUEEZE document (CreateSource)
- Events after the creation of a Core document (DXP Document)
- Rule-based next process step (routing documents into custom processing)

# Events before retrieving and creating SQUEEZE documents

## Audience

This documentation is for third-party developers who want to control **whether** Business Central retrieves documents from SQUEEZE – typically to prevent a test system from retrieving them.

It focuses on the two **integration events** in codeunit `DXP SQZ API Mgt.` that are raised *before* documents are retrieved.

<p class="callout info">Both events are available from SQUEEZE version 2.54.0.0.</p>

## Background: why a test system should not retrieve documents

If a test system is connected to the same SQUEEZE instance as the production system – for example because it was created as a copy of production – it retrieves documents from there as well.

<p class="callout warning">When a document is created, it is set to `FINISHED` in SQUEEZE. A document retrieved by a test system is no longer available to the production system.</p>

If the test system should not communicate with SQUEEZE at all, no extension is needed, see [Test system retrieves documents from Squeeze](https://docs.squeeze.one/books/squeeze-for-dynamics-365-bc-en-us/page/test-system-retrieves-documents-from-squeeze). The events are meant for cases where the test system should detect and prevent the retrieval by itself, or where only certain documents should be held back.

## Context: how documents are retrieved

Retrieval runs in two steps, both in codeunit `DXP SQZ API Mgt.`:

1. `DownloadDocumentData` fetches the ids of the documents waiting for export in SQUEEZE and adds them to the [import queue](https://docs.squeeze.one/link/639). It also fetches documents with status Resynchronize again.
2. `CreateCoreAndSQUEEZEDocument` fetches the document for each open import queue entry, creates the Core document (`DXP Document`) and the SQUEEZE document, marks the entry as processed and sets the document to `FINISHED` in SQUEEZE.

Both steps run in the job queue and with the **Download Documents now** action on the Squeeze Documents, Squeeze Import Queue Entries and Squeeze Queue pages. Each step is preceded by an event with `IsHandled`.

## Event matrix (overview)

<table id="bkmrk-event-when-control-t"><thead><tr><th>Event</th><th>When</th><th>Control</th><th>Typical use cases</th></tr></thead><tbody><tr><td>`OnBeforeDownloadDocumentData`</td><td>before the document ids are fetched from SQUEEZE</td><td>`IsHandled`</td><td>prevent all retrieval in a test system</td></tr><tr><td>`OnBeforeCreateCoreAndSQUEEZEDocumentV2`</td><td>before a single document is fetched and created</td><td>`IsHandled`</td><td>do not create documents that are already queued; hold back certain document classes or documents</td></tr></tbody></table>

## Events in `DXP SQZ API Mgt.`

### `OnBeforeDownloadDocumentData`

Raised at the very start of `DownloadDocumentData`, before any connection to SQUEEZE is made.

Signature:

```al
[IntegrationEvent(false, false)]
local procedure OnBeforeDownloadDocumentData(var IsHandled: Boolean)

```

With `IsHandled := true` the whole step is skipped: no document ids are fetched, no import queue entries are written and no documents with status Resynchronize are fetched again.

### `OnBeforeCreateCoreAndSQUEEZEDocumentV2`

Raised in `CreateCoreAndSQUEEZEDocument` for each document, before it is fetched from SQUEEZE.

Signature:

```al
[IntegrationEvent(false, false)]
local procedure OnBeforeCreateCoreAndSQUEEZEDocumentV2(DocClass: Enum "DXP Document Class"; SQUEEZEDocId: Text[50]; var IsHandled: Boolean)

```

Parameters:

- `DocClass` – document class of the document, e.g. `DXP Invoice / Credit Memo` or `DXP Order Confirmation`
- `SQUEEZEDocId` – id of the document in SQUEEZE
- `IsHandled` – set to `true` to skip the document

With `IsHandled := true`:

- neither a Core document nor a SQUEEZE document is created,
- the document in SQUEEZE stays unchanged – it is not set to `FINISHED`,
- the import queue entry stays unprocessed and is offered again on the next run.

<p class="callout info">To shield a test system completely, you need this event in addition to `OnBeforeDownloadDocumentData`: a copy of the production system also brings along the entries that were already in its import queue. Without this event they would be created in the test system.</p>

<p class="callout warning">Skipped entries are retried on every run and count towards the up to 10 entries a run processes. If you hold back only some documents, these entries can delay the processing of the others.</p>

The older event `OnBeforeCreateCoreAndSQUEEZEDocument` is obsolete and no longer raised. Use `OnBeforeCreateCoreAndSQUEEZEDocumentV2` instead.

## Minimal example: prevent retrieval in a sandbox

The example treats every sandbox as a test system. Adapt object ids/names to your namespace and the check in `IsTestSystem` to your rules.

```al
codeunit 50102 "My SQZ Test System Guard"
{
    [EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP SQZ API Mgt.", 'OnBeforeDownloadDocumentData', '', false, false)]
    local procedure SkipDownloadInTestSystem(var IsHandled: Boolean)
    begin
        if IsTestSystem() then
            IsHandled := true;
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP SQZ API Mgt.", 'OnBeforeCreateCoreAndSQUEEZEDocumentV2', '', false, false)]
    local procedure SkipCreationInTestSystem(DocClass: Enum "DXP Document Class"; SQUEEZEDocId: Text[50]; var IsHandled: Boolean)
    begin
        if IsTestSystem() then
            IsHandled := true;
    end;

    local procedure IsTestSystem(): Boolean
    var
        EnvironmentInformation: Codeunit "Environment Information";
    begin
        // Example: every sandbox counts as a test system.
        // On-premises or for other rules, use a check of your own,
        // e.g. a flag in a setup table of your own.
        exit(EnvironmentInformation.IsSandbox());
    end;
}

```

With the **Download Documents now** action the user gets no feedback that retrieval was skipped. If you want that, show a message in the subscriber – only when `GuiAllowed()` is true, so the job queue is not affected.