Post en páginas API - Respuesta a medida

Ya conocemos las bondades de las páginas de tipo API.

Estas páginas nos permiten trabajar con cualquier entidad expuesta de una manera extremadamente sencilla: "El resultado compensa, y mucho, el esfuerzo realizado en su creación".

Una vez localizados los datos bien sea de una tabla del estándar o de una tabla personalizada e incluso una combinación de ambos, mediante algún asistente tipo File Wizard de AZ AL Dev Tools seremos capaces de exponer dichos datos en unos pocos segundos permitiendo la consulta, modificación, alta y borrado de estos datos.

vemos un ejemplo rápido y sencillo de una página de tipo API que expone datos de la tabla Item:

page 85100 MyItemAPI
{
    APIGroup = 'apiGroup';
    APIPublisher = 'Publisher';
    APIVersion = 'v2.0';
    ApplicationArea = All;
    Caption = 'itemAPI';
    DelayedInsert = true;
    EntityName = 'myItem';
    EntitySetName = 'myItems';
    PageType = API;
    SourceTable = Item;
    ODataKeyFields = "No.";

    layout
    {
        area(content)
        {
            repeater(General)
            {
                field(no; Rec."No.")
                {
                }
                field(description; Rec.Description)
                {
                }
                field(unitPrice; Rec."Unit Price")
                {
                }
            }
        }
    }
}

Al publicar está página tendremos acceso a Código, descripción y precio unitario de todos los productos mediante la llamada GET, podremos realizar modificaciones con una llamada de tipo PATCH, etc.

Nos vamos a centrar en la llamada de tipo POST.
Tal y como está definida esta página podemos realizar una llamada a nuestra API con el tipo POST e indicando en el cuerpo del mensaje al menos el campo clave de la llamada, es decir, campo "no" crearemos un nuevo registro en la tabla.

Body del mensaje:

{
    "no": "425",
    "description": "Producto de prueba"
}

El ejemplo anterior creará el producto 425 con la descripción indicada, siempre y cuando, no exista ya ese producto.
Respuesta:

{
    "@odata.context": "https://xxxxxx/xxxxxx/xxxxxx/api/Publisher/apiGroup/v2.0/$metadata#myItems/$entity",
    "@odata.etag": "W/\"JzE5OzUyNDMwNTA3MDM5OTcwNjAxMzYxOzAwOyc=\"",
    "no": "425",
    "description": "Producto de prueba",
    "unitPrice": 0
}

Como hemos comentado, si intentamos llamar nuevamente con los mismos datos y una llamada de tipo POS, al ya existir el registro, Business central nos devuelve un error:

{
    "error": {
        "code": "Internal_EntityWithSameKeyExists",
        "message": "The record in table Item already exists. Identification fields and values: No.='425'  CorrelationId:  30387204-3c06-44b9-a757-c7b2e7bff608."
    }
}

Una vez visto esto ya tenemos un dibujo global de cómo funciona todo.
Vamos a darle una vuelta a la operativa!!!!!

¿Cómo solucionaríamos una petición en la que bajo demanda de un producto devolvemos una serie de datos de la ficha de producto y otros datos calculados?

Supongamos que una aplicación externa necesita que a petición de un producto más una serie de parámetros se realicen unas operaciones y nos devuelva el resultado.
La petición exacta es esta, se recibe una llamada con estos prámetros:

{
    "no": "425",
    "fechaInicio": "01-01-2024",
    "fechaFinal": "01-31-2024",
    "dtoDesde": 1,
    "dtoHasta": 25,
    "resultadoOperacion1": false,
    "resultadoOperacion2": ""
}

y tras esa llamada debemos consultar si hubo alguna venta del producto 425 entre las fechas 01/01/2024 y 31/01/2024 con un descuento entre 1 y 25% y devolver como resultado true o false en resultadoOperacion1 y en caso afirmativo realizar una operación cuyo resultado informaremos en el campo resultadoOperacion2.
Por ejemplo, en caso de haber alguna venta cumpliendo esas condiciones, localizaremos la tarifa si la hubiere que haya producido ese resultado y la desactivaremos.

Vamos a usar una llamada de tipo POST para obtener el resultado que necesitamos.

Lo primero va a ser "aislar" las operaciones de tablas del estándar, es decir, aunque observamos que el eje sobre el que pivota la consulta es la tabla Item no podemos o no debemos usarla como origen de datos ya que esto implicaría que esta operación crearía un nuevo registro si fuera posible o arrojaría un error si el registro ya existiera, a mayores puede existir código en el evento OnInsert que no queremos que se ejecute.
Por ese motivo, y como una operación de escritura debe escribir en algún sitio, crearemos una nueva tabla. Esta tabla la utilizaremos en modo temporal con lo que el registro existirá únicamente en el periodo que dure la transacción.
Para nuestro propósito sólo necesitamos un campo: "no"

table 85100 APIPostExample
{
    Caption = 'APIPostExample';
    DataClassification = ToBeClassified;    
   
    fields
    {
        field(1; "No."; Code[20])
        {
            Caption = 'No.';
        }
    }
    keys
    {
        key(PK; "No.")
        {
            Clustered = true;
        }
    }
}

Ahora necesitamos exponer esta tabla en una página, modificaremos la página del ejemplo anterior, pero añadiremos el parámetro tabla de origen es temporal (SourceTableTemporary = true):

page 85100 MyItemAPI
{
    APIGroup = 'apiGroup';
    APIPublisher = 'Publisher';
    APIVersion = 'v2.0';
    ApplicationArea = All;
    Caption = 'itemAPI';
    DelayedInsert = true;
    EntityName = 'myItem';
    EntitySetName = 'myItems';
    PageType = API;
    SourceTable = APIPostExample;
    ODataKeyFields = "No.";
    SourceTableTemporary = true;

    layout
    {
        area(content)
        {
            repeater(General)
            {
                field(no; Rec."No.")
                {
                }                
            }
        }
    }
}

Esto todavía no se parece al mensaje que nos van a mandar, así que vamos a ir dándole forma:

  • Añadimos variables globales para los datos que nos faltan

var
        fechaInicio: Text;
        fechaFinal: Text;
        dtoDesde: Decimal;
        dtoHasta: Decimal;
        resultadoOperacion1: Boolean;

  • Añadimos las variables globales que acabamos de crear a la lista de campos expuestos

                field(fechaInicio;fechaInicio)                
                {}
                field(fechaFinal;fechaFinal)
                {}
                field(dtoDesde;dtoDesde)
                {}
                field(dtoHasta;dtoHasta)
                {}
                field(resultadoOperacion1;resultadoOperacion1)
                {}
                field(resultadoOperacion2;resultadoOperacion2)
                {}

Y aquí es donde ocurre la "magia", añadimos el evento

    trigger OnInsertRecord(BelowxRec: Boolean): Boolean    
    begin
       
    end;

la llamada tipo POST ejecutará un insert en nuestra tabla, al ser temporal no va a ningún sitio, pero usamos esa llamada para ejecutar las operaciones requeridas:

    trigger OnInsertRecord(BelowxRec: Boolean): Boolean
    var
        SalesInvoiceLine: Record "Sales Invoice Line";
        PriceListHeader: Record "Price List Header";
        PriceListLine: Record "Price List Line";
        dateFrom: Date;
        dateTo: Date;
        Msg001: Label 'Tarifas desactivadas';
    begin
        resultadoOperacion1 := false; //por si recibieramos valor en la llamada
        resultadoOperacion2 := '';    //por si recibieramos valor en la llamada
        SalesInvoiceLine.SetRange(Type, SalesInvoiceLine.Type::Item);
        SalesInvoiceLine.SetRange("No.", Rec."No.");
        Evaluate(dateFrom, fechaInicio);
        Evaluate(dateTo, fechaFinal);
        SalesInvoiceLine.SetRange("Posting Date", dateFrom, dateTo);
        SalesInvoiceLine.SetRange("Line Discount %", dtoDesde, dtoHasta);
        if SalesInvoiceLine.FindFirst() then begin
            resultadoOperacion1 := true;
            PriceListLine.SetRange(Status, PriceListLine.Status::Active);
            PriceListLine.SetRange("Product No.", Rec."No.");
            PriceListLine.SetLoadFields("Price List Code");
            if PriceListLine.FindSet() then
                repeat
                    PriceListHeader.Get(PriceListLine."Price List Code");
                    PriceListHeader.Status := PriceListHeader.Status::Inactive;
                    PriceListHeader.Modify();
                    resultadoOperacion2 := Msg001;
                until PriceListLine.Next() = 0;
        end;
    end;

Y aquí la respuesta:

{
    "@odata.context": "https://xxxx/xxxxx/xxxxx/xxx/api/Publisher/apiGroup/v2.0/$metadata#myItems/$entity",
    "@odata.etag": "W/\"JzE5OzQ5NjI4MTU0ODAxNjY2NjE1NTMxOzAwOyc=\"",
    "no": "425",
    "fechaInicio": "01-01-2024",
    "fechaFinal": "01-31-2024",
    "dtoDesde": 1,
    "dtoHasta": 25,
    "resultadoOperacion1": false,
    "resultadoOperacion2": ""
}

En caso de localizar alguna venta en el periodo indicado la respuesta cambiaría a:

{
    "@odata.context": "https://xxxx/xxxxx/xxxxx/xxx/api/Publisher/apiGroup/v2.0/$metadata#myItems/$entity",
    "@odata.etag": "W/\"JzE5OzQ5NjI4MTU0ODAxNjY2NjE1NTMxOzAwOyc=\"",
    "no": "425",
    "fechaInicio": "01-01-2024",
    "fechaFinal": "01-31-2024",
    "dtoDesde": 1,
    "dtoHasta": 25,
    "resultadoOperacion1": true,
    "resultadoOperacion2": "Tarifas desactivadas"
}

De esta forma hemos utilizado una operación de escritura (POST) para realizar una serie de procesos y devolver una respuesta personalizada.
Esta operativa tiene un gran potencial ya que permite devolver una respuesta con tantas variables como sean necesarias y ejecutar cuantos procesos necesitemos.

Espero que este pequeño ejemplo os sea de utilidad.

Código en Github


Publicar un comentario

Añade comentario (0)

Artículo Anterior Artículo Siguiente