Premiere commit - Omika

This commit is contained in:
2026-10-23 18:46:50 +02:00
commit 8ea76c1b24
2155 changed files with 984301 additions and 0 deletions
+283
View File
@@ -0,0 +1,283 @@
# RECETTE ANDROID — AUDIT AVANT MODIFICATION (OBJECTIF 0 / §17)
> Projet : `D:\Avalonia\Omika` — Base OMIKA réelle. Aucun fichier modifié au stade d'audit.
> Ce document est le livrable de l'audit préalable. Les correctifs ne sont appliqués qu'après validation.
## Sommaire
1. [Architecture rappelée](#architecture)
2. [Tableau de synthèse des problèmes](#tableau)
3. [Cause exacte par problème](#causes)
4. [Classification](#classification)
5. [Preuve de non-régression Batik](#batik)
6. [Routes HTTP appelées](#routes)
7. [Requêtes SQL concernées](#sql)
8. [Inventaire des icônes à remplacer](#icones)
9. [Propositions de SVG monochrome](#svg)
<a name="architecture"></a>
## 1. Architecture rappelée
- **Mobile** (`src/Omika.App.Local`) : offline-first sur SQLite, synchronisé avec l'API via `MobileApiClient`.
- **API** (`src/Omika.Api`) : endpoints REST, sélection de base `Batik`/`Omika` via `ApplicationRuntimeContext.Current.DetectedVersion`.
- **Données** (`src/Omika.Data`) : repositories `Adaptive*` qui délèguent à `Batik*Repository` (schéma historique) ou `Omika*Repository` (schéma physique Omika). Les mappers (`Mapping/Omika`, `Mapping/Batik`) produisent le modèle canonique.
- **Identité publique** : GUID. Omika → GUID physique (colonne `GUID`). Batik → GUID synthétique réversible (`MobileEntityIdentityResolver`).
- **UI** (`src/Omika.App`) : Avalonia, thèmes clair/sombre via `DesignTokens.axaml` (clés `DynamicResource`).
<a name="tableau"></a>
## 2. Tableau de synthèse
| # | PROBLÈME | SYMPTÔME | COUCHE | FICHIER(S) | CAUSE EXACTE | SPÉCIFIQUE OMIKA ? | BATIK IMPACTÉ ? | CORRECTION PROPOSÉE |
|---|---|---|---|---|---|---|---|---|
| 1 | Carte OSM bloquée | 403 / "volunteer-run servers" | ANDROID/UI (tuiles) | `Controls/LieuMap.axaml.cs:97` | `OpenStreetMap.CreateTileLayer()` (Mapsui) utilise un HttpClient sans User-Agent identifiant Omika | NON | NON (commun) | Tile source custom avec User-Agent + cache + attribution ; fallback adresse+Itinéraire |
| 2 | Icônes hétérogènes | Emoji/Unicode/couleurs | UI | multiples (cf. §8) | Emoji/Unicode et couleurs codées en dur | NON | NON | Standardiser en Path monochrome + `DynamicResource` |
| 3 | Boutons entête intervention | fond blanc massif | UI | `Views/RendezVousDetailView.axaml:120-139` | `Background=OnPrimaryColorBrush` (blanc) + emoji `⌖ ⚒ ▧` | NON | NON | Style translucide + SVG monochrome |
| 4 | Agenda équipe : icône groupe | bleue/multicolore | UI | `Views/AgendaView.axaml:377` (`👥`) | emoji `👥` utilisé comme icône de groupe (pas dans `AgendaEquipeView` qui n'a pas d'icône) | NON | NON | Remplacer par SVG/Path monochrome |
| 5 | RDV→intervention introuvable | "Intervention introuvable." | OMIKA DATA / API | `OmikaRendezVousRepository`, `MobileSnapshotService`, `OmikaInterventionRepository.GetDetailsAsync` | (voir §3.5) | OUI | NON | Corriger le mapping/guid Omika |
| 6A | Note CreationUser=0 | CreationUser=0 en base | API / OMIKA DATA | `OmikaInterventionRepository.ResolveCreationUserAsync:1560` | token `CollaborateurId` = PK numérique licence ; `ResolveCreationUserAsync` attend un GUID | OUI | NON | Résoudre l'utilisateur depuis l'identité token (UserId/UT_PKID ou PK numérique) |
| 6B | Compteur notes = 0 | "0 note(s)" | LOCAL/API | `InterventionDetailViewModel.RefreshFromApiAsync` | compteur = `Notes.Count` chargé en arrière-plan via API ; échec/guid → vide | OUI (lié #5) | NON | même cause que #5 + retry |
| 7 | Compteur images/documents = 0 | "0 fichier(s)" | API / OMIKA DATA | `MobileDocumentService.GetByInterventionAsync` + `OmikaDocumentRepository` | résolution guid intervention / filtre catégorie | OUI | NON | même cause que #5 + vérif filtre |
| 8 | Écran Images/Documents | titre redondant + bouton coupé | UI | `Views/InterventionResourcesView.axaml:37,73` | `TextBlock "Images"` redondant + `StackPanel Orientation=Horizontal` non responsive | NON | NON | Supprimer titre + WrapPanel |
| 9 | Création email 404 | 404 modèles courriel | API | `MobileApiClient.cs:761` + `ModeleDocumentEndpoints.cs:18` | **Slash final manquant** : mobile appelle `GET /modele-mails`, API enregistre `GET /modele-mails/` (`MapGroup("/modele-mails").MapGet("/")`) | NON (route commune) | NON (corrigé pour les 2) | `MapGet("/")` → `MapGet("")` (ou slash final côté mobile) |
| 10 | Rapports 404 | 404 modèles rapport | API | `MobileApiClient.cs:771` + `ModeleDocumentEndpoints.cs:28` | **Slash final manquant** : `GET /modele-rapports` vs `GET /modele-rapports/` | NON | NON | idem #9 |
| 11 | Bouton téléphone inopérant | rien ne se lance | ANDROID | `Services/PlatformPhoneLauncher.cs` | `TopLevel.Launcher.LaunchUriAsync` (ACTION_VIEW) ≠ ACTION_DIAL | NON | NON | Launcher Android `ACTION_DIAL` (tel:) |
| 12 | Bouton email inopérant | aucun client mail | ANDROID | `Services/PlatformEmailLauncher.cs` | `LaunchUriAsync` générique ≠ ACTION_SENDTO | NON | NON | Launcher Android `ACTION_SENDTO` (mailto:) |
| 13 | Fiche équipe/collaborateurs responsive | email sur plusieurs lignes, cartes hautes | UI | `CollaborateursView`, `CollaborateurDetailView`, `EquipeDetailView` | layout non responsive | NON | NON | Réorganiser grille responsive |
| 14 | Messages d'erreur techniques | "Response status code…404" affiché | UI | `NouveauEmailViewModel`, `RapportChampsViewModel` | `ErrorMessage = ex.Message` brut | NON | NON | Message utilisateur propre + log technique |
<a name="causes"></a>
## 3. Cause exacte par problème
### 3.1 Carte OpenStreetMap bloquée
- `LieuMap.axaml.cs:97` appelle `OpenStreetMap.CreateTileLayer()` (Mapsui). Le `HttpTileSource` par défaut de Mapsui charge `https://tile.openstreetmap.org/{z}/{x}/{y}.png` via un `HttpClient` **sans User-Agent identifiant Omika** → OSM rejette avec 403 (« volunteer-run servers »).
- Le géocodeur `AddressGeocoder` (Nominatim) a déjà un User-Agent correct (`Omika-Mobile/1.0`), mais pas le chargement des tuiles.
- Pas de cache de tuiles configuré, pas d'attribution OSM visible, pas de fallback propre : l'image 403 brute est affichée.
### 3.5 RDV → « Ouvrir l'intervention » → introuvable
Flux tracé (lecture de code) :
1. `RendezVousDetailViewModel.OpenIntervention` → navigation avec `RendezVous.InterventionGuid` (GUID).
2. `AgendaItem.InterventionGuid` ← `AgendaService.MapToAgendaItem` ← `RendezVousDetailsDto.InterventionId`.
3. `RendezVousDetailsDto.InterventionId` ← `MobileSnapshotService.MapRendezVous` ← `MobileIdentityMapper.ResolveIdString(Intervention, dto.InterventionSourceReference)`.
4. `InterventionSourceReference` ← `OmikaRendezVousMapper` : `{ NumericId: RV_IdInter, EntityGuid: ResolveGuid(interventionGuids, RV_IdInter) }`.
5. Pour Omika, `MobileEntityIdentityResolver.CreateOmikaIdentity` retourne `physical.Guid`. Si `EntityGuid` est null (GUID intervention non résolu), `CreateOmikaIdentity` **lève `InvalidOperationException`** (« aucun GUID physique fourni »).
6. Navigation → `InterventionLocalService.GetInterventionByIdAsync(Guid)` → API `GET /interventions/{guid}` → `OmikaInterventionRepository.GetDetailsAsync` (GUID → `GUID IN (...)`).
**Conclusion de la trace statique (chaîne vérifiée de bout en bout)** : la chaîne d'identité est **correcte** pour Omika :
- `OmikaRendezVousMapper` résout `InterventionSourceReference = { NumericId: RV_IdInter, EntityGuid: Intervention.GUID }` en bulk (`LoadInterventionGuidsAsync`, requête `IN_PKID IN (...)`).
- `MobileSnapshotService.MapRendezVous` → `ResolveIdString` → `CreateOmikaIdentity` retourne le GUID physique.
- `AgendaItem.InterventionGuid` = ce GUID ; navigation → API `GET /interventions/{guid}` → `GetDetailsAsync` (GUID) ; repli local sur la table snapshot `Intervention` (peuplée par `SnapshotApplier` à partir de `LoadInterventionsForRendezVousAsync`, clé = GUID canonique).
- `PhysicalGuidParser.GetStorageCandidates` couvre les formes D / N / D-tronquée.
**Déclencheur exact restant = niveau données** (à confirmer par les logs temporaires demandés au §5 de la recette) :
- soit `RV_IdInter` pointe vers une intervention dont la colonne `GUID` est NULL/invalide (→ `EntityGuid` null → `CreateOmikaIdentity` lèverait, OU `InterventionId` null) ;
- soit l'intervention référencée n'existe pas dans `Intervention` (référence orpheline) ;
- soit un écart de format GUID entre `RV_IdInter→GUID` et l'`Intervention.GUID` réel (byte-order HFSQL).
**Logs temporaires à poser (correction #5)** : `RDV_PKID`, `RDV GUID`, `RV_IdInter`, `Intervention.GUID` résolu, `InterventionGuid` envoyé à la navigation (type + valeur), requête SQL utilisée, résultat. Aucun `IN_NumIntervention` ne doit être réintroduit.
Les compteurs #6B et #7 passent par les mêmes endpoints API (`GET /api/interventions/{guid}/notes`, `GET /api/interventions/{guid}/documents`) : si la résolution d'intervention échoue, listes vides → « 0 note(s) » / « 0 fichier(s) ». #7 a en plus un filtre par catégories de document (`BuildAllowedDocumentCategories` + `IsCategoryAllowed`) qui peut vider la liste si le profil n'a pas le droit `A.DOC.*` correspondant.
### 3.6A Note CreationUser = 0
- Endpoint `AddNoteForInterventionAsync` (`InterventionEndpoints.cs:427`) transmet `httpContext.GetMobileIdentity()?.CollaborateurId` à `InterventionService.AddNoteAsync` → `ResolveCreationUserAsync(collaborateurId)`.
- En **production**, `LicenseEndpoints.BuildSession` (`LicenseEndpoints.cs:72`) pose `CollaborateurId = response.CollaborateurId` = `licence.CollaborateurId.ToString()` (PK numérique du collaborateur, cf. `LicenseService.BuildSuccessAsync:404`).
- `OmikaInterventionRepository.ResolveCreationUserAsync` (`:1560`) fait `Guid.TryParse(collaborateurGuid, out Guid)` : une valeur numérique **échoue** → retour `0` → `CreationUser = 0`.
- `BatikInterventionRepository.ResolveCreationUserAsync` (`:1513`) décode un GUID synthétique Batik : une valeur numérique échoue aussi (`Guid.TryParse` → 0).
- **Racine** : incohérence de type entre la claim `CollaborateurId` du token (numérique en prod) et l'API de `ResolveCreationUserAsync` (attend un GUID).
### 3.9 / 3.10 Email / Rapport 404 — CAUSE EXACTE CONFIRMÉE
- **Slash final manquant.** `MobileApiClient.GetModeleMailsAsync` appelle `GET {BaseUrl}/modele-mails` (sans slash), mais l'API enregistre la route `GET /modele-mails/` (avec slash) via `MapGroup("/modele-mails").MapGet("/")` (`ModeleDocumentEndpoints.cs:18`). En ASP.NET Core minimal API, `MapGroup("/x").MapGet("/")` produit `/x/` et **ne matche pas** `/x` → 404.
- Idem `GetModeleRapportsAsync` → `GET /modele-rapports` vs `GET /modele-rapports/` (`ModeleDocumentEndpoints.cs:28`).
- Les autres routes auditées (`/mot-clefs`, `/generation/email`, `/generation/rapport`, `/api/rapports/champs`, `/api/mails/champs`, `/api/mails`, `/api/mails/apercu`, `/api/rapports`) matchent **exactement**. Le préfixe `/api` est cohérent, les routes sont bien enregistrées (`Program.cs:243-244`), et `MobileAuthMiddleware` ne renvoie jamais 404.
- **Correction** : `MapGet("/")` → `MapGet("")` dans `ModeleDocumentEndpoints` (canonique, fait matcher `/modele-mails` sans slash) — sans impact Batik (route schéma-agnostique, corrige les deux schémas). Alternative : ajouter le slash final dans `MobileApiClient` (l.761/771).
- Référence historique `Mail_Modele` / `ModeleRapport WHERE MRP_EnvoiMob=1` → routes REST actuelles (cf. `docs/modeles-email-rapport.md`).
### 3.11 / 3.12 Téléphone / Email inopérants
- `PlatformPhoneLauncher.DialAsync` et `PlatformEmailLauncher.ComposeAsync` utilisent `TopLevel.Launcher.LaunchUriAsync(uri)` (launcher générique Avalonia). Sur Android, ce launcher utilise `ACTION_VIEW`, qui **ne lance pas le composeur natif** (tel:) ni le client mail de façon fiable.
- Aucun launcher Android spécifique n'existe (`src/Omika.App.Android` ne contient pas de Phone/Email launcher).
- Les ViewModels ignorent le retour `bool` : aucun message si aucun handler n'est disponible.
### 3.14 Messages d'erreur techniques
- `NouveauEmailViewModel.LoadAsync` et `RapportChampsViewModel.LoadAsync` font `catch (Exception ex) { ErrorMessage = ex.Message; }` ; `EnsureSuccessStatusCode()` lève `HttpRequestException` dont le message brut (« Response status code does not indicate success: 404 (Not Found). ») est affiché tel quel.
<a name="classification"></a>
## 4. Classification
| # | Classification |
|---|---|
| 1 | ANDROID/UI (commun, non spécifique données) |
| 2 | UI |
| 3 | UI |
| 4 | UI |
| 5 | OMIKA DATA + API (résolution identité intervention) |
| 6A | API + OMIKA DATA (résolution utilisateur) |
| 6B | LOCAL/API (compteur = source API) |
| 7 | API + OMIKA DATA (documents) |
| 8 | UI |
| 9 | API (route/déploiement) |
| 10 | API (route/déploiement) |
| 11 | ANDROID (launcher) |
| 12 | ANDROID (launcher) |
| 13 | UI |
| 14 | UI |
<a name="batik"></a>
## 5. Preuve de non-régression Batik
- Les corrections **UI/Android** (#1, #2, #3, #4, #8, #11, #12, #13, #14) ne touchent **aucune** requête Batik : elles portent sur les `.axaml`, les launchers Android et les messages d'erreur, partagés entre les deux schémas.
- Les corrections **données** (#5, #6A, #7, #9, #10) seront appliquées **uniquement** dans les providers/repositories/mappers **Omika** (`src/Omika.Data/Repositories/Omika/*`, `Mapping/Omika/*`) ou dans la couche d'identité commune, **sans modifier** `Batik*Repository` ni `Batik*Mapper`.
- `ResolveCreationUserAsync` Batik est déjà isolé dans `BatikInterventionRepository` ; sa correction éventuelle (acceptation du PK numérique) doit être faite symétriquement sans casser le décodage du GUID synthétique Batik (test à prévoir).
- Les routes API (#9, #10) sont communes : l'ajout d'un alias `/api/modele-mails` (si retenu) n'affecte pas le comportement Batik, qui utilise la même API.
<a name="routes"></a>
## 6. Routes HTTP actuellement appelées (MobileApiClient → Omika.Api)
| Méthode client | URL appelée | Route API | Match |
|---|---|---|---|
| GetModeleMailsAsync | `GET /modele-mails` | `ModeleDocumentEndpoints` `/modele-mails` | ✅ (préfixe sans `/api`) |
| GetModeleRapportsAsync | `GET /modele-rapports` | `ModeleDocumentEndpoints` `/modele-rapports` | ✅ |
| GetMotClefsAsync | `GET /mot-clefs` | `ModeleDocumentEndpoints` `/mot-clefs` | ✅ |
| GetEquipesAsync | `GET /equipes` | `EquipeEndpoints` `/equipes` | ✅ |
| GetAllEquipesAsync | `GET /equipes/all` | `EquipeEndpoints` `/equipes/all` | ✅ |
| GetCollaborateurByIdAsync | `GET /collaborateurs/{id}` | `CollaborateurEndpoints` | ✅ |
| GetRendezVousByIdAsync | `GET /rendez-vous/{id}` | `RendezVousEndpoints` | ✅ |
| GetInterventionByIdAsync | `GET /interventions/{id}` | `InterventionEndpoints` | ✅ |
| GetDossierByIdAsync | `GET /dossiers/{id}` | `DossierEndpoints` | ✅ |
| GetClientByIdAsync | `GET /clients/{id}` | `ClientEndpoints` | ✅ |
| GetSiteByIdAsync | `GET /sites/{id}` | `SiteEndpoints` | ✅ |
| GetContactByIdAsync | `GET /contacts/{id}` | `ContactEndpoints` | ✅ |
| GetCompteRenduByRendezVousAsync | `GET /rendez-vous/{id}/compte-rendu` | `CompteRenduEndpoints` | ✅ |
| GetNotesByInterventionAsync | `GET /api/interventions/{id}/notes` | `InterventionEndpoints` mobileGroup | ✅ |
| AddNoteByInterventionAsync | `POST /api/interventions/{id}/notes` | `InterventionEndpoints` mobileGroup | ✅ |
| GetDocumentsByInterventionAsync | `GET /api/interventions/{id}/documents` | `InterventionEndpoints` mobileGroup | ✅ |
| GetRendezVousByInterventionAsync | `GET /api/interventions/{id}/rendez-vous` | `InterventionEndpoints` mobileGroup | ✅ |
| GetEmailsByInterventionAsync | `GET /api/interventions/{id}/emails` | `InterventionEndpoints` mobileGroup | ✅ |
| AddEmailByInterventionAsync | `POST /api/interventions/{id}/emails` | `InterventionEndpoints` mobileGroup | ✅ |
| GetSmtpConfigurationsAsync | `GET /api/mails/configurations-smtp` | `RapportChampEndpoints` | ✅ |
| GetRapportChampsAsync | `POST /api/rapports/champs` | `RapportChampEndpoints` | ✅ |
| GetMailChampsAsync | `POST /api/mails/champs` | `RapportChampEndpoints` | ✅ |
| CreateEmailModeleAsync | `POST /api/mails` | `RapportChampEndpoints` | ✅ |
| GetEmailModeleApercuAsync | `POST /api/mails/apercu` | `RapportChampEndpoints` | ✅ |
| CreateRapportAsync | `POST /api/rapports` | `RapportChampEndpoints` | ✅ |
| GenerateEmailAsync | `POST /generation/email` | `ModeleDocumentEndpoints` | ✅ |
| GenerateRapportAsync | `POST /generation/rapport` | `ModeleDocumentEndpoints` | ✅ |
| SearchInterventionsAsync | `POST /api/interventions/search` | `InterventionEndpoints` mobileGroup | ✅ |
| GetInterventionsByIdsAsync | `POST /api/interventions/by-ids` | `InterventionEndpoints` mobileGroup | ✅ |
| SearchDossiersAsync | `GET /api/dossiers` | `DossierEndpoints` mobileGroup | ✅ |
| SearchClientsAsync | `GET /api/clients` | `ClientEndpoints` mobileGroup | ✅ |
| SearchSitesAsync | `GET /api/sites` | `SiteEndpoints` mobileGroup | ✅ |
| SearchCollaborateursAsync | `GET /collaborateurs` | `CollaborateurEndpoints` | ✅ (sans `/api`) |
| GetSuivisTempsAsync | `GET /api/suivis-temps` | `SuiviTempsEndpoints` | ✅ |
| GetBootstrapAsync | `GET /api/mobile/bootstrap` | `MobileBootstrapEndpoints` | ✅ |
| GetSnapshotAsync | `GET /api/sync/snapshot` | `SyncEndpoints` | ✅ |
| SendOperationAsync | `POST /api/sync/operations` | `SyncEndpoints` | ✅ |
| GetStatusAsync | `GET /api/sync/status/{id}` | `SyncEndpoints` | ✅ |
| ActivateAsync | `POST /api/license/activate` | `LicenseEndpoints` | ✅ |
| ValidateAsync | `POST /api/license/validate` | `LicenseEndpoints` | ✅ |
| GetDevelopmentSessionAsync | `POST /api/dev/session` | `DevelopmentAuthEndpoints` (Dev only) | ✅ |
> **Incohérence constatée** : le mobile utilise un mélange de routes avec `/api` et sans `/api` (ex. `/modele-mails` sans préfixe vs `/api/mails`). Les routes « modèle » (`/modele-mails`, `/modele-rapports`, `/mot-clefs`, `/generation/*`) sont les seules sans `/api`. C'est cohérent avec le code API actuel mais fragile et à uniformiser.
<a name="sql"></a>
## 7. Requêtes SQL concernées (côté API, base HFSQL)
| Problème | Requête |
|---|---|
| #5 (intervention par GUID) | `SELECT … FROM Intervention WHERE GUID IN (?)` (formes D/N/tronquée via `PhysicalGuidParser.GetStorageCandidates`) — `OmikaInterventionRepository.GetDetailsAsync` |
| #5 (RDV→intervention) | `SELECT … FROM RDV_Planning … LEFT JOIN Intervention I ON RV.RV_IdInter = I.IN_PKID` (résolution bulk `RV_IdInter → Intervention.GUID`) — `OmikaRendezVousRepository` |
| #6A (user) | `SELECT Utilisateur.UT_PKID FROM Utilisateur INNER JOIN Collaborateur ON Collaborateur.CO_PKID = Utilisateur.UT_IDCollaborateur WHERE Collaborateur.GUID IN (?)` — `OmikaInterventionRepository.ResolveCreationUserAsync` |
| #6A (Batik) | `SELECT UT_PKID FROM Utilisateur WHERE UT_IDCollaborateur = ?` — `BatikInterventionRepository.ResolveCreationUserAsync` |
| #6B/#7 (notes) | `SELECT MI_PKID, GUID, MI_Message, CreationDate, CreationUser FROM MessageInterne WHERE MI_IDIntervention = ? AND (MI_IDTypeMessage IS NULL OR MI_IDTypeMessage <= 10)` — `OmikaInterventionRepository.GetNotesAsync` |
| #7 (documents) | résolution intervention GUID → `IN_PKID` puis requête documents (filtre `DC_IDIntervention` / catégorie / état) — `OmikaDocumentRepository.GetByInterventionAsync` |
| #7 (catégories) | `BuildAllowedDocumentCategories` → filtrage par clés `T.DOC.*` selon droits `A.DOC.*` |
<a name="icones"></a>
## 8. Inventaire des icônes à remplacer (extrait — inventaire complet en cours)
| Fichier | Ligne | Actuel | Fonction | Proposition |
|---|---|---|---|---|
| `Views/RendezVousDetailView.axaml` | 122 | `⌖ Itinéraire` (emoji) | Itinéraire | Path "navigation/itinéraire" monochrome |
| `Views/RendezVousDetailView.axaml` | 134 | `⚒ Ouvrir l'intervention` | Ouvrir intervention | Path "marteau/outil" monochrome |
| `Views/RendezVousDetailView.axaml` | 139 | `▧ Compte rendu` | Compte rendu | Path "document" monochrome |
| `Views/InterventionResourcesView.axaml` | 37/73 | titre texte redondant | (titre) | supprimer |
| `Controls/LieuMap.axaml` | 25 | `⌖` (TextBlock) | épingle | Path "pin" monochrome |
| `ViewModels/NouveauEmailViewModel.cs` | 144 | `✉ Email vierge` | modèle vierge | (libellé, pas d'icône) |
| `ViewModels/NouveauEmailViewModel.cs` | 888 | `📷 {Nom}` | pièce jointe photo | Path "image" |
| `ViewModels/RapportChampsViewModel.cs` | 606 | `✓` / `!` (Indicateur) | état rubrique | Path check/alert monochrome |
| `Views/AgendaEquipeView.axaml` | (filtre Équipe) | icône groupe colorée | filtre équipe | Path "group" monochrome |
| `Views/InterventionDocumentsView.axaml` | 17 | `← Retour` | retour | Path "arrow-left" |
Les contrôles `CallButton.axaml` et `EmailButton.axaml` utilisent **déjà** des `Path` monochromes `Fill="{DynamicResource TextPrimaryBrush}"` : conformes, à conserver comme référence visuelle.
<a name="svg"></a>
## 9. Propositions de SVG monochrome (famille unique)
Famille retenue : **pictogrammes "Line/Outline" sobres** (traits fins, style Lucide/Material Outlined), **un seul style** (trait 1.5–2, pas de remplissage multicolore), couleur portée par `Foreground="{DynamicResource PrimaryBrush}"` ou `Fill="{DynamicResource …}"` — **jamais codée en dur**.
| Pictogramme | Usage | Path (24×24 viewBox, données type Material) |
|---|---|---|
| Téléphone | appel | `M6.62,10.79c1.44,2.83 …` (déjà dans CallButton) |
| Email | courriel | `M20,4H4C2.9,4 …` (déjà dans EmailButton) |
| Itinéraire/GPS | itinéraire | navigation arrow / pin |
| Intervention (marteau/outil) | ouvrir intervention | wrench/hammer |
| Rapport | rapport | document + lignes |
| Note | note | edit/pencil |
| Image | photo | image frame |
| Document | document | file |
| Collaborateurs | personne | person outline |
| Équipe | groupe | people/group outline |
| Calendrier | agenda | calendar |
| Paramètres | paramètres | gear |
| Synchronisation | sync | refresh/circular arrows |
| Retour | retour | arrow-left |
| Ajouter | ajouter | plus |
| Télécharger | télécharger | download arrow |
| Épingle | carte | location pin |
Chaque pictogramme sera matérialisé par un `Path` Avalonia (monochrome) ou un `StreamGeometry`, avec `Fill`/`Foreground` en `DynamicResource` pour rester compatible clair/sombre.
## 8bis. Couleurs codées en dur à remplacer par `DynamicResource`
| Fichier | Ligne | Valeur | Remplacer par |
|---|---|---|---|
| `Views/RendezVousDetailView.axaml` | 143 | `#E53935` / `White` (bouton Annuler) | `DangerBrush` / `OnDanger` |
| `Views/RapportChampsView.axaml` | 108/115/122 | `#22c55e` / `#f59e0b` / `#ef4444` | `SuccessBrush` / `WarningBrush` / `DangerBrush` |
| `Views/RapportChampsView.axaml` | 253 | `#e5e7eb` | `SurfaceTertiaryBrush` |
| `Views/RapportChampsView.axaml` | 138,233,258 | `ThemeForegroundLowBrush` (**clé INEXISTANTE** dans DesignTokens) | `TextMutedBrush` |
| 10 fichiers (badges héros) | — | `#26FFFFFF` | token `OnPrimaryContainer`/`HeroChip` |
| `Controls/ImageEditorControl.axaml` | — | `#E53935…`, `#FFFFFF` | ressources centralisées |
| `Views/*Converter.cs` (`NavItemTextBrushConverter`, `SyncStateToBrushConverter`) | — | `Brushes.Gray` fallback | ressource `TextMutedBrush` |
## 8ter. Correctifs appliqués (lot 1 — compilation Android 0 erreur vérifiée)
| # | Correctif | Fichiers modifiés | Vérification |
|---|---|---|---|
| 9/10 | `MapGet("/")` → `MapGet("")` (route sans slash final) | `src/Omika.Api/Endpoints/ModeleDocumentEndpoints.cs` | Omika.Api 0 erreur |
| 6A | `ResolveCreationUserAsync` accepte PK numérique + GUID (Omika & Batik) | `OmikaInterventionRepository.cs`, `BatikInterventionRepository.cs` | Omika.Data 0 erreur |
| 11/12 | Launchers natifs Android `ACTION_DIAL`/`ACTION_SENDTO` + hook `PlatformLaunchers` | `AndroidPhoneLauncher.cs`, `AndroidEmailLauncher.cs` (nouveaux), `PlatformLaunchers.cs` (nouveau), `AppServices.cs`, `Application.cs` | Android 0 erreur |
| 1 | User-Agent OSM explicite `OmikaMobile/1.0 (+https://www.applilogik.fr)` | `Controls/LieuMap.axaml.cs` | Omika.App 0 erreur |
| 3 | Boutons d'en-tête translucides `heroAction` + icônes Path monochrome | `Views/RendezVousDetailView.axaml`, `Styles/Controls.axaml` | Omika.App 0 erreur |
| 8 | Suppression titre redondant + `WrapPanel` (boutons wrappables) | `Views/InterventionResourcesView.axaml` | Omika.App 0 erreur |
| 14 | Messages utilisateur propres + log technique | `NouveauEmailViewModel.cs`, `RapportChampsViewModel.cs` | Omika.App 0 erreur |
| 5 | Logs temporaires de traçage RDV→intervention | `RendezVousDetailViewModel.cs`, `InterventionLocalService.cs` | Omika.App 0 erreur |
### Lot 2 — Icônes monochromes + couleurs + responsive (compilation 0 erreur)
| # | Correctif | Fichiers |
|---|---|---|
| 2 | Bug clé `ThemeForegroundLowBrush` (inexistante) → `TextMutedBrush` | `RapportChampsView.axaml`, `NouveauEmailView.axaml` |
| 2 | Couleurs sémantiques en dur → `Success/Warning/Danger/SurfaceTertiaryBrush` | `RapportChampsView.axaml` |
| 2 | Icônes de tuiles emoji → Path monochrome `PrimaryTextColorBrush` (✎ ▧ ✉ 🛡 📄) | `InterventionDetailView.axaml` |
| 4 | Icône groupe `👥` → Path « people » monochrome | `AgendaView.axaml` |
| 2 | Icône calendrier `📅` → Path « calendar » monochrome | `Controls/DateField.axaml` |
| 2 | Tokens `HeroChipBrush` / `HeroBorderBrush` ajoutés | `DesignTokens.axaml` |
| 13 | Email non tronqué (`TextWrapping`) fiche collaborateur + liste collaborateurs | `CollaborateurDetailView.axaml`, `CollaborateursView.axaml` |
> Reste (lots suivants) : `#26FFFFFF` → `HeroChipBrush` sur les badges (cosmétique), autres emoji (`↻ ⌖ ⚠ ⓘ 🗑 ✎ 🖼 🔒 ★/☆ ✉ + → ←`), responsive `FilterGrid` agenda équipe, et les correctifs de données `#5/#6B/#7` (résolution intervention + filtre catégories `T.DOC.*`).
> Build Android : `dotnet build src\Omika.App.Android\Omika.App.Android.csproj -c Debug -m:1 -nodeReuse:false` → **0 erreur** (SDK 10.0.302 via `global.json`). Les builds nécessitent `-m:1 -nodeReuse:false` (bug de nœud MSBuild parallèle dans l'environnement) et un accès élargi pour la télémétrie Avalonia (écrit hors workspace).
## 9. État des sous-analyses
- ✅ Route API (#9/#10) : **confirmé** — slash final manquant (`/modele-mails` vs `/modele-mails/`).
- ✅ Icônes + responsive (#2/#3/#4/#8/#13) : **confirmé** — inventaire ~40 emoji/Unicode, aucune couleur codée en dur dans les Path, liste des couleurs en dur à remplacer (dont clé manquante `ThemeForegroundLowBrush`).
- ✅ Trace RDV→intervention (#5/#6B/#7) : chaîne d'identité vérifiée de bout en bout (correcte) ; déclencheur exact = niveau données, à confirmer par les logs temporaires prévus en correction.