Skip to content

How to Develop in Smart Processing (Developer Documentation)

Status: 01.08.2026 • Reading time: ~8 minutes

Smart Processing provides extension points where you can implement custom logic to start your own processes or adapt handling to customer requirements. This document explains the key development concept: work through the active session, and trigger custom codeunits in the matching process using the session data.

1. Core Concept: Work Through the Active Processing Session

Smart Processing runs an “active draft session” while a document is being processed. During that time, relevant state is maintained centrally (document context, template context, matched lines, deviations, and intermediate data used by the UI and processing pipeline).

As a developer, the recommended approach is:

  • Read the current processing context from the session.
  • Perform your logic using the provided context and references.
  • Write results/state back into the session when the workflow needs it.
  • Rely on the platform to keep UI and processing aligned through the session architecture.

This makes customizations stable and avoids inconsistent state between what users see and what your logic changed.


2. The Central Object: “SIM_DI Process Session SI”

The codeunit “SIM_DI Process Session SI” is the central entry point for accessing and manipulating the currently active Smart Processing session. It acts as the hub to obtain relevant runtime data (inbound document, process template, line buffers, deviation metadata) and to communicate results back to the pipeline (for example, which header was processed).

Public function overview (from the provided examples)

The following table lists the public functions that are actually used in your example code. If a function exists in multiple variants (overloads), parameters that may not be present in every variant are marked as optional.

Function What it does Parameters (optional marked) Returns
InitSession Initializes the session with an inbound document and resolves the related process template. InboundDocument none
InitSession Initializes the session with an inbound document and an explicitly provided process template (and clears previous session data first). InboundDocument, ProcessTemplate none
GetInboundDocument Retrieves the inbound document stored in the current session. var InboundDocument none
GetProcessTemplate Retrieves the process template stored in the current session. var ProcessTemplate none
ClearProcessSession Clears the full session context including inbound document, process template, mapping keys, and process data. - none
ClearProcessData Clears only processing data (temporary header/lines/remarks, mapped lookup records, processed header id) and clears matching data. - none
SetProcessedHeaderRecordId Stores the record reference of the created/matched target document header in the session for downstream processing (e.g., completion/archiving). RecordId none
GetProcessedHeaderRecordId Returns the stored processed header record reference and clears it from the session afterwards. - RecordId
SetDocumentHeaderVariableRef Stores a copy of the provided temporary header variable in the session (used as a reference-style session header state). var TempDocumentHeader (temporary) none
GetDocumentHeaderVariable Copies the session’s current temporary header data into the provided temporary record variable. var TempDocumentHeader (temporary) none
SetDocumentLineVariableRef Stores a copy of the provided temporary line variable in the session (used as a reference-style session line state). var TempDocumentLine (temporary) none
GetDocumentLineVariable Copies the session’s current temporary line data into the provided temporary record variable. var TempDocumentLine (temporary) none
SetRemarkVariableRef Stores a copy of the provided temporary remark variable in the session (used as a reference-style session remark state). var DocumentRemark (temporary) none
RemoveDocumentRemark Removes a remark from the session based on line number, field number, and validation area. LineNo, FieldNo, FieldValidationArea none
GetRemarks Copies all session remarks into the provided temporary remark record variable. var DocumentRemark (temporary) none
RemoveMappedLookupRecord Removes the mapped lookup record reference for a given template field reference. TemplateFieldRecordId none
GetMappedLookupRecord Retrieves the mapped lookup record reference for a given template field reference (if available). TemplateFieldRecordId, var MappedRecordId Boolean
GetDevitatingLinesJsonObject Returns the JSON object describing deviating lines and their mismatched fields (including matching setup name and mismatch details). - JsonObject
GetExecuteCodeunitRefRecordIds Retrieves record references assigned for execution by a specific codeunit (typically matched lines to process). ExecuteCodeunitNo, var RecordIds Boolean
GetMatchedDocumentHeader Retrieves the matched document header record reference for a given matching setup name (if available). MatchingSetupName, var MatchedDocumentHeaderRecordId Boolean

Note: These are all the public functions that can be confirmed from your examples. If you provide the full codeunit source, I will expand this table so it includes every public function.


3. Customizations in the Matching Process

A common extension scenario is to trigger custom codeunits during matching-especially when the user completes the draft and the target header record has been created.

A key detail of this mechanism is that the custom codeunit is executed with the header record of the matched document type (for example, a purchase header). This record becomes your stable anchor: you can derive context from it, create or import lines, update related records, and then hand the result back to the session so the platform can continue its workflow.


4. Example Custom Process Codeunits (Code Unchanged)

Example 1: Import matched receipt lines after header creation

codeunit 5673320 "SIM_DI Purch.-Get Receipts"
{
    Description = 'This codeunit is used as a matching import codeunit to receive purchase receipt lines and create purchase invoice lines based on the matched document lines.';
    TableNo = "Purchase Header"; // <-- This is the created header after completing the processing
    Access = Internal;

    var
        GlobalCodeunitSIMDIProcessSessionSI: Codeunit "SIM_DI Process Session SI";
        GlobalCodeunitGetReceipts: Codeunit "Purch.-Get Receipt";
        GlobalNoPurchaseReceiptLinesFoundLbl: Label 'There are no purchase receipt lines that can be processed. Please check the purchase receipt lines and check if they already have been invoiced.';

    trigger OnRun()
    var
        LocalRecordPurchRcptLine: Record "Purch. Rcpt. Line";
        LocalMatchedLineRecordId: RecordId;
        LocalToExecuteRecordIds: List of [RecordId];
    begin
        // Receive the matched lines that need to be executed
        GlobalCodeunitSIMDIProcessSessionSI.GetExecuteCodeunitRefRecordIds(Codeunit::"SIM_DI Purch.-Get Receipts", LocalToExecuteRecordIds);

        // Filter the purchase receipt lines based on the incoming matched lines
        foreach LocalMatchedLineRecordId in LocalToExecuteRecordIds do
            if LocalRecordPurchRcptLine.Get(LocalMatchedLineRecordId) then
                LocalRecordPurchRcptLine.Mark(true);

        // Get the purchase receipt lines that have not been invoiced
        LocalRecordPurchRcptLine.MarkedOnly(true);
        LocalRecordPurchRcptLine.SetFilter("Qty. Rcd. Not Invoiced", '<>0');

        // If no purchase receipt lines are found, throw an error
        if LocalRecordPurchRcptLine.IsEmpty() then
            Error(GlobalNoPurchaseReceiptLinesFoundLbl);

        // Execute the process to get receipt lines
        GlobalCodeunitGetReceipts.SetPurchHeader(Rec);
        GlobalCodeunitGetReceipts.CreateInvLines(LocalRecordPurchRcptLine);
        Commit();
    end;
}

This example shows a typical matching-driven process: the session provides the list of “records to execute”, and your codeunit performs an import based on exactly those matched references.


Example 2: Update deviating fields when deviation is accepted

codeunit 5673350 "SIM_DI Update Deviating Fields"
{
    Description = 'This codeunit can be used to update the linked document lines with the incoming values for the fields that have been deviating during the matching process and where deviation is accepted.';
    Access = Public;

    var
        GlobalCodeunitSIMDIProcessSessionSI: Codeunit "SIM_DI Process Session SI";
        GlobalCodeunitSIMCOREEvaluate: Codeunit "SIM_CORE Evaluate";
        GlobalCodeunitSIMCOREJSON: Codeunit "SIM_CORE JSON";

    trigger OnRun()
    var
        TempLocalRecordSIMDITempDocumentLine: Record "SIM_DI Temp. Document Line" temporary;
        LocalRecordSIMDIProcessTemplate: Record "SIM_DI Process Template";
        LocalRecordSIMDIInboundDocument: Record "SIM_DI Inbound Document";
        LocalCodeunitSIMDILogManagement: Codeunit "SIM_DI Log Management";
        LocalDocumentHeaderRecordId: RecordId;
        LocalRecordId: RecordId;
        LocalRecordRef: RecordRef;
        LocalFieldRef: FieldRef;
        LocalRecordIdText: Text;
        LocalDevitatingLinesJsonObject: JsonObject;
        LocalLineMatchingValidationFieldsJsonObject: JsonObject;
        LocalJsonToken: JsonToken;
        LocalFieldNoInteger: Integer;
        LocalFieldNoText: Text;
        LocalCellIndexInteger: Integer;
        LocalMatchingSetupNameText: Text;
        LocalOldValueText: Text;
        LocalNewValueText: Text;
        LocalValueVariant: Variant;
        LocalDevitatingFieldUpdateLbl: Label 'The line %1 has been updated.\The field "%2" has been changed from value "%3" to value "%4".', Comment = '%1 = Line No., %2 = Field Name, %3 = Old Value, %4 = New Value';
    begin
        /// Receive the data from the process session
        LocalDevitatingLinesJsonObject := GlobalCodeunitSIMDIProcessSessionSI.GetDevitatingLinesJsonObject();
        GlobalCodeunitSIMDIProcessSessionSI.GetDocumentLineVariable(TempLocalRecordSIMDITempDocumentLine);
        GlobalCodeunitSIMDIProcessSessionSI.GetInboundDocument(LocalRecordSIMDIInboundDocument);
        GlobalCodeunitSIMDIProcessSessionSI.GetProcessTemplate(LocalRecordSIMDIProcessTemplate);

        // Itterate over the 
        foreach LocalRecordIdText in LocalDevitatingLinesJsonObject.keys() do begin
            if not Evaluate(LocalRecordId, LocalRecordIdText) then continue;
            if not LocalRecordRef.Get(LocalRecordId) then continue;

            // Get the current line record
            LocalDevitatingLinesJsonObject.Get(LocalRecordIdText, LocalJsonToken);
            // Get the matching setup name
            LocalMatchingSetupNameText := GlobalCodeunitSIMCOREJSON.GetJsonPathValueText(LocalJsonToken.AsObject(), 'MatchingSetupName');

            // Get the miss matched fields as a json object
            if not LocalJsonToken.AsObject().Get('MissMatchedFields', LocalJsonToken) then continue;
            LocalLineMatchingValidationFieldsJsonObject := LocalJsonToken.AsObject();
            // Itterate over the miss matched fields and update the linked document line with the incoming value if deviation is accepted
            foreach LocalFieldNoText in LocalLineMatchingValidationFieldsJsonObject.keys() do begin
                if not Evaluate(LocalFieldNoInteger, LocalFieldNoText) then continue;
                LocalLineMatchingValidationFieldsJsonObject.Get(LocalFieldNoText, LocalJsonToken);

                if not GlobalCodeunitSIMCOREJSON.GetJsonPathValueBoolean(LocalJsonToken.AsObject(), 'AcceptDeviation') then
                    continue;
                if not Evaluate(LocalCellIndexInteger, GlobalCodeunitSIMCOREJSON.GetJsonPathValueText(LocalJsonToken.AsObject(), 'CellIndex')) then
                    continue;

                LocalFieldRef := LocalRecordRef.Field(LocalFieldNoInteger);
                LocalNewValueText := GlobalCodeunitSIMCOREJSON.GetJsonPathValueText(LocalJsonToken.AsObject(), 'IncomingValue');
                LocalOldValueText := LocalFieldRef.Value;

                // Update the field with the incoming value
                GlobalCodeunitSIMCOREEvaluate.EvaluateVariable(
                   LocalNewValueText,
                   Format(LocalFieldRef.Type),
                   LocalValueVariant,
                   false
               );
                LocalFieldRef.Validate(LocalValueVariant);
                LocalRecordRef.Modify();

                // Add log entry
                LocalCodeunitSIMDILogManagement.AddInformationInTheLog(
                    StrSubstNo(LocalDevitatingFieldUpdateLbl, Format(LocalRecordIdText), LocalFieldRef.Caption, LocalOldValueText, LocalNewValueText),
                    'SIM_DI Update Deviating Fields',
                    LocalRecordSIMDIInboundDocument."Entry No."
                );
            end;

            // Remember the document header record id to archive the document later
            GlobalCodeunitSIMDIProcessSessionSI.GetMatchedDocumentHeader(LocalMatchingSetupNameText, LocalDocumentHeaderRecordId);
        end;

        // Set the matched document header record id to the session
        GlobalCodeunitSIMDIProcessSessionSI.SetProcessedHeaderRecordId(LocalDocumentHeaderRecordId);
        Commit();
    end;
}

This example illustrates “session-driven deviation handling”: the session supplies a structured deviation dataset; your code applies accepted deviations, records what changed, and reports the processed header back into the session.


5. Additional Customization Points

There are more places where custom codeunits can be integrated, but the guiding principle remains constant: use the session to retrieve the active processing context and to write back workflow-relevant results. This keeps your extensions consistent with the Smart Processing flow and prevents UI/process drift.