Cómo Desarrollar en Smart Processing (Documentación para Desarrolladores)
Estado: 01.08.2026 • Tiempo de lectura: ~10 minutos
Smart Processing proporciona puntos de extensión donde puedes implementar lógica personalizada para iniciar tus propios procesos o adaptar el manejo a los requisitos del cliente. Este documento explica el concepto clave de desarrollo: trabajar a través de la sesión activa y activar codeunits personalizados en el proceso correspondiente utilizando los datos de la sesión.
1. Concepto Central: Trabajar a Través de la Sesión de Procesamiento Activa
Smart Processing ejecuta una “sesión de borrador activa” mientras se procesa un documento. Durante ese tiempo, se mantiene un estado relevante de manera central (contexto del documento, contexto de la plantilla, líneas coincidentes, desviaciones y datos intermedios utilizados por la interfaz de usuario y la tubería de procesamiento).
Como desarrollador, el enfoque recomendado es:
- Leer el contexto de procesamiento actual de la sesión.
- Realizar tu lógica utilizando el contexto y las referencias proporcionadas.
- Escribir resultados/estado de nuevo en la sesión cuando el flujo de trabajo lo necesite.
- Confiar en la plataforma para mantener la interfaz de usuario y el procesamiento alineados a través de la arquitectura de la sesión.
Esto hace que las personalizaciones sean estables y evita un estado inconsistente entre lo que los usuarios ven y lo que tu lógica cambió.
2. El Objeto Central: “SIM_DI Process Session SI”
El codeunit “SIM_DI Process Session SI” es el punto de entrada central para acceder y manipular la sesión activa de Smart Processing. Actúa como el centro para obtener datos de tiempo de ejecución relevantes (documento entrante, plantilla de proceso, búferes de línea, metadatos de desviación) y para comunicar resultados de vuelta a la tubería (por ejemplo, qué encabezado fue procesado).
Resumen de funciones públicas
La siguiente tabla enumera todas las funciones públicas no obsoletas de "SIM_DI Process Session SI". Si una función existe en múltiples variantes (sobrecargas), los parámetros que pueden no estar presentes en cada variante se marcan como opcionales.
| Función | Qué hace | Parámetros (opcionales marcados) | Retorna |
|---|---|---|---|
InitSession |
Inicializa la sesión con un documento entrante y resuelve la plantilla de proceso relacionada. | InboundDocument |
ninguno |
InitSession |
Inicializa la sesión con un documento entrante y una plantilla de proceso proporcionada explícitamente (y borra primero los datos de la sesión anterior). | InboundDocument, ProcessTemplate |
ninguno |
GetInboundDocument |
Recupera el documento entrante almacenado en la sesión actual. | var InboundDocument |
ninguno |
GetProcessTemplate |
Recupera la plantilla de proceso almacenada en la sesión actual. | var ProcessTemplate |
ninguno |
ClearProcessSession |
Borra todo el contexto de la sesión, incluyendo el documento entrante, la plantilla de proceso, las claves de mapeo y los datos de proceso. | - | ninguno |
ClearProcessData |
Borra solo los datos de procesamiento (encabezados/filas/comentarios temporales, registros de búsqueda mapeados, id de encabezado procesado) y borra datos coincidentes. | - | ninguno |
AddProcessedHeaderRecordId |
Agrega un id de registro del encabezado del documento procesado a la lista de la sesión. Admite la coincidencia de múltiples documentos. Las entradas duplicadas se ignoran silenciosamente. | RecordId |
ninguno |
GetProcessedHeaderRecordIds |
Devuelve la lista de todos los ids de registros del encabezado del documento procesado recopilados durante la ejecución de coincidencia actual. | - | List of [RecordId] |
SetDocumentHeaderVariableRef |
Almacena una copia de la variable de encabezado temporal proporcionada en la sesión (utilizada como un estado de encabezado de sesión de estilo referencia). | var TempDocumentHeader (temporal) |
ninguno |
GetDocumentHeaderVariable |
Copia los datos actuales del encabezado temporal de la sesión en la variable de registro temporal proporcionada. | var TempDocumentHeader (temporal) |
ninguno |
SetDocumentLineVariableRef |
Almacena una copia de la variable de línea temporal proporcionada en la sesión (utilizada como un estado de línea de sesión de estilo referencia). | var TempDocumentLine (temporal) |
ninguno |
GetDocumentLineVariable |
Copia los datos actuales de la línea temporal de la sesión en la variable de registro temporal proporcionada. | var TempDocumentLine (temporal) |
ninguno |
SetRemarkVariableRef |
Almacena una copia de la variable de comentario temporal proporcionada en la sesión (utilizada como un estado de comentario de sesión de estilo referencia). | var DocumentRemark (temporal) |
ninguno |
RemoveDocumentRemark |
Elimina un comentario de la sesión basado en el número de línea, número de campo y área de validación. | LineNo, FieldNo, FieldValidationArea |
ninguno |
GetRemarks |
Copia todos los comentarios de la sesión en la variable de registro de comentario temporal proporcionada. | var DocumentRemark (temporal) |
ninguno |
RemoveMappedLookupRecord |
Elimina la referencia del registro de búsqueda mapeado para un campo de plantilla dado. | TemplateFieldRecordId |
ninguno |
GetMappedLookupRecord |
Recupera la referencia del registro de búsqueda mapeado para un campo de plantilla dado (si está disponible). | TemplateFieldRecordId, var MappedRecordId |
Boolean |
GetDevitatingLinesJsonObject |
Devuelve el objeto JSON que describe las líneas desviadas y sus campos no coincidentes (incluyendo el nombre de configuración de coincidencia y detalles de discrepancia). | - | JsonObject |
GetMatchedLineData |
Copia todas las entradas de datos de líneas coincidentes en el registro temporal proporcionado. Cada entrada contiene: número de línea del documento, nombre de configuración de coincidencia, número de codeunit de ejecución y los RecordIds del encabezado/línea coincidentes. | var TempMatchedLineData (temporal) |
ninguno |
3. Personalizaciones en el Proceso de Coincidencia
Esto solo es necesario si el codeunit se utiliza como un proceso personalizado (si la opción "Omitir creación al coincidir" en la configuración de coincidencia está activa).
El proceso de coincidencia en Smart Processing ha sido rediseñado para admitir la ejecución simultánea de múltiples configuraciones de coincidencia. Cada línea de documento entrante ahora puede coincidir de forma independiente con un encabezado de documento objetivo y una línea diferentes (coincidencia de múltiples encabezados y múltiples líneas). Esto significa que la línea 1 de un documento entrante puede asignarse a una orden de compra mientras que la línea 2 va a una completamente diferente.
Un escenario común de extensión es activar codeunits personalizados durante este paso de coincidencia, especialmente cuando el usuario completa el borrador y se han creado o identificado uno o más encabezados objetivo.
El codeunit personalizado se sigue ejecutando con el registro del encabezado del tipo de documento coincidente como Rec (por ejemplo, un encabezado de compra). Este registro es tu ancla estable para ese encabezado en particular. Para determinar qué líneas entrantes se coincidieron con este encabezado (y qué codeunit de ejecución inició la ejecución), utiliza GetMatchedLineData de la sesión y filtra por "Execution Codeunit No.".
Una vez que tu codeunit haya terminado de procesar un encabezado, regístralo a través de AddProcessedHeaderRecordId para que la plataforma pueda archivar y finalizar todos los encabezados procesados correctamente. Dado que se pueden procesar múltiples encabezados en la misma ejecución, cada codeunit de ejecución es responsable de agregar su propio encabezado procesado a la lista de la sesión.
4. Ejemplo de Codeunits de Proceso Personalizado
Ejemplo 1: Importar líneas de recibo coincidentes después de la creación del encabezado
codeunit 5673320 "SIM_DI Purch.-Get Receipts"
{
Description = 'Este codeunit se utiliza como un codeunit de importación de coincidencias para recibir líneas de recibo de compra y crear líneas de factura de compra basadas en las líneas de documento coincidentes.';
TableNo = "Purchase Header"; // <-- Este es el encabezado creado después de completar el procesamiento
Access = Internal;
var
GlobalCodeunitSIMDIProcessSessionSI: Codeunit "SIM_DI Process Session SI";
GlobalCodeunitGetReceipts: Codeunit "Purch.-Get Receipt";
GlobalNoPurchaseReceiptLinesFoundLbl: Label 'No hay líneas de recibo de compra que puedan ser procesadas. Por favor, verifica las líneas de recibo de compra y comprueba si ya han sido facturadas.';
trigger OnRun()
var
TempLocalRecordSIMDIMatchedLineData: Record "SIM_DI Matched Line Data" temporary;
LocalRecordPurchRcptLine: Record "Purch. Rcpt. Line";
begin
GlobalCodeunitSIMDIProcessSessionSI.GetMatchedLineData(TempLocalRecordSIMDIMatchedLineData);
TempLocalRecordSIMDIMatchedLineData.SetRange("Execution Codeunit No.", Codeunit::"SIM_DI Purch.-Get Receipts");
// Filtrar las líneas de recibo de compra basadas en las líneas coincidentes entrantes
if TempLocalRecordSIMDIMatchedLineData.FindSet() then
repeat
if LocalRecordPurchRcptLine.Get(TempLocalRecordSIMDIMatchedLineData."Matched Line RecordId") then
LocalRecordPurchRcptLine.Mark(true);
until TempLocalRecordSIMDIMatchedLineData.Next() = 0;
// Obtener las líneas de recibo de compra que no han sido facturadas
LocalRecordPurchRcptLine.MarkedOnly(true);
LocalRecordPurchRcptLine.SetFilter("Qty. Rcd. Not Invoiced", '<>0');
// Si no se encuentran líneas de recibo de compra, lanzar un error
if LocalRecordPurchRcptLine.IsEmpty() then
Error(GlobalNoPurchaseReceiptLinesFoundLbl);
// Ejecutar el proceso para obtener líneas de recibo
GlobalCodeunitGetReceipts.SetPurchHeader(Rec);
GlobalCodeunitGetReceipts.CreateInvLines(LocalRecordPurchRcptLine);
Commit();
end;
}
Este ejemplo muestra un proceso típico impulsado por coincidencias: la sesión proporciona la lista de “registros a ejecutar”, y tu codeunit realiza una importación basada exactamente en esas referencias coincidentes.
Ejemplo 2: Actualizar campos desviados cuando se acepta la desviación
codeunit 5673350 "SIM_DI Update Deviating Fields"
{
Description = 'Este codeunit se puede utilizar para actualizar las líneas de documento vinculadas con los valores entrantes para los campos que han sido desviados durante el proceso de coincidencia y donde se acepta la desviación.';
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;
TempLocalRecordSIMDIMatchedLineData: Record "SIM_DI Matched Line Data" temporary;
LocalRecordSIMDIInboundDocument: Record "SIM_DI Inbound Document";
LocalCodeunitSIMDILogManagement: Codeunit "SIM_DI Log Management";
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;
LocalUpdatedBoolean: Boolean;
LocalDevitatingFieldUpdateLbl: Label 'La línea %1 ha sido actualizada.\El campo "%2" ha cambiado de valor "%3" a valor "%4".', Comment = '%1 = Número de Línea, %2 = Nombre del Campo, %3 = Valor Antiguo, %4 = Nuevo Valor';
LocalNoLinesUpdatedLbl: Label 'No se han actualizado líneas porque no se aceptaron desviaciones o los valores entrantes son iguales a los valores existentes.';
begin
/// Recibir los datos de la sesión de proceso
LocalDevitatingLinesJsonObject := GlobalCodeunitSIMDIProcessSessionSI.GetDevitatingLinesJsonObject();
GlobalCodeunitSIMDIProcessSessionSI.GetDocumentLineVariable(TempLocalRecordSIMDITempDocumentLine);
GlobalCodeunitSIMDIProcessSessionSI.GetMatchedLineData(TempLocalRecordSIMDIMatchedLineData);
GlobalCodeunitSIMDIProcessSessionSI.GetInboundDocument(LocalRecordSIMDIInboundDocument);
// Iterar sobre las líneas desviadas
foreach LocalRecordIdText in LocalDevitatingLinesJsonObject.keys() do begin
if not Evaluate(LocalRecordId, LocalRecordIdText) then continue;
if not LocalRecordRef.Get(LocalRecordId) then continue;
// Obtener el registro de línea actual
LocalDevitatingLinesJsonObject.Get(LocalRecordIdText, LocalJsonToken);
// Obtener el nombre de configuración de coincidencia
LocalMatchingSetupNameText := GlobalCodeunitSIMCOREJSON.GetJsonPathValueText(LocalJsonToken.AsObject(), 'MatchingSetupName');
// Obtener los campos no coincidentes como un objeto json
if not LocalJsonToken.AsObject().Get('MissMatchedFields', LocalJsonToken) then continue;
LocalLineMatchingValidationFieldsJsonObject := LocalJsonToken.AsObject();
// Iterar sobre los campos no coincidentes y actualizar la línea de documento vinculada con el valor entrante si se acepta la desviación
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;
// Actualizar el campo con el valor entrante
GlobalCodeunitSIMCOREEvaluate.EvaluateVariable(
LocalNewValueText,
Format(LocalFieldRef.Type),
LocalValueVariant,
false
);
LocalFieldRef.Validate(LocalValueVariant);
LocalRecordRef.Modify();
// Agregar entrada de registro
LocalCodeunitSIMDILogManagement.AddInformationInTheLog(
StrSubstNo(LocalDevitatingFieldUpdateLbl, Format(LocalRecordIdText), LocalFieldRef.Caption, LocalOldValueText, LocalNewValueText),
'SIM_DI Update Deviating Fields',
LocalRecordSIMDIInboundDocument."Entry No."
);
TempLocalRecordSIMDIMatchedLineData.SetRange("Matched Line RecordId", LocalRecordId);
if TempLocalRecordSIMDIMatchedLineData.FindFirst() then
GlobalCodeunitSIMDIProcessSessionSI.AddProcessedHeaderRecordId(TempLocalRecordSIMDIMatchedLineData."Matched Header RecordId");
LocalUpdatedBoolean := true;
end;
end;
if not LocalUpdatedBoolean then
Error(LocalNoLinesUpdatedLbl);
Commit();
end;
}
Este ejemplo ilustra el “manejo de desviaciones impulsado por la sesión”: la sesión proporciona un conjunto de datos de desviación estructurado; tu código aplica las desviaciones aceptadas, registra lo que cambió y reporta el encabezado procesado de vuelta en la sesión.
5. Codeunits de Validación de Búsqueda Personalizados
Además de los codeunits de proceso, Smart Processing también admite codeunits de validación de búsqueda personalizados. Estos se ejecutan cuando se valida un campo en el documento entrante y se pueden usar para buscar valores en una tabla externa y asignarlos de vuelta al documento.
El ejemplo siguiente muestra cómo implementar un codeunit de validación de búsqueda personalizado para el codeunit estándar SIM_DI LookUp No.:
/// <summary>
/// Este codeunit se utiliza para buscar el número del artículo, cuenta de mayor,
/// recurso, activo fijo y cuenta de asignación.
/// </summary>
codeunit 5673301 "SIM_DI LookUp No."
{
Access = Internal;
Description = 'Este codeunit se utiliza para buscar el número del artículo, cuenta de mayor, recurso, activo fijo y cuenta de asignación.';
TableNo = "SIM_DI Temp. Document Line";
trigger OnRun()
var
GlobalTypeNotFilledErrLbl: Label 'Para validar este campo, el campo "Tipo" debe estar completado.';
begin
if Rec.Type = Rec.Type::" " then
Error(GlobalTypeNotFilledErrLbl);
case Rec.Type of
Rec.Type::Item:
LookUpItemNo(Rec);
Rec.Type::"G/L Account":
LookUpGLAccountNo(Rec);
Rec.Type::Resource:
LookUpResourceNo(Rec);
Rec.Type::"Fixed Asset":
LookUpFixedAssetNo(Rec);
Rec.Type::"Charge (Item)":
LookUpItemChargeNo(Rec);
end;
end;
local procedure LookUpItemNo(var Rec: Record "SIM_DI Temp. Document Line")
var
LocalRecordItem: Record Item;
begin
LocalRecordItem.SetRange("No.", Rec."No.");
if not LocalRecordItem.FindFirst() then begin
LocalRecordItem.Reset();
LocalRecordItem.SetRange(Description, Rec."No.");
if LocalRecordItem.FindFirst() then
Rec."No." := LocalRecordItem."No.";
end;
end;
local procedure LookUpGLAccountNo(var Rec: Record "SIM_DI Temp. Document Line")
var
LocalRecordGLAccount: Record "G/L Account";
begin
LocalRecordGLAccount.SetRange("No.", Rec."No.");
if not LocalRecordGLAccount.FindFirst() then begin
LocalRecordGLAccount.Reset();
LocalRecordGLAccount.SetRange(Name, Rec."No.");
if LocalRecordGLAccount.FindFirst() then
Rec."No." := LocalRecordGLAccount."No.";
end;
end;
local procedure LookUpResourceNo(var Rec: Record "SIM_DI Temp. Document Line")
var
LocalRecordResource: Record Resource;
begin
LocalRecordResource.SetRange("No.", Rec."No.");
if not LocalRecordResource.FindFirst() then begin
LocalRecordResource.Reset();
LocalRecordResource.SetRange(Name, Rec."No.");
if LocalRecordResource.FindFirst() then
Rec."No." := LocalRecordResource."No.";
end;
end;
local procedure LookUpFixedAssetNo(var Rec: Record "SIM_DI Temp. Document Line")
var
LocalRecordFixedAsset: Record "Fixed Asset";
begin
LocalRecordFixedAsset.SetRange("No.", Rec."No.");
if not LocalRecordFixedAsset.FindFirst() then begin
LocalRecordFixedAsset.Reset();
LocalRecordFixedAsset.SetRange(Description, Rec."No.");
if LocalRecordFixedAsset.FindFirst() then
Rec."No." := LocalRecordFixedAsset."No.";
end;
end;
local procedure LookUpItemChargeNo(var Rec: Record "SIM_DI Temp. Document Line")
var
LocalRecordItemCharge: Record "Item Charge";
begin
LocalRecordItemCharge.SetRange("No.", Rec."No.");
if not LocalRecordItemCharge.FindFirst() then begin
LocalRecordItemCharge.Reset();
LocalRecordItemCharge.SetRange(Description, Rec."No.");
if LocalRecordItemCharge.FindFirst() then
Rec."No." := LocalRecordItemCharge."No.";
end;
end;
}
Puntos de Personalización Adicionales
Hay más lugares donde se pueden integrar codeunits personalizados, pero el principio guía sigue siendo constante: utiliza la sesión para recuperar el contexto de procesamiento activo y para escribir de nuevo los resultados relevantes para el flujo de trabajo. Esto mantiene tus extensiones consistentes con el flujo de Smart Processing y previene la desviación de la interfaz de usuario/proceso.