Saltar al contenido

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.