# Module de Synchronisation Odoo-to-Odoo ## Objectif Ce module permet la synchronisation bidirectionnelle de données entre deux instances Odoo via XML-RPC, avec un système de validation et de reprise robuste. ## Architecture ### Flux Global ```mermaid %%{init: {'theme': 'neutral'}}%% flowchart TD A[Instance Source] -->|1. Détection\nModification| B(SyncManager) B -->|2. Enqueue| C[(SyncQueue)] C -->|3. Worker| D{Connecteur\nDestination} D -->|4a. Succès| E[(SyncLog)] D -->|4b. Erreur| F[Retry Policy] F -->|Retry| C E --> G[Dashboards] ``` ### Séquence de Synchronisation ```mermaid sequenceDiagram participant Source as Instance A participant Manager as SyncManager participant Queue as SyncQueue participant Dest as Instance B Source->>Manager: Notify Change (webhook) Manager->>Queue: Create SyncRecord loop Worker Process Queue->>Manager: Dequeue Manager->>Dest: Prepare Payload Dest-->>Manager: Transform/Validate Manager->>Dest: Apply Changes Dest-->>Manager: ACK/NACK alt Success Manager->>Queue: Mark Success Manager->>Log: Write Audit else Error Manager->>Queue: Increment Retry Manager->>Log: Write Error end end ``` ## Caractéristiques Principales ### 1. Architecture Multi-Instances et Asynchrone - Support de connexions multiples vers différentes instances Odoo - Insertion automatique dans la queue via surcharge des méthodes create/write/unlink - Traitement en arrière-plan des synchronisations via un worker dédié - Possibilité de synchronisation immédiate pour les cas critiques ### 2. Configuration - Mapping des champs configurable par modèle, par relation odoo-odoo - Gestion automatique des dépendances entre modèles - Paramètres de connexion sécurisés pour chaque instance ### 3. Mécanismes de Validation - Validation des données avant synchronisation - Vérification de l'intégrité des données - Gestion des conflits de synchronisation - Journalisation détaillée des opérations ### 4. Système de Reprise - Détection automatique des échecs - File d'attente des tentatives échouées - Stratégie de réessai configurable - Notification des erreurs critiques ### 5. Monitoring - Interface de suivi des synchronisations - Statistiques de performance - Journal des erreurs - Alertes configurables ## Flux de Synchronisation 1. **Détection des Changements** - Surveillance des modifications sur les modèles configurés - Création d'une entrée dans la file de synchronisation 2. **Validation Initiale** - Vérification des données à synchroniser - Validation des dépendances 3. **Synchronisation** - Envoi des données via XML-RPC - Gestion des réponses et erreurs 4. **Validation Finale** - Vérification de la synchronisation - Confirmation de l'intégrité 5. **Journalisation** - Enregistrement du résultat - Mise à jour des statistiques ## Architecture Technique ```plantuml @startuml skinparam monochrome true package "Configuration" { [Instances Odoo] <<(E,LightGreen)>> [Modèles Sync] <<(E,LightGreen)>> [Destinations] <<(E,LightGreen)>> } package "Orchestration" { [Queue] <<(Q,LightBlue)>> [Worker] <<(Q,LightBlue)>> [Scheduler] <<(Q,LightBlue)>> } package "Connectivité" { [Adaptateur RPC] <<(C,Orange)>> [Sérialiseur] <<(C,Orange)>> } [Instances Odoo] --> [Modèles Sync] [Modèles Sync] --> [Destinations] [Queue] --> [Worker] [Worker] --> [Adaptateur RPC] [Adaptateur RPC] --> [Sérialiseur] note right of [Sérialiseur] Transformations de données Gestion des dépendances Mapping de champs end note @enduml ``` ### Configuration et Observers #### Instances Odoo (odoo.sync.instance) Configuration des connexions aux instances distantes : - `name` : Nom de l'instance - `url` : URL de l'instance - `database` : Base de données - `username` : Utilisateur technique - `password` : Mot de passe (chiffré) - `active` : Instance active/inactive - `state` : État de la connexion #### Modèles Synchronisés (odoo.sync.model) Configuration des modèles à synchroniser : - `model_id` : Référence vers ir.model - `name` : Nom du modèle (computed) - `odoo_id` : Mapping avec odoo.sync.instance - `active` : Synchronisation active/inactive - `priority` : Ordre de synchronisation pour les dépendances #### Champs Synchronisés (odoo.sync.model.field) Configuration des champs par modèle : - `field_id` : Référence vers ir.model.fields - `name` : Nom technique du champ (computed) - `required` : Champ obligatoire pour la synchronisation - `sync_default` : Valeur par défaut si non disponible - Exclusion des champs calculés (sauf si modifiables manuellement) #### Destinations (odoo.sync.model.destination) Configuration des destinations par modèle : - `model_sync_id` : Référence vers odoo.sync.model - `instance_id` : Référence vers odoo.sync.instance - `target_model` : Modèle cible sur l'instance distante - `active` : Synchronisation active pour cette destination - `field_ids` : Champs à synchroniser pour cette destination #### Gestionnaire de Synchronisation (odoo.sync.manager) ```python class OdooSyncManager(models.Model): _name = 'odoo.sync.manager' _description = 'Gestionnaire de synchronisation Odoo' @api.model def _get_sync_models(self): """Récupère tous les modèles actifs à synchroniser""" return self.env['odoo.sync.model'].search([('active', '=', True)]) @api.model def _queue_sync(self, record, operation, changed_fields=None): """Ajoute une opération dans la queue de synchronisation""" sync_model = self.env['odoo.sync.model'].search([ ('model_id.model', '=', record._name), ('active', '=', True) ]) if not sync_model: return # Pour chaque destination configurée for destination in sync_model.destination_ids.filtered('active'): # Récupérer les champs configurés pour cette destination sync_fields = destination.field_ids # En cas de mise à jour, vérifier si les champs modifiés sont à synchroniser if operation == 'write' and changed_fields: relevant_fields = set(changed_fields) & set(sync_fields.mapped('name')) if not relevant_fields: continue # Aucun champ modifié n'est à synchroniser # Préparer les données à synchroniser sync_data = {} for field in sync_fields: if field.mapping_type == 'direct': sync_data[field.name] = record[field.name] elif field.mapping_type == 'function' and field.mapping_function: # Appel de la fonction de transformation sync_data[field.name] = getattr(record, field.mapping_function)() elif field.mapping_type == 'computed': # Gestion spéciale pour les champs computed si nécessaire sync_data[field.name] = record[field.name] # Créer l'entrée dans la queue self.env['odoo.sync.queue'].create({ 'model_id': sync_model.model_id.id, 'resource_id': record.id, 'other_odoo_id': destination.instance_id.id, 'other_odoo_resource_id': record.get_external_id().get(record.id), # Si déjà synchronisé 'type': operation, 'state': 'pending', 'data_json': json.dumps(sync_data), 'create_date': record.create_date, 'write_date': record.write_date }) @api.model def _observe_changes(self, method): """Décorateur pour observer les changements sur les modèles configurés""" def wrapper(self, *args, **kwargs): # Capturer les champs modifiés pour write changed_fields = list(kwargs.get('vals', {}).keys()) if method.__name__ == 'write' else None result = method(self, *args, **kwargs) sync_manager = self.env['odoo.sync.manager'] if isinstance(result, models.Model): for record in result: sync_manager._queue_sync(record, method.__name__, changed_fields) return result return wrapper # Application des observers sur les méthodes standard models.Model.create = OdooSyncManager._observe_changes(models.Model.create) models.Model.write = OdooSyncManager._observe_changes(models.Model.write) models.Model.unlink = OdooSyncManager._observe_changes(models.Model.unlink) ### Template de Code pour le Gestionnaire ```python class OdooSyncManager(models.Model): _name = 'odoo.sync.manager' def _process_sync_queue(self): """Template de traitement de la queue""" jobs = self.env['odoo.sync.job'].search([('state', '=', 'pending')]) for job in jobs: try: # Logique de synchronisation self._execute_sync(job) job.write({'state': 'done'}) except Exception as e: job.write({ 'state': 'failed', 'error_message': str(e), 'retry_count': job.retry_count + 1 }) def _execute_sync(self, job): """Template d'exécution d'une synchronisation""" adapter = self._get_rpc_adapter(job.instance_id) serializer = self._get_serializer(job.model_id) data = serializer.serialize(job.record_id) response = adapter.execute(job.operation, data) if not response['success']: raise SyncException(response['error_code']) ``` ## Modèles de Données ### SyncConfiguration #### Configuration des Instances (odoo.sync.instance) - Nom de l'instance - URL de l'instance - Base de données - Identifiants de connexion sécurisés - État de la connexion #### Configuration des Modèles (odoo.sync.model) - Modèle Odoo à synchroniser - Liste des instances Odoo cibles - Mapping des champs - Direction de la synchronisation (uni/bidirectionnelle) - Champs à surveiller - Règles de synchronisation spécifiques ### SyncQueue Table principale pour la gestion des synchronisations : - `model_id` : Modèle Odoo à synchroniser - `resource_id` : ID de la ressource locale - `other_odoo_id` : ID de l'instance Odoo distante - `other_odoo_resource_id` : ID de la ressource sur l'instance distante - `type` : Type d'opération (create, update, unlink) - `state` : État de la synchronisation - `retry_count` : Nombre de tentatives - `last_error` : Dernière erreur rencontrée - `data_json` : Données à synchroniser au format JSON - `create_date` : Date de création dans la queue - `write_date` : Date de dernière modification - `other_create_date` : Date de création sur l'instance distante - `other_write_date` : Date de dernière modification sur l'instance distante ### SyncLog - Journal détaillé des opérations - Erreurs et avertissements - Statistiques de performance ### Gestion des Conflits #### Détection - Comparaison des horodatages `write_date` (source) vs `other_write_date` (cible) - Seuil de tolérance configurable (défaut : 5 minutes) #### Stratégies de Résolution 1. **Priorité source** : Écrasement de la version cible 2. **Priorité destination** : Conservation de la version cible 3. **Fusion manuelle** : - Notification aux administrateurs - Interface de comparaison côte-à-côte - Historique des versions (diff) #### Cas Particuliers - Réconciliation des relations Many2many/One2many - Gestion des suppressions/archivages croisés ### Journalisation Avancée (SyncLog) #### Niveaux de Log - **DEBUG**: Payloads complets et traces d'exécution - **INFO**: Diffs des modifications et métadonnées - **WARNING**: Erreurs non critiques (ex: timeouts) - **ERROR**: Échecs critiques de synchronisation #### Politique de Rétention - Stockage local: 90 jours (accès rapide) - Archivage long terme: AWS S3 Glacier (7 ans) - Format d'archivage: Parquet compressé #### Masquage des Données Sensibles Fonction de masquage automatique : ```python def sanitize_log_entry(entry): sensitive_fields = ['password', 'api_key', 'token'] for field in sensitive_fields: if field in entry['data']: entry['data'][field] = '*****' return entry ``` ### Sécurité des Données #### Chiffrement - TLS 1.3 obligatoire pour les communications - Rotation automatique des certificats (Let's Encrypt) - Chiffrement AES-256 au repos pour : - SyncQueue.data_json - SyncLog.payload #### Gestion des Accès - Authentification mutuelle OAuth2 avec JWT : ```python # Génération de token sécurisé def generate_jwt(secret, payload): return jwt.encode(payload, secret, algorithm="HS256") ``` - RBAC (Role-Based Access Control) : - Rôle 'Sync Admin' : Configuration complète - Rôle 'Sync Auditor' : Lecture seule #### Audit - Logs d'accès horodatés avec IP/user-agent - Intégration SIEM (ex: Splunk, ELK) ## Sécurité - Authentification sécurisée entre instances - Encryption des données sensibles - Validation des permissions - Audit des opérations ## Interface Utilisateur - Configuration des synchronisations - Monitoring en temps réel - Gestion des erreurs - Rapports et statistiques ## Performance - Optimisation des requêtes - Gestion de la charge - Limitation des appels API - Mise en cache intelligente ## Performance à l'Échelle #### Architecture Scalable - File d'attente Redis pour découplage - Scaling horizontal via Kubernetes - Partitionnement par modèle/instance #### Optimisations - Cache des relations fréquemment accédées - Compression LZ4 des payloads volumineux - Traitement batch avec isolation transactionnelle #### Monitoring - Dashboard Grafana avec : - Débit (records/min) - Latence (P50/P90/P99) - Taux d'utilisation des workers ## Maintenance - Outils de diagnostic - Nettoyage automatique des logs - Gestion des sauvegardes - Procédures de mise à jour ## Gestion des Conflits de Synchronisation ### Détection des Conflits - **Conflit de Version** : Détecté lorsque la version locale et distante ont été modifiées depuis la dernière synchronisation - **Conflit de Données** : Détecté lorsque les mêmes champs ont été modifiés différemment sur les deux instances - **Conflit de Relations** : Détecté lorsque des enregistrements liés sont incohérents entre les instances ### Stratégies de Résolution 1. **Automatique** - Priorité configurable par instance (master/slave) - Règles de fusion personnalisables par champ - Horodatage "le plus récent gagne" 2. **Manuelle** - Interface de résolution pour l'utilisateur - Visualisation côte à côte des différences - Options : garder source, garder destination, fusionner, ignorer ### Configuration des Règles de Résolution ```python class OdooSyncModelField(models.Model): _inherit = 'odoo.sync.model.field' conflict_strategy = fields.Selection([ ('source_wins', 'Source gagne'), ('dest_wins', 'Destination gagne'), ('newest', 'Plus récent'), ('manual', 'Résolution manuelle') ], default='newest') ``` ## Gestion des Suppressions ### Stratégies de Suppression 1. **Suppression Douce** - Marquage comme inactif sur les deux instances - Conservation de l'historique - Possibilité de restauration 2. **Suppression Dure** - Suppression physique sur les deux instances - Vérification des dépendances - Journal d'audit détaillé ### Configuration ```python class OdooSyncModel(models.Model): _inherit = 'odoo.sync.model' deletion_strategy = fields.Selection([ ('soft', 'Suppression douce'), ('hard', 'Suppression physique'), ('ignore', 'Ignorer'), ('manual', 'Validation manuelle') ], default='soft') cascade_deletion = fields.Boolean('Cascade aux enregistrements liés') ``` ## Sécurité et Droits d'Accès ### Niveaux de Sécurité 1. **Niveau Instance** - Authentification par token JWT - Chiffrement des communications - Restriction par IP 2. **Niveau Utilisateur** - Groupes de sécurité dédiés - Journalisation des actions - Validation multi-niveau ### Groupes de Sécurité ```xml Synchronisation : Utilisateur Synchronisation : Manager ``` ### Règles de Sécurité ```xml Sync Manager : Accès Total [(1, '=', 1)] ``` ## Exemples de Configuration ### 1. Synchronisation des Produits ```python # Configuration du modèle product_sync = { 'model': 'product.template', 'fields': { 'name': {'type': 'direct'}, 'list_price': {'type': 'direct'}, 'standard_price': { 'type': 'function', 'mapping': 'map_cost_price' }, 'categ_id': { 'type': 'relation', 'model': 'product.category', 'match_field': 'name' } }, 'conflict_strategy': 'newest' } # Fonction de mapping personnalisée def map_cost_price(self, record): return record.standard_price * self.currency_rate ``` ### 2. Synchronisation des Commandes ```python # Configuration du modèle sale_sync = { 'model': 'sale.order', 'fields': { 'name': {'type': 'direct'}, 'partner_id': { 'type': 'relation', 'model': 'res.partner', 'match_field': 'ref' }, 'order_line': { 'type': 'one2many', 'fields': ['product_id', 'quantity', 'price_unit'] } }, 'deletion_strategy': 'soft', 'conflict_strategy': 'manual' } ``` ### 3. Interface de Configuration ```xml odoo.sync.config.form odoo.sync.model
``` ## Scénarios de Test Critiques ### TC-01 : Synchronisation bidirectionnelle **Préconditions**: - 2 instances interconnectées - Modèle 'res.partner' configuré **Étapes**: 1. Créer partenaire sur Instance A 2. Vérifier création sur Instance B 3. Modifier partenaire sur Instance B 4. Vérifier mise à jour sur Instance A **Résultat attendu**: - SyncLog avec code SYNC_200 sur les deux instances - Données cohérentes après boucle complète ### TC-02 : Gestion des conflits **Préconditions**: - Même enregistrement modifié simultanément sur les deux instances **Étapes**: 1. Modifier le champ 'name' sur Instance A 2. Modifier le champ 'email' sur Instance B 3. Déclencher manuellement la synchronisation **Résultat attendu**: - Application de la stratégie de résolution configurée - Journalisation du conflit (SYNC_409) ### TC-03 : Tolérance aux pannes **Préconditions**: - Instance B hors ligne **Étapes**: 1. Tenter une synchronisation 2. Redémarrer Instance B 3. Relancer la synchronisation **Résultat attendu**: - Rejeu automatique des transactions en erreur - Conservation des données en queue pendant 24h ## Procédures de Déploiement ### Prérequis - Odoo 15.0+ - Accès API aux instances distantes - Bibliothèque python-requests ### Installation 1. Copier le répertoire `odoo_to_odoo_sync` dans `addons/` 2. Redémarrer le serveur Odoo 3. Installer le module via l'interface d'administration ### Configuration ```python # Configuration de base dans odoo.conf [odoo_sync] max_retries = 3 retry_delay = 300 # secondes queue_size = 1000 # Activation du mode debug debug = False ``` ### Tests ```bash # Lancer les tests d'intégration $ ./odoo-bin -i odoo_to_odoo_sync --test-enable