8000 scheduled actions · odoo/documentation@61b6252 · GitHub
[go: up one dir, main page]

Skip to content

Commit 61b6252

Browse files
committed
scheduled actions
1 parent 5fcfde4 commit 61b6252

File tree

1 file changed

+157
-3
lines changed

1 file changed

+157
-3
lines changed

content/developer/tutorials/server_framework_101/05_connect_the_dots.rst

Lines changed: 157 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1477,10 +1477,164 @@ action.
14771477
Scheduled actions
14781478
-----------------
14791479

1480-
... also known as **crons**...
1480+
**Scheduled actions**, also known as cron jobs, are automated tasks that run periodically at
1481+
predefined intervals. They enable the automation of recurring operations and allow to offload
1482+
compute-intensive tasks to dedicated workers. Scheduled actions are typically used for background
1483+
operations such as data cleanup, third-party synchronization, report generation, and other tasks
1484+
that don't require immediate user interaction.
14811485

1482-
.. todo: explain magic commands
1483-
.. todo: ex: 6,0,0 to associate tags to properties in data
1486+
In Odoo, scheduled actions are implemented through the `ir.cron` model. When triggered, they execute
1487+
arbitrary code on a specified model, most commonly by calling a model method that implements the
1488+
desired business logic. Creating a scheduled action is simply a matter of adding a record to
1489+
`ir.cron`, after which a cron worker will execute it at the specified intervals.
1490+
1491+
.. example::
1492+
The following example implements a scheduled action that automatically reassigns inactive
1493+
products or products without sellers to the default seller.
1494+
1495+
.. code-block:: xml
1496+
1497+
<record id="reassign_inactive_products_cron" model="ir.cron">
1498+
<field name="name">Reassign Inactive Products</field>
1499+
<field name="model_id" ref="model_product"/>
1500+
<field name="code">model._reassign_inactive_products()</field>
1501+
<field name="interval_number">1</field>
1502+
<field name="interval_type">weeks</field>
1503+
</record>
1504+
1505+
.. code-block:: python
1506+
1507+
from odoo import api, models
1508+
from odoo.fields import Command
1509+
1510+
1511+
class Product(models.Model):
1512+
1513+
@api.model
1514+
def _reassign_inactive_products(self):
1515+
# Clear sellers from underperfoming products.
1516+
underperforming_products = self.search([('sales_count', '<', 10)])
1517+
underperforming_products.write({
1518+
'seller_ids': [Command.clear()], # Remove all sellers.
1519+
})
1520+
1521+
# Assign the default seller to products without sellers.
1522+
products_without_sellers = self.search([('seller_ids', '=', False)])
1523+
if products_without_sellers:
1524+
default_seller = self.env.ref('product.default_seller')
1525+
products_without_sellers.write({
1526+
'seller_ids': [Command.set(default_seller.ids)] # Replace with default seller.
1527+
})
1528+
1529+
.. note::
1530+
- The cron is scheduled to run weekly thanks to `interval_number=1` and
1531+
`interval_type='weeks'`.
1532+
- The `@api.model` decorator indicates the method operates on the model and records in `self`
1533+
are not relevant. This serves both as documentation and enables RPC calls without requiring
1534+
record IDs.
1535+
- Field commands are required for `One2many` and `Many2many` fields since they cannot be
1536+
assigned values directly.
1537+
- `Command.set` takes a list of IDs as argument, which the `ids` recordset attribute
1538+
conveniently provides.
1539+
1540+
.. seealso::
1541+
- Reference documentation on :ref:`scheduled actions <reference/actions/cron>`.
1542+
- Reference documentation on the :meth:`@api.model <odoo.api.model>` decorator.
1543+
- Reference documentation on :ref:`field commands <reference/fields/command>`.
1544+
1545+
.. exercise::
1546+
#. Create a scheduled action that automatically refuses offers that have expired.
1547+
#. Create a scheduled action that automatically applies a 10% discount and adds the "Price
1548+
Reduced" tag to inactive properties. A property is considered inactive if it didn't receive
1549+
any offers 2 months after it was listed.
1550+
1551+
.. tip::
1552+
To test your crons manually, activate the :doc:`developer mode
1553+
</applications/general/developer_mode>`, then go to :menuselection:`Settings --> Technical
1554+
--> Scheduled Actions`, and click :guilabel:`Run Manually` in form view.
1555+
1556+
.. spoiler:: Solution
1557+
1558+
.. code-block:: python
1559+
:caption: `__manifest__.py`
1560+
:emphasize-lines: 3
1561+
1562+
'data': [
1563+
# Model data
1564+
'data/ir_cron_data.xml',
1565+
[...]
1566+
],
1567+
1568+
.. code-block:: xml
1569+
:caption: `ir_cron_data.xml`
1570+
1571+
<?xml version="1.0" encoding="utf-8"?>
1572+
<odoo>
1573+
1574+
<record id="real_estate.discount_inactive_properties_cron" model="ir.cron">
1575+
<field name="name">Real Estate: Discount Inactive Properties</field>
1576+
<field name="model_id" ref="model_real_estate_property"/>
1577+
<field name="code">model._discount_inactive_properties()</field>
1578+
<field name="interval_number">1</field>
1579+
<field name="interval_type">days</field>
1580+
</record>
1581+
1582+
<record id="real_estate.refuse_expired_offers_cron" model="ir.cron">
1583+
<field name="name">Real Estate: Refuse Expired Offers</field>
1584+
<field name="model_id"< 9E81 /span> ref="model_real_estate_offer"/>
1585+
<field name="code">model._refuse_expired_offers()</field>
1586+
<field name="interval_number">1</field>
1587+
<field name="interval_type">days</field>
1588+
</record>
1589+
1590+
</odoo>
1591+
1592+
.. code-block:: python
1593+
:caption: `real_estate_offer.py`
1594+
:emphasize-lines: 1-4
1595+
1596+
@api.model
1597+
def _refuse_expired_offers(self):
1598+
expired_offers = self.search([('expiry_date', '<', fields.Date.today())])
1599+
expired_offers.action_refuse()
1600+
1601+
.. code-block:: xml
1602+
:caption: `real_estate_tag_data.xml`
1603+
:emphasize-lines: 1-4
1604+
1605+
<record id="real_estate.tag_price_reduced" model="real.estate.tag">
1606+
<field name="name">Price Reduced</field>
1607+
<field name="color">1</field>
1608+
</record>
1609+
1610+
.. code-block:: python
1611+
:caption: `real_estate_property.py`
1612+
:emphasize-lines: 3,10-24
1613+
1614+
from odoo import _, api, fields, models
1615+
from odoo.exceptions import UserError, ValidationError
1616+
from odoo.fields import Command
1617+
from odoo.tools import date_utils
1618+
1619+
1620+
class RealEstateProperty(models.Model):
1621+
[...]
1622+
1623+
@api.model
1624+
def _discount_inactive_properties(self):
1625+
two_months_ago = fields.Date.today() - date_utils.relativedelta(months=2)
1626+
price_reduced_tag = self.env.ref('real_estate.tag_price_reduced')
1627+
inactive_properties = self.search([
1628+
('create_date', '<', two_months_ago),
1629+
('active', '=', True),
1630+
('state', '=', 'new'),
1631+
('tag_ids', 'not in', price_reduced_tag.ids), # Only discount once.
1632+
])
1633+
for property in inactive_properties:
1634+
property.write({
1635+
'selling_price': property.selling_price * 0.9,
1636+
'tag_ids': [Command.link(price_reduced_tag.id)],
1637+
})
14841638
14851639
----
14861640

0 commit comments

Comments
 (0)
0