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
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.
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.
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:
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á.
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;
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
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
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;
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.
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.
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é?
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
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