bemade-addons/odoo_to_odoo_sync/Spécifications.md
2025-05-29 21:35:47 -04:00

676 lines
No EOL
21 KiB
Markdown

# 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
<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>
```
### Règles de Sécurité
```xml
<record id="rule_sync_model_manager" model="ir.rule">
<field name="name">Sync Manager : Accès Total</field>
<field name="model_id" ref="model_odoo_sync_model"/>
<field name="groups" eval="[(4, ref('group_sync_manager'))]"/>
<field name="domain_force">[(1, '=', 1)]</field>
</record>
```
## 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
<record id="view_sync_config_form" model="ir.ui.view">
<field name="name">odoo.sync.config.form</field>
<field name="model">odoo.sync.model</field>
<field name="arch" type="xml">
<form>
<group>
<field name="model_id"/>
<field name="active"/>
<field name="deletion_strategy"/>
</group>
<notebook>
<page string="Champs">
<field name="field_ids">
<tree editable="bottom">
<field name="field_id"/>
<field name="sync_type"/>
<field name="conflict_strategy"/>
</tree>
</field>
</page>
</notebook>
</form>
</field>
</record>
```
## 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