Odoo · Ventas · Automatización
El flujo presupuesto → proyecto → factura es el corazón de muchos negocios de servicios. Odoo lo soporta de serie, pero encadenar los pasos sin intervención manual (y sin disparos infinitos) requiere elegir bien entre automatización declarativa, server action o código Python. Aquí están los tres niveles.
Nativo
Lo que Odoo ya hace solo
| Paso | Mecanismo nativo |
|---|---|
| Venta → proyecto | Producto de tipo servicio con service_tracking="task_in_project" |
| Proyecto → partes de horas | Tareas con timesheet_ids facturables |
| Horas → factura | Crear factura sobre líneas facturables del pedido |
Antes de programar nada, revisa si la configuración del producto cubre tu caso. El 70% de las «automatizaciones» que piden los clientes ya existen en los ajustes.
Nivel 1
Automatización declarativa (base.automation)
<record id="automation_so_confirm_notify" model="base.automation">
<field name="name">Aviso al confirmar pedido</field>
<field name="model_id" ref="sale.model_sale_order"/>
<field name="trigger">on_state_set</field>
<field name="trg_selection_field_id"
ref="sale.selection__sale_order__state__sale"/>
<field name="state">code</field>
<field name="code">
for order in records:
order.message_post(body="Pedido confirmado, proyecto en marcha.")
</field>
</record>
on_state_set con trg_selection_field_id, en lugar del antiguo filtro de dominio sobre state. Más preciso y sin re-disparos al guardar.
Nivel 2
Lógica en el modelo (override de método)
class SaleOrder(models.Model):
_inherit = 'sale.order'
def action_confirm(self):
res = super().action_confirm()
for order in self:
if order.project_count and not order.diagnostic_done:
order._schedule_kickoff_activity()
return res
def _schedule_kickoff_activity(self):
self.activity_schedule(
'mail.mail_activity_data_todo',
summary='Reunión de arranque del proyecto',
user_id=self.user_id.id,
)
Sobreescribir action_confirm da control total y es testeable. Llama siempre a super() y opera en bucle sobre self para soportar acciones por lotes.
Estados
Tracking en campos de estado
state = fields.Selection([
('draft', 'Borrador'),
('in_progress', 'En curso'),
('to_invoice', 'Por facturar'),
('done', 'Cerrado'),
], default='draft', tracking=True)
tracking=True registra cada cambio en el chatter (requiere mail.thread). Así cualquier salto de estado queda auditado con autor y fecha, clave para depurar flujos automatizados.
if self.env.context.get('skip_auto')) o condiciona por el estado destino para cortar el ciclo.
Decisión
¿Declarativo o código?
| Caso | Recomendación |
|---|---|
| Aviso o actividad simple | base.automation (sin desplegar código) |
| Lógica con varias condiciones | Override de método en módulo |
| Necesitas tests de regresión | Código Python (TransactionCase) |
| El cliente lo cambiará a menudo | Declarativo, editable desde la UI |
Resumen
Primero exprime la configuración nativa (servicios que crean proyectos, líneas facturables). Para la lógica extra, elige base.automation si es simple y editable, u override de método si necesitas control y tests. Añade tracking=True a los estados y vigila los bucles de disparo.