- Added comprehensive dependency management with proper OdooRPC library integration - Fixed XML-RPC, JSON-RPC, and OdooRPC connection authentication issues - Implemented proper Odoo 18 API key format support with scope parameter - Added intelligent field mapping wizard with Full/Required/Balanced modes - Enhanced error handling and connection state management - Removed legacy API key system for clean reinstall capability - Added detailed debug logging for connection troubleshooting
17 KiB
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.
Potential Issues and Fragile Areas
Broad Exception Handling: Plusieurs fichiers utilisent des clauses except Exception trop larges qui pourraient masquer des problèmes sous-jacents :
sync_instance.pya une gestion d'exception large avec un commentaire de désactivation pylintsync_manager.pya plusieurs gestionnaires d'exception largessync_observer.pya des gestionnaires d'exception larges
Missing Validation: La logique de validation précédemment manquante pour la transformation des payloads dans sync_manager.py a été implémentée.
Architecture
Flux Global
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
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
Configuration et Observers
Instances Odoo (odoo.sync.instance)
Configuration des connexions aux instances distantes :
class OdooSyncInstance(models.Model):
_name = 'odoo.sync.instance'
_description = 'Instance Odoo distante'
name = fields.Char('Nom de l\'instance', required=True)
url = fields.Char('URL de l\'instance', required=True)
database = fields.Char('Base de données', required=True)
api_token = fields.Char('API Token', required=True, help="Token API généré dans l'instance distante")
active = fields.Boolean('Instance active', default=True)
state = fields.Selection([
('connected', 'Connecté'),
('disconnected', 'Déconnecté'),
('error', 'Erreur')
], default='disconnected')
def _get_rpc_connection(self):
"""Connexion XML-RPC avec API token"""
return xmlrpc.client.ServerProxy(
f"{self.url}/xmlrpc/2/object",
context=ssl._create_unverified_context()
)
def _authenticate(self):
"""Authentification via API token"""
common = xmlrpc.client.ServerProxy(f"{self.url}/xmlrpc/2/common")
return common.authenticate_api_key(self.database, self.api_token)
def test_connection(self):
"""Test de connexion à l'instance"""
try:
uid = self._authenticate()
if uid:
self.state = 'connected'
return True
else:
self.state = 'error'
return False
except Exception as e:
self.state = 'error'
raise UserError(f"Erreur de connexion : {str(e)}")
Modèles Synchronisés (odoo.sync.model)
Configuration des modèles à synchroniser :
model_id: Référence vers ir.modelname: Nom du modèle (computed)odoo_id: Mapping avec odoo.sync.instanceactive: Synchronisation active/inactivepriority: 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.fieldsname: Nom technique du champ (computed)required: Champ obligatoire pour la synchronisationsync_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.modelinstance_id: Référence vers odoo.sync.instancetarget_model: Modèle cible sur l'instance distanteactive: Synchronisation active pour cette destinationfield_ids: Champs à synchroniser pour cette destination
Gestionnaire de Synchronisation (odoo.sync.manager)
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),
'type': operation,
'state': 'pending',
'data_json': json.dumps(sync_data),
'create_date': record.create_date,
'write_date': record.write_date
})
def _process_sync_queue(self):
"""Traitement de la queue de synchronisation"""
jobs = self.env['odoo.sync.queue'].search([
('state', '=', 'pending'),
('retry_count', '<', 3)
], limit=100)
for job in jobs:
try:
instance = job.other_odoo_id
if not instance.active or instance.state != 'connected':
continue
# Exécuter la 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):
"""Exécution d'une synchronisation"""
instance = job.other_odoo_id
rpc = instance._get_rpc_connection()
uid = instance._authenticate()
if not uid:
raise ValueError("Échec d'authentification")
data = json.loads(job.data_json)
if job.type == 'create':
result = rpc.execute_kw(
instance.database, uid, instance.api_token,
job.model_id.model, 'create', [data]
)
job.other_odoo_resource_id = result
elif job.type == 'write':
rpc.execute_kw(
instance.database, uid, instance.api_token,
job.model_id.model, 'write',
[[job.other_odoo_resource_id], data]
)
elif job.type == 'unlink':
rpc.execute_kw(
instance.database, uid, instance.api_token,
job.model_id.model, 'unlink', [job.other_odoo_resource_id]
)
Modèles de Données
SyncQueue
Table principale pour la gestion des synchronisations :
model_id: Modèle Odoo à synchroniserresource_id: ID de la ressource localeother_odoo_id: ID de l'instance Odoo distanteother_odoo_resource_id: ID de la ressource sur l'instance distantetype: Type d'opération (create, update, unlink)state: État de la synchronisationretry_count: Nombre de tentativeslast_error: Dernière erreur rencontréedata_json: Données à synchroniser au format JSONcreate_date: Date de création dans la queuewrite_date: Date de dernière modificationother_create_date: Date de création sur l'instance distanteother_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) vsother_write_date(cible) - Seuil de tolérance configurable (défaut : 5 minutes)
Stratégies de Résolution
- Priorité source : Écrasement de la version cible
- Priorité destination : Conservation de la version cible
- 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
Sécurité des Données
Authentification
class OdooSyncInstance(models.Model):
_inherit = 'odoo.sync.instance'
def _secure_connection(self):
"""Configuration de sécurité pour les connexions"""
return {
'use_ssl': True,
'verify_ssl': True,
'timeout': 30,
'headers': {
'User-Agent': 'Odoo-Sync/1.0',
'X-API-Key': self.api_token
}
}
Gestion des Accès
API Tokens Odoo natifs :
- Utilisation du système d'authentification intégré d'Odoo
- Révocation simple via l'interface utilisateur
- Audit automatique des accès
- Permissions granulaires via les groupes de sécurité existants
Avantages :
- Intégration native avec le système de sécurité Odoo
- Pas de complexité supplémentaire (JWT/OAuth2)
- Gestion centralisée des tokens
- Logs d'accès automatiques dans
res.users.log
Chiffrement
- TLS 1.3 obligatoire pour les communications
- Rotation automatique des certificats
- Chiffrement AES-256 au repos pour :
SyncQueue.data_jsonSyncLog.payload
Audit
- Logs d'accès horodatés avec IP/user-agent
- Intégration SIEM possible
- Journal des modifications sensibles
Groupes de Sécurité
<record id="group_sync_user" model="res.groups">
<field name="name">Synchronisation : Utilisateur</field>
<field name="category_id" ref="base.module_category_usability"/>
</record>
<record id="group_sync_manager" model="res.groups">
<field name="name">Synchronisation : Manager</field>
<field name="implied_ids" eval="[(4, ref('group_sync_user'))]"/>
</record>
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 avec métriques :
- Débit (records/min)
- Latence (P50/P90/P99)
- Taux d'utilisation des workers
Exemples de Configuration
1. Synchronisation des Produits
# 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'
}
2. Synchronisation des Commandes
# 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'
}
Procédures de Déploiement
Prérequis
- Odoo 15.0+
- Accès API aux instances distantes
- Génération d'API tokens sur les instances cibles
Installation
- Copier le répertoire
odoo_to_odoo_syncdansaddons/ - Redémarrer le serveur Odoo
- Installer le module via l'interface d'administration
- Configurer les API tokens pour chaque instance
Configuration
# Configuration de base dans odoo.conf
[odoo_sync]
max_retries = 3
retry_delay = 300 # secondes
queue_size = 1000
debug = False
Tests
# Lancer les tests d'intégration
$ ./odoo-bin -i odoo_to_odoo_sync --test-enable
Scénarios de Test Critiques
TC-01 : Synchronisation bidirectionnelle
Préconditions:
- 2 instances interconnectées
- Modèle 'res.partner' configuré
Étapes:
- Créer partenaire sur Instance A
- Vérifier création sur Instance B
- Modifier partenaire sur Instance B
- 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:
- Modifier le champ 'name' sur Instance A
- Modifier le champ 'email' sur Instance B
- 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:
- Tenter une synchronisation
- Redémarrer Instance B
- Relancer la synchronisation
Résultat attendu:
- Rejeu automatique des transactions en erreur
- Conservation des données en queue pendant 24h
Maintenance
Outils de diagnostic
- Interface de monitoring en temps réel
- Logs détaillés par opération
- Métriques de performance
Nettoyage automatique des logs
- Rétention configurable (défaut: 90 jours)
- Archivage automatique des anciennes données
- Compression des logs volumineux
Gestion des sauvegardes
- Sauvegarde automatique de la configuration
- Export/import des paramètres de synchronisation
- Rollback en cas de problème
Procédures de mise à jour
- Tests automatisés avant déploiement
- Migration des données existantes
- Documentation des changements%