[IMP] Base Action Rule (only filter conditions)

bzr revid: api@openerp.com-20121128092928-7kbs1d9g8ung4oul
This commit is contained in:
Arnaud Pineux 2012-11-28 10:29:28 +01:00
parent a8f3510297
commit 3df8c5fe9a
9 changed files with 228 additions and 135 deletions

View File

@ -20,5 +20,6 @@
############################################################################## ##############################################################################
import base_action_rule import base_action_rule
import test_models
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -97,7 +97,8 @@ trigger date, like sending a reminder 15 minutes before a meeting."),
\ne.g.: 'urgent.*' will search for records having name starting with the string 'urgent'\ \ne.g.: 'urgent.*' will search for records having name starting with the string 'urgent'\
\nNote: This is case sensitive search."), \nNote: This is case sensitive search."),
'server_action_ids': fields.one2many('ir.actions.server', 'action_rule_id', 'Server Action', help="Define Server actions.\neg:Email Reminders, Call Object Service, etc.."), #TODO: set domain [('model_id','=',model_id)] 'server_action_ids': fields.one2many('ir.actions.server', 'action_rule_id', 'Server Action', help="Define Server actions.\neg:Email Reminders, Call Object Service, etc.."), #TODO: set domain [('model_id','=',model_id)]
'filter_id':fields.many2one('ir.filters', 'Filter', required=False), #TODO: set domain [('model_id','=',model_id.model)] 'filter_id':fields.many2one('ir.filters', 'Precondition Filter', required=False), #TODO: set domain [('model_id','=',model_id.model)]
'filter_post_id': fields.many2one('ir.filters', 'Postcondition Filter', required=False),
'last_run': fields.datetime('Last Run', readonly=1), 'last_run': fields.datetime('Last Run', readonly=1),
} }
@ -110,7 +111,7 @@ trigger date, like sending a reminder 15 minutes before a meeting."),
_order = 'sequence' _order = 'sequence'
def post_action(self, cr, uid, ids, model, old_records=None, context=None): def post_action(self, cr, uid, ids, model, precondition_ok=None, context=None):
# Searching for action rules # Searching for action rules
cr.execute("SELECT model.model, rule.id FROM base_action_rule rule \ cr.execute("SELECT model.model, rule.id FROM base_action_rule rule \
LEFT JOIN ir_model model on (model.id = rule.model_id) \ LEFT JOIN ir_model model on (model.id = rule.model_id) \
@ -122,7 +123,7 @@ trigger date, like sending a reminder 15 minutes before a meeting."),
# If the rule doesn't involve a time condition, run it immediately # If the rule doesn't involve a time condition, run it immediately
# Otherwise we let the scheduler run the action # Otherwise we let the scheduler run the action
if self.browse(cr, uid, rule_id, context=context).trg_date_type == 'none': if self.browse(cr, uid, rule_id, context=context).trg_date_type == 'none':
self._action(cr, uid, [rule_id], model_pool.browse(cr, uid, ids, context=context), old_records=old_records, context=context) self._action(cr, uid, [rule_id], model_pool.browse(cr, uid, ids, context=context), precondition_ok=precondition_ok, context=context)
return True return True
def _create(self, old_create, model, context=None): def _create(self, old_create, model, context=None):
@ -134,8 +135,17 @@ trigger date, like sending a reminder 15 minutes before a meeting."),
if context is None: if context is None:
context = {} context = {}
new_id = old_create(cr, uid, vals, context=context) new_id = old_create(cr, uid, vals, context=context)
#As it is a new record, we can assume that the precondition is true for every filter.
#(There is nothing before the create so no condition)
precondition_ok = {}
precondition_ok[new_id] = {}
for action in self.browse(cr, uid, self.search(cr, uid, [], context=context), context=context):
if action.filter_id:
precondition_ok[new_id][action.id] = False
else:
precondition_ok[new_id][action.id] = True
if not context.get('action'): if not context.get('action'):
self.post_action(cr, uid, [new_id], model, context=context) self.post_action(cr, uid, [new_id], model, precondition_ok=precondition_ok, context=context)
return new_id return new_id
return wrapper return wrapper
@ -151,15 +161,22 @@ trigger date, like sending a reminder 15 minutes before a meeting."),
if isinstance(ids, (str, int, long)): if isinstance(ids, (str, int, long)):
ids = [ids] ids = [ids]
model_pool = self.pool.get(model) model_pool = self.pool.get(model)
# get the old records (before the write) # We check for the pre-filter. We must apply it before the write
if model and ids: precondition_ok = {}
old_records = model_pool.browse(cr,uid,ids,context=context) for id in ids:
# look at records' states to fill the record cache precondition_ok[id] = {}
for record in old_records: for action in self.browse(cr, uid, self.search(cr, uid, [], context=context), context=context):
record.state precondition_ok[id][action.id] = True
if action.filter_id and action.model_id.model == action.filter_id.model_id:
ctx = dict(context)
ctx.update(eval(action.filter_id.context))
obj_ids = []
if self.pool.get(action.model_id.model)!=None:
obj_ids = self.pool.get(action.model_id.model).search(cr, uid, eval(action.filter_id.domain), context=ctx)
precondition_ok[id][action.id] = id in obj_ids
old_write(cr, uid, ids, vals, context=context) old_write(cr, uid, ids, vals, context=context)
if not context.get('action'): if not context.get('action'):
self.post_action(cr, uid, ids, model, old_records=old_records, context=context) self.post_action(cr, uid, ids, model, precondition_ok=precondition_ok, context=context)
return True return True
return wrapper return wrapper
@ -263,15 +280,15 @@ trigger date, like sending a reminder 15 minutes before a meeting."),
def do_check(self, cr, uid, action, obj, old_records=None, context=None): def do_check(self, cr, uid, action, obj, precondition_ok=True, context=None):
""" check Action """ """ check Action """
if context is None: if context is None:
context = {} context = {}
ok = True ok = precondition_ok
if action.filter_id and action.model_id.model == action.filter_id.model_id: if action.filter_post_id and action.model_id.model == action.filter_post_id.model_id:
ctx = dict(context) ctx = dict(context)
ctx.update(eval(action.filter_id.context)) ctx.update(eval(action.filter_post_id.context))
obj_ids = self.pool.get(action.model_id.model).search(cr, uid, eval(action.filter_id.domain), context=ctx) obj_ids = self.pool.get(action.model_id.model).search(cr, uid, eval(action.filter_post_id.domain), context=ctx)
ok = ok and obj.id in obj_ids ok = ok and obj.id in obj_ids
if getattr(obj, 'user_id', False): if getattr(obj, 'user_id', False):
ok = ok and (not action.trg_user_id.id or action.trg_user_id.id==obj.user_id.id) ok = ok and (not action.trg_user_id.id or action.trg_user_id.id==obj.user_id.id)
@ -284,22 +301,6 @@ trigger date, like sending a reminder 15 minutes before a meeting."),
(action.trg_partner_categ_id.id in map(lambda x: x.id, obj.partner_id.category_id or [])) (action.trg_partner_categ_id.id in map(lambda x: x.id, obj.partner_id.category_id or []))
) )
) )
#state_to = the state after a write or a create
state_to = getattr(obj, 'state', False)
#state_from = nothing or the state of old_records
state_from = ""
if old_records != None:
for record in old_records:
state_from = record.state
else:
state_from = "na" #it means that there was no state before (creation for example)
#if we have an action that check the status
if action.trg_state_to:
if action.trg_state_from:
ok = ok and action.trg_state_from==state_from
else:
ok = state_from!=state_to
ok = ok and action.trg_state_to==state_to
reg_name = action.regex_name reg_name = action.regex_name
result_name = True result_name = True
if reg_name: if reg_name:
@ -341,7 +342,7 @@ trigger date, like sending a reminder 15 minutes before a meeting."),
model_obj.message_subscribe(cr, uid, [obj.id], new_followers, context=context) model_obj.message_subscribe(cr, uid, [obj.id], new_followers, context=context)
return True return True
def _action(self, cr, uid, ids, objects, scrit=None, old_records=None, context=None): def _action(self, cr, uid, ids, objects, scrit=None, precondition_ok=None, context=None):
""" Do Action """ """ Do Action """
if context is None: if context is None:
context = {} context = {}
@ -350,9 +351,11 @@ trigger date, like sending a reminder 15 minutes before a meeting."),
objects = [objects] objects = [objects]
for action in self.browse(cr, uid, ids, context=context): for action in self.browse(cr, uid, ids, context=context):
for obj in objects: for obj in objects:
if self.do_check(cr, uid, action, obj, old_records=old_records, context=context): ok = True
if precondition_ok!=None:
ok = precondition_ok[obj.id][action.id]
if self.do_check(cr, uid, action, obj, precondition_ok=ok, context=context):
self.do_action(cr, uid, action, obj, context=context) self.do_action(cr, uid, action, obj, context=context)
context.update({'action': False}) context.update({'action': False})
return True return True

View File

@ -15,36 +15,31 @@
<sheet> <sheet>
<group col="4"> <group col="4">
<field name="name"/> <field name="name"/>
<field name="model_id"/>
<field name="filter_id" domain="[('model_id','=',model), ('user_id', '=', False)]" context="{'default_model_id': model}"/>
<field name="sequence"/>
<field name="active"/> <field name="active"/>
<field name="model_id"/>
<field name="model" invisible="1"/> <field name="model" invisible="1"/>
<field name="sequence" invisible="1"/>
</group> </group>
<notebook> <notebook>
<page string="Conditions"> <page string="Conditions">
<p class="oe_grey">
Select a filter or a timer as condition.<br/>
To create a new filter,<br/>
- Go to your Model's page and set the filter parameters in the "Search" view<br/>
- In this same "Search" view, select the menu "Save Current Filter", enter the name and add the option "Share with all users"<br/>
The filter must therefore be available in this page.
</p>
<group> <group>
<group name="model" string="Conditions on Model Fields"> <group name="filter" string="Filter Condition">
<field name="regex_name"/> <field name="filter_id" domain="[('model_id','=',model), ('user_id', '=', False)]" context="{'default_model_id': model}"/>
<field name="trg_user_id"/> <field name="filter_post_id" domain="[('model_id','=',model), ('user_id', '=', False)]" context="{'default_model_id': model}"/>
</group> </group>
<group name="partner" string="Conditions on Model Partner"> <group name="timing" string="Timer">
<field name="trg_partner_id"/>
<field name="trg_partner_categ_id"/>
</group>
<group name="state" string="Conditions on Status">
<field name="trg_state_to"/>
<field name="trg_state_from" attrs="{'invisible': ['|',('trg_state_to', '=', ' '),('trg_state_to', '=', '')]}"/>
</group>
<group name="timing" string="Conditions on Timing">
<field name="trg_date_type"/> <field name="trg_date_type"/>
<field name="trg_date_range" string="Delay After Trigger Date" attrs="{'invisible': [('trg_date_type', '=', 'none')]}"/> <field name="trg_date_range" string="Delay After Trigger Date" attrs="{'invisible': [('trg_date_type', '=', 'none')]}"/>
<field name="trg_date_range_type" attrs="{'invisible': [('trg_date_type', '=', 'none')]}"/> <field name="trg_date_range_type" attrs="{'invisible': [('trg_date_type', '=', 'none')]}"/>
</group> </group>
</group> </group>
<group string="Note">
<label string="The rule uses the AND operator. The model must match all non-empty fields so that the rule executes the action described in the 'Actions' tab." />
</group>
</page> </page>
<page string="Actions"> <page string="Actions">
<group name="action_followers"> <group name="action_followers">
@ -63,7 +58,6 @@
</tree> </tree>
</field> </field>
</group> </group>
</page> </page>
</notebook> </notebook>
</sheet> </sheet>

View File

@ -0,0 +1,32 @@
from osv import osv, fields
AVAILABLE_STATES = [
('draft', 'New'),
('cancel', 'Cancelled'),
('open', 'In Progress'),
('pending', 'Pending'),
('done', 'Closed')
]
class lead_test(osv.Model):
_name = "base.action.rule.lead.test"
_columns = {
'name': fields.char('Subject', size=64, required=True, select=1),
'user_id': fields.many2one('res.users', 'Responsible'),
'state': fields.selection(AVAILABLE_STATES, string="Status", readonly=True),
'active': fields.boolean('Active', required=False),
'partner_id': fields.many2one('res.partner', 'Partner', ondelete='set null'),
'date_action_last': fields.datetime('Last Action', readonly=1),
}
_defaults = {
'state' : 'draft',
'active' : True,
}
def message_post(self, cr, uid, thread_id, body='', subject=None, type='notification', subtype=None, parent_id=False, attachments=None, context=None, **kwargs):
pass
def message_subscribe(self, cr, uid, ids, partner_ids, subtype_ids=None, context=None):
pass

View File

@ -0,0 +1,142 @@
import tools
from openerp.tests import common
from .. import test_models
class base_action_rule_test(common.TransactionCase):
def setUp(self):
"""*****setUp*****"""
super(base_action_rule_test, self).setUp()
cr, uid = self.cr, self.uid
self.demo_user = self.registry('ir.model.data').get_object_reference(cr, uid, 'base', 'user_demo')
self.admin_user = self.registry('ir.model.data').get_object_reference(cr, uid, 'base', 'user_admin')
def create_filter_done(self, cr, uid, context=None):
filter_pool = self.registry('ir.filters')
return filter_pool.create(cr, uid, {
'name': "Lead is in done state",
'is_default': False,
'model_id': 'base.action.rule.lead.test',
'domain' : "[('state','=','done')]",
}, context=context)
def create_filter_draft(self, cr, uid, context=None):
filter_pool = self.registry('ir.filters')
return filter_pool.create(cr, uid, {
'name': "Lead is in draft state",
'is_default': False,
'model_id': "base.action.rule.lead.test",
'domain' : "[('state','=','draft')]",
}, context=context)
def create_lead_test_1(self, cr, uid, context=None):
"""
Create a new lead_test
"""
lead_pool = self.registry('base.action.rule.lead.test')
return lead_pool.create(cr, uid, {
'name': "Lead Test 1",
'user_id': self.admin_user[1],
}, context=context)
def create_rule(self, cr, uid, filter_id=None, filter_post_id=None, context=None):
"""
The "Rule 1" says that when a lead goes to the 'draft' state, the responsible for that lead changes to "demo_user"
"""
self.action_pool = self.registry('base.action.rule')
return self.action_pool.create(cr,uid,{
'name' : "Rule 1",
'model_id': self.registry('ir.model').search(cr, uid, [('model','=','base.action.rule.lead.test')], context=context)[0],
'active' : 1,
'trg_date_type' : 'none',
'filter_post_id' : filter_post_id,
'filter_id' : filter_id,
'act_user_id': self.demo_user[1],
}, context=context)
def test_00_check_to_state_draft_pre(self):
"""
Check that a new record (with state = draft) doesn't change its responsible when there is a precondition filter which check that the state is draft.
"""
cr, uid = self.cr, self.uid
context = {}
filter_draft = self.create_filter_draft(cr, uid, context=context)
rule_1_id = self.create_rule(cr, uid, filter_id=filter_draft, context=context)
new_lead_id = self.create_lead_test_1(cr,uid,context=context)
new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context)
self.assertTrue(new_lead.state=='draft')
self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin_user[1], context=context))
def test_01_check_to_state_draft_post(self):
"""
Check that a new record (with state = draft) changes its responsible when there is a postcondition filter which check that the state is draft.
"""
cr, uid = self.cr, self.uid
context = {}
filter_draft = self.create_filter_draft(cr, uid, context=context)
rule_1_id = self.create_rule(cr, uid, filter_post_id=filter_draft, context=context)
new_lead_id = self.create_lead_test_1(cr,uid,context=context)
new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context)
self.assertTrue(new_lead.state=='draft')
self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.demo_user[1], context=context))
def test_02_check_from_draft_to_done_with_steps(self):
"""
A new record will be created and will goes from draft to done state via the other states (open, pending and cancel)
We will create a rule that says in precondition that the record must be in the "draft" state while a postcondition filter says
that the record will be done. If the state goes from 'draft' to 'done' the responsible will change. If those two conditions aren't
verified, the responsible will stay the same
The responsible in that test will never change
"""
cr, uid = self.cr, self.uid
context = {}
filter_draft = self.create_filter_draft(cr, uid, context=context)
filter_done = self.create_filter_done(cr, uid, context=context)
self.create_rule(cr, uid, filter_id=filter_draft, filter_post_id=filter_done, context=context)
new_lead_id = self.create_lead_test_1(cr,uid,context=context)
new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context)
self.assertTrue(new_lead.state=='draft')
self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin_user[1], context=context))
""" change the state of new_lead to open and check that responsible doen't change"""
new_lead.write({'state': 'open'}, context=context)
new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context)
self.assertTrue(new_lead.state=='open')
self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin_user[1], context=context))
""" change the state of new_lead to pending and check that responsible doen't change"""
new_lead.write({'state': 'pending'}, context=context)
new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context)
self.assertTrue(new_lead.state=='pending')
self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin_user[1], context=context))
""" change the state of new_lead to cancel and check that responsible doen't change"""
new_lead.write({'state': 'cancel'}, context=context)
new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context)
self.assertTrue(new_lead.state=='cancel')
self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin_user[1], context=context))
""" change the state of new_lead to done and check that responsible doen't change """
new_lead.write({'state': 'done'}, context=context)
new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context)
self.assertTrue(new_lead.state=='done')
self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin_user[1], context=context))
def test_02_check_from_draft_to_done_without_steps(self):
"""
A new record will be created and will goes from draft to done in one operation
We will create a rule that says in precondition that the record must be in the "draft" state while a postcondition filter says
that the record will be done. If the state goes from 'draft' to 'done' the responsible will change. If those two conditions aren't
verified, the responsible will stay the same
The responsible in that test will change to "demo_user"
"""
cr, uid = self.cr, self.uid
context = {}
filter_draft = self.create_filter_draft(cr, uid, context=context)
filter_done = self.create_filter_done(cr, uid, context=context)
self.create_rule(cr, uid, filter_id=filter_draft, filter_post_id=filter_done, context=context)
new_lead_id = self.create_lead_test_1(cr,uid,context=context)
new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context)
self.assertTrue(new_lead.state=='draft')
self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin_user[1], context=context))
""" change the state of new_lead to done and check that responsible change to Demo_user"""
new_lead.write({'state': 'done'}, context=context)
new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context)
self.assertTrue(new_lead.state=='done')
self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.demo_user[1], context=context))

View File

@ -43,8 +43,8 @@ class base_action_rule(osv.osv):
'act_categ_id': fields.many2one('crm.case.categ', 'Set Category to'), 'act_categ_id': fields.many2one('crm.case.categ', 'Set Category to'),
} }
def do_check(self, cr, uid, action, obj, old_records=None, context=None): def do_check(self, cr, uid, action, obj, precondition_ok=True, context=None):
ok = super(base_action_rule, self).do_check(cr, uid, action, obj, old_records=old_records, context=context) ok = super(base_action_rule, self).do_check(cr, uid, action, obj, precondition_ok=precondition_ok, context=context)
if hasattr(obj, 'section_id'): if hasattr(obj, 'section_id'):
ok = ok and (not action.trg_section_id or action.trg_section_id.id == obj.section_id.id) ok = ok and (not action.trg_section_id or action.trg_section_id.id == obj.section_id.id)

View File

@ -7,16 +7,6 @@
<field name="model">base.action.rule</field> <field name="model">base.action.rule</field>
<field name="inherit_id" ref="base_action_rule.view_base_action_rule_form"/> <field name="inherit_id" ref="base_action_rule.view_base_action_rule_form"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//group[@name='partner']" position="after">
<group name="case" string="Conditions on Case Fields">
<field name="trg_section_id" widget="selection"/>
<field name="trg_categ_id"/>
</group>
<group name="communication" string="Conditions on Communication History">
<field name="regex_history"/>
<field name="trg_max_history"/>
</group>
</xpath>
<xpath expr="//field[@name='act_user_id']" position="after"> <xpath expr="//field[@name='act_user_id']" position="after">
<field name="act_section_id"/> <field name="act_section_id"/>
<field name="act_categ_id"/> <field name="act_categ_id"/>

View File

@ -1,69 +0,0 @@
import tools
from openerp.tests import common
class base_action_rule_test(common.TransactionCase):
def setUp(self):
"""*****setUp*****"""
super(base_action_rule_test, self).setUp()
cr, uid = self.cr, self.uid
"""*****TeamCreation******"""
self.team_pool = self.registry('crm.case.section')
self.team_1_id = self.team_pool.create(cr, uid, {
'name' : "Team 1",
'active' : 1,
'user_id' : uid,
'complete_name' : "Test / Team 1",
}, context=None)
self.team_2_id = self.team_pool.create(cr, uid, {
'name' : "Team 2",
'active' : 1,
'user_id' : uid,
'complete_name' : "Test / Team 2",
}, context=None)
def create_rule_1(self, cr, uid, context=None):
"""
The "Rule 1" says that when a lead goes to the 'open' state, the team responsible for that lead changes to "Team 1"
"""
self.action_pool = self.registry('base.action.rule')
self.rule_1_id = self.action_pool.create(cr,uid,{
'name' : "Rule 1",
'model_id' : self.registry('ir.model').search(cr, uid, [('model','=','crm.lead')], context=context)[0],
'active' : 1,
'trg_state_to' : 'open',
'trg_date_type' : 'none',
'act_section_id' : self.team_1_id,
}, context=context)
def create_rule_2(self, cr, uid, context=None):
"""
The "Rule 2" says that when a lead goes from 'open' state to 'done' state, the team responsible for that lead changes to "Team 2"
"""
self.action_pool = self.registry('base.action.rule')
self.rule_2_id = self.action_pool.create(cr,uid,{
'name' : "Rule 2",
'model_id' : self.registry('ir.model').search(cr, uid, [('model','=','crm.lead')], context=context)[0],
'active' : 1,
'trg_state_to' : 'done',
'trg_state_from' : 'open',
'trg_date_type' : 'none',
'act_section_id' : self.team_2_id,
}, context=context)
def test_00_check_to_state_draft(self):
"""
This test check that the team change when the state is open but doesn't change when the state is different
"""
cr, uid = self.cr, self.uid
context = {}
#First we need to create the Rule 1
self.create_rule_1(cr, uid, context)
#Once it is done, we can create a new lead (with a state = 'draft')
#first we get our team 1
self.team_1 = self.team_pool.browse(cr,uid,self.team_1_id,context=context)
#We get a lead
self.lead_1 = self.registry('ir.model.data').get_object_reference(cr, uid, 'crm', 'crm_case_1')
#We change the team of crm_case_1