Metadata-Version: 2.4
Name: ood-cli
Version: 1.1.3
Summary: Declarative Infrastructure as Code (IaC) for Odoo via XML-RPC
License: GPL-3.0-or-later
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
Classifier: Operating System :: OS Independent
Classifier: Environment :: Console
Classifier: Intended Audience :: System Administrators
Classifier: Topic :: Software Development :: Build Tools
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pyyaml>=6.0
Requires-Dist: pandas
Requires-Dist: openpyxl
Dynamic: license-file

# Odoo Online Deploy (ood) — Infrastructure as Code (IaC)

[![Python Version](https://img.shields.io/badge/python-3.8%20%7C%203.9%20%7C%203.10%20%7C%203.11%20%7C%203.12-blue.svg)](https://www.python.org/)
[![Odoo Compatibility](https://img.shields.io/badge/odoo-18.0%20%7C%2019.0-purple.svg)](https://www.oodoo.com/)
[![IaC Paradigm](https://img.shields.io/badge/paradigm-Infrastructure%20as%20Code-orange.svg)](https://en.wikipedia.org/wiki/Infrastructure_as_code)

Ce package Python, nommé **Odoo Online Deploy (ood)**, implémente un moteur de configuration et de personnalisation **Infrastructure as Code (IaC)** inspiré du fonctionnement de **Terraform** pour n'importe quelle instance d'ERP Odoo (y compris les instances Odoo Online, On-Premise ou Odoo.sh).

Grâce à ce moteur, vous pouvez déclarer la structure de votre base de données, vos règles de sécurité, vos vues utilisateur, vos automatisations et vos crons dans un fichier déclaratif YAML unique (`main.ood`), puis les appliquer de façon **déterministe**, **ordonnée** et **idempotente** via XML-RPC.

---

## 🏗️ Architecture du Système

Le moteur IaC orchestre le cycle de vie de vos personnalisations à travers trois fichiers clés :

```text
  [ main.ood ]       [ variables.ood ]       [ ood.oodstate ]
(Configurations)    (Paramètres/Secrets)     (État local JSON)
       │                     │                     │
       └─────────────┬───────┴─────────────────────┘
                     ▼
              [ Moteur CLI ood ]
                     │
                     ▼
           { Compilateur de Diff }
                     │  (Analyse comparative)
                     ▼
         [ Arbre d'actions (Diff) ]   ──►  + create, ~ update, - destroy
                     │  (Approbation utilisateur)
                     ▼
           [ Handlers XML-RPC ]
                     │  (Modifications ordonnées)
                     ▼
        [( Instance Odoo 18 / 19 )]
                     │
                     ▼  (Enregistrement des IDs Odoo)
              [ ood.oodstate ]
```

*   **`main.ood`** : Fichier YAML décrivant l'ensemble de votre infrastructure et de vos personnalisations (applications, champs, règles de sécurité, automatisations).
*   **`variables.ood`** : Fichier contenant les variables d'environnement locales et les secrets de connexion.
*   **`ood.oodstate`** : Fichier d'état local auto-généré (analogue à un fichier `terraform.tfstate`) qui conserve la trace des identifiants uniques (IDs) des ressources créées pour assurer l'idempotence et les suppressions propres.

---

## ⚙️ Installation

### 1. Prérequis
Assurez-vous de disposer de Python **>= 3.8**.

### 2. Installation depuis PyPI (Production)
Pour installer l'outil globalement ou au sein de votre projet :
```bash
pip install ood-cli
```

### 3. Installation locale pour le développement (Mode Éditable)
Si vous modifiez directement le code source du projet :
```bash
# Créez et activez un environnement virtuel (optionnel mais fortement recommandé)
python3 -m venv venv
source venv/bin/activate

# Installation en mode éditable
pip install -e .
```

Cette commande installe les dépendances requises et enregistre un point d'entrée global dans votre terminal. Vous pouvez désormais lancer le moteur en saisissant la commande :
```bash
ood --help
```

---

## 🚀 Workflow d'Exécution (Terraform-like)

### Étape 1 : Initialisation (`ood init`)
Initialise le répertoire de travail, teste la validité des paramètres de connexion à l'instance Odoo cible et génère un fichier `ood.oodstate` vide s'il n'existe pas.
```bash
ood init
```

### Étape 2 : Comparaison et Prévisualisation (`ood plan`)
Compare les déclarations du fichier `main.ood` avec l'état enregistré dans `ood.oodstate`. Le moteur compile un arbre de différences (diff) ordonné selon les dépendances et affiche un aperçu coloré des actions à effectuer :
*   `+ create` : Ressources déclarées dans la configuration mais absentes de l'état.
*   `~ update` : Ressources modifiées (par exemple, code d'un cron ou d'une action serveur mis à jour).
*   `- destroy` : Ressources supprimées du fichier `main.ood` mais toujours présentes dans l'état (le moteur les désinstalle proprement d'Odoo).

```bash
ood plan
```

### Étape 3 : Application des Changements (`ood apply`)
Valide le plan, demande confirmation à l'utilisateur (sauf si `--auto-approve` est fourni), applique les modifications dans l'ordre strict des dépendances et met à jour le fichier d'état avec les identifiants réels renvoyés par la base de données.

```bash
ood apply
# ou pour une exécution automatisée (ex: CI/CD)
ood apply --auto-approve
```

#### 🛡️ Options avancées d'application
*   **`--skip-acl-checks`** : Permet d'ignorer les vérifications d'autorisation pré-flight (ACL checks) sur l'instance Odoo cible.
    > [!NOTE]
    > Sur les instances **Odoo Online (SaaS)**, l'accès XML-RPC en lecture/création directe sur des modèles système sensibles (comme `ir.model.fields` pour les champs, `ir.ui.view` pour les vues ou `ir.module.module` pour les applications) peut être bridé par l'hébergeur pour des raisons de sandboxing de sécurité. Cette option permet de forcer l'application directe des ressources. Si votre utilisateur ne dispose réellement pas des droits d'écriture, Odoo lèvera une erreur d'accès standard durant la transaction, déclenchant ainsi un rollback sécurisé automatique de l'ensemble du plan.
*   **Optimisation des vérifications d'applications (`app`)** : Les vérifications ACL pré-flight sur les ressources de type `app` (modules Odoo) sont automatiquement ignorées car l'activation des modules repose sur des flux métiers privilégiés (`button_immediate_install`) et non sur des droits CRUD classiques sur `ir.module.module`.

```bash
# Exemple de commande d'application forcée (recommandée sur Odoo Online)
ood apply --auto-approve --skip-acl-checks
```

### Étape 4 : Destruction Complète (`ood destroy`)
Wipe et nettoie l'intégralité des personnalisations créées en les supprimant proprement en base d'Odoo en ordre inverse de dépendance, afin de restituer une instance parfaitement propre.
```bash
ood destroy
# ou sans confirmation manuelle
ood destroy --auto-approve
```

---

## 📝 Guide de la Gestion des Variables et Secrets (`variables.ood`)

Le fichier `variables.ood` centralise les secrets et les valeurs d'interpolation dynamique de votre projet. Les valeurs y sont injectées au sein de vos configurations via la syntaxe `${MA_VARIABLE}`.

### 1. Variables globales et modulaires
*   **Fichier global** : Un fichier `variables.ood` à la racine de votre workspace définit les paramètres généraux (configurations de connexion, etc.).
*   **Fichiers locaux** : Lorsque vous découpez vos configurations dans des sous-dossiers (voir section `include` ci-dessous), vous pouvez placer un fichier `variables.ood` local au sein de chaque dossier fonctionnel (ex : `modules/sales/variables.ood`). Le moteur IaC compile automatiquement l'ensemble de ces fichiers pour alimenter l'interpolation.

### 2. Sécurité contre les collisions de noms (Validation stricte)
Pour éviter qu'une variable ne soit écrasée par inadvertance, le moteur effectue une validation de sécurité stricte lors du chargement. **Si une même clé de variable est déclarée dans plus d'un fichier `variables.ood`, l'initialisation s'interrompt immédiatement** en levant une erreur descriptive indiquant les chemins exacts en conflit :
```text
Error: Failed to load and compile configuration: La variable 'MON_SECRET' est déclarée plusieurs fois : dans 'variables.ood' et dans 'modules/sales/variables.ood'.
```

Exemple standard :
```yaml
# variables.ood
ODOO_URL: "http://localhost:8069"
ODOO_DB: "odoo"
ODOO_LOGIN: "admin"
ODOO_PASSWORD: "mon_api_key_ou_password"
ODOO_VERSION: "19.0"  # Permet d'adapter les syntaxes XML et sécurité (18.0 ou 19.0)
```

---

## 🛠️ Spécifications du fichier `main.ood`

Le fichier `main.ood` est structuré autour de trois sections principales :
1.  `connection` : Référence les paramètres de connexion à l'instance Odoo (généralement interpolés depuis `variables.ood`).
2.  `include` *(Optionnel)* : Une liste de motifs de chemins (glob patterns) pour importer des sous-fichiers de configuration `.ood` de façon modulaire et ordonnée.
3.  `resources` : Une liste d'objets YAML définissant les composants à provisionner directement dans ce fichier.

### 📁 Découpage Modulaire via la Balise `include`
Pour des projets complexes, vous pouvez éclater vos ressources dans des sous-fichiers configurables. Les chemins sont résolus **relativement** au dossier du fichier déclarant l'inclusion. Le moteur supporte la recherche par jokers (wildcards globbing) et les inclusions récursives.

```yaml
# main.ood
connection:
  url: "${ODOO_URL}"
  db: "${ODOO_DB}"
  username: "${ODOO_LOGIN}"
  password: "${ODOO_PASSWORD}"
  odoo_version: "${ODOO_VERSION}"

include:
  - "modules/security/*.ood"
  - "modules/sales/**/*.ood"

resources:
  - type: app
    name: base
```

Les ressources importées sont compilées et insérées *avant* les ressources déclarées localement. De plus, toutes les variables définies dans les fichiers `variables.ood` locaux aux dossiers importés sont compilées et appliquées à l'ensemble du projet.

### 📋 Types de Ressources Supportés (Règles syntaxiques)

Voici la liste exhaustive des types de ressources configurables, avec leur ordre de déploiement topologique :

| Ordre | Type (`type`) | Modèle Odoo cible | Description |
| :--- | :--- | :--- | :--- |
| **1** | `app` | `ir.module.module` | Installe une application ou un module du catalogue natif. |
| **2** | `config_parameter` | `ir.config_parameter` | **(Ajouté en v1.1.1)** Gère les paramètres système globaux (`System Parameters`). |
| **3** | `privilege` | `res.groups.privilege` | Définit une catégorie de privilège (spécifique Odoo 19). |
| **4** | `group` | `res.groups` | Gère les groupes de sécurité, l'héritage (implied) et les privilèges. |
| **5** | `sequence` | `ir.sequence` | **(Ajouté en v1.1.1)** Configure les compteurs et formats de numérotations automatiques. |
| **6** | `model` | `ir.model` | Crée un nouveau modèle de données personnalisé (avec auto-correction du préfixe `x_` et mixins Chatter/Activités). |
| **7** | `field` | `ir.model.fields` | Crée des champs personnalisés de type standard (`char`, `selection`, `many2one`...) ou calculés (`compute`). |
| **8** | `acl` | `ir.model.access` | Renseigne les droits de CRUD au niveau du modèle d'objet pour un groupe donné. |
| **9** | `rule` | `ir.rule` | Applique des filtres de sécurité ligne par ligne (domaines de restriction dynamiques). |
| **10**| `server_action`| `ir.actions.server` | Déploie des scripts Python exécutés côté serveur. |
| **11**| `base_automation`| `base.automation` | Lie un trigger événementiel (on_write, on_create) à un script serveur. |
| **12**| `cron` | `ir.cron` | Programme des exécutions régulières et répétées de scripts Python (tâches planifiées). |
| **13**| `view` | `ir.ui.view` | Hérite/surcharge des vues standard XML ou crée des templates QWeb autonomes. |
| **14**| `window_action` | `ir.actions.act_window` | Crée et configure des actions de fenêtre (Window Actions) pour les menus. |
| **15**| `window_action_filter` | `ir.actions.act_window` | Injecte dynamiquement des filtres par défaut au niveau du contexte d'une action. |
| **16**| `menu` | `ir.ui.menu` | **(Ajouté en v1.1.1)** Crée et organise l'arborescence des menus de l'ERP Odoo. |
| **17**| `paperformat` | `report.paperformat` | **(Ajouté en v1.1.1)** Définit des formats et marges de papier physiques personnalisés pour l'impression. |
| **18**| `report` | `ir.actions.report` | **(Ajouté en v1.1.1)** Enregistre des actions de rapports imprimables (PDF). |
| **19**| `website_page` | `website.page` | Crée une page web dynamique publique liée à un template QWeb. |
| **20**| `attachment` | `ir.attachment` | Stocke des fichiers bruts (CSS, SCSS, JS, OWL) sous forme de pièces jointes binaires à URL virtuelle. |
| **21**| `mail_template` | `mail.template` | **(Ajouté en v1.1.1)** Modèles d'e-mails dynamiques personnalisés. |
| **22**| `sms_template` | `sms.template` | **(Ajouté en v1.1.1)** Modèles de SMS pour envois automatisés. |
| **23**| `asset` | `ir.asset` | Enregistre et lie des pièces jointes à des bundles d'assets Odoo pour compilation et rendu direct. |
| **24**| `record` | Modèle variable | Modifie ou crée des enregistrements de données (valeurs de champs) de façon idempotente (recherche par ID, XML-ID ou domaine). |
| **25**| `import` | Modèle variable | **(Ajouté en v1.1.1)** Importe en masse de volumineuses données CSV (idempotent optimisé). |

---

## 📖 Dictionnaire des Ressources et Exemples YAML

### 1. Applications (`app`)
Installe un module d'Odoo s'il n'est pas encore présent et permet d'en surcharger les métadonnées déclaratives de façon optionnelle (pratique pour l'intégration continue, le masquage ou la réorganisation du catalogue d'apps).
*   **Balises requises** :
    *   `name` : Nom technique du module dans le catalogue d'Odoo (ex : `sale_management`, `crm`).
*   **Balises optionnelles** :
    *   `auto_install` : Active ou désactive l'installation automatique du module dès que toutes ses dépendances sont satisfaites (`true` ou `false`).
    *   `application` : Déclare si le module est considéré comme une application principale (visible sous le filtre de recherche par défaut "Applications" dans Odoo, `true` ou `false`).
    *   `sequence` : Ajuste l'ordre de tri et d'affichage du module dans le catalogue des applications Odoo (entier).
    *   `shortdesc` : Surcharge le titre convivial de l'application affiché à l'écran (chaîne de caractères).
    *   `summary` : Surcharge le résumé / la description courte décrivant l'utilité du module sous son titre (chaîne de caractères).

```yaml
# Installation standard d'une application
- type: app
  name: sale_management

# Installation personnalisée avec personnalisation des métadonnées d'affichage et de comportement
- type: app
  name: crm
  auto_install: false
  application: true
  sequence: 10
  shortdesc: "CRM Commercial"
  summary: "Gestion personnalisée des opportunités et des prospects commerciaux"
```

---

### 2. Privilèges (`privilege`)
Crée un profil de privilèges applicatifs pour la sécurité (Introduit en Odoo 19). *Géré avec repli transparent automatique vers `category_id` sur Odoo 18*.
*   **Balises requises** :
    *   `name` : Identifiant local unique de la ressource.
    *   `privilege_name` : Nom affiché de la règle de privilège dans le menu.
    *   `category_xml_id` : Lien XML-ID vers la catégorie d'app Odoo parente (ex : `base.module_category_all`).

```yaml
- type: privilege
  name: contacts_privilege
  privilege_name: "Contacts"
  category_xml_id: "base.module_category_all"
```

---

### 3. Groupes de sécurité (`group`)
Définit un groupe d'utilisateurs avec héritage de droits d'autres groupes et configurations de sécurité avancées.
*   **Balises requises** :
    *   `name` : Identifiant local unique.
    *   `group_name` : Libellé utilisateur du groupe dans l'interface Odoo.
*   **Balises optionnelles** :
    *   `privilege_name` : Lien vers le privilège déclaratif créé ci-dessus.
    *   `implied_xml_ids` : Liste des groupes parents dont ce groupe hérite les droits (ex: `base.group_user` ou d'autres groupes personnalisés).
    *   `comment` : Commentaire explicatif interne décrivant la fonction et le rôle de ce groupe d'utilisateurs (texte libre).
    *   `share` : Indique s'il s'agit d'un groupe destiné aux utilisateurs externes ou du portail, modifiant les règles de partage globales d'Odoo (`true` ou `false`).
    *   `api_key_duration` : Durée maximale de validité (en jours) des clés API générées par les utilisateurs appartenant à ce groupe (nombre flottant).

```yaml
# Exemple 1 : Groupe interne standard héritant des droits de base
- type: group
  name: contacts_user_group
  group_name: "Contacts / Utilisateur"
  privilege_name: "contacts_privilege"
  implied_xml_ids:
    - "base.group_user"

# Exemple 2 : Groupe portail externe partagé avec sécurité renforcée sur les clés API
- type: group
  name: external_client_group
  group_name: "Clients Externes Portail"
  comment: "Groupe restrictif pour les clients extérieurs accédant au portail"
  share: true
  api_key_duration: 30.0
```

---

### 4. Champs personnalisés (`field`)
Étend la structure des bases de données Odoo. Supporte les champs liés (`related`), les sélections, les champs calculés complexes (`compute`) persistés en base, et toutes les options avancées de personnalisation de la base d'Odoo.
*   **Balises requises** :
    *   `name` : Identifiant unique.
    *   `model` : Nom technique du modèle Odoo à étendre (ex : `res.partner`).
    *   `field_name` : Nom technique du champ (doit obligatoirement commencer par `x_`).
    *   `ttype` : Type de données (`char`, `float`, `boolean`, `date`, `selection`, `many2one`).
    *   `field_description` : Libellé affiché à l'écran.
*   **Balises optionnelles** :
    *   `relation` : Modèle de liaison (requis si `ttype: many2one` ou `many2many`).
    *   `selection` : Liste de tuples au format chaîne de caractères Python (requis si `ttype: selection`).
    *   `related` : Chemin du champ lié (ex : `partner_id.x_first_name`).
    *   `compute` : Code Python s'exécutant pour alimenter le champ de façon automatique.
    *   `depends` : Liste des champs déclenchant le calcul du compute.
    *   `store` : Booléen déterminant si la valeur calculée est stockée physiquement en base (`true` ou `false`).
    *   `readonly` : Empêche la modification manuelle par l'utilisateur (`true` ou `false`).
    *   `required` : Rend le champ obligatoire en saisie (`true` ou `false`).
    *   `index` : Crée un index en base de données pour optimiser les performances des filtres et recherches (`true` ou `false`).
    *   `help` : Libellé d'aide / infobulle affichée à l'utilisateur dans l'interface Odoo.
    *   `translate` : Active la traduction multi-langues (`true` ou `false`).
    *   `tracking` : Active et ordonne le suivi des modifications du champ dans le chatter/fil de discussion (entier ou booléen).
    *   `on_delete` : Configure le comportement à la suppression de l'enregistrement Many2one lié (`'cascade'`, `'restrict'`, `'set null'`).
    *   `company_dependent` : Rend la valeur du champ dépendante de la société (champ de type propriété multi-sociétés, `true` ou `false`).
    *   `copied` : Détermine si la valeur du champ doit être dupliquée lors de la copie d'un enregistrement (`true` ou `false`).
    *   `relation_field` : Nom du champ Many2one inverse sur le modèle distant (indispensable pour les relations de type **One2many**).
    *   `relation_table` : Nom de la table SQL personnalisée pour la liaison de relation (pour les relations **Many2many**).
    *   `column1` : Nom personnalisé de la première colonne de liaison Many2many.
    *   `column2` : Nom personnalisé de la deuxième colonne de liaison Many2many.

```yaml
# Exemple 1 : Champ de sélection simple
- type: field
  name: res_partner_x_type_contrat_habituel
  model: res.partner
  field_name: x_type_contrat_habituel
  ttype: selection
  selection: "[('regie', 'Régie'), ('forfait', 'Forfait'), ('accord_cadre', 'Accord-Cadre')]"
  field_description: "Type de contrat habituel"

# Exemple 2 : Champ calculé stocké en base
- type: field
  name: product_template_x_productivite_heure
  model: product.template
  field_name: x_productivite_heure
  ttype: float
  field_description: "Productivité heure"
  compute: |
    for record in self:
        record['x_productivite_heure'] = round((record.x_productivite_jour or 0.0) / 7.0, 1)
  depends: "x_productivite_jour"
  store: true
  readonly: true

# Exemple 3 : Configuration relationnelle avancée avec index, aide, suivi (chatter) et comportement strict à la suppression
- type: field
  name: res_partner_x_custom_partner_id
  model: res.partner
  field_name: x_custom_partner_id
  ttype: many2one
  relation: res.partner
  field_description: "Partenaire de référence"
  required: true
  index: true
  help: "Sélectionnez le partenaire de référence principal pour ce contact."
  translate: true
  tracking: 10
  on_delete: "restrict"
  copied: false
```

---

### 5. Droits d'accès (`acl`)
Définit les autorisations de lecture, d'écriture, de création et de suppression (CRUD) sur un modèle Odoo pour un groupe donné.
*   **Balises requises** :
    *   `name` : Identifiant local unique.
    *   `acl_name` : Nom descriptif de la règle ACL.
    *   `model` : Nom technique du modèle ciblé.
    *   `group_name` : Identifiant du groupe de sécurité personnalisé associé (ou XML-ID global).
    *   `perm_read`, `perm_write`, `perm_create`, `perm_unlink` : Valeurs booléennes (`true` ou `false`).
*   **Balises optionnelles** :
    *   `active` : Permet d'activer ou de désactiver temporairement l'application de la règle d'accès en base de données sans la supprimer de votre configuration (`true` ou `false`, par défaut `true`).

```yaml
# Exemple 1 : Règle d'accès standard active
- type: acl
  name: contacts_user_acl
  acl_name: "Contacts User"
  model: res.partner
  group_name: "contacts_user_group"
  perm_read: true
  perm_write: true
  perm_create: false
  perm_unlink: false

# Exemple 2 : Règle temporairement inactive pour des tests ou migrations
- type: acl
  name: contacts_admin_temp_acl
  acl_name: "Contacts Admin Restriction"
  model: res.partner
  group_name: "contacts_user_group"
  perm_unlink: true
  active: false
```

---

### 6. Règles d'enregistrement (`rule`)
Fournit une sécurité dynamique au niveau de la ligne de données (sécurité par ligne/row-level security) en utilisant les expressions de filtre Odoo (Domaines).
*   **Balises requises** :
    *   `name` : Identifiant local unique.
    *   `rule_name` : Titre explicite de la règle de sécurité.
    *   `model` : Modèle Odoo ciblé.
    *   `domain_force` : Expression de filtre dynamique au format chaîne (ex : structure de tuples Odoo).
*   **Balises optionnelles** :
    *   `group_name` : Groupe de sécurité limitant la portée de la règle (si absent, la règle devient globale à tous les utilisateurs).
    *   `perm_read`, `perm_write`, `perm_create`, `perm_unlink` : Portée des opérations soumises à la restriction.
    *   `active` : Permet d'activer ou de désactiver temporairement l'application de la règle de sécurité en base de données sans la supprimer de votre configuration (`true` ou `false`, par défaut `true`).

```yaml
# Exemple 1 : Règle de sécurité standard active liée à un groupe
- type: rule
  name: crm_user_rule
  rule_name: "CRM Lead / Utilisateur"
  model: crm.lead
  group_name: "crm_user_group"
  domain_force: "['|', ('user_id', '=', user.id), ('message_partner_ids', 'in', [user.partner_id.id])]"
  perm_read: true
  perm_write: true
  perm_create: false
  perm_unlink: false

# Exemple 2 : Règle globale temporairement inactive (pratique pour des imports massifs)
- type: rule
  name: lead_global_temp_rule
  rule_name: "CRM Lead Global Restriction"
  model: crm.lead
  domain_force: "[('country_id.code', '=', 'FR')]"
  perm_read: true
  active: false
```

---

### 7. Actions Serveur (`server_action`)
Déploie un bloc de code Python à exécuter à la demande, via une automatisation, ou directement depuis l'interface Odoo sous forme de bouton d'action contextuel.
*   **Balises requises** :
    *   `name` : Identifiant local unique.
    *   `action_name` : Libellé descriptif de l'action de serveur (affiché dans le menu Odoo).
    *   `model` : Modèle d'évaluation principal.
    *   `code` : Script Python complet.
*   **Balises optionnelles** :
    *   `bind_to_model` : Si vrai (`true`), lie automatiquement l'action serveur au menu contextuel "Action" de l'interface Odoo pour le modèle principal déclaré (`true` ou `false`).
    *   `binding_model` : Nom technique d'un autre modèle d'Odoo sur lequel l'action contextuelle doit apparaître.
    *   `binding_view_types` : Restreint l'affichage du bouton d'action contextuel à certains types de vues d'Odoo (ex : `'list,form'`, `'list'` uniquement, `'form'` uniquement).
    *   `sequence` : Ajuste la priorité d'affichage de l'action dans le menu contextuel (entier).
    *   `group_ids` : Liste des groupes de sécurité autorisés à voir et exécuter cette action contextuelle (résolus dynamiquement par nom de groupe déclaratif local ou XML-ID).

```yaml
# Exemple 1 : Action serveur classique (destinée à être appelée par une automatisation)
- type: server_action
  name: won_crm_lead_sa
  action_name: "Action: Won CRM Lead on Sales Order confirmed"
  model: sale.order
  code: |
    for record in records:
        if record.opportunity_id:
            record.opportunity_id.action_set_won()

# Exemple 2 : Action contextuelle exposée sous forme de bouton "Action" sécurisé
- type: server_action
  name: validate_selected_leads_sa
  action_name: "Valider les opportunités sélectionnées"
  model: crm.lead
  bind_to_model: true
  binding_view_types: "list"
  sequence: 5
  group_ids:
    - "crm_user_group"
    - "base.group_erp_manager"
  code: |
    for record in records:
        record.write({'stage_id': 3})
```

---

### 8. Règles d'automatisation (`base_automation`)
Orchestre l'exécution d'actions de serveur à la suite de modifications ou d'événements en base de données.
*   **Balises requises** :
    *   `name` : Identifiant local unique.
    *   `rule_name` : Nom explicite de l'automatisation.
    *   `model` : Modèle de données surveillé.
    *   `trigger` : Type d'événement déclencheur (ex : `on_create_or_write`, `on_write`, `on_create`).
    *   `server_actions` : Liste des actions serveur (par leur identifiant déclaratif local) à lancer.
*   **Balises optionnelles** :
    *   `trigger_fields` : Liste des champs techniques dont la mise à jour déclenche la règle.
    *   `filter_domain` : Expression de filtre dynamique sous forme de domaine Odoo servant de condition de déclenchement ("Appliquer sur", ex : `"[('probability', '>', 50)]"`).
    *   `filter_pre_domain` : Expression de filtre dynamique validant l'état de l'enregistrement *avant* sa modification ("Avant la mise à jour du domaine"). Très utile pour capter des transitions d'état précises.
    *   `active` : Permet d'activer ou de désactiver temporairement la règle d'automatisation en base de données sans la supprimer de votre configuration (`true` ou `false`, par défaut `true`).
    *   `description` : Commentaire ou description textuelle expliquant le but fonctionnel de cette règle d'automatisation (texte libre).

```yaml
# Exemple 1 : Règle d'automatisation classique sur modification de champ
- type: base_automation
  name: won_crm_lead_rule
  rule_name: "Won CRM Lead on Sales Order confirmed"
  model: sale.order
  trigger: on_create_or_write
  trigger_fields:
    - state
  server_actions:
    - won_crm_lead_sa

# Exemple 2 : Règle d'automatisation avancée avec pré-filtres et post-filtres de domaines
- type: base_automation
  name: lead_stage_change_notification_rule
  rule_name: "CRM Lead : Notification transition étape"
  model: crm.lead
  trigger: on_create_or_write
  trigger_fields:
    - stage_id
  filter_pre_domain: "[('stage_id.sequence', '<', 3)]"
  filter_domain: "[('stage_id.sequence', '>=', 3)]"
  description: "Déclenche une notification lorsque l'opportunité passe d'une étape préliminaire à une étape avancée."
  server_actions:
    - won_crm_lead_sa
  active: true
```

---

### 9. Tâches Planifiées (`cron`)
Planifie l'exécution cyclique de scripts Python côté serveur. Le moteur IaC gère automatiquement la création/mise à jour synchronisée de la tâche planifiée et de son action serveur interne.
*   **Balises requises** :
    *   `name` : Identifiant local unique.
    *   `cron_name` : Intitulé compréhensible dans le menu des crons.
    *   `model` : Modèle d'exécution.
    *   `code` : Code Python exécuté périodiquement.
*   **Balises optionnelles** :
    *   `interval_number` : Fréquence d'exécution (défaut : `1`).
    *   `interval_type` : Unité de temps (`minutes`, `hours`, `days`, `weeks`, `months`).
    *   `active` : Active ou désactive temporairement l'exécution planifiée du cron (`true` ou `false`, par défaut `true`).
    *   `nextcall` : Date et heure de la prochaine exécution programmée en base Odoo (chaîne de caractères au format UTC `'AAAA-MM-JJ HH:MM:SS'`). Idéal pour cibler des heures creuses précises.
    *   `priority` : Priorité d'exécution du cron dans la file d'attente d'Odoo (les plus petites valeurs s'exécutent en premier, la priorité par défaut étant généralement `5`).
    *   `user_id` : Utilisateur technique sous lequel s'exécute le code Python (résolu dynamiquement par login d'utilisateur, nom complet ou XML-ID d'Odoo pour une traçabilité de sécurité et d'audit optimale, par défaut l'utilisateur Admin ID `1`).

```yaml
# Exemple 1 : Tâche cyclique standard s'exécutant tous les jours
- type: cron
  name: crm_inactivity_cron
  cron_name: "Cron CRM : Alerte inactivité 15j"
  model: crm.lead
  code: |
    limit_date_start = datetime.datetime.now() - datetime.timedelta(days=16)
    limit_date_end = datetime.datetime.now() - datetime.timedelta(days=15)
    leads = env['crm.lead'].search([
        ('stage_id', 'in', [3, 8, 9]),
        ('write_date', '>=', limit_date_start),
        ('write_date', '<=', limit_date_end),
    ])
    for record in leads:
        # Code d'alerte ou de notification...
  interval_number: 1
  interval_type: "days"

# Exemple 2 : Tâche planifiée hautement prioritaire, démarrant à une heure creuse précise et exécutée par un utilisateur technique
- type: cron
  name: dynamic_data_cleanup_cron
  cron_name: "Nettoyage hebdomadaire des logs"
  model: res.partner
  interval_number: 1
  interval_type: "weeks"
  nextcall: "2026-05-30 02:00:00"
  priority: 3
  user_id: "base.user_demo"
  code: |
    # Script de nettoyage et d'archivage...
    pass
```

---

### 10. Vues utilisateur (`view`)
Surcharge et hérite de l'architecture des vues Odoo via XPath sans réécriture complète, ou crée des templates QWeb autonomes. Le moteur IaC compile et traduit automatiquement les balises de structure de carte en fonction de la version majeure d'Odoo ciblée (par exemple, traduction dynamique bidirectionnelle entre `<kanban-box>` et `<card>` pour les vues Kanban Odoo 18 / 19).
*   **Balises requises** :
    *   `name` : Identifiant local unique.
    *   `view_name` : Nom d'identification technique de la vue (utilisé en base).
    *   `model` : Modèle Odoo auquel la vue est rattachée.
    *   `inherit_xml_id` : XML-ID de la vue parente héritée (ex : `base.view_partner_form`).
    *   `arch` : Structure de modification XML / XPath.
*   **Balises optionnelles** :
    *   `priority` : Priorité de chargement de la vue (défaut : `16`).
    *   `active` : Active ou désactive temporairement l'application de la vue ou de sa surcharge XPath (`true` ou `false`, par défaut `true`).
    *   `mode` : Mode d'héritage de la vue (`'primary'` pour créer une nouvelle vue autonome héritant du parent sans l'altérer globalement, `'extension'` pour appliquer la surcharge XPath directement au parent).
    *   `customize_show` : Rend la vue optionnelle et activable/désactivable manuellement par l'administrateur dans l'interface graphique (`true` ou `false`).
    *   `visibility` : Règle de restriction d'accès et de visibilité de la vue (sélection).
    *   `website_id` : Nom ou ID numérique du site Web d'Odoo auquel restreindre l'application de cette vue (très important pour les installations multi-sites d'Odoo).

```yaml
# Exemple 1 : Surcharge XPath standard (extension) d'une vue formulaire existante
- type: view
  name: res_company_form_mycompany
  view_name: "res.company.form.mycompany"
  model: res.company
  inherit_xml_id: "base.view_company_form"
  priority: 16
  arch: |
    <data>
        <xpath expr="//field[@name='company_registry']" position="after">
            <field name="x_forme_juridique" string="Forme juridique"/>
            <field name="x_capital_social" string="Capital social"/>
        </xpath>
    </data>

# Exemple 2 : Création d'une vue de formulaire alternative (primary) restreinte à un site Web spécifique
- type: view
  name: res_partner_form_website
  view_name: "res.partner.form.website"
  model: res.partner
  inherit_xml_id: "base.view_partner_form"
  mode: "primary"
  website_id: "Website"
  arch: |
    <form>
        <sheet>
            <group>
                <field name="name"/>
                <field name="email"/>
            </group>
        </sheet>
    </form>
```

---

### 11. Filtres d'actions (`window_action_filter`)
Injecte dynamiquement des critères de recherche par défaut lors de l'ouverture de menus ou de fenêtres standard d'Odoo.
*   **Balises requises** :
    *   `name` : Identifiant unique de la ressource.
    *   `xml_id` : XML-ID de l'action de fenêtre parente à altérer (ex : `account.action_move_out_invoice_type`).
    *   `filter_key` : Clé de filtre de recherche par défaut (ex : `search_default_late`).
    *   `filter_value` : Valeur d'activation du filtre (généralement `1` pour vrai).

```yaml
- type: window_action_filter
  name: invoicing_out_invoice_filter_main
  xml_id: "account.action_move_out_invoice_type"
  filter_key: "search_default_late"
  filter_value: 1
```

---

### 10b. Actions de fenêtre (`window_action`)
Crée et configure des actions de fenêtre (Window Actions) destinées à ouvrir des modèles sous certaines présentations de vues (ex: vues Kanban, liste, formulaire) et à être liées à des menus d'Odoo.
*   **Balises requises** :
    *   `name` : Identifiant local de la ressource.
    *   `action_name` : Nom de l'action de fenêtre affiché dans l'interface Odoo.
    *   `res_model` : Nom technique du modèle Odoo ciblé (ex : `res.partner`).
*   **Balises optionnelles** :
    *   `view_mode` : Liste ordonnée de types de vues Odoo à proposer (chaîne de caractères séparée par des virgules, ex : `'list,kanban,form'`).
    *   `domain` : Filtre SQL appliqué à l'ouverture de l'action sous forme de domaine Odoo (ex : `"[('customer_rank', '>', 0)]"`).
    *   `context` : Dictionnaire de contexte passé par défaut (ex : `"{'default_is_company': True}"`).
    *   `target` : Comportement d'ouverture de la fenêtre (`'current'` pour remplacer la vue en cours, `'new'` pour ouvrir un dialogue modal Pop-Up).
    *   `help` : Message explicatif d'aide HTML affiché si aucun enregistrement ne correspond aux filtres.
    *   `limit` : Limite d'enregistrements affichés par page (entier, ex : `80`).
    *   `view_id` : Vue spécifique Many2one par défaut (résolu dynamiquement par ID de vue déclaratif local ou XML-ID Odoo).
    *   `search_view_id` : Vue de recherche Many2one par défaut (résolu dynamiquement).

```yaml
# Exemple 1 : Action de fenêtre standard pour le modèle res.partner
- type: window_action
  name: action_partner_clients
  action_name: "Clients"
  res_model: res.partner
  view_mode: "kanban,list,form"
  domain: "[('customer_rank', '>', 0)]"
  context: "{'default_customer_rank': 1}"
  target: "current"
  limit: 100

# Exemple 2 : Action de fenêtre modale pop-up ouvrant une vue spécifique déclarée localement
- type: window_action
  name: action_partner_modal_new
  action_name: "Nouveau Contact Spontané"
  res_model: res.partner
  view_mode: "form"
  target: "new"
  view_id: res_partner_form_website  # Référence à la vue déclarée dans view
```

---

### 12. Modèles de Données Personnalisés (`model`)
Crée un nouveau modèle de données en base. Le moteur applique une auto-correction automatique pour garantir le préfixe obligatoire `x_` sur les modèles personnalisés en base, et supporte les listes d'héritage standard (`inherit`) avec mappage automatique des fonctionnalités transverses Odoo (Chatter/messagerie via `mail.thread`, activités planifiées via `mail.activity.mixin`, etc.).
*   **Balises requises** :
    *   `name` : Identifiant unique local du modèle (auto-corrigé avec préfixe `x_` en base de données si nécessaire).
    *   `model_name` : Libellé utilisateur du modèle affiché dans l'interface Odoo.
*   **Balises optionnelles** :
    *   `inherit` : Liste de modèles Odoo de type mixin dont on souhaite hériter (ex : `mail.thread`, `mail.activity.mixin`, `mail.blacklist`, `mail.thread.sms`).
    *   `abstract` : Déclare si le modèle est abstrait (utilisé comme parent non-physique héritable par d'autres modèles, `true` ou `false`).
    *   `order` : Définit le tri SQL par défaut des enregistrements du modèle (ex : `'sequence, create_date desc'`).
    *   `info` : Documentation technique interne décrivant l'usage du modèle (texte libre).
    *   `website_form_access` : Autorise ou non l'exposition et l'utilisation de ce modèle comme cible de formulaires Web publics Odoo (`true` ou `false`).
    *   `website_form_label` : Libellé convivial utilisé dans l'éditeur graphique de formulaires Web d'Odoo.
    *   `website_form_key` : Clé technique optionnelle pour associer le formulaire au site Web d'Odoo.

```yaml
# Exemple 1 : Modèle standard avec mixins de communication et d'activités
- type: model
  name: x_test_iac_model
  model_name: "Modèle de Test IaC"
  inherit:
    - "mail.thread"
    - "mail.activity.mixin"

# Exemple 2 : Modèle avancé avec tri ordonné, documentation interne et exposition Web Form sécurisée
- type: model
  name: x_candidature_nature
  model_name: "Candidature"
  order: "sequence, create_date desc"
  info: "Tableau de bord stockant les formulaires de candidatures spontanées reçues via le portail Web."
  website_form_access: true
  website_form_label: "Déposer une candidature"
  website_form_key: "nat_spontaneous_candidate"
  inherit:
    - "mail.thread"
```

---

### 13. Pages Web (`website_page`)
Crée et publie des pages web publiques ou privées sur le portail Odoo avec un contrôle total de la structure, des accès et du référencement SEO.
*   **Balises requises** :
    *   `name` : Identifiant local de la ressource.
    *   `url` : Route HTTP publique d'accès à la page (ex : `/ma-page`).
    *   `view_ref` : Identifiant local unique (ou XML-ID) du template QWeb décrivant la structure HTML de la page (de type `view`).
*   **Balises optionnelles** :
    *   `is_published` : Rend la page immédiatement visible aux visiteurs publics (`true` ou `false`, par défaut `true`).
    *   `website_indexed` : Autorise l'indexation de la page par les moteurs de recherche pour le référencement (`true` ou `false`, par défaut `true`).
    *   `active` : Active ou désactive temporairement la page Web (`true` ou `false`, par défaut `true`).
    *   `is_homepage` : Définit si cette page est la **page d'accueil principale** (la racine `/`) du site Web d'Odoo (`true` ou `false`).
    *   `is_in_menu` : Intègre automatiquement un lien vers cette page dans le menu de navigation principal du site Web (`true` ou `false`).
    *   `website_id` : Nom ou ID numérique du site internet Odoo pour restreindre l'affichage de la page (très utile en multi-sites, résolu dynamiquement).
    *   `visibility` : Règle de visibilité d'accès à la page (`'public'` pour tous, `'logged'` pour les utilisateurs connectés uniquement, `'password'` pour restreindre par mot de passe).
    *   `visibility_password` : Mot de passe d'accès requis si `visibility` est réglé sur `'password'`.
    *   `website_meta_title` : Surcharge le méta-titre HTML de la page pour le référencement SEO.
    *   `website_meta_description` : Surcharge la méta-description de la page pour les moteurs de recherche.
    *   `website_meta_keywords` : Mots-clés SEO pour l'indexation de la page.

```yaml
# Exemple 1 : Page web standard publique indexée
- type: website_page
  name: test_iac_website_page
  url: "/test-iac-page"
  view_ref: test_iac_page_template
  is_published: true
  website_indexed: true

# Exemple 2 : Page d'accueil officielle multi-sites avec méta-tags SEO optimisés et intégrée au menu
- type: website_page
  name: landing_page
  url: "/home"
  view_ref: home_template
  is_published: true
  is_homepage: true
  is_in_menu: true
  website_id: "Website"
  website_meta_title: "Accueil"
  website_meta_description: "Bienvenue sur le portail officiel. Découvrez nos activités et nos innovations."
  website_meta_keywords: "Groupe, Innovation, Services"
```

---

### 14. Pièces Jointes Binaires (`attachment`)
Permet d'héberger des fichiers arbitraires (images, PDF, ou code source comme CSS, SCSS, JS) en base de données avec des URLs virtuelles d'accès, et de les lier de manière sécurisée ou contextuelle.
*   **Balises requises** :
    *   `name` : Nom affiché du fichier dans la gestion documentaire Odoo.
    *   `url` : URL virtuelle relative d'accès pour appeler le fichier depuis un template ou un bundle (ex : `/test_iac/static/src/scss/test_style.scss`).
    *   `mimetype` : Type MIME du fichier (ex : `text/css`, `text/scss`, `application/javascript`, `application/pdf`).
    *   `raw_content` : Code brut ou contenu texte du fichier (le moteur l'encode automatiquement en Base64 pour le stockage binaire).
*   **Balises optionnelles** :
    *   `public` : Rend le document publiquement accessible sans authentification via son URL virtuelle (`true` ou `false`, par défaut `true`). Positionner à `false` pour sécuriser un fichier et restreindre son accès aux seuls utilisateurs Odoo autorisés.
    *   `description` : Commentaire ou note textuelle explicative sur la pièce jointe (texte libre).
    *   `website_id` : Nom ou ID numérique du site Web d'Odoo auquel restreindre cet asset (très important en multi-sites, résolu dynamiquement).
    *   `company_id` : Nom ou ID numérique de la société pour restreindre l'accès à ce document en multi-sociétés (résolu dynamiquement).
    *   `res_model` : Nom technique du modèle d'objet Odoo auquel attacher ce document (ex : `'res.partner'`, `'product.template'`).
    *   `res_id` : ID numérique de l'enregistrement de ce modèle auquel lier la pièce jointe.

```yaml
# Exemple 1 : Feuille de style CSS publique accessible sur le portail
- type: attachment
  name: test_iac_custom_style
  url: "/test_iac/static/src/scss/test_style.scss"
  mimetype: "text/scss"
  raw_content: |
    .my-dynamic-style {
        background-color: #f9fafb !important;
        border: 3px dashed #10b981 !important;
        border-radius: 12px;
        padding: 30px;
        transition: all 0.3s ease;
        
        &:hover {
            transform: translateY(-5px);
        }
    }

# Exemple 2 : Fichier PDF privé et sécurisé attaché contextuellement à une fiche de contact client spécifique
- type: attachment
  name: "nat_contract_emmanuel.pdf"
  url: "/private_attachments/contracts/emmanuel.pdf"
  mimetype: "application/pdf"
  public: false
  description: "Contrat de partenariat officiel."
  company_id: "YourCompany"
  res_model: "res.partner"
  res_id: 1
  raw_content: "Contenu brut de démonstration ou fichier encodé..."
```

---

### 15. Liaison d'Assets (`asset`)
Permet d'injecter des fichiers (téléversés au préalable via `attachment`) au sein des pipelines de compilation et de chargement d'Odoo en ciblant un bundle particulier (comme `web.assets_backend` ou `web.assets_frontend`).
*   **Balises requises** :
    *   `name` : Identifiant local unique de la règle d'asset.
    *   `bundle` : Nom technique du bundle ciblé (ex : `web.assets_frontend` ou `web.assets_backend`).
    *   `path` : URL virtuelle relative de la pièce jointe à charger (ou expression glob).
*   **Balises optionnelles** :
    *   `active` : Permet d'activer ou désactiver l'asset (défaut : `true`).
    *   `key` : Clé technique unique d'identification de l'asset.
    *   `directive` : Instruction de traitement de l'asset (`append`, `prepend`, `include`, `remove`, `replace`, défaut : `append`).
    *   `sequence` : Poids ordonné de chargement du fichier (défaut : `16`).
    *   `target` : Cible spécifique de l'asset (par ex. pour une directive de remplacement).
    *   `theme_template_id` : Référence ou XML-ID du modèle de thème lié (`theme.ir.asset`).
    *   `website_id` : Nom ou ID du site web auquel cet asset est restreint.

```yaml
- type: asset
  name: test_iac_custom_asset
  bundle: "web.assets_frontend"
  path: "/test_iac/static/src/scss/test_style.scss"
  directive: "append"
  active: true
  key: "test_iac.custom_style_asset"
  sequence: 10
  website_id: "Mon Site Web Principal"
```

---

### 16. Modifications de valeurs de champs (`record`)
Permet de modifier les valeurs de champs d'enregistrements système existants (comme la société principale, les configurations par défaut, etc.) ou d'insérer de nouvelles données personnalisées de manière parfaitement idempotente.
*   **Balises requises** :
    *   `name` : Identifiant local unique de la ressource.
    *   `model` : Nom technique du modèle Odoo ciblé (ex : `res.company`, `res.partner`).
    *   `values` : Dictionnaire clé-valeur contenant les champs techniques et les valeurs à écrire.
*   **Balises d'identification (au moins une requise)** :
    *   `xml_id` : XML-ID de l'enregistrement à identifier (ex : `base.main_company`). Recommandé pour la portabilité.
    *   `res_id` : Identifiant numérique de l'enregistrement en base de données.
    *   `search` : Liste représentant un domaine de recherche Odoo pour identifier l'enregistrement dynamique (ex : `[('name', '=', 'Mycompany')]`).
*   **Balise optionnelle** :
    *   `noupdate` : Contrôle la valeur du drapeau `noupdate` dans `ir.model.data` associé à l'XML-ID (défaut : `true`). Si `true` et si l'enregistrement existe déjà en base, les futures applications via l'IaC ne mettront pas à jour les valeurs de champs (comportement d'initialisation standard Odoo). Si `false`, les valeurs seront actualisées de manière continue à chaque déploiement.

```yaml
# Exemple 1 : Modification d'une valeur de champ sur un enregistrement système existant
- type: record
  name: update_main_company_phone
  model: res.company
  xml_id: "base.main_company"
  values:
    phone: "+33 1 23 45 67 89"

# Exemple 2 : Création (ou mise à jour) d'une nouvelle donnée personnalisée
- type: record
  name: create_custom_partner
  model: res.partner
  values:
    name: "Emmanuel"
    email: "emmanuel@my-company.com"
    phone: "+33 6 12 34 56 78"
    comment: "Créé via Odoo IaC Engine"

---

### 17. Importation Dynamique de Données (`import`)
Permet de peupler et synchroniser des enregistrements Odoo en masse à partir de fichiers locaux CSV ou Excel (`.xlsx`), avec un mapping déclaratif de colonnes et une gestion de l'unicité pour une idempotence absolue.
*   **Balises requises** :
    *   `name` : Identifiant local unique de la ressource.
    *   `model` : Nom technique du modèle Odoo ciblé (ex : `res.partner`, `product.template`).
    *   `file_path` : Chemin du fichier à importer sur le système local (CSV ou Excel). Les dépendances `pandas` et `openpyxl` sont incluses dans le package pour une installation automatique et transparente.
    *   `mapping` : Dictionnaire mappant le nom technique du champ Odoo au nom de la colonne du fichier.
        *   **Champs traduisibles (JSONB)** : Si un champ nécessite des traductions, vous pouvez passer un sous-dictionnaire où les clés sont les codes de langue Odoo (ex: `fr_FR`, `en_US`) et les valeurs sont les noms des colonnes contenant les traductions correspondantes.
*   **Balises optionnelles** :
    *   `unique_by` : Nom du champ Odoo (ou liste de champs) servant de clé d'unicité. Si un enregistrement correspondant est trouvé, il est mis à jour (`write`) ; sinon, il est créé (`create`). Permet une idempotence parfaite.

```yaml
# Exemple 1 : Importation de contacts standard
- type: import
  name: test_partners_import
  model: res.partner
  file_path: test_contacts.xlsx
  unique_by: email
  mapping:
    name: Nom
    email: Email
    phone: Mobile

# Exemple 2 : Importation de produits avec traductions multiples (champs traduisibles JSONB)
- type: import
  name: test_products_import
  model: product.template
  file_path: test_products.csv
  unique_by: default_code
  mapping:
    default_code: "Référence"
    list_price: "Prix de vente"
    # Nom traduisible en Français et Anglais à partir de colonnes séparées
    name:
      fr_FR: "Nom Français"
      en_US: "Nom Anglais"
```

---

### 18. Liaison de Menus (`menu`)
Permet de créer, positionner et modifier la barre de menus et la navigation d'Odoo (`ir.ui.menu`) de manière purement déclarative et idempotente.
*   **Balises requises** :
    *   `name` : Libellé/Nom du menu d'affichage.
*   **Balises optionnelles** :
    *   `active` : Permet d'activer ou masquer le menu (défaut : `true`).
    *   `parent_id` : Référence, XML-ID ou nom du menu parent (ex: `base.menu_administration`, ou un autre menu déclaré).
    *   `sequence` : Ordre d'affichage du menu parmi ses pairs (défaut : `10`).
    *   `action` : XML-ID ou référence d'action (comme une `window_action` ou `server_action` déclarée) déclenchée au clic (ex : `crm.crm_lead_action_pipeline`).
    *   `web_icon` : Chemin technique vers l'icône web du menu (ex: `base,static/description/icon.png`).
    *   `web_icon_data` : Fichier local d'image ou donnée brute base64 pour servir d'icône personnalisée.
    *   `group_ids` : Liste des groupes de sécurité (`group`), noms ou XML-IDs autorisés à voir et accéder à ce menu.

```yaml
- type: menu
  name: " Mon Super Menu"
  parent_id: "base.menu_administration"
  sequence: 20
  action: "crm.crm_lead_action_pipeline"
  group_ids:
    - "base.group_user"
  active: true
```

---

### 19. Modèles d'Emails (`mail_template`)
Permet de configurer, designer et déployer des modèles d'e-mails (`mail.template`) dynamiques au sein d'Odoo de manière purement déclarative.
*   **Balises requises** :
    *   `name` : Nom descriptif du modèle (ex : `Confirmation de Commande`).
    *   `model` : Nom technique du modèle de document associé (ex : `sale.order`, `crm.lead`, `res.partner`).
*   **Balises optionnelles** :
    *   `active` : Permet d'activer ou désactiver le modèle (défaut : `true`).
    *   `auto_delete` : Supprime automatiquement le mail généré après son envoi (défaut : `false`).
    *   `subject` : Sujet de l'e-mail (supporte les expressions de rendu Odoo comme `{{ object.name }}`).
    *   `body_html` : Contenu riche au format HTML servant de corps du message.
    *   `email_from` : Adresse d'expédition (ex: `{{ object.user_id.email_formatted }}`).
    *   `email_to` : Destinataires directs (e-mails séparés par des virgules).
    *   `email_cc` : Adresses en copie.
    *   `partner_to` : Destinataires directs (IDs de partenaires ou expressions dynamiques, ex : `{{ object.partner_id.id }}`).
    *   `reply_to` : Adresse de retour.
    *   `scheduled_date` : Date de planification d'envoi.
    *   `lang` : Code de langue pour le rendu localisé (ex: `{{ object.partner_id.lang }}`).
    *   `use_default_to` : Si positionné à `true`, utilise par défaut les destinataires liés à l'enregistrement (défaut : `false`).
    *   `description` : Description technique ou fonctionnelle du modèle.
    *   `email_layout_xmlid` : XML-ID du template de notification générale (ex : `mail.mail_notification_layout_with_actions`).
    *   `mail_server_id` : Nom, XML-ID ou ID numérique du serveur de messagerie sortant ciblé (`ir.mail_server`).
    *   `user_id` : Propriétaire du modèle (nom ou login, ex: `admin`).
    *   `attachment_ids` : Liste des pièces jointes (`attachment`) ou XML-IDs à attacher systématiquement lors de l'envoi de l'e-mail.
    *   `report_template_ids` : Liste des actions de rapports PDF (`ir.actions.report`), XML-IDs ou noms à générer et attacher dynamiquement lors de l'envoi.

```yaml
- type: mail_template
  name: "Modèle d'Email de test"
  model: "res.partner"
  subject: "Bienvenue, {{ object.name }} !"
  body_html: |
    <div style="font-family: Arial, sans-serif; font-size: 14px;">
      <p>Bonjour {{ object.name }},</p>
      <p>Nous sommes ravis de vous compter parmi nos partenaires !</p>
      <p>Cordialement,<br/>L'équipe</p>
    </div>
  email_from: "contact@mycompany.fr"
  email_to: "{{ object.email }}"
  use_default_to: true
  active: true
```

---

### 20. Modèles de SMS (`sms_template`)
Permet de configurer et designer des modèles de SMS (`sms.template`) de manière déclarative et idempotente.
*   **Balises requises** :
    *   `name` : Nom du modèle de SMS.
    *   `model` : Nom technique du modèle de document associé (ex : `res.partner`, `crm.lead`).
*   **Balises optionnelles** :
    *   `body` : Contenu textuel brut du SMS (supporte les expressions dynamiques Odoo comme `{{ object.name }}`).
    *   `lang` : Code de langue pour le rendu localisé (ex: `{{ object.partner_id.lang }}`).
    *   `sidebar_action_id` : Nom, XML-ID ou ID numérique de l'action fenêtrée (`ir.actions.act_window`) pour lier ce modèle de SMS dans les raccourcis de la barre latérale d'Odoo.

```yaml
- type: sms_template
  name: "Modèle de SMS de rappel"
  model: "res.partner"
  body: "Bonjour {{ object.name }}, ceci est un rappel automatique de notre part."
  lang: "{{ object.lang }}"
```

---

### 21. Séquences de numérotation (`sequence`)
Permet de configurer les compteurs et les formats de numérotations automatiques (`ir.sequence`) pour vos pièces de vente, d'achats, de facturation ou autres documents.
*   **Balises requises** :
    *   `name` : Nom lisible de la séquence (ex : `Séquence Facture`).
    *   `code` : Code technique d'identification de la séquence (ex : `sale.order`, `account.move`).
*   **Balises optionnelles** :
    *   `active` : Permet d'activer ou masquer la séquence (défaut : `true`).
    *   `prefix` : Préfixe inséré avant le numéro (ex : `IAC-{{year}}-`).
    *   `suffix` : Suffixe inséré après le numéro (ex : `-PROD`).
    *   `padding` : Longueur du numéro avec zéros de remplissage (ex : `4` pour obtenir `0001`, défaut : `0`).
    *   `number_increment` : Incrément du compteur (défaut : `1`).
    *   `number_next` : Prochain numéro de compteur à délivrer.
    *   `implementation` : Algorithme de mise en œuvre (`standard` ou `no_gap`).
    *   `use_date_range` : Utiliser des sous-séquences distinctes par intervalle de date (défaut : `false`).
    *   `company_id` : Nom, XML-ID ou ID SQL de la société liée.

```yaml
- type: sequence
  name: "Séquence de Commande"
  code: "sale.order"
  prefix: "IAC-{{year}}-"
  padding: 4
  number_next: 1
  implementation: "no_gap"
```

---

### 22. Formats de papier d'impression (`paperformat`)
Permet de définir des formats et marges de papier physiques personnalisés (`report.paperformat`) pour les impressions et rapports PDF.
*   **Balises requises** :
    *   `name` : Nom unique du format de papier.
*   **Balises optionnelles** :
    *   `format` : Taille standard du papier (`A0` à `A9`, `US Letter`, `Custom`, etc.).
    *   `orientation` : Orientation physique (`Portrait` ou `Landscape`).
    *   `margin_top`, `margin_bottom`, `margin_left`, `margin_right` : Marges physiques en millimètres (float).
    *   `page_height`, `page_width` : Dimensions sur-mesure de la page en mm (si format `Custom`).
    *   `header_line` : Afficher une ligne horizontale sous l'en-tête (booléen).
    *   `header_spacing` : Espace en millimètres pour l'en-tête (entier).
    *   `dpi` : Résolution d'impression en ppp (entier, ex: `90`).
    *   `disable_shrinking` : Désactive la réduction de page WKHTMLTOPDF (booléen).
    *   `default` : Définir comme format par défaut global (booléen).
    *   `css_margins` : Utiliser des marges spécifiées en CSS (booléen).

```yaml
- type: paperformat
  name: "Format Facture A4 Marges Fines"
  format: "A4"
  orientation: "Portrait"
  margin_top: 10.0
  margin_bottom: 10.0
  margin_left: 5.0
  margin_right: 5.0
  header_line: false
```

---

### 23. Actions de Rapports PDF (`report`)
Permet de déclarer, modifier et lier des actions d'impression et de génération de rapports PDF (`ir.actions.report`) de manière purement déclarative.
*   **Balises requises** :
    *   `name` : Nom de l'action de rapport.
    *   `report_name` : Nom de modèle technique interne (ex : `sale.report_saleorder`).
*   **Balises optionnelles** :
    *   `model` : Nom technique du modèle de document associé (ex : `sale.order`).
    *   `report_type` : Format de sortie (`qweb-pdf`, `qweb-html`, `qweb-text`).
    *   `report_file` : Chemin d'accès technique ou nom de fichier du rapport.
    *   `print_report_name` : Nom du fichier PDF généré au téléchargement (ex : `(object.name or '').replace('/','_')`).
    *   `attachment` : Expression Python de nommage de pièce jointe.
    *   `attachment_use` : Recharger le rapport depuis la pièce jointe s'il a déjà été généré (booléen).
    *   `multi` : Rendre l'action disponible pour plusieurs sélections de documents (booléen).
    *   `binding_type` : Type de liaison de barre d'action (`report`, `action`).
    *   `binding_view_types` : Types de vues associées au raccourci (ex: `form,list`).
    *   `binding_model_id` : Modèle Odoo sur lequel le bouton d'impression doit apparaître dans la barre latérale (ex: `sale.order`).
    *   `paperformat_id` : Nom, XML-ID ou ID SQL du format de papier à utiliser.
    *   `group_ids` : Groupes de sécurité autorisés à utiliser cette impression.

```yaml
- type: report
  name: "Imprimer Bon de Commande"
  report_name: "sale.report_saleorder"
  model: "sale.order"
  report_type: "qweb-pdf"
  paperformat_id: "Format Facture A4 Marges Fines"
  binding_model_id: "sale.order"
  binding_type: "report"
```

---

### 24. Paramètres Système de Configuration (`config_parameter`)
Permet de définir des clés-valeurs globales de paramétrages d'Odoo (`ir.config_parameter`) avec une idempotence totale.
*   **Balises requises** :
    *   `key` : Clé technique unique d'identification du paramètre (ex : `web.base.url`).
    *   `value` : Valeur textuelle associée au paramètre.

```yaml
- type: config_parameter
  key: "web.base.url"
  value: "https://mycompany.fr"
```

---

## 🛡️ Sécurité, Robustesse & Validation en Phase de Compilation (Premium Enhancements)

Pour répondre aux exigences des environnements de production et des chaînes CI/CD d'entreprise, le moteur IaC `ood` intègre des fonctionnalités avancées de protection et de validation pré-vol :

### 1. Masquage des Variables Sensibles
Toutes les valeurs des attributs dont le nom de clé contient un mot clé sensible (comme `password`, `key`, `token`, `secret`, `credential`, `private`) sont automatiquement interceptées et masquées sous la forme `**********` dans toutes les sorties du terminal (`ood plan`, `ood apply`, logs de compilation). Cela élimine tout risque de fuite de secrets dans les tableaux de bord CI/CD ou les fichiers de log.

### 2. Résolution Dynamique des relations Many2one 
Lors de l'importation de données complexes via la ressource `import`, vous pouvez effectuer des recherches de relations Many2one à la volée. Le moteur utilise un sous-dictionnaire déclaratif pour interroger Odoo par XML-RPC et obtenir dynamiquement l'ID SQL de l'enregistrement lié (par exemple, associer un produit à sa catégorie via son nom) :

```yaml
- type: import
  name: import_products_with_categories
  model: product.template
  file_path: articles.xlsx
  mapping:
    default_code: "Référence"
    list_price: "Prix"
    # Résolution Many2one dynamique du modèle product.category
    categ_id:
      relation: "product.category"
      search_by: "name"
      column: "Nom Catégorie"
```

### 3. Contrôle de Syntaxe Python Automatique
Tous les scripts Python embarqués dans les ressources `server_action`, `cron` ou `base_automation` sont compilés et validés par le parseur natif Python de l'hôte lors de l'exécution de la commande `ood plan`. En cas d'erreur de syntaxe ou d'indentation (`SyntaxError`, `IndentationError`), **la phase de planification s'interrompt immédiatement** en affichant un rapport clair contenant :
* Le message d'erreur précis du compilateur.
* Le numéro de la ligne en faute.
* L'extrait de code concerné.

### 4. Validation Stricte des Schémas Odoo
Pendant l'étape `ood plan`, pour chaque ressource de type `record` ou `import`, le moteur effectue une vérification précoce ("fail-fast") en interrogeant le dictionnaire de structure (`fields_get`) de l'instance Odoo (avec mise en cache optimisée à un seul appel par modèle). 
* Si le **modèle Odoo cible n'existe pas**, la planification échoue avec une erreur descriptive.
* Si un **champ technique cible n'existe pas** sur le modèle Odoo (ex: faute de frappe `emaiil` au lieu de `email`), la planification échoue immédiatement en listant la liste des champs valides disponibles pour ce modèle.

---

## ⚡ Robustesse, Vitesse & Transactionnalité d'Entreprise

Pour supporter les déploiements industriels à grande échelle et garantir une intégrité absolue de la base de données Odoo même en cas de panne réseau ou de mauvaise configuration applicative, le moteur IaC `ood` intègre les mécanismes avancés suivants :

### 1. Déploiement Parallèle Multi-threadé
Afin d'accélérer drastiquement la phase d'`apply`, le moteur regroupe les actions du plan par tier topologique (les ressources de même type et même action, par exemple la création en masse de 50 champs personnalisés).
* **Parallélisme avec ThreadPoolExecutor** : Les ressources d'un même tier sont déployées en parallèle.
* **Isolation Thread-Local du Client XML-RPC** : Le partage de connexions ou de proxies XML-RPC entre les threads d'exécution pouvant causer des corruptions de protocole HTTP, le client `OdooClient` encapsule ses proxies et sessions dans un état thread-local (`threading.local`). Chaque thread dispose ainsi de sa propre connexion isolée sans surcharge mémoire.
* **Verrou de Sécurité sur l'État Local** : Les écritures dans le fichier d'état local `ood.oodstate` sont synchronisées via un verrou d'exclusion mutuelle (`threading.Lock`), garantissant une intégrité parfaite des données d'état.
* **Configuration** : Le paramètre optionnel `max_workers` peut être renseigné sous la clé `connection` de votre configuration (par défaut `4`).

### 2. Audits de Sécurité Pré-vol ACL
Le déploiement IaC requiert des droits élevés d'administration (création de champs, vues, automatisations). Pour éviter qu'une exécution s'interrompe à mi-chemin à cause d'une permission manquante, le moteur effectue une vérification précoce de sécurité avant tout déploiement :
* **Analyse Pré-vol des Permissions** : Le moteur interroge Odoo via `check_access_rights` sur tous les modèles cibles affectés par le plan (ex : `ir.model.fields`, `ir.ui.view`, `ir.cron`, etc.).
* **Exclusion Intelligente des Nouveaux Modèles** : Si le plan contient la création de modèles personnalisés (de type `model`), le moteur omet dynamiquement ces tables lors de la vérification pré-vol de sécurité, puisqu'elles n'existent pas encore physiquement en base.
* **Fail-Fast** : Si une seule autorisation CRUD requise fait défaut, le déploiement s'interrompt immédiatement avec un rapport détaillé avant toute écriture en base.

### 3. Rollback Transactionnel sur Échec
En cas d'erreur inattendue survenant lors d'un `apply` (panne réseau, erreur système Odoo, validation métier bloquante) :
* **Restauration Automatique de l'État** : Le moteur IaC dépile la pile d'exécution dans l'ordre rigoureusement inverse de leur application.
* **Annulation des Créations** : Les ressources créées durant la session courante sont supprimées d'Odoo (`unlink`).
* **Rétablissement des Mises à Jour** : Pour chaque ressource mise à jour, sa configuration d'origine (préalablement sauvegardée en mémoire avant modification) est réécrite en base Odoo pour restaurer son état original exact.
* **Persistance Atomique** : À chaque étape du rollback, le fichier d'état local `ood.oodstate` est mis à jour en temps réel pour refléter le statut physique réel de la base, évitant tout désalignement d'état en cas d'interruption abrupte.

---

## 📄 Licence

Ce projet est distribué sous licence libre **GNU General Public License v3 (GPLv3)**. Pour plus de détails, veuillez consulter le fichier [LICENSE] situé à la racine du projet.

