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.