2267 lines
102 KiB
Markdown
2267 lines
102 KiB
Markdown
# 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é.
|
|
|
|
|
|
|