Skip to content

20 Useful Odoo Server Action Code Snippets You Can Copy

DeployMonkey Team · March 23, 2026 12 min read

How Server Actions Work

Server actions (ir.actions.server) let you run Python code from buttons, automated actions, or menu items without writing a custom module. In the code, you have access to records (the active recordset), env (the environment), model (the model), time, datetime, and log.

1. Mass Confirm Sale Orders

for record in records:
    if record.state == 'draft':
        record.action_confirm()

2. Set a Field Based on Another Field

for record in records:
    if record.partner_id.country_id.code == 'US':
        record.write({'fiscal_position_id': env.ref('account.fiscal_position_us').id})

3. Send an Email Using a Template

template = env.ref('sale.email_template_edi_sale')
for record in records:
    template.send_mail(record.id, force_send=True)

4. Create a Follow-Up Activity

for record in records:
    record.activity_schedule(
        'mail.mail_activity_data_call',
        date_deadline=datetime.date.today() + datetime.timedelta(days=3),
        summary='Follow up with customer',
        note='Check if the customer received the quotation.',
    )

5. Archive Old Records

cutoff = datetime.date.today() - datetime.timedelta(days=365)
old = env['sale.order'].search([
    ('state', '=', 'cancel'),
    ('date_order', '<', str(cutoff))
])
old.write({'active': False})
log(f'Archived {len(old)} cancelled orders older than 1 year.')

6. Duplicate Records

for record in records:
    new = record.copy(default={'name': record.name + ' (Copy)'})
    log(f'Created copy: {new.name}')

7. Mass Update Tags

tag = env.ref('sale.tag_important')  # or search by name
for record in records:
    record.write({'tag_ids': [(4, tag.id)]})

8. Generate a Unique Reference

for record in records:
    if not record.client_order_ref:
        seq = env['ir.sequence'].next_by_code('sale.order.reference')
        record.write({'client_order_ref': seq or f'REF-{record.id}'})

9. Compute and Set Margin

for record in records:
    if record.amount_total:
        cost = sum(line.product_id.standard_price * line.product_uom_qty for line in record.order_line)
        margin = ((record.amount_total - cost) / record.amount_total) * 100
        record.message_post(body=f'Margin: {margin:.1f}%')

10. Reassign Salesperson by Region

region_map = {
    'US': env.ref('base.user_admin').id,
    'GB': env.ref('base.user_demo').id,
}
for record in records:
    country = record.partner_id.country_id.code
    if country in region_map:
        record.write({'user_id': region_map[country]})

11. Create Invoice from Sale Order

for record in records:
    if record.state == 'sale' and record.invoice_status == 'to invoice':
        record._create_invoices()
        log(f'Invoice created for {record.name}')

12. Auto-Set Payment Terms

for record in records:
    if record.amount_total > 10000:
        record.write({'payment_term_id': env.ref('account.account_payment_term_30days').id})
    else:
        record.write({'payment_term_id': env.ref('account.account_payment_term_immediate').id})

13. Post Internal Note

for record in records:
    record.message_post(
        body='This record was reviewed automatically.',
        message_type='comment',
        subtype_xmlid='mail.mt_note',
    )

14. Subscribe Users as Followers

users = env['res.users'].search([('groups_id', 'in', env.ref('sales_team.group_sale_manager').id)])
for record in records:
    record.message_subscribe(partner_ids=users.mapped('partner_id').ids)

15. Validate Stock Picking

for record in records:
    if record.state == 'assigned':
        for move_line in record.move_line_ids:
            move_line.write({'quantity': move_line.quantity_product_uom})
        record.button_validate()

16. Export Data to Log

total = sum(records.mapped('amount_total'))
count = len(records)
avg = total / count if count else 0
log(f'Selected {count} orders. Total: {total:.2f}, Average: {avg:.2f}')

17. Update Product Prices

for record in records:
    if record.list_price < record.standard_price:
        record.write({'list_price': record.standard_price * 1.3})
        log(f'Updated {record.name} price to {record.list_price}')

18. Create a Task from Sale Order

project = env['project.project'].search([('name', '=', 'Implementation')], limit=1)
for record in records:
    env['project.task'].create({
        'name': f'Setup for {record.partner_id.name}',
        'project_id': project.id,
        'partner_id': record.partner_id.id,
        'description': f'Sale Order: {record.name}\nAmount: {record.amount_total}',
    })

19. Merge Duplicate Partners

# Find duplicates by email
for record in records:
    if record.email:
        dupes = env['res.partner'].search([
            ('email', '=ilike', record.email),
            ('id', '!=', record.id)
        ])
        if dupes:
            log(f'Potential duplicates of {record.name}: {dupes.mapped("name")}')

20. Bulk Set Analytic Account

analytic = env['account.analytic.account'].search([('name', '=', 'Marketing')], limit=1)
if analytic:
    for record in records:
        for line in record.order_line:
            line.write({'analytic_distribution': {str(analytic.id): 100}})
    log(f'Set analytic account on {len(records)} orders.')

Using Server Actions Effectively

  • Go to Settings > Technical > Server Actions to create actions
  • Set the model, then choose "Execute Python Code" as the action type
  • Use records variable for the selected recordset
  • Use log() to output debug messages
  • Test on a single record before running on a batch
  • Server actions run with the current user permissions unless you use .sudo()