Premiere commit - Omika
This commit is contained in:
@@ -0,0 +1,445 @@
|
||||
# Omika — Analyse technique de l'application (mobile offline-first + desktop)
|
||||
|
||||
> Source : `D:\Avalonia\Omika` — projets `src/Omika.App`, `src/Omika.App.Local`,
|
||||
> `src/Omika.App.Desktop`, `src/Omika.App.Android`, `src/Omika.App.iOS`.
|
||||
> Analyse factuelle destinée à la rédaction d'un site web marketing pour vendre
|
||||
> le logiciel de gestion d'interventions (field-service management).
|
||||
|
||||
---
|
||||
|
||||
## Vue d'ensemble de la pile technique
|
||||
|
||||
| Élément | Détail | Preuve |
|
||||
|---|---|---|
|
||||
| Langage / runtime | C# sur **.NET 10** (`net10.0`) | `src/Omika.App/Omika.App.csproj` |
|
||||
| UI | **Avalonia UI** (thème Fluent, police Inter), rendu identique sur desktop + mobile | `Omika.App.csproj`, `App.axaml` |
|
||||
| MVVM | **CommunityToolkit.Mvvm** (`[ObservableProperty]`, `[RelayCommand]`) | `ViewModelBase.cs` |
|
||||
| Base locale | **SQLite** (`Microsoft.Data.Sqlite`) — fichier `omika.db` | `LocalDatabase.cs` |
|
||||
| Cartes | **Mapsui.Avalonia12** (vue lieu/intervention) | `Omika.App.csproj` |
|
||||
| QR | **QRCoder** (génération) + **ZXing.Net** (scan Android) | `Omika.App.csproj`, `Omika.App.Android.csproj` |
|
||||
| Images | **SkiaSharp** (optimisation poids photos) + **MetadataExtractor** (date EXIF) | `Omika.App.csproj` |
|
||||
| Hôtes | Desktop (`Avalonia.Desktop`, `WinExe`), Android (`net10.0-android`, min SDK 23, `com.applilogik.omika.app`), iOS (`net10.0-ios`, min 14.0) | les 3 `.csproj` hôtes |
|
||||
| Injection | **Pas de conteneur DI** — composition racine manuelle unique | `AppServices.cs` |
|
||||
|
||||
Point notable : l'application est un **monolithe UI partagé**. `Omika.App` contient
|
||||
toute la logique d'interface (Vues + ViewModels + Navigation + Services UI) ; les
|
||||
trois hôtes ne contiennent que le strict minimum natif (lancement + implémentations
|
||||
plateforme : caméra, QR, téléphone, email, carte, secure storage, connectivité).
|
||||
|
||||
---
|
||||
|
||||
## 1. Les écrans de l'application
|
||||
|
||||
Toutes les vues sont dans `src/Omika.App/Views/*.axaml`, les ViewModels dans
|
||||
`src/Omika.App/ViewModels/*.cs`, et le routage page → ViewModel dans
|
||||
`src/Omika.App/Navigation/PageViewModelFactory.cs` (enum `AppPage`).
|
||||
|
||||
### 1.1 Activation / licence
|
||||
- **`LicenseActivationView.axaml` + `LicenseActivationViewModel.cs`** — écran
|
||||
d'inscription au premier démarrage. Deux entrées vers la même procédure : saisie
|
||||
manuelle d'un code d'inscription ou **scan d'un QR** (avec aperçu caméra en direct,
|
||||
annulable). Affiche aussi les états « Connexion requise pour vérifier votre
|
||||
licence » et « Licence expirée ou révoquée ». Aucune URL codée en dur.
|
||||
|
||||
### 1.2 Accueil / Shell
|
||||
- **`ShellView.axaml` + `ShellViewModel.cs`** — coquille applicative : rail latéral
|
||||
(desktop/tablette), **barre de navigation basse mobile** (5 accès directs max,
|
||||
le reste dans un menu « Plus »), topbar avec titre, carte profil, **bouton
|
||||
flottant de synchronisation avec badge du nombre d'opérations en attente**.
|
||||
Mode « immersif » plein écran pour l'éditeur d'image.
|
||||
|
||||
### 1.3 Agenda / Planning / Rendez-vous
|
||||
- **`AgendaView.axaml` + `AgendaViewModel.cs`** — agenda personnel : vues
|
||||
**Jour / Semaine / Mois**, bandeau 7 jours avec compteurs, sélecteur de date.
|
||||
Lecture 100 % SQLite locale (aucun appel API au chargement).
|
||||
- **`AgendaEquipeView.axaml` + `AgendaEquipeViewModel.cs`** — agenda d'équipe :
|
||||
filtres par équipe/collaborateur, modes liste/calendrier.
|
||||
- **`RendezVousDetailView.axaml` + `RendezVousDetailViewModel.cs`** — fiche
|
||||
rendez-vous : contacts, **saisie de temps inline** (brouillons), **annulation**
|
||||
(origine + motif + note), **finalisation** (« Fait » après validation des temps),
|
||||
accès au **compte rendu**.
|
||||
|
||||
### 1.4 Interventions
|
||||
- **`InterventionsView.axaml` + `InterventionsViewModel.cs`** — recherche
|
||||
d'interventions (filtres référence/client/site/collaborateur/état/type/relation
|
||||
aux RDV) avec **onglets « Récentes » (dernière recherche persistée) et
|
||||
« Épinglées » (favoris)**. Recherche à la demande (API), affichage offline du cache.
|
||||
- **`InterventionDetailView.axaml` + `InterventionDetailViewModel.cs`** — fiche
|
||||
intervention : hero (référence, client, site, dossier), **contacts, documents/
|
||||
images, notes internes, courriels, brouillons de rapport, notes administratives**,
|
||||
actions **Modifier / Archiver** (offline-first via Outbox).
|
||||
- **`InterventionEditView.axaml` + `InterventionEditViewModel.cs`** — formulaire
|
||||
partagé de création/édition d'intervention (offline-first).
|
||||
- Sous-écrans d'une intervention :
|
||||
- `InterventionContactsView` + `ContactDetailView` — contacts liés + fiche contact.
|
||||
- `InterventionDocumentsView` + `PhotoViewerView` + `ImageEditorView` — documents/
|
||||
photos, carrousel, **annotation d'image plein écran** (édition + signature).
|
||||
- `InterventionNotesView` + `InterventionNotesAdministrativesView` — notes internes
|
||||
et administratives (droit `A.NOTE.ADMIN`).
|
||||
- `InterventionEmailsView` + `NouveauEmailView` — courriels d'intervention +
|
||||
composition d'email (depuis modèle, rapport ou contact).
|
||||
- `RapportChampsView` — saisie des champs/rubriques d'un modèle de rapport.
|
||||
- `InterventionResourcesView` — vue regroupant notes / images / documents.
|
||||
|
||||
### 1.5 Dossiers / Clients / Sites
|
||||
- **`DossiersView` + `DossierDetailView`** — liste (recherche + **épinglage local**)
|
||||
et fiche dossier.
|
||||
- **`ClientsView` + `ClientDetailView`** — liste (recherche + épinglage) et fiche client.
|
||||
- **`SitesView` + `SiteDetailView`** — liste (recherche + épinglage) et fiche site,
|
||||
avec **carte géographique** (contrôle `Controls/LieuMap.axaml`, Mapsui) et
|
||||
lancement d'itinéraire.
|
||||
|
||||
### 1.6 Équipe / Collaborateurs
|
||||
- **`EquipesView.axaml` + `EquipesViewModel.cs`** — menu « Équipe » : liste des
|
||||
équipes de l'utilisateur.
|
||||
- **`EquipeDetailView` + `EquipeDetailViewModel.cs`** — membres d'une équipe.
|
||||
- **`CollaborateurDetailView` + `CollaborateurDetailViewModel.cs`** — fiche
|
||||
collaborateur (dont **attribution/révocation de licence mobile**).
|
||||
- `CollaborateursView.axaml` / `CollaborateursViewModel.cs` — liste de collaborateurs
|
||||
existante mais **non câblée** dans la factory de pages (voir §8).
|
||||
|
||||
### 1.7 Suivi des temps / Compte rendu
|
||||
- **`SuiviTempsCreateView.axaml` + `SuiviTempsCreateViewModel.cs`** — création d'une
|
||||
saisie de temps (offline-first : brouillons SQLite + Outbox). Prise en compte de la
|
||||
**pause déjeuner** (préférence locale par utilisateur).
|
||||
- **`CompteRenduCreateView.axaml` + `CompteRenduCreateViewModel.cs`** — saisie du
|
||||
compte rendu d'un rendez-vous (individuel ou groupe/journée, anti-doublon).
|
||||
|
||||
### 1.8 Synchronisation / Paramètres
|
||||
- **`SynchronisationView.axaml` + `SynchronisationViewModel.cs`** — file de
|
||||
synchronisation : liste des opérations Outbox en attente (temps, annulations,
|
||||
photos/documents, interventions…) + lancement manuel.
|
||||
- **`ParametresView.axaml` + `ParametresViewModel.cs`** — réglages : **thème
|
||||
clair/sombre/système**, **couleur principale** (presets + curseurs RVB), section
|
||||
**Compte/Licence** (n° masqué, validité, expiration, société), **limite de poids
|
||||
des photos** (politique client, lecture seule), **horaires de pause déjeuner**,
|
||||
**maintenance des données** (purge cache documents, suppression données,
|
||||
réinitialisation complète), **email au support** (export des logs en pièce jointe),
|
||||
déconnexion, version.
|
||||
|
||||
### 1.9 Composants réutilisables (`Controls/`)
|
||||
Cartes (`ClientCard`, `SiteCard`, `ContactCard`, `DossierCard`, `InterventionCard`,
|
||||
`RendezVousCard`, `EquipeCard`), boutons d'action (`CallButton`, `EmailButton`,
|
||||
`OpenButton`), champs (`DateField`, `TimeField`, `SignaturePad` pour signature,
|
||||
`ImageEditorControl`), `LieuMap`, aperçu HTML d'email
|
||||
(`EmailHtmlPreviewControl`), templates d'édition de champs
|
||||
(`Views/ChampEditionTemplates.axaml`).
|
||||
|
||||
---
|
||||
|
||||
## 2. Navigation et MVVM
|
||||
|
||||
### ViewLocator (`src/Omika.App/ViewLocator.cs`)
|
||||
`IDataTemplate` standard d'Avalonia : résout la vue à partir du type du ViewModel en
|
||||
remplaçant le suffixe `ViewModel` par `View` (réflexion). Cas particulier pour
|
||||
`PlaceholderViewModel` → `PlaceholderPage`. Retour `Match` sur `ViewModelBase`.
|
||||
|
||||
### NavigationService (`Navigation/NavigationService.cs`, `INavigationService.cs`)
|
||||
- Liste d'items `NavigationItem` : Agenda (Home), Interventions, Dossiers, Équipe,
|
||||
Clients, Sites, Paramètres. Les 5 premiers sont « primaires » sur mobile, les
|
||||
autres passent par le menu « Plus ».
|
||||
- **Pile de retour** (`_backStack` de `NavigationEntry`) pour `GoBack`.
|
||||
- Événements `PageChanged` / `ParameterChanged` (le Shell écoute pour recharger le VM).
|
||||
- **Blocage en amont par droits** via `PermissionGuard.IsPageAllowed` — impossible de
|
||||
naviguer vers une page non autorisée (pas seulement masquée).
|
||||
- Le changement de page est **purement visuel** : aucun appel API.
|
||||
|
||||
### ShellViewModel (`ViewModels/ShellViewModel.cs`)
|
||||
Orchestre : page courante + ViewModel courant, titre, état de synchronisation
|
||||
(`SyncUiState`), identité utilisateur (nom/initiales), mode immersif, **badge de
|
||||
synchronisation en attente** (`PendingSyncCount` via Outbox), commande
|
||||
`SyncNowCommand` (avec verrou anti-double-clic), filtrage du menu par droits,
|
||||
`RefreshCurrentPage()` après sync.
|
||||
|
||||
### RootViewModel (`ViewModels/RootViewModel.cs`)
|
||||
Décide au démarrage entre **écran d'activation** et **Shell**. Règles :
|
||||
- rejet terminal serveur (licence inactive/expirée/révoquée/device mismatch) → activation avec QR ;
|
||||
- session valide (token valide, renouvelé, ou période offline autorisée) → Shell ;
|
||||
- session présente mais hors grace ET serveur injoignable → activation sans QR (« Connexion requise ») ;
|
||||
- aucune session → inscription QR.
|
||||
|
||||
### PageViewModelFactory (`Navigation/PageViewModelFactory.cs`)
|
||||
Fabrique les ViewModels métier par page, avec **routage des paramètres** (Guid,
|
||||
`AgendaItem`, `DetailNavigation`, `InterventionEditNavigation`, `EmailEditionNavigation`,
|
||||
`NouveauEmailRapportNavigation`, `NouveauEmailContactNavigation`, `ImageEditorNavigation`…).
|
||||
Réutilise les instances Agenda / Agenda-équipe pour conserver date/filtres/mode au
|
||||
retour. Câble `SetShell` pour résoudre la dépendance circulaire Shell ↔ Factory.
|
||||
|
||||
### CommunityToolkit.Mvvm (`ViewModels/ViewModelBase.cs`)
|
||||
`ObservableObject` de base, `[ObservableProperty]` (code généré), `[RelayCommand]`,
|
||||
helpers `LogError`/`TrackFeature` (télémétrie d'usage/erreurs best-effort).
|
||||
|
||||
### Droits (`Services/PermissionService.cs`, `Navigation/PermissionGuard.cs`)
|
||||
Droits centralisés par clés canoniques (`MobilePermissionKeys` : `VoirClients`,
|
||||
`VoirSites`, `VoirDossiers`, `PeutEnvoyerEmail`, `PeutCreerRapport`,
|
||||
`PeutVoirAgendaEquipe`, `LicenceMobile`, `A.TEMP.COLB`…). Le menu et les pages sont
|
||||
filtrés/recalculés dès que le bootstrap (droits serveur) est rafraîchi.
|
||||
|
||||
---
|
||||
|
||||
## 3. La couche offline-first
|
||||
|
||||
### 3.1 Base SQLite (`Sqlite/LocalDatabase.cs`, `Sqlite/SqliteSchema.cs`)
|
||||
- Fichier **`omika.db`** dans `%LocalAppData%\Omika` (desktop) / dossier app (mobile).
|
||||
- Schéma versionné (`CurrentVersion = 1`), créé **idempotent** (`CREATE TABLE IF NOT
|
||||
EXISTS`) + **migrations de colonnes** via `PRAGMA table_info` (ajout de colonnes
|
||||
sans détruire les lignes).
|
||||
- Séparation stricte :
|
||||
- **Tables métier** (remplacées par snapshot) : `Client`, `Personne`, `Site`,
|
||||
`Intervention`, `Dossier`, `RendezVous`, `Collaborateur`, `DocumentMetadata`,
|
||||
`SuiviTemps` — chacune = `Id` (PK GUID) + `PayloadJson` (DTO complet) + colonnes
|
||||
de référence typées (`ClientId`, `SiteId`, `DossierId`, `InterventionId`…).
|
||||
- **Tables techniques** (jamais purgées) : `Outbox`, `IdentityMapping`, `SyncState`,
|
||||
`DocumentCache`, `LocalBootstrap`, `MobileEnrollment`, `UiPreferences`,
|
||||
`PreferencesUtilisateur`, `SuiviTempsDraft`, `RendezVousTempsValidation`,
|
||||
`RendezVousDraftReference`, `InterventionCache`, `DossierLocalState`,
|
||||
`DossierInterventionCache`, `ContextInterventionCache(State)`, `RapportBrouillon`,
|
||||
`NoteInterne`, + caches de dernière recherche (`DossierLastSearch`,
|
||||
`ClientLastSearch`, `SiteLastSearch`).
|
||||
- `LocalRepository<TEntity>` (`Repositories/LocalRepository.cs`) : repository
|
||||
générique `Upsert/GetById/GetAll/Delete/Clear/Count` sur ce schéma JSON + colonnes
|
||||
de référence (mapper `ReferenceColumnMapper`).
|
||||
|
||||
### 3.2 L'Outbox (`Services/OutboxService.cs`, `Models/OutboxOperation.cs`, `Models/OutboxState.cs`, `Repositories/OutboxRepository.cs`)
|
||||
- Chaque mutation métier locale produit une **opération Outbox** (idempotente).
|
||||
- `OutboxOperation` porte : `OperationId` (GUID de transaction **immuable**, créé une
|
||||
fois et conservé à travers les retries), `EntityType`, `OperationType`
|
||||
(CREATE/UPDATE/DELETE/ARCHIVE), `RequestedEntityGuid` (GUID local temporaire),
|
||||
`CanonicalEntityGuid` (GUID serveur après réconciliation A→B), `Payload` (JSON),
|
||||
`DependencyEntityType`/`DependencyRequestedEntityGuid` (ordonnancement), `State`,
|
||||
`RetryCount`, `LastError`.
|
||||
- `OutboxState` : `Pending → Sending → Accepted → WaitingResult → Succeeded | Rejected
|
||||
| StatusNotAvailable` (7 états, récupérables après crash).
|
||||
|
||||
### 3.3 Le SyncEngine (`Services/SyncEngine.cs`) — cycle complet
|
||||
Protégé par `SemaphoreSlim` (une seule sync à la fois). Cycle :
|
||||
1. **Vérifier la connectivité** (`INetworkConnectivity`) — hors ligne → retour
|
||||
« Hors ligne. » **sans aucune tentative HTTP**.
|
||||
2. **Envoyer les opérations prêtes** (`GetReadyAsync`) en différant celles dont la
|
||||
dépendance (ex. note → intervention créée) est encore active ; marquage `Sending`
|
||||
avant envoi ; `DeviceId` réel transmis (cohérence serveur token/opération).
|
||||
Erreur réseau → retour `Pending` + `RetryCount++`.
|
||||
**Documents** : upload **synchrone** multipart (jamais de Base64) ; résultat
|
||||
connu immédiatement, sans polling.
|
||||
3. **Interroger les statuts** (`GET /api/sync/status/{OperationId}`) : `Success`
|
||||
(réconciliation A→B), `Rejected` (définitif, conservé pour diagnostic),
|
||||
`StatusNotAvailable` → **ré-émission bornée à 5** puis anomalie visible (jamais
|
||||
convertie artificiellement en succès).
|
||||
4. **Appliquer le snapshot atomiquement** (voir ci-dessous).
|
||||
Résultat structuré `SyncCycleResult` (Executed, SnapshotApplied, OperationsSent/
|
||||
Succeeded/Rejected/Anomaly, FailureStage, ExceptionType, Message).
|
||||
|
||||
### 3.4 SnapshotApplier (`Services/SnapshotApplier.cs`) — transaction atomique
|
||||
- Le snapshot est **entièrement téléchargé et validé AVANT** toute modification.
|
||||
- **Transaction SQLite unique** : remplace `Personne`, `Intervention`, `RendezVous`
|
||||
(DELETE + INSERT), **préserve les rendez-vous ayant une mutation locale en
|
||||
attente** (annulation non confirmée), puis purge les tables « à la demande »
|
||||
(Client, Site, Dossier, Collaborateur, DocumentMetadata, SuiviTemps).
|
||||
- En cas d'erreur : **rollback** — l'ancienne base métier reste intacte.
|
||||
- Les tables techniques (Outbox, IdentityMapping, DocumentCache, LocalBootstrap,
|
||||
favoris, brouillons…) **ne sont jamais touchées**.
|
||||
|
||||
### 3.5 Réconciliation d'identités (`Services/MobileIdentityReconciliationService.cs`)
|
||||
GUID mobile temporaire (A) → GUID canonique serveur (B). Idempotente et
|
||||
**transactionnelle** : enregistre le mapping (`IdentityMapping`), réécrit la PK
|
||||
logique, réconcilie le cache document, les **références enfants** (foreign keys
|
||||
locales), l'Outbox en attente, et **réécrit les payloads Outbox** via des
|
||||
`IOutboxPayloadRewriter` typés (`PayloadRewriters/ClientPayloadRewriter.cs`,
|
||||
`SitePayloadRewriter.cs`, `DossierPayloadRewriter.cs`, `InterventionPayloadRewriter.cs`,
|
||||
`RendezVousPayloadRewriter.cs`, `NoteInternePayloadRewriter.cs`,
|
||||
`NoteAdministrativePayloadRewriter`).
|
||||
|
||||
### 3.6 Cache document (`Services/DocumentCacheService.cs`, `Repositories/DocumentCacheRepository.cs`)
|
||||
- Les **binaires sont dans des fichiers locaux** (`%LocalAppData%\Omika\documents`) ;
|
||||
SQLite ne contient que les **métadonnées** (`DocumentCache` : LocalPath, NomFichier,
|
||||
MimeType, Size, State).
|
||||
- Téléchargement **à la demande**, survit au snapshot, purge possible depuis les
|
||||
Paramètres.
|
||||
|
||||
### 3.7 Détection de connectivité
|
||||
- Repli portable (`Services/NetworkConnectivity.cs`) : interface `NetworkInterface`
|
||||
active non loopback.
|
||||
- **Android** (`AndroidNetworkConnectivity.cs`, branché dans `Application.cs`) :
|
||||
`ConnectivityManager` — évite les faux positifs « en ligne » (mode avion, hotspot
|
||||
sans Internet).
|
||||
|
||||
---
|
||||
|
||||
## 4. Session et activation
|
||||
|
||||
### 4.1 MobileSessionService (`Services/MobileSessionService.cs`)
|
||||
- **Token stocké en secure storage dédié**, jamais en SQLite en clair
|
||||
(`ISecureTokenStore`) ; le bootstrap (profil/droits/licence) est, lui, en SQLite
|
||||
(`LocalBootstrapState`, singleton).
|
||||
- **Période offline autorisée** : une session déjà validée reste accessible hors
|
||||
connexion pendant `OfflineGraceDays` jours, calculés **strictement** depuis la
|
||||
dernière confirmation serveur (`LastLicenseValidationUtc`), jamais depuis une
|
||||
écriture locale.
|
||||
- **Renouvellement single-flight** (`SemaphoreSlim`) : un seul `license/validate`
|
||||
à la fois ; mémorisation d'un rejet terminal pour ne pas marteler le serveur.
|
||||
- `ClearSessionAsync` supprime **seulement** token + bootstrap — **jamais les données
|
||||
métier** (SQLite, cache, Documents, Outbox).
|
||||
- `RefreshBootstrapAsync` recharge référentiels/équipes/collaborateurs/droits sans
|
||||
repousser la grace.
|
||||
|
||||
### 4.2 Flux d'activation côté mobile (`Services/MobileEnrollmentService.cs`, `Services/LicenseEnrollmentClient.cs`)
|
||||
1. Lecture du **QR d'inscription** (`EnrollmentQrCodec.Decode`, **vérification de
|
||||
signature par `kid`** via `Security/EnrollmentVerificationKey.cs`).
|
||||
2. **Validation stricte HTTPS non-local** de l'URL du service de licence
|
||||
(`EndpointValidator`).
|
||||
3. Appel `POST /api/license/activate` avec `CodeClient`, `NumeroLicence`, `DeviceId`
|
||||
+ snapshot d'appareil.
|
||||
4. Validation HTTPS de l'URL d'API Omika retournée par le serveur.
|
||||
5. Persistance de la configuration (`MobileEnrollment` / `MobileEnrollmentStore`).
|
||||
6. `ApplyEnrollmentSessionAsync` : token en secure storage + bootstrap en SQLite.
|
||||
Aucune URL codée en dur — tout provient du QR puis du serveur de licence.
|
||||
|
||||
### 4.3 Licence de développement (Debug uniquement)
|
||||
- `Services/DevelopmentLicenseProvider.cs` + `DevelopmentLicenseConfigurationLoader.cs` :
|
||||
licence locale valide chargée depuis un fichier **non sensible**
|
||||
`development-license.json`, **compilée sous `#if DEBUG` uniquement** — en Release,
|
||||
le provider retourne toujours `null` (le serveur reste l'autorité).
|
||||
- Permet de tester navigation/SQLite/snapshot/offline sans base de licences HFSQL.
|
||||
- `EnsureDevelopmentSessionAsync` obtient un **vrai token signé** via
|
||||
`POST /api/dev/session` pour le flux Desktop de développement.
|
||||
|
||||
### 4.4 Secure storage par plateforme
|
||||
- **Android** : `AndroidSecureTokenStore` — **Keystore AES-GCM**, clé non exportée,
|
||||
supprimée à la désinstallation (→ réinstallation = écran QR). `LegacyTokenMigrator`
|
||||
migre l'ancien `session.token` en clair vers le blob chiffré.
|
||||
- **iOS** : `IosSecureTokenStore` — **Keychain** ; `IosTokenBootstrap` gère le marker
|
||||
d'installation (purge Keychain à la réinstallation) + migration.
|
||||
- **Desktop/fallback** : `PlatformSecureTokenStore` (fichier, développement).
|
||||
|
||||
---
|
||||
|
||||
## 5. Le client HTTP (`Services/MobileApiClient.cs`, ~1135 lignes)
|
||||
|
||||
- Client HTTP typé vers l'API mobile Omika, réutilisant les **contrats backend
|
||||
partagés** (`Omika.Core.Contracts.Mobile.*`).
|
||||
- `BaseUrl` : **`null` tant que l'appareil n'est pas inscrit**, puis fixée depuis la
|
||||
configuration d'inscription (jamais de localhost/fallback codé en dur).
|
||||
`RequireBaseUrl()` lève si aucune URL.
|
||||
- **Bearer token** : `AccessToken` posé via `Authorization: Bearer` dans
|
||||
`CreateAuthenticatedRequest` (jamais loggé).
|
||||
- ~50 endpoints : licence (`activate`/`validate`/`dev/session`), bootstrap, sync
|
||||
(`operations`, `status/{id}`, `snapshot`), recherche intervention/dossier/client/
|
||||
site/collaborateur, fiches par GUID, documents (download/upload), notes, emails,
|
||||
modèles (mail/rapport), génération (`email`/`rapport`), compte rendu, suivi temps.
|
||||
- **Deux `DelegatingHandler`** branchés dans `AppServices` :
|
||||
- `TraceIdPropagationHandler` — propagation TraceId/SessionId (corrélation).
|
||||
- `SessionRefreshHandler` — **renouvellement 401 single-flight** (réémission
|
||||
transparente), anti-récursion via option `SkipAuthRefresh` sur les appels licence.
|
||||
|
||||
---
|
||||
|
||||
## 6. Plateformes
|
||||
|
||||
### Desktop (`src/Omika.App.Desktop/Program.cs`)
|
||||
- `WinExe`, `[STAThread]`, `IClassicDesktopStyleApplicationLifetime`.
|
||||
- Capture globale des exceptions non gérées → `crash.log` + télémétrie
|
||||
(`ExceptionTelemetry`). Rendu Windows/macOS/Linux via `Avalonia.Desktop`.
|
||||
|
||||
### Android (`src/Omika.App.Android/`)
|
||||
- `MainActivity` (`AvaloniaMainActivity`) : demande de **permission caméra** runtime,
|
||||
**sélecteur de fichiers SAF** (`ACTION_OPEN_DOCUMENT`, multi-sélection), **capture
|
||||
photo** (`ACTION_IMAGE_CAPTURE` + FileProvider), gestion des résultats, handlers
|
||||
d'exceptions natives (`AndroidEnvironment.UnhandledExceptionRaiser`).
|
||||
- `Application.cs` (`CustomizeAppBuilder`) : installe **tous** les services natifs
|
||||
AVANT l'init Avalonia — `AndroidEnrollmentQrScanner` (Camera2 + ZXing), launchers
|
||||
`tel:`/`mailto:`/`geo:`, `AndroidFilePicker`, `AndroidPhotoCapture`,
|
||||
`AndroidFileLauncher`, `AndroidSecureTokenStore` (Keystore), `AndroidDeviceInfoProvider`,
|
||||
`AndroidNetworkConnectivity` (ConnectivityManager), migration du token.
|
||||
|
||||
### iOS (`src/Omika.App.iOS/`)
|
||||
- `AppDelegate.cs` (`CustomizeAppBuilder`) : `IosEnrollmentQrScanner`, launchers
|
||||
`tel:`/`mailto:`/Apple Maps, `IosFilePicker` (UIDocumentPicker + PHPicker photos),
|
||||
`IosPhotoCapture` (UIImagePickerController), `IosFileLauncher`
|
||||
(UIDocumentInteractionController), `IosSecureTokenStore` (Keychain) +
|
||||
`IosTokenBootstrap` (marker d'installation), `IosDeviceInfoProvider`.
|
||||
- `Main.cs` : entrée `UIApplication.Main`.
|
||||
|
||||
### Points spécifiques transverse
|
||||
- **QR code** : génération (`QrCodeToImageConverter.cs`) + scan natif (aperçu caméra
|
||||
en direct via `IQrScannerPreviewSink` / `LicenseActivationViewModel.OnFrame`).
|
||||
- **Caméra/photo** : capture native + **optimisation du poids** (`SkiaImageOptimizer`)
|
||||
+ **date EXIF** (`MetadataDocumentDateResolver`) avant enregistrement.
|
||||
- **Téléphone/email/carte** : abstractions `IPhoneLauncher`/`IEmailLauncher`/
|
||||
`IMapLauncher` avec implémentations natives par plateforme.
|
||||
- **Secure storage** : Keystore / Keychain / fichier (voir §4.4).
|
||||
|
||||
---
|
||||
|
||||
## 7. Extraction marketing (fonctionnalités concrètes, avec preuve technique)
|
||||
|
||||
| Argument marketing | Réalité technique (fichier) |
|
||||
|---|---|
|
||||
| **100 % hors-ligne** | Agenda et fiches lisent SQLite locale sans appel API (`AgendaViewModel.cs` « Ne déclenche aucun appel API », `InterventionDetailViewModel.cs`). Les mutations sont écrites localement puis envoyées plus tard (Outbox). |
|
||||
| **Synchronisation automatique** | Sync au lancement (`App.axaml.cs` `StartInitialSyncAsync`) + après inscription + manuelle (`ShellViewModel.SyncNowAsync`). |
|
||||
| **Aucune perte de données** | Snapshot atomique avec rollback (`SnapshotApplier.cs`), préservation des RDV en attente, réconciliation d'identités idempotente (`MobileIdentityReconciliationService.cs`). |
|
||||
| **File d'attente hors-ligne fiable** | Outbox avec états récupérables après crash, ré-émission bornée, jamais de faux succès (`SyncEngine.cs`, `OutboxState.cs`). |
|
||||
| **Multi-plateforme, une seule base de code** | ~165 fichiers UI partagés (`Omika.App`), 3 hôtes fins (Desktop/Android/iOS). |
|
||||
| **Interface native** | Avalonia Fluent + Inter, adaptative (rail desktop vs barre basse mobile, `ShellView.axaml`), thème clair/sombre/système + couleur personnalisable (`ThemeService`, `ParametresViewModel`). |
|
||||
| **Activation sécurisée par QR** | QR signé (`kid`), validation HTTPS stricte, token en Keystore/Keychain (`MobileEnrollmentService`, `AndroidSecureTokenStore`, `IosSecureTokenStore`). |
|
||||
| **Photos/documents optimisés** | Compression poids (SkiaSharp), date réelle EXIF, cache fichier, upload multipart sans Base64 (`DocumentLocalService`, `SkiaImageOptimizer`, `MetadataDocumentDateResolver`). |
|
||||
| **Signature + annotation d'image** | `Controls/SignaturePad.axaml`, `ImageEditorView`/`ImageEditorControl` + `SignatureStrokeTracker`. |
|
||||
| **Emails & rapports depuis modèles** | `NouveauEmailView`, `RapportChampsView`, génération serveur (`/generation/email`, `/generation/rapport`). |
|
||||
| **Suivi des temps intelligent** | Pré-remplissage, pause déjeuner, saisie pour autrui (`A.TEMP.COLB`), brouillons offline (`RendezVousDetailViewModel`, `SuiviTempsLocalService`). |
|
||||
| **Sécurité par droits** | Menu/pages filtrés par droits serveur, rechargés à chaque sync (`PermissionGuard`, `PermissionService`). |
|
||||
| **Diagnostic & support intégrés** | Télémétrie locale (JSONL), export logs en email au support, capture de crash (`AppServices`, `SupportLogExporter`, `ExceptionTelemetry`). |
|
||||
| **Gestion de licence & licences collaborateurs** | Activation/validation/grace offline, attribution/révocation de licence mobile (`MobileSessionService`, `MobileApiClient.AssignMobileLicenceAsync/ReleaseMobileLicenceAsync`). |
|
||||
| **Cartographie des interventions** | `Controls/LieuMap.axaml` (Mapsui) + itinéraire (`IMapLauncher`). |
|
||||
|
||||
---
|
||||
|
||||
## 8. Limites / risques
|
||||
|
||||
1. **Journalisation de diagnostic « temporaire » abondante** : de nombreux
|
||||
`Console.WriteLine` marqués « à retirer après diagnostic » subsistent dans le
|
||||
code de production (`[TOKEN-DIAG]`, `[INTERVENTION-HTTP]`, `[API-CONFIG]`,
|
||||
`[STARTUP]`, `REFRESH START/SUCCESS`, `SQLITE IMPORT END`) — `MobileSessionService.cs`,
|
||||
`MobileApiClient.cs`, `AppServices.cs`, `SyncEngine.cs`. Aucun secret n'est loggé,
|
||||
mais c'est du bruit à nettoyer avant une démo publique.
|
||||
2. **Licence de développement (Debug)** : bypass local `#if DEBUG` — correctement
|
||||
compilé hors Release, mais à ne jamais laisser fuir en production
|
||||
(`DevelopmentLicenseProvider.cs`).
|
||||
3. **Commentaires d'énumération obsolètes** : `AppPage.cs` qualifie encore Agenda/
|
||||
Interventions/Dossiers/Clients/Sites de « placeholder pour ce lot » alors qu'ils
|
||||
sont **entièrement implémentés** (`PageViewModelFactory.cs` route tout vers de
|
||||
vrais ViewModels). Risque de confusion lors de la relecture.
|
||||
4. **Écran orphelin** : `CollaborateursView.axaml` + `CollaborateursViewModel.cs`
|
||||
existent mais ne sont **pas référencés** par la navigation (le menu « Équipe »
|
||||
passe par `EquipesView`/`EquipeDetail`).
|
||||
5. **Composition racine manuelle** : `AppServices.cs` (~840 lignes) câble tout sans
|
||||
conteneur DI — explicite mais long à maintenir.
|
||||
6. **Snapshot réduit** : le snapshot ne fournit plus que Personne/Intervention/
|
||||
RendezVous ; Client/Site/Dossier/Collaborateur/DocumentMetadata/SuiviTemps sont
|
||||
« à la demande » et **purgés à chaque snapshot** (`SnapshotApplier.cs`). La
|
||||
première consultation hors ligne de ces fiches dépend donc des **caches/épinglages**
|
||||
locaux (`DossierLocalState`, `ClientLocalState`, `SiteLocalState`,
|
||||
`InterventionCache`, `ContextInterventionCache`) — comportement à bien expliquer
|
||||
(les favoris et la dernière recherche restent disponibles offline).
|
||||
7. **Connectivité portable naïve** : le repli `NetworkConnectivity` (interface
|
||||
active ≠ internet) est corrigé sur Android par `ConnectivityManager`, mais iOS et
|
||||
desktop utilisent le repli.
|
||||
8. **Stockage du token Desktop** : le repli fichier (`PlatformSecureTokenStore`) est
|
||||
moins robuste que Keystore/Keychain (documenté « développement/desktop »).
|
||||
9. **Grace offline pilotée serveur** : la durée hors-ligne autorisée dépend de
|
||||
`OfflineGraceDays` renvoyé par la licence ; en mode Dev elle est fixée à la durée
|
||||
d'expiration (`DevelopmentLicenseProvider.cs`).
|
||||
|
||||
---
|
||||
|
||||
## Résumé (5 lignes)
|
||||
|
||||
Omika est un logiciel de gestion d'interventions **offline-first** réécrit en
|
||||
**.NET 10 / Avalonia UI**, avec une UI partagée unique déployée sur **desktop
|
||||
(Windows/macOS/Linux), Android et iOS**. Il couvre l'ensemble du cycle terrain —
|
||||
**agenda/planning, interventions, dossiers, clients, sites, équipe, suivi des temps,
|
||||
comptes rendus, documents/emails** — avec une base **SQLite locale** et un
|
||||
**moteur de synchronisation robuste (Outbox + snapshot atomique + réconciliation
|
||||
d'identités)**. L'**activation par QR sécurisée** et le **token en Keystore/Keychain**
|
||||
assurent un onboarding et une session maîtrisés, avec **période offline autorisée**.
|
||||
Les points de vigilance avant commercialisation sont la **journalisation de
|
||||
diagnostic temporaire** à nettoyer, quelques **commentaires/écrans obsolètes**, et le
|
||||
**repli fichier du token desktop**.
|
||||
Reference in New Issue
Block a user