Créer une application d'inspection de sites avec Power Apps, Power Automate et SharePoint (immeubles, locaux, équipements et échéances)
Si vos inspections d'immeubles se font encore sur une planchette à pince ou dans un tableur partagé, vous en connaissez déjà les problèmes. Quelqu'un saute la salle de la génératrice, les étiquettes des extincteurs ne sont pas vérifiées pendant un mois, et personne ne peut dire quels sites sont en retard avant qu'un locataire se plaigne.
Dans ce guide, nous allons créer un système complet d'inspection de sites pour les services techniques immobiliers dans Microsoft 365. Chaque site a ses propres immeubles, locaux et équipements. Une bibliothèque maîtresse contient les questions standard pour les locaux et les équipements, et chaque administrateur de site choisit celles dont il a besoin, les reformule, ajoute les siennes et fixe l'ordre. Les opérateurs disposent d'une application canevas adaptée aux tablettes qui les guide dans les vérifications des immeubles, des locaux et des équipements. Power Automate planifie les inspections selon leur fréquence, signale tout ce qui est en retard et envoie par courriel à l'administrateur du site un sommaire des éléments non conformes une fois l'inspection terminée.

Ce que vous allez créer
Une hiérarchie de sites, d'immeubles, de locaux et d'équipements, entièrement dans des listes SharePoint.
Une bibliothèque maîtresse de questions pour les locaux et les équipements. Les questions peuvent s'appliquer à tous les locaux (ou à tous les équipements) ou à un seul type, comme Washroom (salle de toilettes) ou Fire Extinguisher (extincteur).
Des ensembles de questions par site, gérés par l'administrateur du site : importer depuis la bibliothèque maîtresse, reformuler, réordonner, masquer ou ajouter des questions propres au site.
Trois types de réponses : Pass / Fail / N/A (conforme, non conforme, sans objet), Reading (une valeur numérique, comme une température ou un niveau de carburant) et Note.
Un flux de planification qui crée les inspections selon la fréquence de chaque site (hebdomadaire, mensuelle ou trimestrielle), les marque en retard et envoie des rappels.
Un écran pour les opérateurs qui descend de l'immeuble au local, puis à l'équipement, avec une barre de progression et des compteurs par local.
Un flux de fin d'inspection qui envoie par courriel aux administrateurs du site un tableau des éléments non conformes avec les commentaires de l'opérateur.
Un écran de résultats pour les administrateurs, avec les vérifications non conformes en tête.
Ce qu'il vous faut
Microsoft 365 avec SharePoint Online, Outlook, Power Apps et Power Automate. Tout ce qui suit utilise des connecteurs standard; aucune licence premium n'est donc nécessaire.
Un site SharePoint dont vous êtes propriétaire (nous en créerons un nommé Site Inspections).
Une tablette ou un ordinateur portable pour les opérateurs. L'application est conçue en format tablette et fonctionne dans un navigateur, dans l'application mobile Power Apps et dans Teams.
Étape 1 : Créer les listes SharePoint
Créez un nouveau site d'équipe SharePoint nommé Site Inspections, puis ajoutez-y les neuf listes suivantes. Nous lions les enregistrements avec de simples colonnes Number (SiteID, BuildingID, RoomID) plutôt qu'avec des colonnes de recherche. Les filtres sur des nombres sont délégués sans problème dans Power Apps et sont faciles à renseigner depuis Power Automate.
La structure des sites
Sites : Title (nom du site), Address, City, Frequency (Choice : Weekly, Monthly, Quarterly), NextDue (Date), DefaultOperator (texte, le courriel de l'opérateur), Active (Yes/No).
Buildings : Title, SiteID, SortOrder.
Rooms : Title, SiteID, BuildingID, Floor, RoomType (Choice : Office, Washroom, Kitchen, Lobby, Mechanical Room, Electrical Room, Parking), SortOrder.
Equipment : Title, AssetTag, EquipmentType (Choice : HVAC Unit, Fire Extinguisher, Emergency Lighting, Elevator, Generator, Fire Alarm Panel, Water Heater), SiteID, BuildingID, RoomID, SortOrder.
Chaque équipement appartient à un local, et chaque local appartient à un immeuble. C'est ce qui permet aux opérateurs de descendre dans la hiérarchie : choisir un immeuble, choisir un local, puis vérifier le local lui-même et chacun de ses équipements.

La bibliothèque de questions
Master Questions : Title (la question), AppliesTo (Choice : Room, Equipment), Category (texte : un type de local, un type d'équipement ou All), AnswerType (Choice : Pass / Fail, Reading, Note), SortOrder, Active.
Site Questions : les mêmes colonnes, plus SiteID et MasterID (la question maîtresse d'origine, ou 0 pour une question propre au site).
Une Category égale à All signifie que la question s'applique à tous les locaux ou à tous les équipements. « Planchers propres et sans risque de trébuchement » est une question All pour les locaux, tandis que « Manomètre dans la zone verte » ne s'applique qu'à Fire Extinguisher.

Pourquoi copier les questions dans chaque site au lieu de pointer vers la liste maîtresse? Parce que les administrateurs de site doivent être maîtres de leur liste de vérification. Une salle de toilettes d'hôpital et celle d'un entrepôt n'appellent pas les mêmes questions, et la reformulation d'une question par un site ne doit pas la modifier partout ailleurs. MasterID conserve le lien, pour que vous puissiez toujours produire des rapports multisites.
Utilisateurs et inspections
Site Users : Title (courriel, en minuscules), SiteID, Role (Choice : Global Admin, Site Admin, Operator). Les administrateurs globaux utilisent SiteID 0.
Inspections : Title, SiteID, SiteName, DueDate, Status (Choice : Scheduled, In progress, Completed, Past due), AssignedTo (courriel), StartedOn, CompletedOn, CompletedBy, Answered, Failed, SummarySent (Yes/No, No par défaut).
Inspection Results : Title (le texte de la question au moment de l'inspection), InspectionID, SiteID, BuildingID, RoomID, EquipmentID (0 pour les vérifications de local), TargetType, TargetName, QuestionID, Answer, IsFail (Yes/No), Comments.
Les résultats conservent le texte de la question et le nom du local ou de l'équipement, pas seulement des identifiants. Si quelqu'un reformule une question le mois prochain, le rapport du mois dernier montre toujours ce à quoi l'opérateur a réellement répondu.
Étape 2 : Planifier les inspections avec Power Automate
Créez un flux de cloud planifié (Scheduled cloud flow) nommé Inspection Scheduler qui s'exécute tous les matins à 6 h dans votre fuseau horaire.

Get sites due soon : une action SharePoint Get items sur Sites avec la requête de filtre (Filter Query) suivante, pour que les inspections apparaissent une semaine avant leur échéance :
Active eq 1 and NextDue le '@{addDays(utcNow(),7,'yyyy-MM-dd')}'Pour chaque site arrivé à échéance, l'action Get open inspection vérifie si une inspection est déjà en attente :
SiteID eq @{items('For_each_due_site')?['ID']} and Status ne 'Completed'Si le résultat est vide (la condition est length(outputs('Get_open_inspection')?['body/value']) égal à 0) :
Create inspection : Create item sur Inspections avec Title « Nom du site - date d'échéance », SiteID, SiteName, DueDate = le NextDue du site, Status = Scheduled et AssignedTo = DefaultOperator.
Move next due date : Update item sur Sites. NextDue avance selon la fréquence du site :
if(equals(items('For_each_due_site')?['Frequency']?['Value'],'Weekly'), addDays(items('For_each_due_site')?['NextDue'],7,'yyyy-MM-dd'), addToTime(items('For_each_due_site')?['NextDue'], if(equals(items('For_each_due_site')?['Frequency']?['Value'],'Quarterly'),3,1), 'Month', 'yyyy-MM-dd'))Email the operator : Send an email (V2) avec le site, l'adresse et la date d'échéance.

Le flux signale ensuite tout ce qui est en retard. L'action Get overdue inspections utilise ce filtre :
Status eq 'Scheduled' and DueDate lt '@{utcNow('yyyy-MM-dd')}'Pour chacune, il règle Status sur Past due, récupère les administrateurs du site dans Site Users (SiteID eq le SiteID de l'inspection et Role eq 'Site Admin'), les convertit en liste de courriels avec une action Select (map : item()?['Title']) et envoie un rappel à l'opérateur, avec les administrateurs en copie (CC) :
join(body('Admin_emails'),';')

Seules les inspections Scheduled sont marquées en retard. Dès qu'un opérateur en commence une, elle passe à In progress, et l'application affiche plutôt le nombre de jours de retard.
Étape 3 : Configurer l'application et les rôles
Dans Power Apps, créez une application canevas vide en format tablette et ajoutez les neuf listes comme sources de données. Sélectionnez ensuite App et ajoutez ces formules nommées dans la propriété Formulas :
MyEmail = Lower(User().Email);
IsGlobalAdmin = !IsBlank(LookUp('Site Users', Title = MyEmail && Role.Value = "Global Admin"));
MyAdminSiteIDs = Filter('Site Users', Title = MyEmail && Role.Value = "Site Admin");
IsAnyAdmin = IsGlobalAdmin || !IsEmpty(MyAdminSiteIDs);Les opérateurs voient les inspections qui leur sont attribuées. Les administrateurs de site voient aussi toutes les inspections de leurs sites et ont accès aux boutons Manage sites et Results. Les administrateurs globaux voient tout.
Étape 4 : Créer l'écran d'accueil
L'écran d'accueil affiche trois tuiles (en retard, à faire dans les 7 prochains jours, terminées ce mois-ci) et une galerie des inspections ouvertes. Chargez les données dans la propriété OnVisible de l'écran :
ClearCollect(colVisible, SortByColumns(Filter(Inspections, Status.Value <> "Completed" && (IsGlobalAdmin || AssignedTo = MyEmail || SiteID in MyAdminSiteIDs.SiteID)), "DueDate", SortOrder.Ascending));
ClearCollect(colDone, Filter(Inspections, Status.Value = "Completed" && (IsGlobalAdmin || AssignedTo = MyEmail || SiteID in MyAdminSiteIDs.SiteID)))La tuile Past due correspond à CountRows(Filter(colVisible, DueDate < Today())). Chaque ligne de la galerie affiche la date d'échéance, un badge d'état et un message de retard convivial :
With({d: DateDiff(Today(), ThisItem.DueDate, TimeUnit.Days)}, If(d < 0, -d & " days overdue", d = 0, "Due today", "Due in " & d & " days"))Le bouton Start inspection marque l'inspection In progress et ouvre l'écran d'inspection :
Set(varInsp, ThisItem); If(ThisItem.Status.Value <> "In progress", Set(varInsp, Patch(Inspections, ThisItem, {Status: {Value: "In progress"}, StartedOn: Now()}))); Navigate(Screen2, ScreenTransition.Cover)Étape 5 : Créer l'écran d'inspection
C'est là que les opérateurs passent leur temps. À gauche, ils choisissent un immeuble, puis un local. En haut à droite, une rangée de boutons affiche Room checks ainsi que chaque équipement du local. En dessous apparaissent les questions de l'élément sélectionné.

La propriété OnVisible de l'écran charge une seule fois tout ce qui concerne le site, pour que la navigation soit instantanée :
ClearCollect(colQ, Filter('Site Questions', SiteID = varInsp.SiteID && Active));
ClearCollect(colRooms, SortByColumns(Filter(Rooms, SiteID = varInsp.SiteID), "SortOrder"));
ClearCollect(colEquip, SortByColumns(Filter(Equipment, SiteID = varInsp.SiteID), "SortOrder"));
ClearCollect(colRes, Filter('Inspection Results', InspectionID = varInsp.ID));
Set(varTotal, Sum(colRooms As r, CountRows(Filter(colQ, AppliesTo.Value = "Room" && (Category = "All" || Category = r.RoomType.Value)))) + Sum(colEquip As e, CountRows(Filter(colQ, AppliesTo.Value = "Equipment" && (Category = "All" || Category = e.EquipmentType.Value)))));varTotal est le nombre de vérifications de toute l'inspection. L'en-tête affiche « 12 of 65 checks done », une barre de progression se remplit en haut, et chaque local affiche son propre compteur qui passe au vert une fois terminé.
Lorsqu'un local est choisi, un bouton masqué construit la liste des éléments à inspecter dans ce local :
ClearCollect(colTargets, {ID: 0, Name: "Room checks", Kind: varRoom.RoomType.Value});
Collect(colTargets, ForAll(Filter(colEquip, RoomID = varRoom.ID), {ID: ThisRecord.ID, Name: ThisRecord.Title, Kind: ThisRecord.EquipmentType.Value}))Un clic sur un bouton de cette rangée définit varEquipID (0 pour les vérifications du local), varAT (Room ou Equipment) et varCat (le type de local ou d'équipement). La galerie de questions n'a alors besoin que d'un seul filtre :
SortByColumns(Filter(colQ, AppliesTo.Value = varAT && (Category = "All" || Category = varCat)), "SortOrder")
Les questions Pass / Fail affichent trois boutons, les questions Reading et Note affichent une zone de texte, et chaque question a une zone de commentaire. Chaque réponse est enregistrée dans SharePoint immédiatement; rien n'est donc perdu si la tablette perd le Wi-Fi au milieu d'un immeuble. La propriété OnSelect du bouton Fail crée le résultat la première fois, puis le met à jour :
With({cur: LookUp(colRes, QuestionID = ThisItem.ID && RoomID = varRoom.ID && EquipmentID = varEquipID)},
If(IsBlank(cur),
Collect(colRes, Patch('Inspection Results', Defaults('Inspection Results'), {Title: ThisItem.Title, InspectionID: varInsp.ID, SiteID: varInsp.SiteID, BuildingID: varRoom.BuildingID, RoomID: varRoom.ID, EquipmentID: varEquipID, TargetType: {Value: varAT}, TargetName: varTargetName, QuestionID: ThisItem.ID, Answer: "Fail", IsFail: true})),
Patch(colRes, cur, Patch('Inspection Results', cur, {Answer: "Fail", IsFail: true}))))Les lignes non conformes passent au rouge pâle, grâce au même LookUp dans la propriété Fill de la ligne. Le bouton Complete inspection reste désactivé tant que toutes les vérifications n'ont pas de réponse :
If(Coalesce(varTotal, 0) > 0 && CountRows(Filter(colRes, !IsBlank(Answer))) >= varTotal, DisplayMode.Edit, DisplayMode.Disabled)
La fin de l'inspection règle Status sur Completed avec CompletedOn, CompletedBy, Answered et Failed, et le flux de l'étape 7 prend le relais.
Étape 6 : Laisser les administrateurs de site gérer leurs propres questions
L'écran Manage sites permet aux administrateurs de gérer leurs sites sans appeler les TI. En haut, ils règlent la fréquence d'inspection et la prochaine échéance. En dessous, ils choisissent les questions Room ou Equipment et une catégorie, et voient la liste de vérification du site dans l'ordre.

Chaque ligne comporte une question modifiable, des flèches vers le haut et vers le bas et un bouton Active. La flèche vers le haut échange le SortOrder avec celui de la question précédente :
With({prev: Last(Filter(colAdmQ, SortOrder < ThisItem.SortOrder))},
If(!IsBlank(prev),
Patch('Site Questions', LookUp('Site Questions', ID = ThisItem.ID), {SortOrder: prev.SortOrder});
Patch('Site Questions', LookUp('Site Questions', ID = prev.ID), {SortOrder: ThisItem.SortOrder});
Select(btnReloadQ)))Les questions masquées restent dans la liste pour l'historique, mais disparaissent des nouvelles inspections, car l'écran d'inspection ne charge que les questions actives.
À droite, la bibliothèque maîtresse (Master library) affiche les questions de la même catégorie que ce site n'utilise pas encore, chacune avec un bouton Add :
Filter('Master Questions', Active && AppliesTo.Value = varAdmAT && Category = ddCat.Selected.Value && !(ID in colAdmQ.MasterID))
Pour un tout nouveau site, le bouton Import all master questions copie toutes les questions maîtresses actives dont la catégorie correspond à un type de local ou d'équipement du site (plus les questions All) :
ClearCollect(colSiteCats, {Cat: "All"});
Collect(colSiteCats, ForAll(Filter(Rooms, SiteID = ddSite.Selected.ID), {Cat: ThisRecord.RoomType.Value}));
Collect(colSiteCats, ForAll(Filter(Equipment, SiteID = ddSite.Selected.ID), {Cat: ThisRecord.EquipmentType.Value}));
ClearCollect(colExisting, Filter('Site Questions', SiteID = ddSite.Selected.ID));
ForAll(Filter('Master Questions', Active) As m, If(m.Category in colSiteCats.Cat && !(m.ID in colExisting.MasterID), Patch('Site Questions', Defaults('Site Questions'), {Title: m.Title, SiteID: ddSite.Selected.ID, MasterID: m.ID, AppliesTo: {Value: m.AppliesTo.Value}, Category: m.Category, AnswerType: {Value: m.AnswerType.Value}, SortOrder: m.SortOrder, Active: true})))Une zone de texte et un sélecteur de type de réponse au bas de l'écran permettent d'ajouter des questions propres au site avec MasterID 0, comme « Vitre de l'armoire intacte et marteau présent » pour les armoires à extincteur d'un immeuble.
Étape 7 : Envoyer un courriel à l'administrateur du site à la fin d'une inspection
Créez un flux de cloud automatisé (Automated cloud flow) nommé Inspection Completed Summary avec le déclencheur SharePoint When an item is created or modified sur Inspections. Dans les paramètres (Settings) du déclencheur, ajoutez cette condition de déclenchement pour qu'il ne s'exécute qu'une fois par inspection terminée :
@and(equals(triggerOutputs()?['body/Status/Value'],'Completed'), not(equals(triggerOutputs()?['body/SummarySent'],true)))
Le flux effectue ensuite les actions suivantes :
Get results : Get items sur Inspection Results avec le filtre InspectionID eq l'ID du déclencheur et Top Count 5000.
Failed items : un Filter array où IsFail est égal à true.
Failed rows : un Select qui associe Location (TargetName), Check (Title), Answer et Comments.
Failed table : Create HTML table à partir du Select.
Get site admins et Admin emails : le même modèle que dans le flux de planification.
Mark summary sent : Update item avec SummarySent = Yes, plus les nombres Answered et Failed. Grâce à la condition de déclenchement, cette mise à jour ne relance pas le flux.
Email site admins : un badge rouge ou vert, le site, la date d'échéance, qui a terminé l'inspection et quand, et le tableau des éléments non conformes (ou « No issues found »).

Create HTML table n'applique aucun style; enveloppez donc le résultat dans un replace rapide pour ajouter des bordures et un en-tête ombré :
replace(replace(body('Failed_table'),'<table>','<table style="border-collapse:collapse" border="1" cellpadding="6">'),'<th>','<th style="background:#f0f4f8;text-align:left">')
Étape 8 : Afficher les résultats aux administrateurs
L'écran Results liste les inspections terminées avec un badge indiquant le nombre de non-conformités. Sélectionnez-en une pour voir toutes les réponses, les vérifications non conformes triées en tête :
SortByColumns(Filter('Inspection Results', InspectionID = varResInsp.ID), "IsFail", SortOrder.Descending, "TargetName", SortOrder.Ascending)
Les pièges rencontrés en cours de route
Listes déroulantes classiques : lorsque Items est une formule, le champ Value du volet des propriétés est grisé et ne peut pas être défini depuis la barre de formule. Réglez d'abord Items sur la liste simple, choisissez la colonne (par exemple Site), puis remettez votre formule.
Galeries : les étiquettes sont superposées à la ligne, si bien qu'un clic sur le texte ne sélectionne pas la ligne. Réglez la propriété OnSelect de chaque étiquette sur Select(Parent).
Vide n'est pas zéro en Power Fx. If(varTotal = 0, ...) renvoie false tant que varTotal est vide; une barre de progression qui divise par varTotal déclenche donc une erreur « division by zero ». Enveloppez la valeur : Coalesce(varTotal, 0).
Colonnes Choice dans les filtres OData : Status eq 'Completed' fonctionne dans Get items. Dans Power Apps, comparez Status.Value.
Sécurité et montée en charge
L'application montre à chacun uniquement ses propres inspections et sites, mais c'est du filtrage, pas de la sécurité. Quiconque a l'autorisation Contribute sur les listes pourrait les ouvrir dans SharePoint. Pour un déploiement réel :
Accordez aux opérateurs l'autorisation Contribute uniquement sur Inspections et Inspection Results, et Read sur les listes de structure et de questions.
Accordez aux administrateurs de site un accès en modification à Site Questions et à Sites au moyen d'un groupe SharePoint par site, ou rompez les autorisations par élément depuis un flux, comme nous l'avons fait dans notre application de demandes de vacances.
Réservez la modification de la bibliothèque maîtresse aux administrateurs globaux.
SharePoint convient pour des centaines de sites et des dizaines de milliers de résultats. Les filtres utilisés ici portent sur des colonnes Number et Choice et sont donc délégués, mais « in » et « <> » ne le sont pas. Si un site dépasse 2 000 locaux ou si vous avez besoin d'une véritable sécurité relationnelle, transposez la même conception dans Dataverse.
Préparer la mise en production
Photos : ajoutez un contrôle Add picture à chaque ligne non conforme et enregistrez l'image en pièce jointe du résultat, ou dans une bibliothèque de documents nommée selon le site.
Mesures correctives : faites créer par le flux de fin d'inspection un bon de travail pour chaque élément non conforme dans une liste Corrective Actions (ou dans votre GMAO), avec un responsable et une échéance.
Codes QR : apposez un code QR sur la porte de chaque local et sur chaque équipement. Un lecteur de codes-barres dans l'écran d'inspection mène directement au local ou à l'équipement visé.
Hors connexion : mettez les collections en cache avec SaveData et LoadData pour que les opérateurs puissent inspecter les sous-sols et les stationnements sans réseau, puis synchroniser au retour de la connexion.
Signatures : ajoutez un contrôle de saisie au stylet à la fin de l'inspection et enregistrez l'image avec l'inspection.
Des fréquences différentes par immeuble ou par type d'équipement : déplacez Frequency et NextDue dans Buildings, ou ajoutez une fréquence à chaque question maîtresse.
La partie amusante : le bingo du gestionnaire d'immeubles
Cochez tout ce que vous avez entendu cette année :
« La génératrice va bien, on l'a fait tourner... à un moment donné. »
Un extincteur qui sert de butoir de porte.
Une salle électrique qui sert aussi d'entrepôt pour les décorations des Fêtes.
« Qui a la clé de la salle mécanique? »
Une planchette d'inspection vue pour la dernière fois en 2019.
Trois ou plus? Cette application sera rentabilisée avant la prochaine inspection incendie.
Besoin d'un coup de main?
Smart Solutions conçoit des solutions Power Apps et Power Automate pour les équipes immobilières et de gestion d'installations au Canada, des inspections et bons de travail jusqu'aux applications complètes de services techniques et de gestion des baux. Communiquez avec nous et nous vous aiderons à déployer cette solution dans votre portefeuille, ou à l'adapter à vos immeubles et à vos règles de conformité.
Vous gérez aussi des baux, pas seulement des immeubles? Découvrez Frontage, notre CRM de location commerciale pour les courtiers canadiens.




Commentaires