Files
Omika/docs/LOT_MOBILE_0.md
T
2026-10-23 18:46:50 +02:00

102 KiB

LOT MOBILE 0 — Fondation API Mobile + Worker

Statut

Ce document enregistre les décisions d'architecture du LOT MOBILE 0 comme VALIDÉES.

Décisions validées

# Décision Statut
1 La base HFSQL n'est pas modifiable. Aucune colonne, table ou contrainte ne sera ajoutée/modifiée. VALIDÉE
2 Le desktop Avalonia continue d'accéder directement à HFSQL. Inchangé. VALIDÉE
3 Le mobile accède uniquement via l'API ASP.NET Core. Jamais directement à HFSQL. VALIDÉE
4 Batik et Omika sont invisibles pour le mobile. Le mobile ne connaît jamais la génération de base. VALIDÉE
5 OperationId est présent dans l'enveloppe JSON de synchronisation. GUID de transaction créé par le téléphone. VALIDÉE
6 RetryCount est présent dans l'enveloppe JSON de synchronisation. VALIDÉE
7 Maximum 30 tentatives de traitement d'une opération. VALIDÉE
8 MobLog = historique de synchronisation (journal). VALIDÉE
9 Rétention MobLog = 30 jours. VALIDÉE
10 Logs uniquement pertinents (réception, succès terminal, rejet terminal, erreur significative, retry significatif, conflit, document introuvable, exception serveur). VALIDÉE
11 Document identifié publiquement par GUID (DocumentGuid). Le PK HFSQL reste interne. VALIDÉE
12 Profil Batik provenant de ParametreMetier. VALIDÉE
13 Profil Omika provenant de Role. VALIDÉE
14 Différences RDV gérées côté backend. Le mobile ne connaît que le modèle commun RendezVous. VALIDÉE
15 Licence offline par défaut = 10 jours, configurable serveur (License:OfflineGraceDays). VALIDÉE
16 Aucun secret HFSQL communiqué au mobile (adresse, port, login, password, connection string). VALIDÉE

Architecture cible

Mobile (Avalonia Android/iOS)
   │
   ▼
API ASP.NET Core (Omika.Api)
   │
   ├── Contrats DTO (Omika.Core.Contracts)
   ├── Services applicatifs (Omika.Core.Services)
   ├── Repositories adaptatifs (Omika.Data.Repositories)
   │     ├── Batik
   │     └── Omika
   │
   ▼
Tables techniques HFSQL (TOSRV, MobReception, MobLog, Licence, LicenceHisto, ...)
   │
   ▼
Repositories métier existants → Batik ou Omika

Composants créés

API Mobile ASP.NET Core

  • Endpoints Minimal API dans Omika.Api/Endpoints/Mobile/
  • /health (existant)
  • /api/license/activate
  • /api/license/validate
  • /api/mobile/bootstrap
  • /api/sync/operations
  • /api/sync/status/{operationId}
  • /api/sync/snapshot
  • /api/documents/...

Contrats DTO

  • Omika.Core.Contracts.Mobile : licence, bootstrap, synchronisation, snapshot
  • Omika.Core.Contracts.Documents : métadonnées, téléchargement, upload

Couche applicative mobile/API

  • Omika.Core.Services.Mobile : licence, bootstrap, synchronisation, snapshot
  • Omika.Core.Interfaces : interfaces de services

Authentification + bootstrap identité/profil/droits

  • ITokenService / TokenService : token stateless signé (HMAC-SHA256), lié au client, DeviceId, utilisateur, collaborateur, licence. Aucune table HFSQL supplémentaire.
  • MobileAuthMiddleware : valide le token Bearer et stocke l'identité dans HttpContext.Items.
  • IMobileIdentityProvider / MobileIdentityProvider : fournisseur d'identité commun (Batik via ParametreMetier, Omika via Role).
  • IMobileIdentityRepository + AdaptiveMobileIdentityRepository + BatikMobileIdentityRepository + OmikaMobileIdentityRepository : résolution adaptative du profil/droits selon la génération de base.
  • IMobileBootstrapService / MobileBootstrapService : construit le contrat commun (utilisateur, collaborateur, profil, droits, configuration, licence). Vérifie que le DeviceId du token correspond à la licence attribuée.
  • GET /api/mobile/bootstrap : endpoint protégé par authentification.

Infrastructure API

  • Authentification par token signé (HMAC-SHA256, stateless)
  • Configuration via appsettings.json + variables d'environnement
  • ProblemDetails / contrat d'erreur structuré
  • CancellationToken partout
  • Fail-fast Production : MobileAuth:SigningKey obligatoire en Production (jamais en source)

Contrat de synchronisation (réception API)

  • POST /api/sync/operations : réception d'une opération de l'Outbox mobile (authentifiée).
  • Contrat public SyncOperation : OperationId, DeviceId, EntityType, OperationType, EntityGuid, Payload.
  • OperationId = GUID de transaction créé par le téléphone. EntityGuid = identité de l'objet métier. Jamais interchangeables.
  • OperationType couvre CREATE, UPDATE, DELETE, ARCHIVE.
  • Enveloppe technique interne SyncEnvelope persistée dans TS_JSON / MR_JSON : contient les identités serveur (ClientId, UserId, CollaborateurId) issues du token et le RetryCount contrôlé côté serveur. Le mobile ne peut pas imposer ces champs.
  • Le DeviceId de l'opération doit correspondre à l'identité authentifiée. Le CollaborateurId fourni par le client n'est jamais utilisé : l'identité provient du token/bootstrap.
  • La réponse signifie uniquement RECEIVED (jamais SUCCESS), car l'opération n'est pas encore réellement appliquée.
  • Idempotence : une répétition du même OperationId ne crée pas deux opérations métier. Abstraction ISyncOperationStatusStore (stockage terminal MobLog). Un verrou par OperationId garantit l'idempotence même en cas de requêtes simultanées.
  • Ordre logique respecté par le futur moteur : CREATE → UPDATE → DELETE / ARCHIVE.
  • TS_CMD reçoit une valeur technique interne moderne (MOB.OP), jamais le contrat public de l'API. Les anciennes commandes S.xxx ne sont pas reproduites publiquement.

Worker de synchronisation serveur

  • Omika.Worker : worker .NET de synchronisation
  • Transfert TOSRV → MobReception
  • Traitement MobReception → métier
  • Purge MobLog (rétention 30 jours)

Pipeline de traitement (PROMPT 5)

  • SyncWorker : BackgroundService qui déclenche périodiquement le traitement.
  • SyncProcessor : logique pure et testable, indépendante du BackgroundService.
    • Phase 1 — transfert : TOSRV → copie vers MobReception → suppression de TOSRV uniquement si la copie réussit.
    • Phase 2 — traitement : lit les opérations MR_Fait = false arrivées à échéance, parse l'enveloppe JSON (OperationId, RetryCount, DeviceId, EntityType, OperationType, EntityGuid, Payload).
    • Ordre logique : CREATE → UPDATE → DELETE / ARCHIVE, puis ordre de réception le plus ancien.
  • ISyncOperationHandler / SyncOperationHandlerRegistry : handlers métier par EntityType, extensibles. Le handler public manipule le contrat commun SyncOperation (jamais de différence Batik/Omika visible).
  • DefaultSyncOperationHandler : handler par défaut (traitement métier réel branché ultérieurement via les services/repositories existants).
  • SyncOperationResult : résultat structuré (Success, TransientFailure, PermanentFailure).
    • Success → SUCCESS dans MobLog + retrait de la file de travail.
    • TransientFailure → incrément RetryCount, réécriture MR_JSON, report de MR_DateHeureRecep.
    • PermanentFailure → REJECTED immédiat (ne consomme pas 30 retries).
    • Après la 30e tentative impossible → REJECTED, plus de retraitement automatique.
  • SyncOptions : MaxRetryCount = 30, RetryDelaySeconds = 120, LogRetentionDays = 30 (aucune valeur codée en dur dans les handlers).
  • MobLog : journal structuré (SyncLogEntry) avec OperationId, statut, EntityType, EntityGuid, DeviceId, RetryCount, erreur/code, date. Seuls les événements pertinents sont logués (réception, succès terminal, rejet terminal, erreur significative, retry significatif, conflit, document introuvable, exception serveur).

Handler Intervention (PROMPT 5 — application métier)

  • InterventionSyncPayload : contrat mobile commun (métadonnées d'intervention, aucun Row HFSQL).
  • InterventionSyncOperationHandler : handler métier Intervention, commun (aucune condition Batik/Omika visible). Il délègue le traitement physique à IInterventionService (qui sélectionne Batik/Omika via le runtime adaptatif existant) et l'adaptation d'identité publique à IMobileEntityIdentityResolver.
  • CREATE : transmet le EntityGuid mobile comme Guid métier à CreateInterventionRequest (rétrocompatible : une création Desktop sans GUID conserve le comportement historique). Idempotent : si l'intervention existe déjà, SUCCESS sans recréer.
  • UPDATE : résout l'intervention par identifiant public via le resolver, puis UpdateAsync. Absente → TransientFailure (INTERVENTION_NOT_FOUND).
  • ARCHIVE : ArchiveAsync sur l'identifiant public résolu. Absente → TransientFailure.
  • DELETE : non supporté pour Intervention → PermanentFailure (INTERVENTION_OPERATION_NOT_SUPPORTED).
  • CreatedEntityId : identifiant public serveur de l'objet créé (GUID pour Omika, identifiant numérique public pour Batik), jamais le PK HFSQL brut.
  • Le handler ne dépend que de IInterventionService et IMobileEntityIdentityResolver (vérifié par test) : aucune dépendance vers un repository physique Batik/Omika.

Identité publique mobile ↔ identité physique backend (résolveur central)

  • IMobileEntityIdentityResolver / MobileEntityIdentityResolver : abstraction Core unique qui adapte l'identité publique mobile à l'identité physique backend selon la génération de base détectée (ApplicationRuntimeContext). Aucune condition Batik/Omika dans les handlers.
  • Omika : l'identité publique est le GUID physique. Le resolver le conserve tel quel ; la résolution est directe par GUID.
  • Batik : les tables historiques n'ont pas de GUID physique exploitable. L'identité publique stable est un GUID synthétique réversible dérivé du PK numérique (marqueur + version + type d'entité + PK big-endian + checksum CRC32). Il est déterministe, stable, versionné, recalculable sans stockage et décodable vers le PK sans scanner toutes les lignes. Le PK HFSQL n'est jamais exposé au mobile.
  • PhysicalEntityIdentity : contrat interne (GUID physique et/ou PK numérique) porté par LegacyEntityReference (SourceReference, [JsonIgnore] sur le DTO) pour calculer l'identité canonique sans exposer le PK.
  • SyncOperationResult : porte RequestedEntityGuid (GUID mobile demandé) et CanonicalEntityGuid (GUID canonique stable) lorsqu'ils diffèrent (cas Batik CREATE). Le mobile doit utiliser le GUID canonique pour les UPDATE/ARCHIVE ultérieurs.
  • Idempotence par OperationId renforcée (reconstitution du résultat terminal) : le SyncProcessor vérifie, avant d'appeler le handler, si l'opération a déjà produit un résultat terminal (SUCCESS / REJECTED) dans MobLog via ISyncTransferRepository.GetTerminalResultAsync. Si oui, il reconstitue ce résultat sans rappeler le handler métier : un même OperationId ne produit jamais deux applications métier (notamment pour un CREATE Batik où le GUID mobile temporaire n'est pas stocké). Le CanonicalEntityGuid, le RequestedEntityGuid et le CreatedEntityId d'origine sont conservés dans MobLog (SyncLogEntry) et réutilisés lors de la reconstitution. Une protection défensive par HashSet<Guid> empêche aussi tout doublon d'OperationId au sein d'un même cycle de traitement.
  • Blocage documenté (Batik) : un GUID mobile temporaire non stocké n'est pas résolvable sans stockage durable. Le mobile doit utiliser le GUID canonique retourné après le CREATE. L'idempotence CREATE Batik par GUID n'est donc pas garantie pour un GUID mobile temporaire (documenté, sans ajout de colonne). L'idempotence par OperationId (reconstitution du résultat terminal) reste néanmoins garantie côté serveur.

Suivi de statut (PROMPT 6)

  • GET /api/sync/status/{operationId} : authentifié. Le résultat appartient au même client/device que l'identité authentifiée.
  • Source de vérité : MobLog pour les états terminaux (SUCCESS / REJECTED) ; TOSRV / MobReception pour les opérations encore en attente (PENDING). Aucun statut PROCESSING persistant n'est inventé : il reste PENDING entre deux tentatives.
  • Contrat public SyncStatusResponse : OperationId, Status, RetryCount, EntityType, OperationType, EntityGuid, RequestedEntityGuid, CanonicalEntityGuid, DeviceId, ErrorCode, ErrorMessage, LastUpdatedUtc, CompletedAt. Ne contient jamais de PK HFSQL, d'IDTOSRV, de MR_PKID, de nom de table technique ni de commande historique S.xxx / W.xxx.
  • Résultat riche pour les états terminaux : GetStatusAsync réutilise GetTerminalResultAsync (parsing de l'enveloppe JSON de MobLog) pour reconstituer un statut complet, y compris RequestedEntityGuid et CanonicalEntityGuid (utile pour un CREATE Batik : le mobile récupère le GUID canonique à utiliser pour les UPDATE/ARCHIVE ultérieurs) et CompletedAt.
  • Isolation par appareil : si l'opération existe mais appartient à un autre DeviceId, GetStatusAsync retourne null (aucune information métier sensible ne fuit vers un autre appareil). Le endpoint traduit ce cas en STATUS_NOT_AVAILABLE avec un HTTP approprié.
  • OperationId inconnu / purgé : retourne STATUS_NOT_AVAILABLE avec un HTTP cohérent avec le contrat d'erreur global.

Purge de MobLog (PROMPT 6)

  • Rétention Sync:LogRetentionDays = 30 (aucune valeur codée en dur dans la logique).
  • Ciblage strict : la purge ne supprime QUE les logs structurés appartenant au mécanisme de synchronisation mobile (JSON versionné contenant "schemaVersion" et "operationId") et dont le statut est terminal ("status":"Success" ou "status":"Rejected"). Les autres types de logs historiques présents dans MobLog ne sont jamais supprimés par cette purge.
  • Jamais de purge des opérations non terminées : une opération PENDING n'est jamais supprimée uniquement parce qu'elle est ancienne.
  • Après purge, un OperationId ancien peut retourner STATUS_NOT_AVAILABLE.
  • Le nettoyage est périodique et léger, exécuté par un worker de purge distinct qui ne monopolise pas le worker principal.

Documents mobile (téléchargement / upload)

  • GET /api/documents/{documentGuid:guid} : téléchargement d'un document par son GUID public (authentifié). Le mobile ne reçoit jamais le chemin physique du serveur.
  • POST /api/documents : upload d'un document. Un upload = UN document par requête. Le serveur conserve le DocumentGuid fourni par le mobile.
  • Identifiant public : le mobile utilise exclusivement DocumentGuid (GUID). Le PK HFSQL (DC_PKID) reste strictement interne au backend.
  • Résolution d'identité : le DocumentGuid est résolu vers l'identité physique backend via IMobileEntityIdentityResolver (GUID physique Omika ou PK numérique Batik), puis le document est chargé par cette identité via IDocumentRepository.GetByPhysicalIdentityAsync.
  • Stockage physique : le contenu binaire est lu/écrit via IDocumentFileStorage (abstraction anti path traversal, taille maximale configurable). Le DC_Chemin stocké en base est un chemin relatif à la racine configurée (Documents:RootPath). Le chemin serveur n'est jamais exposé au mobile.
  • Compatibilité future : le contrat mobile DocumentUploadRequest / DocumentDownloadResponse est stable et indépendant de la génération de base (Batik/Omika). Les métadonnées de document sont lues depuis la table Document (GUID métier stable si présent, sinon Guid.Empty et le resolver produit le GUID public synthétique à partir du PK numérique Batik). Aucune colonne HFSQL n'est ajoutée/modifiée.

Configuration

Clé Valeur par défaut Description
License:OfflineGraceDays 10 Jours de fonctionnement offline
Sync:MaxRetryCount 30 Nombre max de tentatives
Sync:RetryDelaySeconds 120 Délai entre tentatives
Sync:LogRetentionDays 30 Rétention MobLog
Auth:Issuer — Émetteur du token
Auth:Audience — Audience du token
Auth:SigningKey — Clé de signature (secret)

Sécurité

  • Aucun secret HFSQL dans le code source.
  • Aucune connection string en dur.
  • Aucun password en dur.
  • Aucun Row HFSQL exposé publiquement.
  • Le mobile ne reçoit jamais le chemin physique des fichiers.
  • Le mobile ne reçoit jamais les adresses/ports/logins/passwords HFSQL.

MOBILE UI FOUNDATION

Statut

Socle visuel et de navigation de l'application Avalonia mobile. Ce lot pose le shell responsive, la navigation, le thème clair/sombre, la couleur principale personnalisable, la structure commune des pages et le branchement aux services locaux existants. Aucun écran métier complet n'est développé ici.

Principe maquette UX vs données réelles

docs/maquetteOmika.html est une référence UX (comportement responsive, hiérarchie visuelle, navigation, ergonomie téléphone/tablette), pas une source de données. Aucun champ métier, propriété DTO ou donnée fictive n'est créé parce qu'il apparaît dans le HTML. Les modèles/services existants restent la source de vérité métier. Le HTML/CSS n'est pas converti mécaniquement en XAML : l'expérience est reproduite avec des composants Avalonia propres.

Architecture MVVM

View (ShellView, PlaceholderPage, ParametresPage)
   → ViewModel (ShellViewModel, PlaceholderViewModel, ParametresViewModel)
   → Service (ThemeService, NavigationService, SyncEngine existant)
  • Une View n'appelle jamais directement l'API ni SQLite.
  • Aucune logique métier importante dans les Views.
  • MVVM via CommunityToolkit.Mvvm (déjà présent, aucune nouvelle bibliothèque).

Shell responsive

Le shell principal (ShellView) contient :

  • zone de marque / application
  • navigation principale (latérale desktop/tablette, barre basse smartphone)
  • contenu de la page courante
  • accès au profil (état neutre : aucune identité fictive)
  • accès synchronisation
  • état de connectivité/synchronisation non intrusif

Aucune donnée utilisateur fictive. Sans identité réelle, un placeholder technique neutre est affiché.

Navigation

Abstraction INavigationService / NavigationService :

  • page courante (CurrentPage)
  • éléments disponibles (Items)
  • navigation active (état sélection)
  • fonctions primaires/secondaires smartphone

Le ShellViewModel connaît la page courante, les éléments disponibles et la navigation active. Le changement de page placeholder ne déclenche aucun appel API : il est purement visuel. La navigation est préparée pour les futurs écrans détail (ex. Agenda → RendezVous → Intervention) sans les implémenter.

Pages

Page Type
Home / Agenda Placeholder
Interventions Placeholder
Dossiers Placeholder
Collaborateurs / Équipe Placeholder
Clients Placeholder
Sites Placeholder
Paramètres Réel minimal (thème, couleur, version, sync)

Breakpoints / seuils responsive

Centralisés dans DesignTokens.axaml (pas de valeurs magiques répétées dans les Views). La logique responsive dépend de la largeur disponible (classes adaptatives Avalonia), pas de Android/iOS directement.

  • Smartphone : navigation latérale masquée, barre basse avec 5 accès directs maximum, fonctions secondaires via menu "Plus".
  • Tablette : rail réduit / navigation compacte (transition douce).
  • Desktop / grande tablette : navigation latérale complète.

Thème clair / sombre

ThemeService gère Light, Dark et System (si l'architecture le permet). Le changement est dynamique sans redémarrage. Le choix est persisté dans la configuration locale (UiPreferencesRepository, table technique UiPreferences), jamais dans une table métier snapshot.

Couleur principale personnalisable

Palette basée sur une PrimaryColor configurable. Le changement met à jour :

  • boutons principaux
  • éléments actifs de navigation
  • accents
  • focus
  • badges / éléments de sélection

8 presets prédéfinis dans ThemeService.PresetColors. Pas de sélecteur libre (évite une dépendance inutile). Persistance locale obligatoire.

Design tokens

DesignTokens.axaml centralise les ressources visuelles : couleurs, espacements principaux, radius, tailles communes, surfaces, bordures, texte principal / secondaire, success / warning / danger / info. Light et Dark utilisent les mêmes clés de ressources. Aucune valeur couleur hardcodée dans les Views.

Composants / styles réutilisables

Controls.axaml définit des styles/ControlThemes pour : PageHeader, Card, boutons principaux/secondaires, badges, champs de saisie, sections, listes, états vides, messages d'erreur, indicateur de chargement. Les contrôles natifs ne sont pas recréés ; les styles apportent une cohérence.

Persistance des préférences UI

UiPreferences (table technique UiPreferences, ligne unique) stocke :

  • ThemeMode (System / Light / Dark)
  • PrimaryColor (hexadécimal)

Persistance via IUiPreferencesRepository / UiPreferencesRepository. Cette table n'est jamais touchée par le refresh snapshot.

Branchement Synchroniser maintenant

ParametresViewModel.SyncNowCommand → ShellViewModel.SyncNowCommand → ISyncEngine.SyncAsync (LE SyncEngine existant, jamais un second moteur).

L'UI affiche : sync en cours, succès, erreur significative, hors ligne. Le bouton est désactivé pendant l'opération et un verrou UI (SemaphoreSlim) empêche deux clics simultanés de lancer deux cycles UI.

État de synchronisation

Dans le shell, un petit état non intrusif reflète l'état réel du SyncEngine :

  • synchronisé
  • synchronisation
  • hors ligne
  • erreur / rejet

Aucune donnée réseau fictive. Les détails techniques TOSRV/MobReception/MobLog ne sont pas affichés.

Startup visuel

Loading → Shell avec un état d'erreur récupérable pendant l'initialisation SQLite, la restauration locale, la vérification bootstrap local et l'éventuelle synchronisation initiale. L'orchestration Startup existante n'est pas réécrite.

Accessibilité / terrain

  • zones tactiles suffisamment grandes
  • lisibilité et contrastes
  • texte adaptable
  • navigation utilisable au doigt
  • pas d'actions critiques uniquement accessibles par hover
  • focus clavier correct sur desktop/tablette clavier

Safe area mobile

Les safe areas (barre système, encoche, navigation Android/iOS, bottom navigation) sont prises en compte via les APIs Avalonia/platform disponibles. Aucune marge spécifique à un téléphone particulier n'est hardcodée.

Icônes

Réutilisation de la bibliothèque d'icônes déjà présente. Aucun emoji comme icône finale de navigation. Une seule bibliothèque d'icônes.

Fichiers créés (UI Foundation)

  • src/Omika.App/Styles/DesignTokens.axaml
  • src/Omika.App/Styles/Controls.axaml
  • src/Omika.App/Services/ThemeMode.cs
  • src/Omika.App/Services/ThemeService.cs
  • src/Omika.App/Navigation/AppPage.cs
  • src/Omika.App/Navigation/NavigationItem.cs
  • src/Omika.App/Navigation/INavigationService.cs
  • src/Omika.App/Navigation/NavigationService.cs
  • src/Omika.App/Navigation/IPageViewModelFactory.cs
  • src/Omika.App/Navigation/PageViewModelFactory.cs
  • src/Omika.App/ViewModels/ViewModelBase.cs
  • src/Omika.App/ViewModels/SyncUiState.cs
  • src/Omika.App/ViewModels/ShellViewModel.cs
  • src/Omika.App/ViewModels/PlaceholderViewModel.cs
  • src/Omika.App/ViewModels/ParametresViewModel.cs
  • src/Omika.App/Views/ShellView.axaml (+ code-behind)
  • src/Omika.App/Views/PlaceholderPage.axaml (+ code-behind)
  • src/Omika.App/Views/ParametresPage.axaml (+ code-behind)
  • src/Omika.App/Views/MainView.axaml
  • src/Omika.App/Views/NavItem*Converter.cs
  • src/Omika.App/Views/SyncStateToBrushConverter.cs
  • src/Omika.App/AppServices.cs
  • src/Omika.App.Local/Models/UiPreferences.cs
  • src/Omika.App.Local/Abstractions/IUiPreferencesRepository.cs
  • src/Omika.App.Local/Repositories/UiPreferencesRepository.cs
  • src/Omika.App.Local/Services/NetworkConnectivity.cs
  • src/Omika.App.Local/Services/MobileApiClient.cs

Fichiers modifiés

  • src/Omika.App.Local/Sqlite/SqliteSchema.cs (table UiPreferences)
  • src/Omika.App/App.axaml / App.axaml.cs (thème, ressources, DI)
  • src/Omika.App/Views/MainView.axaml (racine Shell)
  • tests/Omika.Tests/Omika.Tests.csproj (référence Omika.App)

Tests ajoutés

  • tests/Omika.Tests/Mobile/Local/ThemeServiceTests.cs : thème clair/sombre, persistance, restauration, couleur principale, fallback config invalide.
  • tests/Omika.Tests/Mobile/Local/NavigationServiceTests.cs : page initiale, sélection, changement de page, fonctions secondaires, aucun appel API.
  • tests/Omika.Tests/Mobile/Local/ShellViewModelTests.cs : navigation, page active, thème, sync UI (appel unique, bouton désactivé, double clic, erreur, offline), aucune identité fictive.

DESIGN SYSTEM OMIKA — ORGANISATION DES STYLES

Principe

Le style de l'application est centralisé dans deux fichiers de ressources Avalonia. Toute modification visuelle globale (couleur principale, radius, typographie, espacements, formes de boutons) se fait sans toucher aux Views.

src/Omika.App/Styles/
├── DesignTokens.axaml   → Ressources brutes (couleurs, espacements, radius,
│                          tailles, breakpoints, typographie)
└── Controls.axaml       → Styles réutilisables (boutons, cartes, badges,
                           champs, navigation, états, typographie sémantique)

1. DesignTokens.axaml — Ressources brutes

Couleurs sémantiques

Light et Dark utilisent les mêmes clés de ressources. Le changement de thème est dynamique via ThemeService (aucune View à recréer).

Clé Rôle
BackgroundBrush Fond de page
SurfaceBrush Surface de carte / panneau
SurfaceSecondaryBrush Surface secondaire (fond alterné)
SurfaceElevatedBrush Surface élevée (cartes avec ombre)
SurfaceOverlayBrush Surface de survol / overlay
TextPrimaryBrush Texte principal
TextSecondaryBrush Texte secondaire
TextMutedBrush Texte atténué (captions, désactivé)
TextOnPrimaryBrush Texte sur fond Primary
BorderBrush Bordure standard
BorderStrongBrush Bordure forte
SuccessBrush / SuccessContainerBrush Succès
WarningBrush / WarningContainerBrush Avertissement
DangerBrush / DangerContainerBrush Erreur / danger
InfoBrush / InfoContainerBrush Information
PrimaryColorBrush Couleur principale (pilotée par ThemeService)
PrimaryColorDarkBrush Primary assombrie (hover)
PrimaryColorLightBrush Primary éclaircie
PrimaryColorContainerBrush Conteneur Primary (fond clair)

Typographie

Clé Usage
FontFamilyBase Police générale de l'application
FontSizeCaption 12 — captions, badges
FontSizeBodySmall 13 — texte secondaire
FontSizeBody 14 — corps de texte
FontSizeButton 14 — boutons
FontSizeCardTitle 16 — titres de cartes
FontSizeSectionTitle 18 — titres de sections
FontSizePageTitle 24 — titres de pages
FontSizeDisplay 28 — affichage / hero

Espacements (échelle)

Clé Valeur
SpaceXXS 2
SpaceXS 4
SpaceS 8
SpaceM 12
SpaceL 16
SpaceXL 24
SpaceXXL 32

Dimensions communes : ControlHeight (44), ControlHeightCompact (36), TouchTargetMin (48), PagePadding (24), CardPadding (16), IconSizeS (16), IconSizeM (20), IconSizeL (24), AvatarSize (36).

Thickness réutilisables pour Margin/Padding : ThicknessXS, ThicknessS, ThicknessM, ThicknessL, ThicknessXL, ThicknessPage, ThicknessCard.

Épaisseurs de bordure : BorderThicknessThin (1), BorderThicknessStrong (2).

Radius

Clé Valeur Usage
Radius.Small 4 Petits éléments
Radius.Control 8 Contrôles (champs, listes)
Radius.Button 8 Boutons
Radius.Card 12 Cartes
Radius.Dialog 16 Dialogues
Radius.Full 999 Badges, avatars

Breakpoints responsive

Clé Valeur
BreakpointPhone 600
BreakpointTablet 960

La logique responsive dépend de la largeur disponible (classes adaptatives Avalonia), pas de Android/iOS directement. Les seuils sont centralisés ici.

2. Controls.axaml — Styles réutilisables

Typographie sémantique

Classe Usage
TextBlock.pageTitle Titre de page
TextBlock.sectionTitle Titre de section
TextBlock.cardTitle Titre de carte
TextBlock.body Corps de texte
TextBlock.bodySmall Texte secondaire
TextBlock.caption Caption / texte atténué
TextBlock.listTitle Titre d'élément de liste (semi-gras)

Boutons

Variantes : primary, secondary, ghost, danger, icon. Tailles : compact (en plus de la taille normale).

Variante Usage
Button.primary Action principale
Button.secondary Action secondaire
Button.ghost Action discrète (navigation)
Button.danger Action destructive
Button.icon Bouton icône seule
Button.compact Taille réduite

Autres composants

Classe Usage
Border.card Carte de contenu
Border.card.cardElevated Carte avec ombre
TextBox.field Champ de saisie
ComboBox.field Liste déroulante
Border.badge + badgeSuccess/Warning/Danger/Info/Primary Badge
Border.statusBadge + statusSuccess/Warning/Danger/Info Badge de statut
ListBoxItem.listItem Élément de liste
ListBoxItem.navItem Élément de navigation latérale
ListBoxItem.bottomNavItem Élément de navigation basse
StackPanel.pageHeader En-tête de page
StackPanel.section Section de contenu
StackPanel.emptyState État vide
StackPanel.loadingState État de chargement
Border.errorMessage Message d'erreur
Border.infoMessage Message d'information

3. Règles pour les Views

  1. Toute nouvelle View métier doit d'abord chercher si un style/composant global existe.
  2. Ne pas répéter dans les Views des valeurs visuelles telles que Background="#...", Foreground="#...", FontSize="...", CornerRadius="...", Padding="...", Height="..." pour les composants standards lorsqu'une ressource ou un style réutilisable peut être utilisé.
  3. Ne créer un style local que lorsque :
    • le besoin est réellement spécifique à cet écran ;
    • aucun style global ne convient ;
    • la duplication n'est pas justifiée.
  4. Si le même style spécifique apparaît dans deux écrans ou plus : le déplacer dans les ressources globales.

4. DynamicResource

Tout ce qui doit changer dynamiquement (Light/Dark, PrimaryColor) utilise DynamicResource. Le changement de thème/couleur ne nécessite pas de recréer chaque View.

5. Validation

Une modification centrale simulée de :

  • PrimaryColor (via ThemeService)
  • Radius.Button (dans DesignTokens.axaml)
  • FontSizeBody (dans DesignTokens.axaml)

se répercute sans modification des Views utilisant les styles correspondants. Les Views ne référencent jamais ces valeurs en dur : elles passent par les styles globaux (Button.primary, TextBlock.body, etc.) et les DynamicResource des design tokens.


LOT AGENDA — ÉCRANS MÉTIER RÉELS (Agenda + Détail Rendez-vous)

Statut

Ce lot implémente les premiers écrans métier réels de l'application mobile : l'Agenda (liste des rendez-vous d'une journée) et le Détail d'un rendez-vous. Les données proviennent exclusivement du snapshot local SQLite (dernier synchronisé) : aucun appel API n'est déclenché par ces écrans.

Principe

  • Lecture locale uniquement via IAgendaService (abstraction) / AgendaService (implémentation SQLite).
  • Enrichissement des rendez-vous avec les références Client / Site / Intervention disponibles localement. Les relations optionnelles absentes ne provoquent pas d'erreur.
  • MVVM strict : les Views ne contiennent aucune logique métier ni appel de données ; elles réutilisent exclusivement les styles globaux du Design System (DesignTokens.axaml + Controls.axaml).

Navigation avec paramètre

INavigationService.NavigateTo(AppPage page, object? parameter) a été étendu avec un paramètre optionnel (rétrocompatible : l'ancienne surcharge sans paramètre reste disponible). Le paramètre est exposé via CurrentParameter. IPageViewModelFactory accepte le même paramètre optionnel pour construire le ViewModel de la page cible (ex. RendezVousDetailViewModel reçoit le GUID du rendez-vous à afficher).

Composants créés

Couche locale (Omika.App.Local)

  • Abstractions/IAgendaService.cs : contrat de lecture de l'agenda.
  • Services/AgendaService.cs : implémentation SQLite (filtrage par journée, tri chronologique, enrichissement Client/Site/Intervention, détail par GUID).
  • Models/AgendaItem.cs : modèle de présentation d'un rendez-vous enrichi.

Couche présentation (Omika.App)

  • ViewModels/AgendaViewModel.cs : liste du jour, navigation jour précédent / suivant / aujourd'hui, états chargement / vide / erreur, ouverture du détail.
  • ViewModels/RendezVousDetailViewModel.cs : chargement du détail par GUID, retour à l'agenda.
  • Views/AgendaView.axaml (+ code-behind) : liste des rendez-vous du jour.
  • Views/RendezVousDetailView.axaml (+ code-behind) : détail d'un rendez-vous.

Navigation

  • Navigation/AppPage.cs : ajout de RendezVousDetail.
  • Navigation/INavigationService.cs / NavigationService.cs : paramètre optionnel de navigation.
  • Navigation/IPageViewModelFactory.cs / PageViewModelFactory.cs : paramètre optionnel transmis au ViewModel.
  • ViewModels/ShellViewModel.cs : transmission du paramètre lors de la navigation.

Injection de dépendances

  • AppServices.cs : enregistrement des repositories LocalRendezVous, LocalClient, LocalSite, LocalIntervention et du service IAgendaService.

Fichiers créés (LOT AGENDA)

  • src/Omika.App.Local/Abstractions/IAgendaService.cs
  • src/Omika.App.Local/Services/AgendaService.cs
  • src/Omika.App.Local/Models/AgendaItem.cs
  • src/Omika.App/ViewModels/AgendaViewModel.cs
  • src/Omika.App/ViewModels/RendezVousDetailViewModel.cs
  • src/Omika.App/Views/AgendaView.axaml (+ code-behind)
  • src/Omika.App/Views/RendezVousDetailView.axaml (+ code-behind)

Fichiers modifiés (LOT AGENDA)

  • src/Omika.App/Navigation/AppPage.cs (ajout RendezVousDetail)
  • src/Omika.App/Navigation/INavigationService.cs (paramètre optionnel)
  • src/Omika.App/Navigation/NavigationService.cs (implémentation paramètre)
  • src/Omika.App/Navigation/IPageViewModelFactory.cs (paramètre optionnel)
  • src/Omika.App/Navigation/PageViewModelFactory.cs (paramètre optionnel)
  • src/Omika.App/ViewModels/ShellViewModel.cs (transmission du paramètre)
  • src/Omika.App/AppServices.cs (repositories + service Agenda)
  • tests/Omika.Tests/Mobile/Local/LocalTestBase.cs (exposition SiteRepository)

Tests ajoutés (LOT AGENDA)

  • tests/Omika.Tests/Mobile/Local/AgendaServiceTests.cs : journée sans rendez-vous, filtrage par journée, tri chronologique, enrichissement Client/Site/Intervention, rendez-vous annulé, détail par GUID, introuvable, payload invalide ignoré.
  • tests/Omika.Tests/Mobile/Local/AgendaViewModelTests.cs : chargement, état vide, navigation jour précédent/suivant, retour à aujourd'hui, libellé de date, ouverture du détail (navigation avec paramètre), paramètre null ignoré.

Conformité Design System

Les nouvelles Views (AgendaView, RendezVousDetailView) réutilisent exclusivement les styles globaux : pageHeader, pageTitle, pageSubtitle, section, sectionTitle, body, bodySmall, caption, card, badge, errorMessage, ghost, compact. Aucune valeur visuelle (couleur hex, FontSize, CornerRadius, Padding, Height) n'est répétée dans ces Views : tout passe par les ressources/styles centraux. Une modification centrale de PrimaryColor, Radius.Control ou de la taille typographique principale se répercute sans toucher à ces écrans.


LOT FICHE INTERVENTION — ÉCRAN MÉTIER RÉEL

Statut

Ce lot implémente la Fiche Intervention : un écran métier réel qui affiche le détail d'une intervention chargée depuis le snapshot local SQLite (aucun appel API). La fiche est enrichie avec les relations Client / Site / Dossier / Rendez-vous / Documents disponibles localement. Les relations optionnelles absentes ne provoquent pas d'erreur.

Principe

  • Lecture locale uniquement via IInterventionLocalService (abstraction) / InterventionLocalService (implémentation SQLite).
  • Enrichissement de la fiche avec les références Client / Site / Dossier / Rendez-vous / Documents disponibles localement.
  • MVVM strict : la View ne contient aucune logique métier ni appel de données ; elle réutilise exclusivement les styles globaux du Design System.
  • Navigation bidirectionnelle : Agenda → Détail Rendez-vous → Fiche Intervention (et retour). Depuis la fiche, un rendez-vous lié peut être rouvert.

Composants créés

Couche locale (Omika.App.Local)

  • Abstractions/IInterventionLocalService.cs : contrat de lecture de la fiche.
  • Services/InterventionLocalService.cs : implémentation SQLite (détail par GUID, enrichissement Client/Site/Dossier/Rendez-vous/Documents).
  • Models/InterventionDetail.cs : modèle de présentation d'une intervention enrichie (+ InterventionRendezVous, InterventionDocument).

Couche présentation (Omika.App)

  • ViewModels/InterventionDetailViewModel.cs : chargement par GUID, indicateurs de relations (HasClient, HasSite, HasDossier, HasRendezVous, HasDocuments), ouverture d'un rendez-vous lié, retour à l'agenda.
  • Views/InterventionDetailView.axaml (+ code-behind) : fiche intervention.

Navigation

  • Navigation/AppPage.cs : ajout de InterventionDetail.
  • Navigation/PageViewModelFactory.cs : cas InterventionDetail (injection du service + paramètre GUID).
  • ViewModels/RendezVousDetailViewModel.cs : ajout de HasIntervention, InterventionReference et de la commande OpenIntervention (navigation vers la fiche).
  • Views/RendezVousDetailView.axaml : bouton "Ouvrir l'intervention" visible uniquement si le rendez-vous est lié à une intervention.

Injection de dépendances

  • AppServices.cs : enregistrement des repositories LocalDossier, LocalDocumentMetadata et du service IInterventionLocalService.

Fichiers créés (LOT FICHE INTERVENTION)

  • src/Omika.App.Local/Abstractions/IInterventionLocalService.cs
  • src/Omika.App.Local/Services/InterventionLocalService.cs
  • src/Omika.App.Local/Models/InterventionDetail.cs
  • src/Omika.App/ViewModels/InterventionDetailViewModel.cs
  • src/Omika.App/Views/InterventionDetailView.axaml (+ code-behind)

Fichiers modifiés (LOT FICHE INTERVENTION)

  • src/Omika.App/Navigation/AppPage.cs (ajout InterventionDetail)
  • src/Omika.App/Navigation/PageViewModelFactory.cs (cas InterventionDetail)
  • src/Omika.App/ViewModels/RendezVousDetailViewModel.cs (action ouvrir intervention)
  • src/Omika.App/Views/RendezVousDetailView.axaml (bouton intervention)
  • src/Omika.App/AppServices.cs (repositories + service Intervention)
  • tests/Omika.Tests/Mobile/Local/LocalTestBase.cs (exposition DossierRepository, DocumentMetadataRepository)
  • tests/Omika.Tests/Mobile/Local/ShellViewModelTests.cs (injection du service Intervention dans la factory)

Tests ajoutés (LOT FICHE INTERVENTION)

  • tests/Omika.Tests/Mobile/Local/InterventionLocalServiceTests.cs : fiche introuvable, fiche simple, enrichissement Client/Site/Dossier, rendez-vous liés triés chronologiquement, documents liés, relations absentes sans erreur, payload invalide.
  • tests/Omika.Tests/Mobile/Local/InterventionDetailViewModelTests.cs : chargement, état introuvable, indicateurs de relations, ouverture d'un rendez-vous lié (navigation avec paramètre), paramètre null ignoré, retour.
  • tests/Omika.Tests/Mobile/Local/RendezVousDetailViewModelTests.cs : indicateur d'intervention liée, navigation RDV → Fiche Intervention, absence d'intervention sans navigation, retour.

Conformité Design System

La nouvelle View (InterventionDetailView) réutilise exclusivement les styles globaux : pageHeader, pageTitle, section, sectionTitle, body, bodySmall, caption, card, badge, errorMessage, ghost, secondary, compact. Aucune valeur visuelle (couleur hex, FontSize, CornerRadius, Padding, Height) n'est répétée : tout passe par les ressources/styles centraux. Une modification centrale de PrimaryColor, Radius.Control ou de la taille typographique principale se répercute sans toucher à cet écran.

LOT DOCUMENTS / PHOTOS MOBILE

Statut

Ce lot ajoute la consultation, le téléchargement à la demande, l'ouverture offline et l'ajout offline-first des documents / photos d'une intervention.

Métadonnées depuis SQLite

L'ouverture de l'écran Documents fonctionne uniquement depuis SQLite (snapshot). Aucun appel réseau automatique. Les métadonnées réellement disponibles sont lues depuis LocalDocumentMetadata (payload DocumentSummaryDto) : DocumentGuid, nom, titre, catégorie, MIME type, extension, taille, date de création, InterventionGuid. Aucune propriété absente n'est supposée.

Cache fichier local

Le contenu binaire reste dans le filesystem local (répertoire contrôlé par l'application). SQLite stocke uniquement les informations techniques du cache (DocumentCacheEntry : chemin local, nom, MIME, taille, état). Le cache survit au refresh snapshot : après un refresh, un document serveur retrouve son cache grâce au DocumentGuid.

Téléchargement à la demande

Un téléchargement = un document, strictement à la demande. Ouvrir l'écran ne télécharge rien. Si le fichier est déjà en cache, aucun HTTP. Si hors ligne, aucun HTTP et la métadonnée reste visible.

Ouverture offline

Si le fichier est présent localement, Ouvrir ouvre la copie locale via l'abstraction plateforme IFileLauncher (aucun code Android/iOS dans le ViewModel). Le ViewModel ne connaît pas le chemin physique.

Ajout photo / document offline-first

Flux obligatoire : capture/sélection → copie fichier locale → création métadonnée locale → création Outbox → UI mise à jour immédiatement. La connexion réseau n'est pas nécessaire. Le fichier est copié dans un espace contrôlé par l'application (aucune référence fragile vers un temp file ou un content URI).

Outbox

L'Outbox existante est réutilisée (aucun mécanisme parallèle). Le payload (DocumentOutboxPayload) ne contient pas le fichier en Base64 : il contient DocumentGuid, InterventionGuid, métadonnée utile et une référence locale persistante vers le fichier (survit au redémarrage).

DocumentGuid vs OperationId

DocumentGuid = identité métier publique du document. OperationId = identité de l'opération de synchronisation. Ils sont toujours différents. OperationId est généré une seule fois et conservé pendant les retries.

Comportement SUCCESS

Après SUCCESS : métadonnée locale conservée, fichier cache conservé, Outbox mise à jour, aucun fichier supprimé automatiquement, aucun nouveau DocumentGuid généré.

Comportement REJECTED

Après REJECTED : document local conservé, fichier local conservé, Outbox conservée avec état Rejected, erreur nettoyée disponible au ViewModel, badge / état visuel possible. Un fichier rejeté n'est jamais supprimé automatiquement.

Fichier local manquant avant upload

Si une opération Outbox Document existe mais que le fichier local a disparu, l'upload n'est pas prétendu réussi : l'opération est mise dans un état d'erreur/rejet local cohérent et un message exploitable est affiché sans exposer de chemin système.

Purge cache ≠ suppression métier

La purge du cache (suppression des fichiers téléchargés) est distincte de la suppression du document métier. Aucun DELETE métier n'est émis. Les fichiers nécessaires à une Outbox Pending/Rejetée ne sont jamais supprimés. Le bouton de purge n'est pas branché dans cette tâche tant que la protection n'est pas fiable.

Design System réutilisé

La View Documents réutilise exclusivement les styles globaux : card, listTitle, body, bodySmall, caption, badge, primary, secondary, ghost, errorMessage, emptyState. Aucune couleur hexadécimale, aucun breakpoint local supplémentaire, aucun FontSize/CornerRadius/Padding standard répété. Les espacements passent par DynamicResource Space* et ThicknessPage.

Fichiers créés (LOT DOCUMENTS / PHOTOS)

  • src/Omika.App.Local/Abstractions/IDocumentLocalService.cs
  • src/Omika.App.Local/Abstractions/IFileLauncher.cs
  • src/Omika.App.Local/Abstractions/IFilePicker.cs
  • src/Omika.App.Local/Models/InterventionDocumentItem.cs
  • src/Omika.App.Local/Models/DocumentOutboxPayload.cs
  • src/Omika.App.Local/Services/DocumentLocalService.cs
  • src/Omika.App/Services/PlatformFileLauncher.cs
  • src/Omika.App/Services/PlatformFilePicker.cs
  • src/Omika.App/ViewModels/InterventionDocumentsViewModel.cs
  • src/Omika.App/Views/InterventionDocumentsView.axaml (+ code-behind)
  • tests/Omika.Tests/Mobile/Local/DocumentLocalServiceTests.cs
  • tests/Omika.Tests/Mobile/Local/DocumentSyncTests.cs
  • tests/Omika.Tests/Mobile/Local/InterventionDocumentsViewModelTests.cs
  • tests/Omika.Tests/Mobile/Local/DocumentDesignSystemTests.cs

Fichiers modifiés (LOT DOCUMENTS / PHOTOS)

  • src/Omika.App.Local/Abstractions/IOutboxRepository.cs (ajout GetRejectedAsync)
  • src/Omika.App.Local/Repositories/OutboxRepository.cs (implémentation GetRejectedAsync)
  • src/Omika.App.Local/Services/DocumentLocalService.cs (lecture des états Rejected)
  • src/Omika.App/Navigation/AppPage.cs (ajout InterventionDocuments)
  • src/Omika.App/Navigation/PageViewModelFactory.cs (cas InterventionDocuments)
  • src/Omika.App/ViewModels/InterventionDetailViewModel.cs (navigation vers Documents)
  • src/Omika.App/Views/InterventionDetailView.axaml (accès Documents réel)
  • src/Omika.App/AppServices.cs (enregistrement du service Documents)
  • tests/Omika.Tests/Mobile/Local/ShellViewModelTests.cs (injection du service Documents dans la factory)
  • tests/Omika.Tests/Mobile/Local/InterventionDetailViewModelTests.cs (navigation Intervention → Documents)

Tests ajoutés (LOT DOCUMENTS / PHOTOS)

  • DocumentLocalServiceTests : liste (documents d'une intervention, aucun document, ordre cohérent, DocumentGuid conservé, metadata invalide ignorée, aucun téléchargement automatique, distinction photo/document), cache (déjà en cache sans HTTP, non cache + online téléchargé, non cache + offline sans HTTP, entrée cache fichier absent invalide, snapshot refresh cache conservé), ouverture offline, ajout (photo/document offline, DocumentGuid généré, OperationId distinct, fichier copié, metadata visible, Outbox créée, redémarrage exploitable, SUCCESS/REJECTED fichier conservé, source temporaire supprimée).
  • DocumentSyncTests : Outbox Document Pending → upload, OperationId conservé au retry, DocumentGuid conservé, fichier manquant sans faux SUCCESS, offline opération conservée, serveur REJECTED état conservé.
  • InterventionDocumentsViewModelTests : chargement, états visuels, actions télécharger/ouvrir/ajouter, navigation.
  • DocumentDesignSystemTests : aucune couleur hexadécimale standard, aucun breakpoint local supplémentaire, réutilisation des styles sémantiques, espacements via DynamicResource.

LOT CONTACTS INTERVENTION

Statut

Ce lot implémente la consultation des contacts réellement liés à une intervention, entièrement depuis le snapshot local SQLite (aucun appel API). Il permet d'ouvrir le détail d'un contact (lecture seule) et de préparer des actions téléphone / email via les abstractions plateforme. Aucune modification d'écriture (création / modification / suppression de contact) n'est développée dans ce lot.

Relation réellement utilisée

La relation utilisée est la liste explicite ContactIds portée par le DTO d'intervention (InterventionDetailsDto), résolue vers les Personnes / Contacts stockées localement (table Personne, payload ContactDto).

  • Aucun contact n'est déduit du Client : seule la relation explicite Intervention → Contact est utilisée.
  • Un contact du même Client mais non listé dans ContactIds n'est jamais associé à l'intervention.
  • Les identités publiques (ContactIds sous forme de GUID) sont résolues vers les LocalPersonne par leur Id (GUID canonique local).

Source SQLite

  • Table Intervention (payload InterventionDetailsDto) : porte ContactIds.
  • Table Personne (payload ContactDto) : porte les données du contact.

Identité publique Contact

L'identité publique du contact est le GUID canonique local (LocalPersonne.Id, ContactDto.Id). Aucun PK HFSQL n'est utilisé ni transporté dans la navigation.

Champs réellement disponibles

Seules les propriétés réellement alimentables depuis le payload ContactDto sont exposées (InterventionContact) :

  • ContactGuid (identité publique)
  • DisplayName (nom affiché, sinon construit prénom + nom)
  • LastName, FirstName, Civilite
  • Email, Phone, Mobile, Fax (si présents dans Coordonnees)
  • Roles (libellés des rôles/fonctions, si présents)
  • ClientGuid (identité publique du client parent, si présente)
  • Actif, EstArchive

Aucune propriété fictive (fonction, service, portable, commentaire) n'est créée.

Fonctionnement offline

L'écran Contacts et le détail Contact fonctionnent entièrement offline : navigation Intervention → Contacts → ContactDetail sans aucun HTTP. Les actions Appeler / Email sont des actions plateforme locales et ne modifient pas la stratégie de synchronisation.

Téléphone / email plateforme

  • IPhoneLauncher / PlatformPhoneLauncher : ouvre le composeur système via un URI tel: (Avalonia TopLevel.Launcher).
  • IEmailLauncher / PlatformEmailLauncher : ouvre le client email système via un URI mailto:.
  • Aucun code Android/iOS dans les ViewModels. Aucune donnée personnelle n'est loguée. Le numéro / l'adresse métier n'est jamais modifié.

Read-only

Ce lot est lecture seule. Aucune Outbox n'est créée pour modifier, ajouter ou supprimer un contact. Les fonctions d'édition viendront plus tard si le besoin métier est confirmé.

Donnée manquante éventuelle

La relation Intervention → Contact est explicitement disponible dans le snapshot (ContactIds). Aucune donnée manquante ne bloque ce lot. Les coordonnées d'un contact peuvent être absentes du payload : dans ce cas, les actions téléphone / email sont simplement indisponibles (commandes désactivées).

Réutilisation Design System

Les Views Contacts et Détail Contact réutilisent exclusivement les styles globaux : card, listTitle, body, bodySmall, caption, badge, badgeInfo, primary, secondary, ghost, emptyState, errorMessage, loadingState. Aucune couleur hexadécimale, aucun breakpoint local supplémentaire, aucun FontSize / CornerRadius / Padding standard répété. Les espacements passent par DynamicResource Space* et ThicknessPage.

Fichiers créés (LOT CONTACTS INTERVENTION)

  • src/Omika.App.Local/Abstractions/IInterventionContactsService.cs
  • src/Omika.App.Local/Abstractions/IPhoneLauncher.cs
  • src/Omika.App.Local/Abstractions/IEmailLauncher.cs
  • src/Omika.App.Local/Models/InterventionContact.cs
  • src/Omika.App.Local/Services/InterventionContactsService.cs
  • src/Omika.App/Services/PlatformPhoneLauncher.cs
  • src/Omika.App/Services/PlatformEmailLauncher.cs
  • src/Omika.App/Navigation/ContactDetailNavigation.cs
  • src/Omika.App/ViewModels/InterventionContactsViewModel.cs
  • src/Omika.App/ViewModels/ContactDetailViewModel.cs
  • src/Omika.App/Views/InterventionContactsView.axaml (+ code-behind)
  • src/Omika.App/Views/ContactDetailView.axaml (+ code-behind)
  • tests/Omika.Tests/Mobile/Local/InterventionContactsServiceTests.cs
  • tests/Omika.Tests/Mobile/Local/InterventionContactsViewModelTests.cs
  • tests/Omika.Tests/Mobile/Local/ContactDetailViewModelTests.cs

Fichiers modifiés (LOT CONTACTS INTERVENTION)

  • src/Omika.App/Navigation/AppPage.cs (ajout InterventionContacts, ContactDetail)
  • src/Omika.App/Navigation/PageViewModelFactory.cs (cas InterventionContacts, ContactDetail)
  • src/Omika.App/ViewModels/InterventionDetailViewModel.cs (navigation vers Contacts)
  • src/Omika.App/Views/InterventionDetailView.axaml (accès Contacts réel)
  • src/Omika.App/AppServices.cs (enregistrement du service Contacts + launchers)
  • tests/Omika.Tests/Mobile/Local/LocalTestBase.cs (exposition PersonneRepository)
  • tests/Omika.Tests/Mobile/Local/InterventionDetailViewModelTests.cs (navigation Intervention → Contacts)
  • tests/Omika.Tests/Mobile/Local/ShellViewModelTests.cs (adaptation du constructeur PageViewModelFactory aux nouveaux paramètres Contacts/launchers)

Tests ajoutés (LOT CONTACTS INTERVENTION)

  • InterventionContactsServiceTests : relation (Intervention A retourne uniquement ses contacts, Intervention B ne retourne pas les contacts de A, contact sans relation non inclus, aucune association basée sur le Client, GUID public conservé), service (un contact, plusieurs contacts triés, aucun contact, contact incomplet, téléphone absent, email absent, relation invalide ignorée, intervention inconnue, offline sans appel API, détail par GUID, payload invalide ignoré).
  • InterventionContactsViewModelTests : chargement, liste chargée, état vide, erreur, ouverture du détail (navigation avec paramètre), paramètre null ignoré, retour.
  • ContactDetailViewModelTests : chargement, contact introuvable, commande téléphone disponible / indisponible, commande email disponible / indisponible, retour à la liste des contacts.
  • InterventionDetailViewModelTests : navigation Intervention → Contacts, intervention null sans navigation.

LOT NOTES INTERVENTION

Statut

Ce lot visait à développer l'écran Notes d'une Intervention (liste, ajout, édition éventuelle) selon le modèle métier réel. Après inspection ciblée du modèle métier, des contrats, du snapshot et du schéma SQLite local, la conclusion est CAS E : aucune donnée Note Intervention exploitable n'existe dans les contrats / snapshot locaux. Conformément à la règle du CAS E, l'implémentation métier est stoppée : aucun écran, aucun service, aucune fausse fonctionnalité n'est créée. Le backend n'est pas modifié.

Représentation métier réelle d'une Note Intervention

Il n'existe pas d'entité métier « Note Intervention » autonome exposée au mobile. Les candidats inspectés sont :

Candidat Existence Exposé au mobile ? Conclusion
CompteRendu (entité canonique + CompteRenduDto) Oui (domaine + contrat) InterventionDetailsDto.ComptesRendus Toujours vide : le repository GetDetailsAsync (Omika/Batik) ne peuple jamais ComptesRendus. Aucun AddCompteRendu dans les repositories.
MessageInterne (table HFSQL) Oui (HFSQL) Non Absent du contrat mobile, du snapshot et du sync.
IN_NoteInterne (colonne Intervention) Oui (HFSQL) Non Marqué « ignoré » dans les mappers (AddIgnored). Absent du contrat mobile.
DocumentNote / NoteDocument Oui (HFSQL + domaine) Non (métadonnées document uniquement) Concerne uniquement les Documents (DocumentId). Distinct des notes d'intervention.

Différence avec DocumentNote

DocumentNote / NoteDocument est une note rattachée à un document (DocumentId). Ce n'est pas une note d'intervention. Il n'est pas utilisé comme remplacement : le CAS D s'applique (ne pas détourner DocumentNote).

Relation Note ↔ Intervention

Aucune relation exploitable n'existe dans le contrat mobile. La table HFSQL MessageInterne porte MI_IDIntervention (relation physique), mais elle n'est pas exposée dans le snapshot ni dans le sync. CompteRendu porte CR_IDIntervention mais n'est jamais peuplé par les repositories.

Champs réellement disponibles

Aucun champ de note d'intervention n'est disponible dans le snapshot local. Les champs de CompteRenduDto (Id, InterventionId, AuteurId, Contenu, DateCreation, DateInterventionDebut/Fin, SignataireIds, StatutValidation, EstArchive) existent dans le contrat mais restent toujours vides car le repository ne les peuple pas.

Source SQLite

Le schéma SQLite local (SqliteSchema) ne contient aucune table Note. Les tables métier sont : Client, Personne, Site, Intervention, Dossier, RendezVous, Collaborateur, DocumentMetadata. Aucune table technique ne porte de note d'intervention.

Lecture offline

Non implémentée : aucune donnée Note Intervention n'est disponible dans SQLite. Aucun écran Notes n'est créé.

CREATE / UPDATE réellement supportés ou non

Non supportés. SyncEntityTypes.Supported ne contient pas de type « Note ». InterventionSyncPayload ne porte aucun champ note. Aucun handler Sync Note n'existe. Aucune création / édition offline n'est possible.

NoteGuid vs OperationId

Sans objet : aucune entité Note autonome n'existe. Aucun NoteGuid n'est fabriqué artificiellement pour l'UI.

Outbox

Non utilisée pour les notes : aucun type d'entité Note n'est déclaré dans SyncEntityTypes. L'Outbox existante reste inchangée.

Réconciliation avec Intervention

Sans objet : aucune Note ne référence une Intervention dans le contrat mobile.

Comportement Rejected

Sans objet : aucune opération Note n'est émise.

Design System

Non utilisé pour un écran Notes (aucun écran créé). Le Design System existant (DesignTokens.axaml, Controls.axaml) reste inchangé.

Différences avec DocumentNote

  • DocumentNote / NoteDocument : note rattachée à un Document (DocumentId).
  • Note Intervention : n'existe pas dans le contrat mobile.
  • DocumentNote n'est jamais utilisé comme note d'intervention.

Donnée manquante et évolution backend minimale nécessaire

Pour rendre les notes d'intervention exploitables au mobile, il faudrait plus tard (hors périmètre de ce lot, sans modification HFSQL) :

  1. Exposer une entité Note Intervention dans le contrat mobile : soit peupler réellement InterventionDetailsDto.ComptesRendus (ou une collection dédiée) dans les repositories GetDetailsAsync (Omika/Batik), soit exposer MessageInterne dans le snapshot.
  2. Déclarer un type d'entité dans SyncEntityTypes (ex. Note) si la création / édition offline est souhaitée.
  3. Ajouter un handler Sync pour le type Note (CREATE / UPDATE) et un payload dédié.
  4. Ajouter une table SQLite locale pour les notes (métier ou technique).

Aucune de ces évolutions n'est réalisée dans ce lot : le backend n'est pas modifié.

Fichiers créés

Aucun (CAS E : pas de fausse fonctionnalité).

Fichiers modifiés

  • docs/LOT_MOBILE_0.md (documentation de ce lot).

Tests ajoutés

Aucun (CAS E : pas de fonctionnalité à tester). La suite unitaire existante reste inchangée.

Validation

  • Suite unitaire : inchangée (aucun code ajouté).
  • Build solution : inchangé.
  • Build Android : inchangé.
  • Aucun backend / HFSQL modifié.

LOT SUIVI DES TEMPS

Statut

Ce lot visait à développer le suivi des temps (saisie, historique, éventuel chronomètre) selon le modèle métier réel. Après inspection ciblée du modèle métier, des contrats, du snapshot, du schéma SQLite local et de la couche Sync, la conclusion est CAS C : il existe une entité métier réelle SuiviTemps, mais elle n'est disponible que côté backend et pas dans le snapshot / local SQLite. Conformément à la règle du CAS C, l'implémentation mobile est stoppée : aucun écran, aucun service, aucune fausse fonctionnalité n'est créée. Le backend n'est pas modifié.

Représentation métier réelle du temps

Il existe une entité métier canonique autonome SuiviTemps (Omika.Core/Domain/Entities/Planning/SuiviTemps.cs), avec relations explicites vers Intervention, RendezVous et Collaborateur. C'est structurellement un CAS A, mais l'entité n'est pas exposée au mobile (CAS C).

Candidat Existence Exposé au mobile ? Conclusion
SuiviTemps (entité canonique) Oui (Omika.Core/Domain/Entities/Planning/SuiviTemps.cs) Non Absent du snapshot, du sync, du schéma SQLite local.
SuiviTempsDto (contrat) Oui (Omika.Core/Contracts/Planning/SuiviTempsDto.cs) Non Jamais retourné par un service ni un endpoint.
CreateSuiviTempsRequest (contrat) Oui (Omika.Core/Contracts/Planning/CreateSuiviTempsRequest.cs) Non Jamais utilisé par un service ni un endpoint.
RendezVousContractMapper.ToSuiviTemps Oui (mapper) Non Défini mais jamais appelé par aucun service.
SuiviDesTempsRow (Batik / Omika) Oui (Omika.Data/HfSql/Models/) Non Modèles physiques HFSQL uniquement. Aucun repository ne les utilise.
RendezVous (entité) Oui Oui (snapshot) Porte TypeActivite (type d'activité du RDV), pas un temps saisi.
AffectationCollaborateur.TempsValide Oui Oui (snapshot) Booléen de validation d'affectation, pas un temps saisi.

Relation Temps ↔ Intervention

L'entité SuiviTemps porte InterventionId (relation explicite). Mais cette relation n'est pas exploitable au mobile : SuiviTemps n'est pas dans le snapshot, ni dans le sync, ni dans SQLite local. Aucune donnée de temps liée à une Intervention n'est disponible localement.

Relation Temps ↔ RendezVous

L'entité SuiviTemps porte RendezVousId (relation explicite). Même constat : non exposée au mobile. Aucune donnée de temps liée à un RendezVous n'est disponible localement.

Relation Temps ↔ Collaborateur

L'entité SuiviTemps porte CollaborateurId (relation explicite). Non exposée au mobile. Le collaborateur authentifié/bootstrap est disponible localement (LocalBootstrap), mais il n'y a aucune donnée de temps à lui associer.

Champs réellement disponibles

Les champs de l'entité SuiviTemps (et du SuiviTempsDto) sont :

  • Id (identité publique)
  • CollaborateurId
  • InterventionId
  • RendezVousId
  • Date
  • DureeMinutes (durée en minutes)
  • TypeTemps (paramètre configurable, ReferenceValue)
  • Description
  • Valide

Ces champs existent dans le contrat mais restent inaccessibles au mobile : aucun service ne les retourne, aucun endpoint ne les expose, aucun snapshot ne les transporte.

Source SQLite

Le schéma SQLite local (SqliteSchema) ne contient aucune table SuiviTemps. Les tables métier sont : Client, Personne, Site, Intervention, Dossier, RendezVous, Collaborateur, DocumentMetadata. Aucune table technique ne porte de suivi des temps.

Identité publique

L'entité SuiviTemps possède une identité publique (Id, GUID). Mais comme l'entité n'est pas exposée au mobile, aucun TimeEntryGuid n'est fabriqué artificiellement pour l'UI. Aucun OperationId de temps n'est généré.

Lecture offline

Non implémentée : aucune donnée de temps n'est disponible dans SQLite. Aucun écran Suivi des Temps n'est créé.

Création offline supportée ou non

Non supportée. SyncEntityTypes.Supported (backend et mobile) ne contient pas de type « SuiviTemps ». Aucun handler Sync SuiviTemps n'existe (seuls DefaultSyncOperationHandler et InterventionSyncOperationHandler sont enregistrés). Aucune création offline n'est possible.

Édition supportée ou non

Non supportée. Aucun UPDATE SuiviTemps n'existe dans la couche Sync/métier.

Suppression / archivage supporté ou non

Non supporté. Aucune sémantique de suppression/archivage SuiviTemps n'est exposée au mobile.

Chronomètre

Non développé. Aucun chronomètre n'est implémenté : le modèle métier réel (SuiviTemps) n'est pas accessible au mobile, et aucun concept de démarrage/arrêt n'est exposé.

Identité métier vs OperationId

Sans objet : aucune entité SuiviTemps autonome n'est exposée au mobile. Aucun TimeEntryGuid ni OperationId de temps n'est fabriqué artificiellement.

Outbox

Non utilisée pour le suivi des temps : aucun type d'entité SuiviTemps n'est déclaré dans SyncEntityTypes. L'Outbox existante reste inchangée.

Réconciliation avec Intervention / RendezVous

Sans objet : aucune donnée de temps ne référence une Intervention ou un RendezVous dans le contrat mobile. MobileIdentityReconciliationService reste inchangé (aucun second mécanisme).

Comportement SUCCESS

Sans objet : aucune opération SuiviTemps n'est émise.

Comportement REJECTED

Sans objet : aucune opération SuiviTemps n'est émise.

ParametreMetier

L'entité SuiviTemps référence un TypeTemps (paramètre configurable, ReferenceValue). Le ParametreMetier existe dans le domaine (Omika.Core/Domain/Entities/Configuration/ParametreMetier.cs) et est utilisé pour le profil Batik. Mais aucun type de temps n'est exposé au mobile : le snapshot ne transporte pas de liste de types de temps, et SuiviTemps n'est pas dans le sync. Aucune liste fictive de catégories n'est créée.

Navigation depuis Intervention / RendezVous

Non implémentée : aucune navigation vers un écran Suivi des Temps n'est ajoutée depuis InterventionDetail ou RendezVousDetail. Aucun AppPage SuiviTemps n'est créé.

Smartphone / Tablette

Non implémenté : aucun écran Suivi des Temps n'est créé.

Design System

Non utilisé pour un écran Suivi des Temps (aucun écran créé). Le Design System existant (DesignTokens.axaml, Controls.axaml) reste inchangé.

Donnée manquante et évolution backend minimale nécessaire

Pour rendre le suivi des temps exploitable au mobile, il faudrait plus tard (hors périmètre de ce lot, sans modification HFSQL) :

  1. Exposer SuiviTemps dans le snapshot mobile : ajouter une collection SuiviTemps dans MobileSnapshotResponse, la charger dans MobileSnapshotService (via un service/repository SuiviTemps à créer), et l'appliquer dans SnapshotApplier (table SQLite locale SuiviTemps).
  2. Déclarer un type d'entité dans SyncEntityTypes (ex. SuiviTemps) si la création / édition offline est souhaitée.
  3. Ajouter un handler Sync pour le type SuiviTemps (CREATE / UPDATE) et un payload dédié.
  4. Créer un service métier SuiviTemps (ISuiviTempsService) et un repository adaptatif (Batik/Omika) qui peuple réellement les données depuis HFSQL.
  5. Ajouter une table SQLite locale pour les temps (métier ou technique).

Aucune de ces évolutions n'est réalisée dans ce lot : le backend n'est pas modifié.

Fichiers créés

Aucun (CAS C : pas de fausse fonctionnalité).

Fichiers modifiés

  • docs/LOT_MOBILE_0.md (documentation de ce lot).

Tests ajoutés

Aucun (CAS C : pas de fonctionnalité à tester). La suite unitaire existante reste inchangée.

Validation

  • Suite unitaire : inchangée (aucun code ajouté).
  • Build solution : inchangé.
  • Build Android : inchangé.
  • Aucun backend / HFSQL modifié.

LOT DOSSIERS MOBILE

Statut

Ce lot développe l'écran Dossiers (liste + détail) entièrement depuis les données locales SQLite (dernier snapshot). Le lot est lecture seule : aucune création, modification, suppression ou archivage de dossier n'est implémenté. Aucune Outbox Dossier n'est créée. Le backend / HFSQL n'est pas modifié.

Source SQLite

Les données affichées proviennent exclusivement du snapshot local SQLite (tables Dossier, Client, Intervention). Aucun appel API n'est déclenché à l'ouverture de Dossiers, DossierDetail ou InterventionDetail. La synchronisation reste exclusivement gérée par le SyncEngine existant.

Champs réellement disponibles

Le modèle d'affichage est construit uniquement à partir des données réellement présentes dans le payload DossierDetailsDto (snapshot local) :

  • ReferenceMetier (référence métier)
  • Titre (libellé)
  • Description
  • ClientId (relation client, résolue localement)
  • Type / Statut / Priorite (libellés ReferenceValueDto)
  • DateOuverture / DateCloture
  • EstArchive

Aucun champ n'est créé parce qu'il apparaît dans la maquette. Les relations optionnelles absentes restent nulles/vides sans provoquer d'erreur.

Identité publique

L'identité utilisée est exclusivement le GUID public local (DossierGuid, InterventionGuid, ClientGuid). Aucun PK HFSQL n'est utilisé. Navigation :

  • Dossiers → DossierDetail(DossierGuid)
  • DossierDetail → InterventionDetail(InterventionGuid)

Relation Dossier → Intervention

La relation principale est portée par DossierId sur l'intervention (table Intervention). Aucune relation n'est déduite du Client. Le détail d'un dossier affiche uniquement les interventions dont DossierId correspond au dossier courant. Les interventions d'un autre dossier sont exclues.

Client associé

Si le dossier possède un ClientId, le client est résolu localement (table Client) et son nom / code sont affichés. Un dossier sans client reste un dossier valide (aucune erreur). Aucune grosse fiche Client n'est créée dans ce lot.

Recherche locale

La liste des dossiers propose une recherche 100 % locale, insensible à la casse, portant sur les champs réellement disponibles : référence, titre et nom du client. Aucun HTTP. Aucun moteur de recherche complexe.

Filtres

Aucun filtre supplémentaire n'est ajouté : le modèle ne porte pas de propriété métier justifiant un filtre dédié (actif/archivé, état, client) exploitable de façon fiable dans ce lot. La recherche locale couvre le besoin.

Tri

Tri déterministe : date d'ouverture (décroissante, si disponible), puis référence, puis titre (insensible à la casse). Aucun ordre métier fictif.

Fonctionnement offline

Tout le lot fonctionne depuis SQLite. Ouvrir Dossiers, DossierDetail ou InterventionDetail ne déclenche aucun HTTP obligatoire. Aucun nouveau mécanisme de refresh n'est créé : la synchronisation reste gérée par le SyncEngine existant.

Read-only

Ce lot est lecture seule. Aucune Outbox Dossier n'est créée. Aucun CREATE / UPDATE / DELETE / ARCHIVE pour Dossier.

États UI

  • Liste : Loading, liste, EmptyState, ErrorState.
  • Détail : Loading, Dossier introuvable, détail, ErrorState.

Les composants du Design System sont réutilisés (emptyState, errorMessage, card, listTitle, body, bodySmall, caption, badge, ghost).

Smartphone / Tablette

Même ViewModel pour toutes les tailles. Sur smartphone, la recherche est facilement accessible, les cartes sont tactiles, la référence/libellé sont immédiatement lisibles et les interventions liées sont présentées en liste simple. Sur tablette/desktop, l'espace supplémentaire est utilisé pour afficher davantage de détails, sans seconde logique métier. Breakpoints globaux uniquement.

Design System

Les Views Dossiers et Détail Dossier réutilisent exclusivement les styles globaux : pageHeader, pageTitle, pageSubtitle, section, sectionTitle, card, listTitle, body, bodySmall, caption, badge, ghost, emptyState, errorMessage, field. Aucune couleur hexadécimale, aucun breakpoint local, aucun FontSize / CornerRadius / Padding standard répété. Les espacements passent par DynamicResource Space* et ThicknessPage.

Fichiers créés (LOT DOSSIERS MOBILE)

  • src/Omika.App.Local/Abstractions/IDossierLocalService.cs
  • src/Omika.App.Local/Models/DossierDetail.cs
  • src/Omika.App.Local/Services/DossierLocalService.cs
  • src/Omika.App/ViewModels/DossiersViewModel.cs
  • src/Omika.App/ViewModels/DossierDetailViewModel.cs
  • src/Omika.App/Views/DossiersView.axaml (+ code-behind)
  • src/Omika.App/Views/DossierDetailView.axaml (+ code-behind)
  • tests/Omika.Tests/Mobile/Local/DossierLocalServiceTests.cs
  • tests/Omika.Tests/Mobile/Local/DossiersViewModelTests.cs
  • tests/Omika.Tests/Mobile/Local/DossierDetailViewModelTests.cs

Fichiers modifiés (LOT DOSSIERS MOBILE)

  • src/Omika.App/Navigation/AppPage.cs (ajout DossierDetail)
  • src/Omika.App/Navigation/PageViewModelFactory.cs (cas Dossiers, DossierDetail)
  • src/Omika.App/Navigation/NavigationService.cs (item de navigation Dossiers)
  • src/Omika.App/AppServices.cs (enregistrement IDossierLocalService)
  • docs/LOT_MOBILE_0.md (documentation de ce lot)

Tests ajoutés (LOT DOSSIERS MOBILE)

  • DossierLocalServiceTests : liste (aucun dossier, un dossier, plusieurs dossiers, tri déterministe, recherche locale, payload invalide ignoré, offline sans appel API, identité publique conservée, client résolu) et détail (dossier trouvé, inconnu, client résolu, sans client, une intervention liée, plusieurs interventions liées, intervention d'un autre dossier exclue, relation optionnelle absente sans crash, GUID publics conservés).
  • DossiersViewModelTests : loading, liste, empty, erreur, recherche, sélection Dossier, navigation DossierDetail avec DossierGuid, aucun appel API.
  • DossierDetailViewModelTests : chargement, Dossier introuvable, données affichées, interventions liées, navigation vers InterventionDetail, retour, état sans intervention, client résolu / absent.

Validation

  • Tests DossierLocalService : OK.
  • Tests DossiersViewModel : OK.
  • Tests DossierDetailViewModel : OK.
  • Tests navigation : OK.
  • Tests Mobile complets : OK.
  • Suite unitaire : OK (hors 10 intégrations HFSQL/OLEDB connues).
  • Build solution Omika.slnx : OK.
  • Build Android : OK.
  • Aucun backend / HFSQL modifié.

LOT CLIENTS MOBILE

Statut

Ce lot est VALIDÉ. Il développe la liste et la fiche Client en lecture seule, entièrement depuis le snapshot SQLite local. Aucun backend / HFSQL n'est modifié.

Source SQLite

Les clients sont lus exclusivement depuis la table locale Client (dernier snapshot). Le payload JSON est désérialisé en ClientDetailsDto. Aucun appel API n'est déclenché lors de la navigation ni du chargement.

DTO Client réel

Le DTO utilisé est Omika.Core.Contracts.Clients.ClientDetailsDto. Les champs réellement exploités sont ceux présents dans le snapshot : Id, Code, Nom, Type, Statut, FormeJuridique, Siren, Siret, Pays, Coordonnees (Email, Phone, Mobile, Fax), AdressePrincipale, InformationsComplementaires, EstArchive, Sites, Contacts.

Champs affichés

Liste (ClientListItem) :

  • Nom / raison sociale (Nom)
  • Code (Code)
  • Statut (Statut)
  • Ville (AdressePrincipale.AdressePostale.City)
  • Téléphone (Coordonnees.Phone ou Mobile)
  • Email (Coordonnees.Email)

Fiche (ClientDetail) : en-tête (Nom, Code, Statut, Type, FormeJuridique, Siren, Siret, Pays), coordonnées (Email, Phone, Mobile, Fax, adresse principale) et relations. Les champs optionnels absents restent nuls sans erreur.

Identité publique

La navigation utilise exclusivement ClientGuid (GUID canonique local). Aucun PK HFSQL n'est transporté. Les relations associées utilisent leurs GUID publics (ContactGuid, DossierGuid, InterventionGuid, SiteGuid).

Recherche

Recherche locale insensible à la casse sur les champs réellement disponibles : Nom, Code, Ville, Statut. Aucun HTTP.

Filtres

Aucun filtre métier artificiel. Le tri est déterministe : nom (insensible à la casse) puis code.

Relations réellement disponibles

Chaque relation est démontrée localement, jamais déduite par commodité :

  • Client → Contacts : liste Contacts embarquée dans le DTO Client. Un contact sans rattachement n'est pas ajouté. Ne pas confondre avec les contacts explicitement liés à une Intervention (contexte distinct).
  • Client → Sites : liste Sites embarquée dans le DTO Client.
  • Client → Dossiers : DossierDetailsDto.ClientId == ClientGuid.
  • Client → Interventions : InterventionDetailsDto.ClientId == ClientGuid.

Aucune relation transitive (via Dossier ou Site) n'est utilisée.

Navigation

  • Clients → ClientDetail(ClientGuid)
  • Client → Contact : ContactDetail via ClientContactDetailNavigation (réutilise l'écran Contact existant, retour vers la fiche Client).
  • Client → Dossier : DossierDetail(DossierGuid) (écran existant).
  • Client → Intervention : InterventionDetail(InterventionGuid) (écran existant).
  • Client → Site : aucun écran Site réel n'existe encore ; le lot Sites viendra ensuite. Les sites sont affichés en lecture seule dans la fiche Client.

Téléphone / Email

Pour le Client lui-même, les launchers génériques existants sont réutilisés (IPhoneLauncher, IEmailLauncher). La commande est désactivée si la donnée est absente. Aucune donnée personnelle n'est loguée inutilement.

Adresse / Carte

Aucune cartographie n'est développée dans ce lot. Si une adresse réelle existe, elle est affichée. Aucune géolocalisation, itinéraire ou API Maps.

Fonctionnement offline

ClientsView, ClientDetail, ContactDetail, DossierDetail, InterventionDetail restent utilisables depuis SQLite. Aucun HTTP obligatoire. Le SyncEngine existant reste le seul mécanisme de mise à jour.

Read-only

Aucune Outbox Client. Aucune création, modification, suppression ou archivage de Client. Ce lot est strictement en lecture.

États UI

Liste : Loading, liste, EmptyState, ErrorState. Détail : Loading, Client introuvable, détail, relations vides, ErrorState. Une collection associée vide n'est pas une erreur.

Smartphone / Tablette

Même ViewModel pour toutes les tailles. Sur smartphone, la recherche est facilement accessible, le nom du client immédiatement lisible, les coordonnées essentielles accessibles, les sections empilées et les collections liées compactes. Sur tablette/desktop, l'espace supplémentaire est exploité (coordonnées d'un côté, relations de l'autre) sans seconde logique métier. Breakpoints globaux uniquement.

Design System

Les Views Clients et Détail Client réutilisent exclusivement les styles globaux (pageHeader, pageTitle, pageSubtitle, section, sectionTitle, card, listTitle, body, bodySmall, caption, badge, ghost, emptyState, errorMessage, field). Aucune couleur hexadécimale, aucun breakpoint local, aucun FontSize / CornerRadius / Padding standard répété. Les espacements passent par DynamicResource Space* et ThicknessPage.

Fichiers créés (LOT CLIENTS MOBILE)

  • src/Omika.App.Local/Abstractions/IClientLocalService.cs
  • src/Omika.App.Local/Models/ClientDetail.cs
  • src/Omika.App.Local/Services/ClientLocalService.cs
  • src/Omika.App/ViewModels/ClientsViewModel.cs
  • src/Omika.App/ViewModels/ClientDetailViewModel.cs
  • src/Omika.App/Views/ClientsView.axaml (+ code-behind)
  • src/Omika.App/Views/ClientDetailView.axaml (+ code-behind)
  • src/Omika.App/Navigation/ClientContactDetailNavigation.cs
  • tests/Omika.Tests/Mobile/Local/ClientLocalServiceTests.cs
  • tests/Omika.Tests/Mobile/Local/ClientsViewModelTests.cs
  • tests/Omika.Tests/Mobile/Local/ClientDetailViewModelTests.cs

Fichiers modifiés (LOT CLIENTS MOBILE)

  • src/Omika.App/Navigation/AppPage.cs (ajout ClientDetail)
  • src/Omika.App/Navigation/PageViewModelFactory.cs (cas Clients, ClientDetail)
  • src/Omika.App/Navigation/NavigationService.cs (item de navigation Clients)
  • src/Omika.App/AppServices.cs (enregistrement IClientLocalService)
  • src/Omika.App/ViewModels/ContactDetailViewModel.cs (gestion du retour depuis la fiche Client via ClientContactDetailNavigation)
  • docs/LOT_MOBILE_0.md (documentation de ce lot)

Tests ajoutés (LOT CLIENTS MOBILE)

  • ClientLocalServiceTests : liste (aucun client, un client, plusieurs clients, tri déterministe, recherche locale, payload invalide ignoré, identité publique conservée, offline sans appel API) et détail (client trouvé, inconnu, données réelles chargées, champs optionnels absents, aucune relation sans erreur, identité publique conservée). Relations : Client → Contacts (uniquement ses contacts, contact du client B exclu, contact sans client exclu), Client → Dossiers (uniquement ses dossiers, dossier B exclu), Client → Interventions (uniquement ses interventions, intervention B exclue), Client → Sites (sites embarqués, client sans sites).
  • ClientsViewModelTests : loading, liste, empty, erreur, recherche (nom, ville, sans résultat), sélection Client, navigation ClientDetail avec ClientGuid, aucun appel API.
  • ClientDetailViewModelTests : chargement, client introuvable, coordonnées, contacts, dossiers, interventions, sites, relations vides sans erreur, navigation Contact (ClientContactDetailNavigation), navigation Dossier, navigation Intervention, retour, identité publique conservée.

Validation

  • Tests ClientLocalService : OK.
  • Tests relations Client : OK.
  • Tests ClientsViewModel : OK.
  • Tests ClientDetailViewModel : OK.
  • Tests navigation : OK.
  • Tests Mobile complets : OK.
  • Suite unitaire : OK (hors 10 intégrations HFSQL/OLEDB connues).
  • Build solution Omika.slnx : OK.
  • Build Android : OK.
  • Aucun backend / HFSQL modifié.

Données manquantes éventuelles

  • Aucun écran Site réel n'existe encore : les sites sont affichés en lecture seule dans la fiche Client ; le lot Sites viendra ensuite.
  • Les contacts du Client sont affichés depuis la liste embarquée dans le DTO Client ; le détail Contact réutilise l'écran existant qui lit la table Personne.

LOT SITES / LIEUX MOBILE

Statut

Ce lot est VALIDÉ. Il développe la liste et la fiche des Sites / Lieux, entièrement en lecture seule depuis le snapshot SQLite local. Aucun backend, aucune API, aucun snapshot ni aucune relation artificielle n'a été modifié.

Terminologie retenue

Le mobile manipule le concept métier commun Site / Lieu. Les structures physiques historiques (ClientLieu, LieuPersonne, PK HFSQL, différences Batik/Omika) ne sont jamais exposées dans les Views / ViewModels. Le mobile n'utilise que les abstractions locales existantes : Client, Site, Contact, Intervention.

Source de vérité

La source métier est exclusivement :

  • les contrats réels (SiteDetailsDto, ClientDetailsDto, ContactDto, InterventionDetailsDto) ;
  • le snapshot SQLite local ;
  • les relations explicitement disponibles dans ces contrats.

maquetteOmika.html sert uniquement de référence UX. Aucun champ ni relation n'a été créé depuis la maquette.

DTO / modèle local

  • DTO source : SiteDetailsDto (Omika.Core.Contracts.Sites).
  • Modèle de présentation : SiteListItem (liste) et SiteDetail (détail) dans Omika.App.Local.Models.
  • Payload local : table Site (colonne PayloadJson), relation ClientId.

Champs affichés

Liste (SiteListItem) :

  • Nom
  • Code
  • Type (libellé)
  • Ville (depuis l'adresse)
  • ClientNom (si relation résolue)

Détail (SiteDetail) :

  • Nom, Code, Type
  • InformationsComplementaires, EstArchive
  • Adresse (composée depuis les champs réels du DTO)
  • ClientGuid, ClientNom, ClientCode
  • Contacts (liste SiteContact)
  • Interventions (liste SiteIntervention)

Aucune propriété UI toujours nulle n'a été créée pour reproduire la maquette.

Identité publique

Navigation et relations utilisent exclusivement les GUID publics locaux :

  • Sites → SiteDetail(SiteGuid)
  • Site → Client : ClientGuid
  • Site → Contact : ContactGuid
  • Site → Intervention : InterventionGuid

Jamais de PK HFSQL.

Recherche / filtres

  • Recherche locale insensible à la casse sur : Nom, Code, Ville, ClientNom (propriétés réellement disponibles).
  • Aucun filtre supplémentaire : aucun filtre n'était justifié par une donnée métier réelle dans ce lot.

Tri

Tri déterministe : Nom (insensible à la casse) puis Code.

Relation Site → Client

Relation explicite SiteDetailsDto.ClientId (CAS A). Le détail du site résout le client local correspondant. Cardinalité constatée : un Site peut être lié à un Client via ClientId ; le modèle ne suppose pas de correspondance par nom/adresse/code. Si le ClientId ne correspond à aucun client local, le client reste absent (pas d'invention).

Relation Site → Contacts

Relation explicite SiteDetailsDto.ContactIds (liste de GUID), résolue vers les Personnes/Contacts stockées localement. Un Contact du Client n'est pas automatiquement un Contact du Site : seuls les contacts listés dans ContactIds sont affichés. Si la liste est absente/vide, aucune fausse liste Contacts n'est affichée.

Relation Site → Interventions

Relation directe InterventionDetailsDto.SiteId. Les interventions du site sont celles dont SiteId == SiteGuid. Aucune déduction via Client, Dossier, adresse ou RendezVous.

Relation Site → RendezVous

Aucune section RendezVous n'a été ajoutée dans ce lot : les RendezVous restent accessibles naturellement via la fiche Intervention. Aucune relation directe Site → RendezVous n'est chargée pour éviter de surcharger les relations.

Navigations

  • Site → Client : ClientDetail(ClientGuid) (réutilise l'écran existant).
  • Site → Contact : ContactDetail(SiteContactDetailNavigation) (réutilise l'écran existant).
  • Site → Intervention : InterventionDetail(InterventionGuid) (réutilise l'écran existant).

Téléphone / email

Le Site ne transporte pas de coordonnées propres dans ce lot (le DTO Site ne porte pas de téléphone/email dédié). Aucune coordonnée du Client n'est déduite comme coordonnée du Site. Les launchers IPhoneLauncher / IEmailLauncher restent disponibles pour les contacts liés.

Adresse

L'adresse est affichée uniquement depuis les données réelles du DTO Site (AddressDto), composée dans SiteAdresse (modèle de présentation). La présentation sur une ligne (OneLine) n'est pas stockée comme donnée métier.

Carte / itinéraire

Aucune carte ni dépendance Maps n'a été introduite dans ce lot. Affichage texte de l'adresse uniquement. La navigation GPS reste un lot séparé.

Offline

SitesView, SiteDetail, ClientDetail, ContactDetail et InterventionDetail fonctionnent depuis SQLite. Aucun HTTP obligatoire. SyncEngine reste l'unique mécanisme de mise à jour.

États UI

Liste : Loading, liste, EmptyState, ErrorState.

Détail : Loading, Site inconnu, détail, collections associées vides, ErrorState. Une relation absente n'est pas une erreur générale.

Smartphone / Tablette / Desktop

Même ViewModel pour toutes les tailles. Smartphone : sections empilées, grandes zones tactiles. Tablette/Desktop : l'espace supplémentaire est utilisé via les sections (coordonnées, client, relations). Breakpoints globaux uniquement.

Design System

Les Views SitesView et SiteDetailView réutilisent exclusivement les styles globaux : pageHeader, pageTitle, pageSubtitle, section, sectionTitle, card, listTitle, body, bodySmall, caption, badge, ghost, emptyState, errorMessage, field. Aucune couleur hexadécimale, aucun FontSize/CornerRadius/Padding standard répété, aucun breakpoint local.

Read-only

Aucune Outbox Site. Aucun CREATE / UPDATE / DELETE / ARCHIVE pour Site.

Fichiers créés (LOT SITES / LIEUX MOBILE)

  • src/Omika.App.Local/Models/SiteDetail.cs
  • src/Omika.App.Local/Abstractions/ISiteLocalService.cs
  • src/Omika.App.Local/Services/SiteLocalService.cs
  • src/Omika.App/ViewModels/SitesViewModel.cs
  • src/Omika.App/ViewModels/SiteDetailViewModel.cs
  • src/Omika.App/Navigation/SiteContactDetailNavigation.cs
  • src/Omika.App/Views/SitesView.axaml (+ code-behind)
  • src/Omika.App/Views/SiteDetailView.axaml (+ code-behind)
  • tests/Omika.Tests/Mobile/Local/SiteLocalServiceTests.cs
  • tests/Omika.Tests/Mobile/Local/SitesViewModelTests.cs
  • tests/Omika.Tests/Mobile/Local/SiteDetailViewModelTests.cs

Fichiers modifiés (LOT SITES / LIEUX MOBILE)

  • src/Omika.App/Navigation/AppPage.cs (ajout SiteDetail)
  • src/Omika.App/Navigation/PageViewModelFactory.cs (cas Sites, SiteDetail, gestion SiteContactDetailNavigation dans CreateContactDetail)
  • src/Omika.App/Navigation/NavigationService.cs (item de navigation Sites)
  • src/Omika.App/AppServices.cs (enregistrement ISiteLocalService)
  • src/Omika.App/ViewModels/ContactDetailViewModel.cs (gestion du retour depuis la fiche Site via SiteContactDetailNavigation)
  • docs/LOT_MOBILE_0.md (documentation de ce lot)

Tests ajoutés (LOT SITES / LIEUX MOBILE)

  • SiteLocalServiceTests : liste (aucun site, un site, plusieurs sites, tri déterministe, recherche locale, payload invalide ignoré, identité publique conservée, offline sans appel API) et détail (site trouvé, inconnu, données réelles chargées, champs optionnels absents, aucune relation sans erreur, identité publique conservée). Relations : Site → Client (bon client, client sans site exclu, aucune correspondance par nom/adresse, site sans client valide), Site → Contacts (uniquement ses contacts, contact du même client mais autre site exclu, contact non relié exclu, GUID public conservé), Site → Interventions (intervention du site incluse, intervention d'un autre site exclue, intervention sans site exclue, aucune déduction via Client, GUID public conservé).
  • SitesViewModelTests : loading, liste, empty, erreur, recherche (nom, ville, sans résultat), sélection Site, navigation SiteDetail avec SiteGuid, aucun appel API.
  • SiteDetailViewModelTests : chargement, site introuvable, coordonnées, client lié, site sans client, contacts, interventions, relations vides sans erreur, navigation Client, navigation Contact (SiteContactDetailNavigation), navigation Intervention, retour, identité publique conservée.

Validation

  • Tests SiteLocalService : OK.
  • Tests relations Site : OK.
  • Tests SitesViewModel : OK.
  • Tests SiteDetailViewModel : OK.
  • Tests navigation : OK.
  • Tests Mobile complets : OK.
  • Suite unitaire : OK (hors 10 intégrations HFSQL/OLEDB connues).
  • Build solution Omika.slnx : OK.
  • Build Android : OK.
  • Aucun backend / HFSQL modifié.

Relations éventuellement absentes

  • Le DTO Site ne transporte pas de téléphone/email dédié : aucune coordonnée propre au Site n'est affichée (les coordonnées du Client ne sont pas déduites).
  • Aucune relation Site → RendezVous directe n'est chargée dans ce lot.
  • Les contacts du Site sont affichés uniquement depuis la liste explicite ContactIds du DTO Site ; si cette relation n'est pas peuplée dans le snapshot, aucune fausse liste Contacts n'est affichée.

LOT ÉQUIPE / COLLABORATEURS — LISTE + DÉTAIL

Statut

Ce lot est VALIDÉ. Il développe la section Équipe / Collaborateurs du mobile en lecture seule, entièrement depuis le snapshot SQLite local, sans aucun appel HTTP et sans modification backend/HFSQL.

DTO réel

  • CollaborateurDetailsDto (Omika.Core.Contracts.Organisation) : détail d'un collaborateur. Champs disponibles : Code, Nom, Prenom, NomAffiche, Coordonnees (ContactDetailsDto : Email, Phone, Mobile, Fax), Civilite (ReferenceValueDto), Statut (ReferenceValueDto), CouleurPlanning, Actif, EstArchive.
  • RendezVousDetailsDto (Omika.Core.Contracts.Planning) : porte la relation Collaborateur → RendezVous via la liste Collaborateurs (AffectationCollaborateurDto.CollaborateurId).
  • InterventionDetailsDto (Omika.Core.Contracts.Interventions) : porte la relation Collaborateur → Intervention via la liste CollaborateurIds.

Champs affichés

  • Liste : DisplayName (construit dans la couche présentation à partir de NomAffiche, sinon Prenom + Nom, avec fallback propre), Code, Fonction (libellé Statut), Email, Telephone (fixe sinon mobile), Actif, EstArchive.
  • Détail : Code, Nom, Prenom, NomAffiche, Civilite, Statut, CouleurPlanning, Actif, EstArchive, coordonnées (Email, Phone, Mobile, Fax), relations RendezVous et Interventions.

Identité publique

Navigation exclusivement par CollaborateurGuid (GUID canonique local). Aucun PK HFSQL n'est transporté. Aucun DTO de navigation spécialisé (EquipeCollaborateurNavigation, PlanningCollaborateurNavigation) n'est créé : le mécanisme consolidé DetailNavigation / NavigationContext est réutilisé.

Recherche / filtres

  • Recherche locale insensible à la casse sur DisplayName, Code, Fonction, Email (champs réellement disponibles). Aucun HTTP.
  • Aucun filtre inventé (Disponible / Occupé / En intervention) : seuls les champs réels sont utilisés.

Relation Collaborateur → RendezVous

Relation réellement démontrable via RendezVousDetailsDto.Collaborateurs (AffectationCollaborateurDto.CollaborateurId). Un rendez-vous apparaît pour chaque collaborateur explicitement associé (cardinalité multiple respectée). Tri chronologique croissant. Tap → RendezVousDetail avec DetailNavigation(RendezVousGuid, NavigationContext(CollaborateurDetail, CollaborateurGuid)).

Relation Collaborateur → Intervention

Relation réellement démontrable via InterventionDetailsDto.CollaborateurIds. Aucune déduction transitive (Collaborateur → RendezVous → Intervention) n'est faite. Tap → InterventionDetail avec DetailNavigation(InterventionGuid, NavigationContext(CollaborateurDetail, CollaborateurGuid)).

Navigation contextuelle

  • Équipe → CollaborateurDetail(CollaborateurGuid).
  • CollaborateurDetail → RendezVousDetail → Retour → CollaborateurDetail.
  • CollaborateurDetail → InterventionDetail → Retour → CollaborateurDetail.
  • Aucun flag IsFromX ni DTO de retour spécialisé n'est réintroduit.

Offline / read-only

  • CollaborateursView, CollaborateurDetailView, RendezVousDetail, InterventionDetail fonctionnent depuis SQLite. Aucun appel HTTP obligatoire.
  • SyncEngine reste l'unique système de mise à jour.
  • Aucune Outbox Collaborateur. Aucun CREATE/UPDATE/DELETE/ARCHIVE : lecture seule.

Design System

  • Aucune couleur hex standard, aucun breakpoint local, aucun FontSize standard, aucun CornerRadius/Padding standard répété dans les Views.
  • Styles sémantiques existants réutilisés. Avatar neutre / initiales générées localement (présentation UI, pas donnée métier). AvatarSize centralisé.

Fichiers créés

  • src/Omika.App.Local/Models/CollaborateurDetail.cs (modèles de présentation)
  • src/Omika.App.Local/Abstractions/ICollaborateurLocalService.cs
  • src/Omika.App.Local/Services/CollaborateurLocalService.cs
  • src/Omika.App/ViewModels/CollaborateursViewModel.cs
  • src/Omika.App/ViewModels/CollaborateurDetailViewModel.cs
  • src/Omika.App/Views/CollaborateursView.axaml (+ .cs)
  • src/Omika.App/Views/CollaborateurDetailView.axaml (+ .cs)
  • tests/Omika.Tests/Mobile/Local/CollaborateurLocalServiceTests.cs
  • tests/Omika.Tests/Mobile/Local/CollaborateursViewModelTests.cs
  • tests/Omika.Tests/Mobile/Local/CollaborateurDetailViewModelTests.cs

Fichiers modifiés

  • src/Omika.App/Navigation/AppPage.cs (ajout CollaborateurDetail)
  • src/Omika.App/Navigation/PageViewModelFactory.cs (cas Collaborateurs, CollaborateurDetail)
  • src/Omika.App/AppServices.cs (enregistrement ICollaborateurLocalService)
  • tests/Omika.Tests/Mobile/Local/LocalTestBase.cs (ajout CollaborateurRepository)
  • docs/LOT_MOBILE_0.md (documentation de ce lot)

Tests ajoutés

  • CollaborateurLocalServiceTests : liste (aucun, un, plusieurs, tri déterministe, recherche locale, payload invalide ignoré, identité publique conservée, offline sans appel API) et détail (trouvé, inconnu, données réelles chargées, champs optionnels absents, identité publique conservée, relations vides sans erreur). Relations : Collaborateur → RendezVous (uniquement ses rendez-vous, rendez-vous d'un autre collaborateur exclu, rendez-vous sans relation exclu, cardinalité multiple respectée, tri chronologique, GUID public conservé), Collaborateur → Interventions (uniquement ses interventions, intervention d'un autre collaborateur exclue, aucune déduction transitive, GUID public conservé).
  • CollaborateursViewModelTests : loading, liste, empty, erreur, recherche (nom, sans résultat, réinitialisation), sélection Collaborateur, navigation CollaborateurDetail avec CollaborateurGuid, aucun appel API.
  • CollaborateurDetailViewModelTests : chargement, collaborateur introuvable, coordonnées, champs optionnels absents, RendezVous, Interventions, relations vides sans erreur, téléphone/email (disponible et absent), navigation RendezVous, navigation Intervention, retour, contexte retour conservé.

Validation

  • Tests CollaborateurLocalService : OK.
  • Tests relations Collaborateur : OK.
  • Tests CollaborateursViewModel : OK.
  • Tests CollaborateurDetailViewModel : OK.
  • Tests navigation contextuelle : OK.
  • Tests Mobile complets : OK.
  • Suite unitaire : OK (hors intégrations HFSQL/OLEDB connues).
  • Build solution Omika.slnx : OK.
  • Build Android : OK.
  • Aucun backend / HFSQL modifié.

Relations éventuellement absentes

  • La relation Collaborateur → RendezVous est portée par RendezVousDetailsDto.Collaborateurs ; si cette liste n'est pas peuplée dans le snapshot, aucune fausse liste RendezVous n'est affichée.
  • La relation Collaborateur → Intervention est portée par InterventionDetailsDto.CollaborateurIds ; si cette liste n'est pas peuplée dans le snapshot, aucune fausse liste Interventions n'est affichée.
  • Aucune relation n'est déduite par transitivité (Collaborateur → RendezVous → Intervention) ni par commodité.
  • Aucun poste, fonction, équipe, disponibilité, statut, avatar, compétence, téléphone ou email n'est créé si le DTO réel ne le contient pas.

LOT INTERVENTIONS — LISTE

Statut

VALIDÉ — lecture seule depuis SQLite, recherche locale, filtre Actif/Archivé, navigation contextuelle vers la fiche Intervention.

Objectif

Afficher la liste des interventions disponibles dans le snapshot local, les rechercher localement, filtrer les archivées et ouvrir la fiche Intervention par identité publique. Fonctionne totalement offline, sans appel API.

Architecture

InterventionsView
→ InterventionsViewModel
→ IInterventionLocalService
→ repositories SQLite
  • InterventionsView.axaml / .axaml.cs : vue de la liste.
  • InterventionsViewModel : chargement, recherche, filtre, navigation.
  • IInterventionLocalService.GetInterventionsAsync() : lecture locale.
  • InterventionLocalService : agrégation depuis les repositories SQLite (Intervention, Client, Site, Dossier, Collaborateur).

DTO / modèle local

  • DTO source : InterventionDetailsDto (Omika.Core.Contracts.Interventions).
  • Modèle local : LocalIntervention (payload JSON + colonnes relationnelles).
  • Modèle de présentation : InterventionListItem.

Champs affichés

Uniquement les champs réellement disponibles dans le DTO :

  • Reference
  • Titre
  • Etat (libellé)
  • EstArchive (filtre)
  • DatePrevueDebut (tri)
  • Relations résolues localement : ClientNom, SiteNom, DossierReference, Collaborateurs (noms des collaborateurs affectés).

Identité publique

  • InterventionGuid (GUID public) utilisé exclusivement pour la navigation.
  • Jamais de PK HFSQL.

Recherche

  • Recherche locale insensible à la casse sur Reference et Titre.
  • Aucun HTTP.

Filtres

  • Filtre Actif / Archivé basé sur EstArchive (donnée réelle du DTO).
  • Par défaut, les interventions archivées sont masquées.

Tri

  • Tri déterministe par DatePrevueDebut puis Reference.

Navigation contextuelle

  • InterventionsView → InterventionDetail(InterventionGuid) via DetailNavigation(InterventionGuid, NavigationContext(AppPage.Interventions, null)).
  • Retour contextuel vers la liste Interventions via NavigationContext.
  • Aucun flag IsFromX ni DTO de navigation spécialisé.

Offline

  • Lecture uniquement depuis SQLite.
  • Aucun appel HTTP obligatoire.
  • SyncEngine reste l'unique système de mise à jour.

Read-only

  • Aucune Outbox Intervention.
  • Aucun CREATE / UPDATE / DELETE / ARCHIVE.
  • Lot strictement lecture.

Design System

  • Réutilise les styles sémantiques existants (pageHeader, pageTitle, field, ghost, listTitle, caption, bodySmall, emptyState, errorMessage).
  • Aucune couleur hex, FontSize, CornerRadius, Padding standard en dur.
  • Aucun breakpoint local.

États UI

  • Loading
  • Liste
  • EmptyState
  • ErrorState

Tests ajoutés

  • InterventionLocalServiceTests : aucun, un, plusieurs (tri déterministe), payload invalide ignoré, GUID public conservé, relations résolues, flag archivé conservé.
  • InterventionsViewModelTests : loading, liste, empty, erreur (payload invalide), recherche (référence, sans résultat), filtre Actif/Archivé, navigation InterventionDetail avec GUID public et retour contextuel, aucun appel API.
  • ShellViewModelTests : mise à jour du test de navigation pour vérifier que AppPage.Interventions retourne un InterventionsViewModel.

Validation

  • Tests InterventionLocalService : OK.
  • Tests InterventionsViewModel : OK.
  • Tests navigation contextuelle : OK.
  • Tests Mobile complets : OK.
  • Suite unitaire : OK (hors intégrations HFSQL/OLEDB connues).
  • Build solution Omika.slnx : OK.
  • Build Android : OK.
  • Aucun backend / HFSQL modifié.