Canvas App Timesheet

El fin de semana pasado tuve el placer de compartir con la comunidad de usuarios de PowerBI Barcelona la Global Power Platform Bootcamp.

Durante la sesión vimos, de manera muy resumida los pasos necesarios para crear una app que integra Dataverse, Canvas Apps, Power Automate Flows y Power BI.

Componentes de la Canvas App Timesheet presentada en la Global Power Platform Bootcamp.

Dataverse se utiliza para almacenar los datos de la app.

La app guarda un reporte de horas semanal por usuario. La tabla Timesheet almacena esta información. Contiene una columna para referenciar el usuario, y otra para el primer día de la semana a la que corresponde su reporte de horas.

La tabla Timesheet Task guarda información sobre las tareas que el usuario podrá seleccionar cuando complete su reporte de horas.

Por último, Timesheet Entry guardará el detalle de horas por cada tarea y día.

Modelo de datos de la Timesheet App

En cuanto al flujo de Power Automate , se utiliza para enviar un email al supervisor pidiendo aprobación. Esto se consigue con una actividad de Enviar Email del conector de Outlook.

Para Power BI, hemos creado un reporte muy sencillo que muestra las horas reportadas por cada tarea en un gráfico circular, y un calendario que muestra si en ese día hemos reportado horas o no.

Este es el resultado final. Es una App muy básica, pero que se puede conseguir completar en muy pocas horas. Con esta base, se pueden ir añadiendo mejoras, y el resultado final sería una app completamente ajustada a las necesidades de nuestra empresa.

A continuación los pasos para recrear esta app, junto con las fórmulas.

Pasos

  1. Crear una solución llamada Timesheets
  2. Añadir una nueva tabla llamada Timesheet Task, schema name csp_timesheet_task
  3. Crear una nueva tabla llamada Timesheet , schema name csp_timesheet
  4. En la misma tabla, crear una columna llamada Week Starting On, schema name csp_week_starting_on, solo fecha.
  5. Crear una nueva tabla llamada Timesheet Entry, schema name csp_timesheet_entry
  6. En la misma tabla, crear una columna llamada Timesheet, schema name csp_timesheetid
  7. Crear otra columna llamada Task, tipo búsqueda, schema name csp_taskid
  8. Crear otra columna Date, schema name csp_date, solo fecha.
  9. Crear otra columna Hours, tipo decimal, schema name csp_hours
  10. En la solución, crear una Canvas App llamada Timesheets, formato Tablet.
  11. Añadir las tres tablas creadas en pasos anteriores a la App.
  12. Renombrar la pantalla screen a scrTimesheet
  13. Añadir una etiqueta lblDate y un date picker dpDate
  14. Añadir una etiqueta lblFirstDayOfTheWeek
  15. Añadir un botón btnLoad. Establecer la propiedad OnSelect
// Esta fórmula calcula el día Lunes de la fecha seleccionada
// y la guarda en una variable
Set( FirstDayOfTheWeek;
     dpDate.SelectedDate - Weekday(dpDate.SelectedDate) + 2
);;
  1. Establecer la propiedad OnChange de dpDate
// Esta fórmula hará que cada vez que seleccionemos una fecha
// se ejecute el OnSelect de btnLoad
Select(btnLoad)
  1. Añadir una Galería Vertical galTimesheet
  2. Añadir etiquetas para las cabeceras lblTaskLabel, lblMon, lblMonDate lblTotal
  3. Dentro de la galería, añadir cajas de texto para introducir las horas: lblTask, txtMon, … txtFri, lblTotal . En las cajas de texto, establecer la propiedad format a Number.
  4. Establecer la fórmula en la propiedad OnStart de la App.
// Esta fórmula guardará el ID de Dataverse 
// del usuario actual en una variable
Set(CurrentUserId;LookUp(Usuarios;'Correo electrónico principal'=User().Email).Usuario)
  1. Añadir la fórmula en la propiedad btnLoad. OnSelect
// Esta fórmula intentará selecionar un reporte 
// de horas para el usuario actual. Si no existe, dejará el 
// valor de CurrentTimeSheet vacío
Set(
   CurrentTimesheet;
   LookUp(Timesheets; 'Week Starting On'=FirstDayOfTheWeek And Autor.Usuario=CurrentUserId)
);;
  1. Añadir la fórmula en la propiedad btnLoad. OnSelect
// Si no se ha encontrado una Timesheet, se creará una 
// para la semana y usuario actuales
If(IsBlank(CurrentTimesheet.Timesheet);
  Set(CurrentTimesheet;Patch(Timesheets;Defaults(Timesheets);{'Week Starting On':FirstDayOfTheWeek; Name:Text(FirstDayOfTheWeek)}))
);;

// Obtener todas las Timesheet Entries para la Timesheet actual
ClearCollect(
   colEntries;
     Filter(
      'Timesheet Entries';
       Timesheet.Timesheet = CurrentTimesheet.Timesheet
      )
);;

// Obtener todas las tareas distintas en la lista 
// de Timesheet Entries
ClearCollect(
   colTasks;
   Distinct(
    colEntries;
      Task
    )
);;
  1. Añadir la fórmula en la propiedad btnLoad. OnSelect
// Obtener la información de horas para cada tarea y día 
// de la semana
ClearCollect(
        colTimesheet;
        ForAll(
            colTasks As ThisRecordTask;
            {
                Task: ThisRecordTask.Result;
                TaskName: ThisRecordTask.Result.Name;
                _Monday: LookUp(colEntries;Date=FirstDayOfTheWeek And Task.'Timesheet Task'=ThisRecordTask.Result.'Timesheet Task');
                _Tuesday: LookUp(colEntries;Date=FirstDayOfTheWeek+1 And Task.'Timesheet Task'=ThisRecordTask.Result.'Timesheet Task');
                _Wednesday: LookUp(colEntries;Date=FirstDayOfTheWeek+2 And Task.'Timesheet Task'=ThisRecordTask.Result.'Timesheet Task');
                _Thursday: LookUp(colEntries;Date=FirstDayOfTheWeek+3 And Task.'Timesheet Task'=ThisRecordTask.Result.'Timesheet Task');
                _Friday: LookUp(colEntries;Date=FirstDayOfTheWeek+4 And Task.'Timesheet Task'=ThisRecordTask.Result.'Timesheet Task')
            }
        )
);;

// Establecer las horas para cada día de la semana
ClearCollect(colTimesheet;AddColumns(DropColumns(colTimesheet;"Monday");"Monday";Coalesce(_Monday.Hours; 0,0)));;

ClearCollect(colTimesheet;AddColumns(DropColumns(colTimesheet;"Tuesday");"Tuesday";Coalesce(_Tuesday.Hours; 0,0)));;

ClearCollect(colTimesheet;AddColumns(DropColumns(colTimesheet;"Wednesday");"Wednesday";Coalesce(_Wednesday.Hours; 0,0)));;

ClearCollect(colTimesheet;AddColumns(DropColumns(colTimesheet;"Thursday");"Thursday";Coalesce(_Thursday.Hours; 0,0)));;

ClearCollect(colTimesheet;AddColumns(DropColumns(colTimesheet;"Friday");"Friday";Coalesce(_Friday.Hours; 0,0)));;
  1. Añadir un control Drop Down drpTask
  2. Establecer la fórmula en la propiedad drpTask.OnChange
// Esta fórmula añade una nueva tarea a la Timesheet
// Hasta que no se guarde, esta tarea sólo existe
// en la App, no en Dataverse
If(
   CountIf(
            colTimesheet;
            Task.'Timesheet Task' = Self.Selected.'Timesheet Task'
    )=0;
    Collect(
            colTimesheet;
            {
                Task: Self.Selected;
                TaskName: Self.Selected.Name;
                Monday: 0,0;
                Tuesday: 0,0;
                Wednesday: 0,0;
                Thursday: 0,0;
                Friday: 0,0
            }
        )
)
  1. Conectar la galería y controles dentro de la galería a los datos
  2. Establecer la fórmula lblTotal.Text
// Suma las horas de todos los días de la semana
ThisItem.Monday+ThisItem.Tuesday+ThisItem.Wednesday+ThisItem.Thursday+ThisItem.Friday
  1. Establecer la propiedad OnChange de los controles txtMon txtFri
// Esta fórmula establece el valor de la hora 
// para la tarea y día seleccionados
Patch(colTimesheet;LookUp(colTimesheet;Task.'Timesheet Task'=ThisItem.Task.'Timesheet Task');{Monday:Value(Self.Text)})
  1. Añadir un botón btnOnSave
  2. Establecer la fórmula en la propiedad btnOnSave.OnSelect
// Esta fómula usa la función Patch para guardar
// los datos de toda la Timesheet en Dataverse
// Recorre cada elemento de la Timesheet usando 
// la función ForAll
ForAll(
   colTimesheet As ThisTSRecord;
   // Lunes
   Patch('Timesheet Entries';
     If( IsBlank(ThisTSRecord._Monday.'Timesheet Entry'); Defaults('Timesheet Entries') ; ThisTSRecord._Monday);
          { Name:ThisTSRecord.TaskName; Timesheet:CurrentTimesheet; Task: ThisTSRecord.Task; Date: FirstDayOfTheWeek; Hours: ThisTSRecord.Monday});;
    
    // Martes
    Patch('Timesheet Entries';
       If( IsBlank(ThisTSRecord._Monday.'Timesheet Entry'); Defaults('Timesheet Entries') ; ThisTSRecord._Tuesday);
        { Name:ThisTSRecord.TaskName; Timesheet:CurrentTimesheet; Task: ThisTSRecord.Task; Date: FirstDayOfTheWeek+1; Hours: ThisTSRecord.Tuesday});;

        // Miércoles
        Patch('Timesheet Entries';
            If( IsBlank(ThisTSRecord._Monday.'Timesheet Entry'); Defaults('Timesheet Entries') ; ThisTSRecord._Wednesday);
            { Name:ThisTSRecord.TaskName; Timesheet:CurrentTimesheet; Task: ThisTSRecord.Task; Date: FirstDayOfTheWeek+2; Hours: ThisTSRecord.Wednesday});;

        // Jueves
        Patch('Timesheet Entries';
          If( IsBlank(ThisTSRecord._Monday.'Timesheet Entry'); Defaults('Timesheet Entries') ; ThisTSRecord._Thursday);
      { Name:ThisTSRecord.TaskName; Timesheet:CurrentTimesheet; Task: ThisTSRecord.Task; Date: FirstDayOfTheWeek+3; Hours: ThisTSRecord.Thursday});;

        // Viernes
        Patch('Timesheet Entries';
         If( IsBlank(ThisTSRecord._Monday.'Timesheet Entry'); Defaults('Timesheet Entries') ; ThisTSRecord._Friday);
          { Name:ThisTSRecord.TaskName; Timesheet:CurrentTimesheet; Task: ThisTSRecord.Task; Date: FirstDayOfTheWeek+4; Hours: ThisTSRecord.Friday});;
    )
  1. Añadir el botón btnSend
  2. En la solución, añadir un nuevo Flujo Power Automate. Llamarlo TimesheetApp – Send for Approval
  3. Establecer el trigger a Power Apps V2
  4. Añadir un parámetro TimesheetGuid. Añadir e inicializar una variable para contener el valor del parámetro.
  5. Añadir una actividad Dataverse Get Row By Id. Establecer el parámetro Columns a csp_week_starting_on,_ownerid_value
  6. Establecer la propiedad Expands Column a owninguser($select=internalemailaddress,_parentsystemuserid_value;$expand=parentsystemuserid($select=internalemailaddress))
  7. Añadir una actividad Send Email de Outlook
  8. Establecer la propiedad To: Esta fórmula devuelve la dirección de email del supervisor del usuario propietario de la Timesheet
@{outputs('Obtener_una_fila_por_id')?['body/owninguser/parentsystemuserid/internalemailaddress']}
  1. Establecer la propiedad Body: Esta fómula incluye en el cuerpo del email la fecha de la Timesheet seleccionada. Aquí habría que añadir más información.
Please aprove my timesheet for the week @{outputs('Obtener_una_fila_por_id')?['body/csp_week_starting_on']}
  1. Establecer la propiedad cc: Esta fómula selecciona la dirección de email del usuario propietario de la hoja de horas
@{outputs('Obtener_una_fila_por_id')?['body/owninguser/internalemailaddress']}
  1. Volver a la App. Añadir el flujo creado
  2. Establecer la propiedad btnSend.OnChange
// Esta fómula llama al flujo pasándo como valor el 
// id de Timesheet actual
'TimesheetApp-SendforApproval'.Run(CurrentTimesheet.Timesheet)
  1. Añadir una nueva pantalla en blando scrHome
  2. Añadir una nueva etiqueta lblGreeting. En la propiedad Default establecer:
// Esta fórmula establece un saludo introduciendo 
// el nombre del usuario actual
$"Hola, {User().FullName}!"
  1. Añadir un botón btnTimesheets. Establecer la propiedad OnSelect
// Esta fómula llevará al usuario actual a la pantalla
// de Timesheets
Navigate(scrTimesheet)
  1. En la ventana scrTimesheet añadir un botón btnHome. Establecer su propiedad OnSelect
// Esta fómula llevará al usuario a la pantalla de inicio
Navigate(scrHome)
  1. Crear un Reporte de Power BI un un gráfico circular y un calendario
  2. Publcarlo y añadirlo a un Workspace.
  3. Crear un Dashboard con los visuales del Reporte.
  4. En la pantalla de inicio, añadir un control Power BI
  5. Establecer la propiedad Workspace property del control y seleccionar el Dashboard correcto.

Conclusión

La lista de pasos y resumen anterior no son completas. Es un resumen de los pasos y la fórmulas que seguí para crear la app.

Si quisieras crear una App similar, podrían servirte como guía. Puedes también echarle un vistazo a la grabación de la sesión en el video a continuación.

También tienes disponible la solución con la aplicación.

Y si necesitas ayuda con esta u otras applicaciones, no dudes en contactarnos.

Post Actualizado el 14/03/2023

Published by Cris Fernandez

Solutions Architect at Creativity Spark

2 thoughts on “Canvas App Timesheet

  1. Hola Cris,
    aparentemente no se puede importar la solución que está compartida en este post:
    “Some dependencies are missing”, en concreto “csp_sharedcommondataserviceforapps_589eb” y “csp_sharedoffice365_ad579” (en el paquete exportado no están).

    saludos,

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: