Microsoft Graph - Eventos calendarios Outlook III

 

Microsoft graph

 

Seguimos con la tercera parte del post-tutorial.
La segunda parte la podemos encontrar en este enlace Microsoft graph - Eventos calendarios Outlook II

Como dijimos en el anterior post, la página que creamos era simplemente para testear la conexión y comprobar que pudiéramos acceder a los eventos del calendario principal. 
Así pues, la eliminamos ya que no le vamos a dar ya ningún uso.

¿Qué más vamos a necesitar?

Mediante servicios web y a través de Microsoft graph estamos accediendo a los eventos del calendario principal del usuario, pero existen más calendarios para ese usuario así que nos vamos a preparar para gestionarlos.
Echamos un vistazo rápido a la estructura de un calendario en una respuesta del graph explorer


Se puede observar que recibimos:
  • un id
  • un nombre
  • un código de color
  • si es el calendario por defecto
y más valores a los que por el momento no daremos importancia.
Así pues, crearemos un tabla para poder consultar qué calendarios tenemos disponibles.

table 60801 Calendars
{
    Caption = 'Calendarios';
    DataClassification = ToBeClassified;

    fields
    {
        field(1; Id; Text[250])
        {
            Caption = 'Id';
        }
        field(2; Name; Text[100])
        {
            Caption = 'Nombre';
        }
        field(3; Colour; Text[10])
        {
            Caption = 'Color';
        }
        field(4; UserGuid; Guid)
        {
            Caption = 'Id usuario';
        }
        field(5; IsDefault; Boolean)
        {
            Caption = 'Calendario principal';
        }
    }
    keys
    {
        key(PK; Id, UserGuid)
        {
            Clustered = true;
        }
    }
    fieldgroups
    {
        fieldgroup(DropDown; Id, Name)
        {
        }
    }
}



En la tabla hemos añadido los campos que hemos visto en el mensaje devuelto por graph explorer y uno más que debemos tener en cuenta, el usuario. Esto es para evitar problemas con calendarios compartidos y que cada usuario acceda sólo a los suyos.

Por otro lado vamos a necesitar gestionar los mensajes de llamada, primero debemos recuperar los calendarios disponibles para el usuario y después los eventos de cada uno de ellos.
Hay que recordar nuevamente que las llamadas que hemos visto hasta ahora devuelven los eventos del calendario principal del usuario, no del resto.
La estructura de calendarios-eventos sería similar a esto:


Primero debemos recuperar los calendarios 1, 2 y 3 y una vez tengamos los datos del calendario que necesitemos, podemos gestionar los eventos de ese calendario.

Vamos a nuestra codeunit en la que creamos en el post anterior las funciones para obtener el código de autorización y el token.
Lo primero crearemos una función para gestionar la vida útil token, esto es, si el token ha caducado nos derivará a  la validación de credenciales para obtener un nuevo token y si el token actual es válido, lo utilizará.

    procedure GetOrUpdateToken() Token: Text
    var
        GraphSetup: Record GraphSetup;
        ElapsedSecs: Integer;
        AuthCode: Text;
        NeedToken: Boolean;
    begin
        CheckConfig();
        GraphSetup.Get();
        if (GraphSetup."Authorization Time" <> 0DT) then begin
            ElapsedSecs := Round((CurrentDateTime() - GraphSetup."Authorization Time") / 1000, 1, '>');
            if ElapsedSecs >= GraphSetup."Expires In" then
                NeedToken := true;
        end else
            NeedToken := true;
        if NeedToken then begin
            AuthCode := GetAuthorizationCode();
            GetAccessToken(AuthCode);
        end;
        Token := GraphSetup.AccessTokenToText();
    end;

Por el momento, esta función quedará como pública.
Comprueba la vida útil del token almacenado y en caso de haber caducado, pasamos por el proceso de obtener un nuevo token.
Finalmente devuelve el valor de un token válido para operar.

Seguimos ...

Vamos a crear las funciones correspondientes para gestionar las entidades.
Como entidades entenderemos en este caso calendarios y eventos

    local procedure GetEntity(Url: Text) Response: Text
    var
        Calendars: Record Calendars;
        Client: HttpClient;
        Headers: HttpHeaders;
        RequestMessage: HttpRequestMessage;
        ResponseMessage: HttpResponseMessage;
        ResponseText: Text;
        Token: Text;
    begin
        Token := GetOrUpdateToken();
        Headers := Client.DefaultRequestHeaders();
        Headers.Add('Authorization', StrSubstNo('Bearer %1', Token));
        RequestMessage.SetRequestUri(Url);
        RequestMessage.Method := 'GET';

        if Client.Send(RequestMessage, ResponseMessage) then begin
            ResponseMessage.Content.ReadAs(ResponseText);
            if ResponseMessage.IsSuccessStatusCode() then
                Response := ResponseText
            else
                Error(ResponseText);
        end;
    end;

Esta función se encarga de recoger el mensajes devuelto a partir de una llamada a la url proporcionada, por lo que cada entidad deberá controlar su url de llamada, centralizando posteriormente la llamada en esta función.
También se encarga de disponer de un token actualizado.

    procedure GetAllCalendars()
    var
        JResponse: JsonObject;
        CalendarObject: JsonObject;
        JToken: JsonToken;
        CalendarToken: JsonToken;
        CalendarArray: JsonArray;
        ResponseText: Text;
        Token: Text;
        Url: Label 'https://graph.microsoft.com/v1.0/me/calendars?$select=name,hexColor,isDefaultCalendar';
    begin
        ResponseText := GetEntity(Url);
        Calendars.SetRange(UserGuid, UserSecurityId());
        Calendars.DeleteAll();
        JResponse.ReadFrom(ResponseText);
        if JResponse.Get('value', JToken) then begin
            CalendarArray := JToken.AsArray();
            foreach JToken in CalendarArray do begin
                CalendarObject := JToken.AsObject();
                if CalendarObject.Get('id', CalendarToken) then begin
                    if not Calendars.Get(CalendarToken.AsValue().AsText()) then begin
                        Calendars.Init();
                        Calendars.Id := CalendarToken.AsValue().AsText();
                        if CalendarObject.Get('name', CalendarToken) then
                            Calendars.Name := CalendarToken.AsValue().AsText();
                        if CalendarObject.Get('hexColor', CalendarToken) then
                            Calendars.Colour := CalendarToken.AsValue().AsText();
                        if CalendarObject.Get('isDefaultCalendar', CalendarToken) then
                            Calendars.IsDefault := CalendarToken.AsValue().AsBoolean();
                        Calendars.UserGuid := UserSecurityId();
                        Calendars.Insert();
                    end;
                end;
            end;
        end;
    end;

En esta función controlamos la entidad calendario, solicitamos todos los calendarios del usuario.
En la url utilizamos un filtro para recibir sólo los datos necesarios y aligerar el tiempo de respuesta así como el tamaño de dicha respuesta.
Al recibir datos, actualizamos los calendarios del usuario; se filtra la tabla por usuario y se eliminan todos, posteriormente a partir del mensaje de respuesta, se crean de nuevo.
También se podría comprobar si existe el calendario y en caso de que no exista crearlo y si existe y se ha modificado algún dato, actualizarlo.

Por el momento no crearemos más funciones para gestionar calendarios, aunque como entidad, podríamos modificar, eliminar o crearlos.

Ahora vamos a gestionar la entidad eventos

    procedure GetCalendarEvents(CalendarId: Text) JResponse: JsonArray;
    var
        Client: HttpClient;
        Headers: HttpHeaders;
        RequestMessage: HttpRequestMessage;
        ResponseMessage: HttpResponseMessage;
        ResponseText: Text;
        JObject: JsonObject;
        JArray: JsonArray;
        JToken: JsonToken;
        EventToken: JsonToken;
        Token: Text;
        id: Text;
        subject: Text;
        StartDateTime: DateTime;
        DateTimeObject: JsonObject;
        EndDateTime: DateTime;
        AllDay: Boolean;
        Url: Label 'https://graph.microsoft.com/v1.0/me/calendars/%1/events?$select=subject,start,end,isAllDay';
    begin
        ResponseText := GetEntity(StrSubstNo(Url, CalendarId));
        Calendars.SetRange(Id, CalendarId);
        if Calendars.FindFirst() then begin
            JObject.ReadFrom(ResponseText);
            if JObject.Get('value', JToken) then begin
                JArray := JToken.AsArray();
                foreach JToken in JArray do begin
                    JObject := JToken.AsObject();
                    if JObject.Get('id', EventToken) then begin
                        id := EventToken.AsValue().AsText();
                        if JObject.Get('subject', EventToken) then
                            subject := EventToken.AsValue().AsText();
                        if JObject.Get('isAllDay', EventToken) then
                            AllDay := EventToken.AsValue().AsBoolean();
                        if JObject.Get('start', EventToken) then begin
                            DateTimeObject := EventToken.AsObject();
                            if DateTimeObject.Get('dateTime', EventToken) then
                                StartDateTime := EventToken.AsValue().AsDateTime();
                        end;
                        if (JObject.Get('end', EventToken)) and (not AllDay) then begin
                            DateTimeObject := EventToken.AsObject();
                            if DateTimeObject.Get('dateTime', EventToken) then
                                EndDateTime := EventToken.AsValue().AsDateTime();
                        end;
                        if AllDay then EndDateTime := 0DT;

                        JResponse.Add(AddEvent(id, subject, StartDateTime, EndDateTime, Calendars.Colour, '#000000', '#000000'));
                    end;
                end;
            end;
        end;
    end;

Como en la función anterior, usamos una url con filtros de campos para recibir sólo los necesarios.
Posteriormente procesamos el mensaje de respuesta, pero en esta ocasión almacenamos los datos en un JSON Array que es lo que devuelve la función. De esta manera será más fácil trabajar con esos datos.

Ya que trabajaremos con estos eventos, crearemos el resto de funciones necesarias para gestionarlos:

Nuevo evento


    procedure NewEvent(Calendar: Text; Subject: Text; BodyContent: Text; StartEvent: DateTime; EndEvent: DateTime; AllDay: Boolean): Boolean
    var
        Client: HttpClient;
        Headers: HttpHeaders;
        ContentHeaders: HttpHeaders;
        Content: HttpContent;
        RequestMessage: HttpRequestMessage;
        ResponseMessage: HttpResponseMessage;
        JObject: JsonObject;
        Token: Text;
        ContentTxt: Text;
        ResponseText: Text;
        Url: Label 'https://graph.microsoft.com/v1.0/me/calendars/%1/events';
    begin
        Token := GetOrUpdateToken();
        Headers := Client.DefaultRequestHeaders();
        Headers.Add('Authorization', StrSubstNo('Bearer %1', Token));
        JObject := CreateBodyForRequest(Subject, BodyContent, StartEvent, EndEvent, AllDay);
        JObject.WriteTo(ContentTxt);
        Content.WriteFrom(ContentTxt);
        Content.GetHeaders(ContentHeaders);
        ContentHeaders.Remove('Content-Type');
        ContentHeaders.Add('Content-Type', 'application/json');
        RequestMessage.SetRequestUri(StrSubstNo(Url, Calendar));
        RequestMessage.Method := 'POST';
        RequestMessage.Content(Content);
        if Client.Send(RequestMessage, ResponseMessage) then begin
            ResponseMessage.Content.ReadAs(ResponseText);
            if ResponseMessage.IsSuccessStatusCode() then
                exit(true)
            else
                exit(false);
        end;
    end;

A partir de los parámetros, creará en el calendario indicado un evento con los datos del resto de parámetros.
El cuerpo del mensaje se crea con otra función que veremos un poco más adelante ya que también es usada para la modificación de eventos.

Modificar evento


    procedure ModifyEvent(id: Text; StartEvent: DateTime; EndEvent: DateTime; AllDay: Boolean): Boolean
    var
        Client: HttpClient;
        Headers: HttpHeaders;
        ContentHeaders: HttpHeaders;
        Content: HttpContent;
        RequestMessage: HttpRequestMessage;
        ResponseMessage: HttpResponseMessage;
        JObject: JsonObject;
        Token: Text;
        ContentTxt: Text;
        ResponseText: Text;
        Url: Label 'https://graph.microsoft.com/v1.0/me/events/%1';
    begin
        Token := GetOrUpdateToken();
        Headers := Client.DefaultRequestHeaders();
        Headers.Add('Authorization', StrSubstNo('Bearer %1', Token));
        JObject := CreateBodyForRequest(StartEvent, EndEvent, AllDay);
        JObject.WriteTo(ContentTxt);
        Content.WriteFrom(ContentTxt);
        Content.GetHeaders(ContentHeaders);
        ContentHeaders.Remove('Content-Type');
        ContentHeaders.Add('Content-Type', 'application/json');
        RequestMessage.SetRequestUri(StrSubstNo(Url, id));
        RequestMessage.Method := 'PATCH';
        RequestMessage.Content(Content);
        if Client.Send(RequestMessage, ResponseMessage) then begin
            ResponseMessage.Content.ReadAs(ResponseText);
            if ResponseMessage.IsSuccessStatusCode() then
                exit(true)
            else
                exit(false);
        end;
    end;

Muy parecida a la función anterior.
Mención especial el método de llamada; para consultar o leer utilizamos GET, PATCH para modificar, POST para crear nuevo y DELETE para eliminar.

Eliminar evento


    procedure DeleteEvent(id: Text): Boolean
    var
        Client: HttpClient;
        Headers: HttpHeaders;
        Content: HttpContent;
        RequestMessage: HttpRequestMessage;
        ResponseMessage: HttpResponseMessage;
        Token: Text;
        ResponseText: Text;
        Url: Label 'https://graph.microsoft.com/v1.0/me/events/%1';
    begin
        Token := GetOrUpdateToken();
        Headers := Client.DefaultRequestHeaders();
        Headers.Add('Authorization', StrSubstNo('Bearer %1', Token));

        RequestMessage.SetRequestUri(StrSubstNo(Url, id));
        RequestMessage.Method := 'DELETE';
        RequestMessage.Content(Content);
        if Client.Send(RequestMessage, ResponseMessage) then begin
            ResponseMessage.Content.ReadAs(ResponseText);
            if ResponseMessage.IsSuccessStatusCode() then
                exit(true)
            else
                exit(false);
        end;
    end;

Muy similar a las anteriores, sólo que para este caso no es necesario un cuerpo de mensaje, sólo referirnos al evento en cuestión a través de su id.

Por último unas funciones auxiliares  que completarían lo visto hasta ahora

Formatear eventos en un JSON object

    local procedure AddEvent(id: text; Title: Text; StartEvent: DateTime; EndEvent: DateTime; color: text; BorderColor: Text; TextColor: Text): JsonObject
    var
        MyEvent: JsonObject;
        TxtBuilder: TextBuilder;
    begin
        MyEvent.Add('id', id);
        MyEvent.Add('title', Title);
        if EndEvent <> 0DT then begin
            MyEvent.Add('start', StartEvent);
            TxtBuilder.AppendLine(Format(DT2Date(StartEvent)) + ' ' + Format(DT2Time(StartEvent)))
        end else begin
            MyEvent.Add('start', DT2Date(StartEvent));
            TxtBuilder.AppendLine(Format(DT2Date(StartEvent)) + ' todo el día');
        end;
        if EndEvent <> 0DT then begin
            MyEvent.Add('end', EndEvent);
            TxtBuilder.AppendLine(Format(DT2Date(EndEvent)) + ' ' + Format(DT2Time(EndEvent)))
        end;
        TxtBuilder.AppendLine('   ');
        TxtBuilder.AppendLine(Title);
        MyEvent.Add('description', TxtBuilder.ToText());
        MyEvent.Add('color', color);
        MyEvent.Add('borderColor', BorderColor);
        MyEvent.Add('textColor', TextColor);
        MyEvent.Add('display', 'block');
        exit(MyEvent);
    end;

Crear el cuerpo para las funciones de modificar y crear eventos

    local procedure CreateBodyForRequest(StartEvent: DateTime; EndEvent: DateTime; AllDay: Boolean) Response: JsonObject
    var
        Type: Option "New","Modify";
    begin
        Response := CreateBodyRequest('', '', StartEvent, EndEvent, AllDay, Type::Modify);
    end;

    local procedure CreateBodyForRequest(Subject: Text; BodyContent: Text; StartEvent: DateTime; EndEvent: DateTime; AllDay: Boolean) Response: JsonObject
    var
        Type: Option "New","Modify";
    begin
        Response := CreateBodyRequest(Subject, BodyContent, StartEvent, EndEvent, AllDay, Type::New);
    end;

    local procedure CreateBodyRequest(Subject: Text; BodyContent: Text; StartEvent: DateTime; EndEvent: DateTime; AllDay: Boolean; Type: Option "New","Modify") Response: JsonObject
    var
        BodyObject: JsonObject;
        StartObject: JsonObject;
        EndObject: JsonObject;
    begin
        if Type = type::New then begin
            Response.Add('subject', Subject);
            BodyObject.Add('contentType', 'HTML');
            BodyObject.Add('content', BodyContent);
            Response.Add('body', BodyObject);
        end;

        Response.Add('isAllDay', AllDay);
        StartObject.Add('dateTime', StartEvent);
        StartObject.Add('timeZone', 'UTC');
        EndObject.Add('dateTime', EndEvent);
        EndObject.Add('timeZone', 'UTC');
        Response.Add('start', StartObject);
        Response.Add('end', EndObject);
    end;

En este caso se ha utilizado el polimorfismo para posibilitar la llamada a una misma función con distintos parámetros para modificar o dar de alta.

Recomiendo agrupar las funciones por funcionalidad y acotarlas por region usando el snippet rregion.

Este post se ha alargado más de lo esperado y no quiero crear post demasiado extensos por lo que finalizaremos este post aquí a pesar de dejarnos un poco "con la miel en los labios".
Ya tengo todas las funciones ...¿y ahora qué?




En el siguiente post veremos cómo mostrar esos datos en un calendario. Al no disponer Business central de un calendario visual, nos "fabricaremos" uno mediante un addin.
En un post posterior crearemos eventos a partir de órdenes de fabricación, aunque también podría ser a partir de proyectos, pedidos de venta y cuando esperamos que salgan los envíos, pedidos de compra ya cuando se estima su recepción, etc.

Espero que os hay sido de utilidad. Aunque hoy no hayamos visto un resultado final, llevamos recorrido ya más de la mitad del camino, creedme 👍

Microsoft Graph - Eventos calendarios Outlook IV

Publicar un comentario

Añade comentario (0)

Artículo Anterior Artículo Siguiente