Se rendre au contenu

Actions serveur, cron et automations en Odoo 19 : ir.cron, ir.actions.server et base.automation

Bloc 5 · Rapports et automatisations — Article 3/4 Actions serveur, cron et automations en Odoo 19 Trois outils natifs, zéro serveur externe : automatiser les…
26 avril 2026 par
Actions serveur, cron et automations en Odoo 19 : ir.cron, ir.actions.server et base.automation
B.Mustapha

Bloc 5 · Rapports et automatisations — Article 3/4

Actions serveur, cron et automations en Odoo 19

Trois outils natifs, zéro serveur externe : automatiser les tâches récurrentes avec ir.cron, exposer des boutons métier via ir.actions.server, et déclencher des règles sur événement avec base.automation.

~14 minutes de lecture

Form base.automation — règle VIP auto-priorité sur helpdesk.ticket
Objectif de l'article — une règle base.automation qui bascule automatiquement la priorité en Haute quand un ticket concerne un client VIP, plus un cron qui escalade les tickets dont l'échéance est imminente.

Ce que tu vas apprendre

ir.cron

Les tâches planifiées, leur intervalle, leur code Python sécurisé.

ir.actions.server

Déclencher du code depuis un bouton ou un menu contextuel.

base.automation

Règles déclenchées sur create/write /change de champ / alarme temps.

safe_eval

Ce qui est autorisé dans le code XML et ce qui ne l'est pas.

Prérequis

  • Module odooskills_helpdesk v19.0.1.13.0 (T21).
  • Module base_automation installé (natif, mais pas toujours présent : l'ajouter dans depends).
  • Méthodes Python métier sur helpdesk.ticket (T15).

1. Trois outils, trois cas d'usage

En Odoo 19, ir.cron hérite directement d'ir.actions.server. Techniquement, une tâche planifiée est une action serveur avec un intervalle. Cette unification simplifie le mental model.

ir.actions.server Déclenchement MANUEL bouton, menu Actions binding_model_id "Escalader ce ticket" ir.cron Déclenchement TEMPOREL toutes les N min/heures/j hérite ir.actions.server "Escalader SLA toutes 30 min" base.automation Déclenchement ÉVÉNEMENTIEL on_create, on_write, on_unlink + filter_domain "Ticket VIP → priorité haute" Les 3 outils d'automatisation Odoo 19 Tous exécutent au final du code Python via safe_eval — la différence, c'est QUAND.
OutilQuandCas typique
ir.actions.server L'utilisateur clique. Bouton "Escalader", "Envoyer email groupé", "Générer PDF batch".
ir.cron Régulièrement, sans intervention humaine. Nettoyage nocturne, rapports quotidiens, rappels J-1, escalade SLA.
base.automation Un événement métier survient. Sur création d'un ticket VIP, sur changement d'état, sur dépassement de deadline.

2. ir.actions.server — le bouton métier

Le pattern type : une méthode Python métier bien isolée, et une action serveur minimaliste qui l'appelle.

# models/helpdesk_ticket.py
def action_escalate_sla(self):
    """Escalade : bascule en priorité Urgente et poste un message.

    Appelable depuis une ir.actions.server (manuel) ou un ir.cron (batch).
    Idempotent : ignore les tickets déjà urgents ou résolus.
    """
    escalated = self.filtered(lambda t: t.state != 'done' and t.priority != '3')
    if not escalated:
        return False
    escalated.write({'priority': '3'})
    for ticket in escalated:
        ticket.message_post(
            body="⚠️ <strong>Escalade SLA automatique</strong> — priorité Urgente"
        )
    return True

Puis l'action serveur XML :

<record id="server_action_escalate_sla" model="ir.actions.server">
    <field name="name">Escalader en priorité Urgente</field>
    <field name="model_id" ref="model_helpdesk_ticket"/>
    <field name="binding_model_id" ref="model_helpdesk_ticket"/>
    <field name="binding_view_types">form,list</field>
    <field name="state">code</field>
    <field name="code">
if records:
    records.action_escalate_sla()
    </field>
</record>
Form ir.actions.server dans l'UI Odoo avec code Python et binding
La form ir.actions.server côté backend. binding_model_id fait apparaître l'action dans le menu Actions de la form et de la liste — pratique pour opérer en masse depuis une vue liste cochée.

Champs clés :

ChampRôle
model_id Modèle sur lequel opère l'action.
state code (Python), object_write (updater déclaratif), object_create, webhook, mail_post, etc.
binding_model_id Expose l'action dans le menu Actions — comme vu en T19 pour les wizards.
code Python exécuté via safe_eval avec un contexte restreint : records, record, env, Warning, UserError.

3. safe_eval — le piège STORE_ATTR

Le code d'une ir.actions.server passe par safe_eval : un exécuteur Python restreint qui bloque les opérations dangereuses (imports arbitraires, écriture fichier, socket, etc.).

Effet de bord : certaines syntaxes Python "normales" sont interdites, en particulier l'assignation directe d'attribut sur un record :

# ❌ CRASH — "forbidden opcode(s) in ... STORE_ATTR"
for record in records:
    record.priority = '2'

# ✅ OK — utiliser write() ou l'ORM
for record in records:
    record.write({'priority': '2'})

# ✅ OK — batch
records.write({'priority': '2'})
Règle d'or — dans le code d'une action serveur XML, toujours passer par .write() ou des appels de méthodes, jamais par record.field = valeur. Le STORE_ATTR n'est autorisé que dans le code Python compilé classique (fichiers .py du module).

Variables disponibles dans code :

VariableValeur
env L'environnement ORM — équivalent self.env.
records Recordset sur lequel l'action a été lancée (1+ records).
record Premier record si le contexte en définit un — à éviter pour les actions batch.
model Le modèle sans filtrage — env['helpdesk.ticket']. Utile dans les crons pour model.search([...]).
Warning, UserError Exceptions standard à lever pour remonter un message à l'utilisateur.

4. ir.cron — la tâche planifiée

On ajoute au modèle une méthode _cron_escalate_sla qui bouclera sur les tickets à risque :

@api.model
def _cron_escalate_sla(self):
    """Cron : escalade les tickets dont l'échéance est dans les 24h."""
    from datetime import timedelta
    threshold = fields.Date.today() + timedelta(days=1)
    tickets = self.search([
        ('state', '!=', 'done'),
        ('priority', '!=', '3'),
        ('deadline', '!=', False),
        ('deadline', '<=', threshold),
    ])
    if tickets:
        tickets.action_escalate_sla()
    return len(tickets)

Puis le cron XML :

<record id="ir_cron_escalate_sla" model="ir.cron">
    <field name="cron_name">Helpdesk — Escalade SLA automatique</field>
    <field name="name">Helpdesk — Escalade SLA automatique</field>
    <field name="model_id" ref="model_helpdesk_ticket"/>
    <field name="state">code</field>
    <field name="code">model._cron_escalate_sla()</field>
    <field name="interval_number">30</field>
    <field name="interval_type">minutes</field>
    <field name="active" eval="True"/>
</record>
Form ir.cron — Helpdesk Escalade SLA automatique toutes les 30 min avec bouton Exécuter manuellement
La form ir.cron en v19 : bouton Exécuter manuellement — pratique en dev pour valider sans attendre l'intervalle. La Prochaine date d'exécution est tracée automatiquement.
Disparu en v19 — les champs numbercall et doall n'existent plus. Le cron runner gère nativement les reprises et le catch-up. Moins de paramètres à comprendre, comportement plus prévisible.

Intervalles disponibles

interval_type ∈ {minutes, hours, days, weeks, months}. Jamais seconds — Odoo n'est pas un moteur temps réel.

Exécuter le cron maintenant pour tester

# Depuis un shell Odoo
./odoo-bin shell -c config/odoo.conf -d ma_db

# Python interactif
>>> env['helpdesk.ticket']._cron_escalate_sla()
2
>>> env.cr.commit()

Ou via l'UI, bouton Exécuter manuellement en haut de la form cron. Très utile en dev pour itérer sans attendre 30 min.

5. base.automation — la règle événementielle

En Odoo 19, base.automation ne porte plus le code directement : elle déclare un trigger (quand ?) et un filter_domain (sur quoi ?), puis pointe vers une ou plusieurs ir.actions.server via action_server_ids.

Étape 1 — l'action serveur qui fait le boulot :

<record id="server_action_set_vip_priority" model="ir.actions.server">
    <field name="name">Ticket : priorité haute pour client VIP</field>
    <field name="model_id" ref="model_helpdesk_ticket"/>
    <field name="state">code</field>
    <field name="code">
for record in records:
    if record.priority in ('0', '1'):
        record.write({'priority': '2'})
        record.message_post(body="Priorité auto-ajustée (client VIP).")
    </field>
</record>

Étape 2 — la règle qui branche l'action sur un événement :

<record id="automation_vip_priority" model="base.automation">
    <field name="name">Ticket VIP → priorité haute auto</field>
    <field name="model_id" ref="model_helpdesk_ticket"/>
    <field name="trigger">on_create_or_write</field>
    <field name="filter_domain">
        [('partner_id.is_vip', '=', True), ('priority', 'in', ['0', '1'])]
    </field>
    <field name="action_server_ids"
           eval="[(6, 0, [ref('server_action_set_vip_priority')])]"/>
    <field name="active" eval="True"/>
</record>

Les trigger disponibles :

TriggerEffet
on_create À la création d'un record.
on_write À chaque écriture.
on_create_or_write Les deux (le plus courant).
on_unlink À la suppression.
on_change À la modification d'un champ précis (spécifier trigger_field_ids).
on_time Alarme temporelle basée sur un champ Date/Datetime (ex : 2j après deadline).
on_webhook Appel externe sur une URL unique générée par Odoo.
Chatter d'un ticket VIP avec le message 'Priorité auto-ajustée (client VIP)' posté par OdooBot
Preuve que l'automation a tourné : à la création du ticket pour InfoSphere SARL (VIP), la priorité passe automatiquement de Normale à Haute et un message est ajouté dans le chatter par OdooBot.

6. Bonnes pratiques

Idempotence

Un cron ou une automation peut tourner 10 fois avant que tu t'en aperçoives. Écris toujours ton code comme s'il allait s'exécuter N fois sans effet de bord. Exemple : records.filtered(lambda t: t.priority != '3') avant d'écrire priority='3'.

Logique métier dans le modèle, pas dans le XML

Garde le code XML court (1 à 3 lignes) et pointe vers une méthode .py. Raisons :

  • Testable avec TransactionCase.
  • Lintable par ton IDE.
  • Réutilisable depuis plusieurs déclencheurs.
  • Git diff propre.

Manifest — dépendance à base_automation

# __manifest__.py
'depends': ['base', 'mail', 'base_automation'],
'data': [
    # ...
    'data/automation.xml',
    # ...
],

Le module base_automation n'est pas dans base — il faut l'inclure explicitement si tu utilises base.automation. Omettre cette dépendance = crash à l'install : External ID not found: model_base_automation.

Runner cron en développement

Odoo démarre un cron runner interne dès qu'un worker est disponible. En mode "serveur simple" (sans --workers), les crons tournent dans le même process que HTTP — latence jusqu'à une minute. En prod, préférer au moins --workers=2 pour un worker cron dédié.

Les pièges à connaître

  1. record.field = valeur dans le code XMLforbidden opcode STORE_ATTR. Utiliser .write().
  2. Oublier base_automation dans dependsExternal ID not found: model_base_automation.
  3. numbercall / doall → champs v18 supprimés en v19. Si tu copies un vieux cron, nettoie.
  4. Cron non idempotent → doubler ou tripler les effets (deux emails envoyés, priorité remontée puis re-remontée). Toujours filter préalable.
  5. Action automation sans action_server_ids → règle déclenchée mais rien ne s'exécute. Silencieux mais vide.
  6. filter_domain trop large → automation qui tourne sur TOUS les tickets à chaque write. Performance catastrophique. Commencer par un domain strict et élargir si besoin.
  7. Code XML qui accède à selfself n'existe pas. Utiliser records ou record.

Voir aussi dans cette série

T15 — Méthodes de modèle

create, write, actions

T19 — Wizards

TransientModel, modals

T21 — Email templates

mail.template, envoi auto

Prochain article — T23 · fin du Bloc 5

Place aux controllers HTTP et API REST : http.Controller, route @http.route(type='json'), authentification et payload, pour exposer ton module à l'extérieur.

Télécharger le guide technique Odoo 19 (PDF gratuit)
Email templates et mail.thread en Odoo 19 : envoi automatique depuis create et write
Bloc 5 · Rapports et automatisations — Article 2/4 Email templates et mail.thread en Odoo 19 Déclarer des modèles d'email avec variables QWeb inline…