Automated actions in Odoo: let the ERP do the boring work
Server actions, automation rules and scheduled jobs: the three pieces that make Odoo react, notify and run on its own —without anyone having to remember.
Part of our complete guide: Odoo module development.
A good part of an ERP's value isn't in what it stores, but in what it does without anyone asking. Move an order to "in preparation" when it's confirmed. Notify the salesperson if an offer has gone three days without a reply. Close, at month-end, the records that were left half-done. In too many companies people do all of this by hand, and it's exactly the kind of repetitive work Odoo automates with three pieces worth not confusing: what runs, when it fires and how often it repeats.
The central piece: the server action
A server action (ir.actions.server) is, simply, "a named chunk of logic" that Odoo can run. It can send an email, create a record, change a state… or run a snippet of Python you write. That last one is the powerful part:
# "code" field of an ir.actions.server, model estate.property.offer
for record in records:
if record.status == "accepted":
continue
record.write({"status": "accepted"})
record.property_id.write({"selling_price": record.price})
record.property_id.message_post(
body="Offer accepted by %s" % record.partner_id.name
)
Inside that code you have a handful of variables Odoo injects for you: records (the records you act on), record (if it's a single one), env (to reach any model) and model. It's the same ORM you use in a module, but without writing a module: ideal for automations that don't justify deployed code.
The trigger: the automation rule
An action on its own doesn't run; it needs a when. That's provided by an automation rule (the base.automation model), which hooks the action to an event: on record creation, when a specific field changes, when a certain time passes from a date. It's what turns "this logic exists" into "this logic fires on its own".
The big advantage is that it's configured from the interface, with nothing to deploy: you define the model, the trigger ("when status becomes accepted") and the action to run. For business automations that change often, this is gold: the client adjusts them without calling you.
The clock: scheduled jobs (cron)
The third type doesn't react to an event, but to time. A scheduled job (ir.cron) runs a method every X hours or days: the classic "every night at 3, review the overdue orders".
<record id="cron_expire_offers" model="ir.cron">
<field name="name">Expire old offers</field>
<field name="model_id" ref="model_estate_property_offer"/>
<field name="state">code</field>
<field name="code">model._cron_expire_offers()</field>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
</record>
And in the model, the method that does the real work:
@api.model
def _cron_expire_offers(self):
deadline = fields.Date.today() - timedelta(days=7)
stale = self.search([("status", "=", "new"), ("create_date", "<", deadline)])
stale.write({"status": "refused"})
The mistake that turns a helper into a wildfire
Here's the warning that prevents the most grief. When you put logic in an "on field change" rule and that logic, in turn, changes a field, you can cause a loop: the write fires the rule, which writes, which fires the rule again… I've seen it bring a server down in minutes. The defence is to write idempotent automations: ones that first check whether anything needs doing (if record.status == "accepted": continue) and don't re-trigger in a chain. An automation that isn't idempotent isn't a helper; it's a bomb with a timer.
Same with crons: if your nightly job processes "everything pending" with no limits, the day 100,000 records pile up it'll take hours and overlap with the next run. Always scope the domain and think about volume.
Code or configuration?
A question that always comes up: do I automate from the interface or with a module? My practical rule: if the logic is business-level and will change (thresholds, deadlines, who gets notified), it goes in a configurable automation rule, so the client can touch it without depending on anyone. If it's structural and critical —part of how the product works—, it goes in versioned code, with tests, like any other piece of the module (see best practices). Mixing the two criteria is how you end up with the Odoo systems where "nobody knows why this happened".
Where the return really shows
Of everything an ERP does, automation is where the return is most visible and fastest: every repetitive task you remove is hours your team spends on something else, and human errors that stop happening. You don't need to start with something big; the first "it does itself" is usually the one that convinces everyone.
Is there a process your team repeats by hand every day that smells automatable? Tell us in a diagnosis session and we'll tell you if it pays off, or see it within our custom development. And to understand which piece runs what, review computed fields, onchange and constraints.
Comments (0)
Be the first to comment.
Sign in to leave a comment.
Sign inComments are reviewed before publishing.
Related articles
PDF reports with QWeb in Odoo: from the form to the document that closes deals
QWeb is HTML that turns into PDF. We show you how to build a report, loop over records with t-foreach, and why t-field saves you a thousand headaches.
Inheritance in Odoo: the feature that decides whether your code survives upgrades
Classical extension, delegation and view inheritance. The three ways to modify Odoo without touching the core —and why inheriting instead of copying saves you every migration.
Relations between models in Odoo: Many2one, One2many and Many2many without the confusion
The three relations that connect your data, what each one really stores in the database, and the commands to write them without breaking anything.