From cfdaf8c50ad2da8fbec92ae01c3810a782d12dd7 Mon Sep 17 00:00:00 2001
From: "Yogesh (Serpent Consulting Services)"
Date: Fri, 16 Mar 2012 17:49:15 +0530
Subject: [PATCH 01/87] [FIX] action :- send multiple email on server action.
lp bug: https://launchpad.net/bugs/956898 fixed
bzr revid: yogesh.sakhreliya@serpentcs.com-20120316121915-t5kjscd0bnwm72xi
---
openerp/addons/base/ir/ir_actions.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/openerp/addons/base/ir/ir_actions.py b/openerp/addons/base/ir/ir_actions.py
index 236c6af7119..77eeb8d58f3 100644
--- a/openerp/addons/base/ir/ir_actions.py
+++ b/openerp/addons/base/ir/ir_actions.py
@@ -670,6 +670,9 @@ class actions_server(osv.osv):
_logger.info('No partner email address specified, not sending any email.')
continue
+ if not isinstance(address, tuple) and not isinstance(address, list):
+ address = [address]
+
if not email_from:
_logger.debug('--email-from command line option is not specified, using a fallback value instead.')
if user.user_email:
@@ -681,7 +684,7 @@ class actions_server(osv.osv):
body = self.merge_message(cr, uid, action.message, action, context)
ir_mail_server = self.pool.get('ir.mail_server')
- msg = ir_mail_server.build_email(email_from, [address], subject, body)
+ msg = ir_mail_server.build_email(email_from, address, subject, body)
res_email = ir_mail_server.send_email(cr, uid, msg)
if res_email:
_logger.info('Email successfully sent to: %s', address)
From 16e8bdc6b292082f962cfb6a193a21ef6cbda090 Mon Sep 17 00:00:00 2001
From: Yannick Vaucher
Date: Thu, 10 May 2012 15:50:20 +0200
Subject: [PATCH 02/87] [IMP] base_action_rule - Allow using any object dates
as a triggering date
bzr revid: yannick.vaucher@camptocamp.com-20120510135020-x7m08ojkxjzy4fs9
---
addons/base_action_rule/base_action_rule.py | 47 ++++++++-----------
.../base_action_rule_view.xml | 4 +-
2 files changed, 21 insertions(+), 30 deletions(-)
diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py
index 50ebd96a3c2..c558939470e 100644
--- a/addons/base_action_rule/base_action_rule.py
+++ b/addons/base_action_rule/base_action_rule.py
@@ -37,7 +37,7 @@ def get_datetime(date_field):
date_split = date_field.split(' ')
if len(date_split) == 1:
date_field = date_split[0] + " 00:00:00"
-
+
return datetime.strptime(date_field[:19], '%Y-%m-%d %H:%M:%S')
@@ -79,13 +79,10 @@ class base_action_rule(osv.osv):
it will allow you to hide the rule without removing it."),
'sequence': fields.integer('Sequence', help="Gives the sequence order \
when displaying a list of rules."),
- 'trg_date_type': fields.selection([
- ('none', 'None'),
- ('create', 'Creation Date'),
- ('action_last', 'Last Action Date'),
- ('date', 'Date'),
- ('deadline', 'Deadline'),
- ], 'Trigger Date', size=16),
+ 'trg_date_id': fields.many2one('ir.model.fields',
+ 'Trigger Date',
+ domain="[('model_id', '=', model_id),"
+ "('ttype','in',('date','datetime'))]"),
'trg_date_range': fields.integer('Delay after trigger date', \
help="Delay After Trigger Date,\
specifies you can put a negative number. If you need a delay before the \
@@ -132,7 +129,6 @@ the rule to mark CC(mail to any other person defined in actions)."),
_defaults = {
'active': lambda *a: True,
- 'trg_date_type': lambda *a: 'none',
'trg_date_range_type': lambda *a: 'day',
'act_mail_to_user': lambda *a: 0,
'act_remind_partner': lambda *a: 0,
@@ -168,7 +164,7 @@ the rule to mark CC(mail to any other person defined in actions)."),
obj = self.pool.get(obj_name)
# If the rule doesn't involve a time condition, run it immediately
# Otherwise we let the scheduler run the action
- if self.browse(cr, uid, rule_id, context=context).trg_date_type == 'none':
+ if not self.browse(cr, uid, rule_id, context=context).trg_date_id:
self._action(cr, uid, [rule_id], obj.browse(cr, uid, ids, context=context), context=context)
return True
@@ -185,7 +181,7 @@ the rule to mark CC(mail to any other person defined in actions)."),
self.post_action(cr, uid, [new_id], model, context=context)
return new_id
return wrapper
-
+
def _write(self, old_write, model, context=None):
"""
Return a wrapper around `old_write` calling both `old_write` and
@@ -230,7 +226,7 @@ the rule to mark CC(mail to any other person defined in actions)."),
def _check(self, cr, uid, automatic=False, use_new_cursor=False, \
context=None):
"""
- This Function is call by scheduler.
+ This Function is called by scheduler.
"""
rule_pool = self.pool.get('base.action.rule')
rule_ids = rule_pool.search(cr, uid, [], context=context)
@@ -248,22 +244,17 @@ the rule to mark CC(mail to any other person defined in actions)."),
obj = model_pool.browse(cr, uid, obj_id, context=context)
# Calculate when this action should next occur for this object
base = False
- if rule.trg_date_type=='create' and hasattr(obj, 'create_date'):
- base = obj.create_date
- elif (rule.trg_date_type=='action_last'
- and hasattr(obj, 'create_date')):
- if hasattr(obj, 'date_action_last') and obj.date_action_last:
- base = obj.date_action_last
- else:
- base = obj.create_date
- elif (rule.trg_date_type=='deadline'
- and hasattr(obj, 'date_deadline')
- and obj.date_deadline):
- base = obj.date_deadline
- elif (rule.trg_date_type=='date'
- and hasattr(obj, 'date')
- and obj.date):
- base = obj.date
+ if rule.trg_date_id:
+ date_type = rule.trg_date_id.name
+ if (date_type=='date_action_last'
+ and hasattr(obj, 'create_date')):
+ if hasattr(obj, 'date_action_last') and obj.date_action_last:
+ base = obj.date_action_last
+ else:
+ base = obj.create_date
+ elif (hasattr(obj, date_type)
+ and obj.read([date_type])[0][date_type]):
+ base = obj.read([date_type])[0][date_type]
if base:
fnct = {
'minutes': lambda interval: timedelta(minutes=interval),
diff --git a/addons/base_action_rule/base_action_rule_view.xml b/addons/base_action_rule/base_action_rule_view.xml
index 4ba7e6f9018..b041d322193 100644
--- a/addons/base_action_rule/base_action_rule_view.xml
+++ b/addons/base_action_rule/base_action_rule_view.xml
@@ -39,8 +39,8 @@
-
-
+
+
From d41a234599e6d214761d6344e70a37051bff9d87 Mon Sep 17 00:00:00 2001
From: Ronald Portier
Date: Wed, 24 Oct 2012 09:28:07 +0200
Subject: [PATCH 03/87] [FIX] Fix vacuum cleaning both for time and count based
cleaning, adding a safeguard against cleaning recently used rows.
bzr revid: ronald@therp.nl-20121024072807-vsvhub1m0y3j5ude
---
openerp/osv/orm.py | 41 +++++++++++++++++++++++------------------
1 file changed, 23 insertions(+), 18 deletions(-)
diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py
index 4c3330c97b7..6916f94730a 100644
--- a/openerp/osv/orm.py
+++ b/openerp/osv/orm.py
@@ -684,9 +684,6 @@ class BaseModel(object):
# Transience
_transient = False # True in a TransientModel
- _transient_max_count = None
- _transient_max_hours = None
- _transient_check_time = 20
# structure:
# { 'parent_model': 'm2o_field', ... }
@@ -5067,20 +5064,26 @@ class BaseModel(object):
def _transient_clean_rows_older_than(self, cr, seconds):
assert self._transient, "Model %s is not transient, it cannot be vacuumed!" % self._name
- cr.execute("SELECT id FROM " + self._table + " WHERE"
- " COALESCE(write_date, create_date, (now() at time zone 'UTC'))::timestamp <"
- " ((now() at time zone 'UTC') - interval %s)", ("%s seconds" % seconds,))
- ids = [x[0] for x in cr.fetchall()]
- self.unlink(cr, SUPERUSER_ID, ids)
-
- def _transient_clean_old_rows(self, cr, count):
- assert self._transient, "Model %s is not transient, it cannot be vacuumed!" % self._name
+ '''Never delete rows used in last 5 minutes'''
+ seconds = max(seconds, 600)
+ now_str = "(now() at time zone 'UTC')"
cr.execute(
- "SELECT id, COALESCE(write_date, create_date, (now() at time zone 'UTC'))::timestamp"
- " AS t FROM " + self._table +
- " ORDER BY t LIMIT %s", (count,))
+ "SELECT id FROM " + self._table + " WHERE"
+ " COALESCE(write_date, create_date, " + now_str + ")::timestamp"
+ "< (" + now_str + " - interval %s)", ("%s seconds" % seconds,))
ids = [x[0] for x in cr.fetchall()]
- self.unlink(cr, SUPERUSER_ID, ids)
+ if ids:
+ self.unlink(cr, SUPERUSER_ID, ids)
+
+ def _transient_clean_old_rows(self, cr, max_count):
+ # Check how many rows we have in the table
+ cr.execute(
+ "SELECT count(*) as row_count FROM %s", (self._table, ))
+ res = cr.fetchall()
+ row_count = res[0][0]
+ if row_count <= max_count:
+ return # max not reached, nothing to do
+ self._transient_clean_rows_older_than(cr, 600)
def _transient_vacuum(self, cr, uid, force=False):
"""Clean the transient records.
@@ -5092,10 +5095,12 @@ class BaseModel(object):
a new record is created).
"""
assert self._transient, "Model %s is not transient, it cannot be vacuumed!" % self._name
+ _transient_check_time = 20 # arbitrary limit on vacuum executions
self._transient_check_count += 1
- if (not force) and (self._transient_check_count % self._transient_check_time):
- self._transient_check_count = 0
- return True
+ if ((not force)
+ and (self._transient_check_count >= _transient_check_time)):
+ return True # no vacuum cleaning this time
+ self._transient_check_count = 0
# Age-based expiration
if self._transient_max_hours:
From 2747e5bef21ef9361900d61218e594b85ed22b20 Mon Sep 17 00:00:00 2001
From: Ronald Portier
Date: Wed, 24 Oct 2012 16:01:19 +0200
Subject: [PATCH 04/87] [FIX] Correct both age based as count based vacuum
cleaning.
lp bug: https://launchpad.net/bugs/1009014 fixed
bzr revid: ronald@therp.nl-20121024140119-hv72ffxmf1gvf2sj
---
openerp/osv/orm.py | 20 ++++++++++++++------
1 file changed, 14 insertions(+), 6 deletions(-)
diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py
index 6916f94730a..ca2a4fb6268 100644
--- a/openerp/osv/orm.py
+++ b/openerp/osv/orm.py
@@ -5065,7 +5065,7 @@ class BaseModel(object):
def _transient_clean_rows_older_than(self, cr, seconds):
assert self._transient, "Model %s is not transient, it cannot be vacuumed!" % self._name
'''Never delete rows used in last 5 minutes'''
- seconds = max(seconds, 600)
+ seconds = max(seconds, 300)
now_str = "(now() at time zone 'UTC')"
cr.execute(
"SELECT id FROM " + self._table + " WHERE"
@@ -5077,13 +5077,13 @@ class BaseModel(object):
def _transient_clean_old_rows(self, cr, max_count):
# Check how many rows we have in the table
- cr.execute(
- "SELECT count(*) as row_count FROM %s", (self._table, ))
+ sql_statement = "SELECT count(*) as row_count FROM %s" % (self._table, )
+ cr.execute(sql_statement)
res = cr.fetchall()
row_count = res[0][0]
if row_count <= max_count:
return # max not reached, nothing to do
- self._transient_clean_rows_older_than(cr, 600)
+ self._transient_clean_rows_older_than(cr, 300)
def _transient_vacuum(self, cr, uid, force=False):
"""Clean the transient records.
@@ -5093,12 +5093,20 @@ class BaseModel(object):
Actual cleaning will happen only once every "_transient_check_time" calls.
This means this method can be called frequently called (e.g. whenever
a new record is created).
+ Example with both max_hours and max_count active:
+ Suppose max_hours = 0.2 (e.g. 12 minutes), max_count = 20, there are 55 rows in the
+ table, 10 created/changed in the last 5 minutes, an additional 12 created/changed between
+ 5 and 10 minutes ago, the rest created/changed more then 12 minutes ago.
+ - age based vacuum will leave the 22 rows created/changed in the last 12 minutes
+ - count based vacuum will wipe out another 12 rows. Not just 2, otherwise each addition
+ would immediately cause the maximum to be reached again.
+ - the 10 rows that have been created/changed the last 5 minutes will NOT be deleted
"""
assert self._transient, "Model %s is not transient, it cannot be vacuumed!" % self._name
- _transient_check_time = 20 # arbitrary limit on vacuum executions
+ _transient_check_time = 20 # arbitrary limit on vacuum executions
self._transient_check_count += 1
if ((not force)
- and (self._transient_check_count >= _transient_check_time)):
+ and (self._transient_check_count < _transient_check_time)):
return True # no vacuum cleaning this time
self._transient_check_count = 0
From db5a4e70751cc3f1bdd7f72cacdcf3c7292614c5 Mon Sep 17 00:00:00 2001
From: Arnaud Pineux
Date: Fri, 23 Nov 2012 11:31:13 +0100
Subject: [PATCH 05/87] [FIX] 1st test for automated action
bzr revid: api@openerp.com-20121123103113-x9rspq1oh1887153
---
addons/base_action_rule/base_action_rule.py | 50 +++++++++++++--------
addons/base_status/base_stage.py | 8 +---
addons/base_status/base_state.py | 3 +-
addons/crm/crm_action_rule.py | 4 +-
addons/crm/crm_lead.py | 13 ++++--
addons/crm_claim/crm_claim.py | 6 ++-
6 files changed, 51 insertions(+), 33 deletions(-)
diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py
index 856d2626f69..349721db8c4 100644
--- a/addons/base_action_rule/base_action_rule.py
+++ b/addons/base_action_rule/base_action_rule.py
@@ -109,7 +109,7 @@ trigger date, like sending a reminder 15 minutes before a meeting."),
_order = 'sequence'
- def post_action(self, cr, uid, ids, model, context=None):
+ def post_action(self, cr, uid, ids, model, old_records_states=None, context=None):
# Searching for action rules
cr.execute("SELECT model.model, rule.id FROM base_action_rule rule \
LEFT JOIN ir_model model on (model.id = rule.model_id) \
@@ -117,11 +117,11 @@ trigger date, like sending a reminder 15 minutes before a meeting."),
res = cr.fetchall()
# Check if any rule matching with current object
for obj_name, rule_id in res:
- obj = self.pool.get(obj_name)
+ model_pool = self.pool.get(obj_name)
# If the rule doesn't involve a time condition, run it immediately
# Otherwise we let the scheduler run the action
if self.browse(cr, uid, rule_id, context=context).trg_date_type == 'none':
- self._action(cr, uid, [rule_id], obj.browse(cr, uid, ids, context=context), context=context)
+ self._action(cr, uid, [rule_id], model_pool.browse(cr, uid, ids, context=context), old_records_states=old_records_states, context=context)
return True
def _create(self, old_create, model, context=None):
@@ -144,13 +144,23 @@ trigger date, like sending a reminder 15 minutes before a meeting."),
`post_action`, in that order.
"""
def wrapper(cr, uid, ids, vals, context=context):
+ old_records_states = {}
if context is None:
context = {}
if isinstance(ids, (str, int, long)):
ids = [ids]
+ model_pool = self.pool.get(model)
+ # get the old records (before the write)
+ if model and ids:
+ for record in model_pool.browse(cr,uid,ids,context=context):
+ old_records_states[record.id] = record.state
+ #old_records = model_pool.browse(cr,uid,ids,context=context)
+ #print old_records[0]
+ #print old_records[0].state
old_write(cr, uid, ids, vals, context=context)
+ #print old_records[0].state
if not context.get('action'):
- self.post_action(cr, uid, ids, model, context=context)
+ self.post_action(cr, uid, ids, model, old_records_states=old_records_states, context=context)
return True
return wrapper
@@ -242,7 +252,7 @@ trigger date, like sending a reminder 15 minutes before a meeting."),
- def do_check(self, cr, uid, action, obj, context=None):
+ def do_check(self, cr, uid, action, obj, old_records_states=None, context=None):
""" check Action """
if context is None:
context = {}
@@ -263,14 +273,21 @@ 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 []))
)
)
- state_to = context.get('state_to', False)
- state = getattr(obj, 'state', False)
- if state:
- ok = ok and (not action.trg_state_from or action.trg_state_from==state)
- if state_to:
- ok = ok and (not action.trg_state_to or action.trg_state_to==state_to)
- elif action.trg_state_to:
- ok = False
+ #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_states != None:
+ if old_records_states[obj.id]:
+ state_from = old_records_states[obj.id]
+ #if we have an action that check the status
+ if action.trg_state_from:
+ print action.trg_state_from, " and ", state_to
+ ok = ok and action.trg_state_from==state_to
+ if action.trg_state_to:
+ print action.trg_state_to, " for ", state_from, " and ", state_to
+ ok = state_from!=state_to
+ ok = ok and action.trg_state_to==state_to
reg_name = action.regex_name
result_name = True
if reg_name:
@@ -312,19 +329,16 @@ trigger date, like sending a reminder 15 minutes before a meeting."),
model_obj.message_subscribe(cr, uid, [obj.id], new_followers, context=context)
return True
- def _action(self, cr, uid, ids, objects, scrit=None, context=None):
+ def _action(self, cr, uid, ids, objects, scrit=None, old_records_states=None, context=None):
""" Do Action """
if context is None:
context = {}
-
context.update({'action': True})
- if not scrit:
- scrit = []
if not isinstance(objects, list):
objects = [objects]
for action in self.browse(cr, uid, ids, context=context):
for obj in objects:
- if self.do_check(cr, uid, action, obj, context=context):
+ if self.do_check(cr, uid, action, obj, old_records_states=old_records_states, context=context):
self.do_action(cr, uid, action, obj, context=context)
context.update({'action': False})
diff --git a/addons/base_status/base_stage.py b/addons/base_status/base_stage.py
index 86edff46920..ba4f62a181a 100644
--- a/addons/base_status/base_stage.py
+++ b/addons/base_status/base_stage.py
@@ -19,6 +19,7 @@
#
##############################################################################
+import pdb
from osv import fields, osv
from tools.translate import _
@@ -280,15 +281,10 @@ class base_stage(object):
# 2. update values
if values_to_update:
self.write(cr, uid, ids, values_to_update, context=context)
- # 3. call _action for base action rule
- if new_state_name:
- self._action(cr, uid, cases, new_state_name, context=context)
- elif not (new_stage_id is None):
- new_state_name = self.read(cr, uid, ids, ['state'], context=context)[0]['state']
- self._action(cr, uid, cases, new_state_name, context=context)
return True
def _action(self, cr, uid, cases, state_to, scrit=None, context=None):
+ print "--- Base_Stage (_action) ---"
if context is None:
context = {}
context['state_to'] = state_to
diff --git a/addons/base_status/base_state.py b/addons/base_status/base_state.py
index cebf5537485..aad9129406e 100644
--- a/addons/base_status/base_state.py
+++ b/addons/base_status/base_state.py
@@ -157,13 +157,14 @@ class base_state(object):
:params: update_values: values that will be added with the state
update when writing values to the record.
"""
+ print "-------------------------BASE STATE--------------------------------"
cases = self.browse(cr, uid, ids, context=context)
cases[0].state # fill browse record cache, for _action having old and new values
if update_values is None:
update_values = {}
update_values['state'] = state_name
self.write(cr, uid, ids, update_values, context=context)
- self._action(cr, uid, cases, state_name, context=context)
+ #self._action(cr, uid, cases, state_name, context=context)
def _action(self, cr, uid, cases, state_to, scrit=None, context=None):
if context is None:
diff --git a/addons/crm/crm_action_rule.py b/addons/crm/crm_action_rule.py
index 4a8ee98fbae..d7caafd7d1d 100644
--- a/addons/crm/crm_action_rule.py
+++ b/addons/crm/crm_action_rule.py
@@ -43,8 +43,8 @@ class base_action_rule(osv.osv):
'act_categ_id': fields.many2one('crm.case.categ', 'Set Category to'),
}
- def do_check(self, cr, uid, action, obj, context=None):
- ok = super(base_action_rule, self).do_check(cr, uid, action, obj, context=context)
+ def do_check(self, cr, uid, action, obj, old_records_states=None, context=None):
+ ok = super(base_action_rule, self).do_check(cr, uid, action, obj, old_records_states=old_records_states, context=context)
if hasattr(obj, 'section_id'):
ok = ok and (not action.trg_section_id or action.trg_section_id.id == obj.section_id.id)
diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py
index 0a824aef83e..b9d92639e63 100644
--- a/addons/crm/crm_lead.py
+++ b/addons/crm/crm_lead.py
@@ -29,6 +29,7 @@ from tools.translate import _
from tools import html2plaintext
from base.res.res_partner import format_address
+import pdb
CRM_LEAD_PENDING_STATES = (
crm.AVAILABLE_STATES[2][0], # Cancelled
@@ -380,8 +381,10 @@ class crm_lead(base_stage, format_address, osv.osv):
""" Mark the case as lost: state=cancel and probability=0 """
for lead in self.browse(cr, uid, ids):
stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, [('probability', '=', 0.0)], context=context)
- if stage_id:
- self.case_set(cr, uid, [lead.id], values_to_update={'probability': 0.0}, new_stage_id=stage_id, context=context)
+ if not stage_id:
+ return False
+ else:
+ self.write(cr, uid, ids, {'probability' : 0.0, 'stage_id' : stage_id}, context=context)
self.case_mark_lost_send_note(cr, uid, ids, context=context)
return True
@@ -389,8 +392,10 @@ class crm_lead(base_stage, format_address, osv.osv):
""" Mark the case as lost: state=done and probability=100 """
for lead in self.browse(cr, uid, ids):
stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, [('probability', '=', 100.0)], context=context)
- if stage_id:
- self.case_set(cr, uid, [lead.id], values_to_update={'probability': 100.0}, new_stage_id=stage_id, context=context)
+ if not stage_id:
+ return False
+ else:
+ self.write(cr, uid, ids, {'probability' : 100.0, 'stage_id' : stage_id}, context=context)
self.case_mark_won_send_note(cr, uid, ids, context=context)
return True
diff --git a/addons/crm_claim/crm_claim.py b/addons/crm_claim/crm_claim.py
index 70dcc18baf7..5c141c81080 100644
--- a/addons/crm_claim/crm_claim.py
+++ b/addons/crm_claim/crm_claim.py
@@ -167,8 +167,10 @@ class crm_claim(base_stage, osv.osv):
""" Mark the case as refused: state=done and case_refused=True """
for lead in self.browse(cr, uid, ids):
stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, ['&', ('state', '=', 'done'), ('case_refused', '=', True)], context=context)
- if stage_id:
- self.case_set(cr, uid, [lead.id], values_to_update={}, new_stage_id=stage_id, context=context)
+ if not stage_id:
+ return False
+ else:
+ self.write(cr, uid, ids, {'stage_id' : stage_id}, context=context)
return self.case_refuse_send_note(cr, uid, ids, context=context)
def onchange_partner_id(self, cr, uid, ids, part, email=False):
From 084371b51b6fb7b72c5fb3eaa11c7d4696a4f6a6 Mon Sep 17 00:00:00 2001
From: Arnaud Pineux
Date: Mon, 26 Nov 2012 12:22:05 +0100
Subject: [PATCH 06/87] [FIX] Automated action rules
bzr revid: api@openerp.com-20121126112205-bf80xlf1ja5o2f7i
---
openerp/modules/loading.py | 6 ++++++
openerp/osv/orm.py | 4 ++++
2 files changed, 10 insertions(+)
diff --git a/openerp/modules/loading.py b/openerp/modules/loading.py
index bcda1690c91..f7f70c51ad5 100644
--- a/openerp/modules/loading.py
+++ b/openerp/modules/loading.py
@@ -413,6 +413,12 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
_logger.error('At least one test failed when loading the modules.')
else:
_logger.info('Modules loaded.')
+
+ # STEP 7: for every model call _register_hook
+ for models in pool.models:
+ pool_mod = pool.get(models)
+ pool_mod._register_hook(cr)
+
finally:
cr.close()
diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py
index 6a1f917caa1..693757345f5 100644
--- a/openerp/osv/orm.py
+++ b/openerp/osv/orm.py
@@ -5155,6 +5155,10 @@ class BaseModel(object):
# for backward compatibility
resolve_o2m_commands_to_record_dicts = resolve_2many_commands
+ def _register_hook(self, cr):
+ """ This method is called right after the registry is filled """
+ pass
+
# keep this import here, at top it will cause dependency cycle errors
import expression
From 72616d0ba2b355004f28590d3815ca18137c66c9 Mon Sep 17 00:00:00 2001
From: Arnaud Pineux
Date: Mon, 26 Nov 2012 12:22:48 +0100
Subject: [PATCH 07/87] [FIX] Automated Action Rules
bzr revid: api@openerp.com-20121126112248-oiq40hn9lgro0lq4
---
addons/base_action_rule/base_action_rule.py | 29 +++++++++++--------
.../base_action_rule_view.xml | 2 +-
2 files changed, 18 insertions(+), 13 deletions(-)
diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py
index 0e9db7fc60d..71b376f6946 100644
--- a/addons/base_action_rule/base_action_rule.py
+++ b/addons/base_action_rule/base_action_rule.py
@@ -23,6 +23,7 @@ from datetime import datetime
from datetime import timedelta
import re
import time
+from openerp import SUPERUSER_ID
from osv import fields, osv, orm
from tools.translate import _
@@ -54,7 +55,7 @@ class base_action_rule(osv.osv):
def state_get(self, cr, uid, context=None):
""" Get State """
- return [('', '')]
+ return [('', ''), ('na','N/A (No previous state)')]
def priority_get(self, cr, uid, context=None):
""" Get Priority """
@@ -86,8 +87,8 @@ trigger date, like sending a reminder 15 minutes before a meeting."),
'trg_user_id': fields.many2one('res.users', 'Responsible'),
'trg_partner_id': fields.many2one('res.partner', 'Partner'),
'trg_partner_categ_id': fields.many2one('res.partner.category', 'Partner Category'),
- 'trg_state_from': fields.selection(_state_get, 'Status', size=16),
- 'trg_state_to': fields.selection(_state_get, 'Button Pressed', size=16),
+ 'trg_state_from': fields.selection(_state_get, 'and previously was', size=16),
+ 'trg_state_to': fields.selection(_state_get, 'Status changes to', size=16),
'act_user_id': fields.many2one('res.users', 'Set Responsible to'),
'act_state': fields.selection(_state_get, 'Set State to', size=16),
@@ -162,29 +163,30 @@ trigger date, like sending a reminder 15 minutes before a meeting."),
return True
return wrapper
- def _register_hook(self, cr, uid, ids, context=None):
+ def _register_hook(self, cr):
"""
Wrap every `create` and `write` methods of the models specified by
the rules (given by `ids`).
"""
- for action_rule in self.browse(cr, uid, ids, context=context):
+ ids = self.search(cr,SUPERUSER_ID,[])
+ for action_rule in self.browse(cr, SUPERUSER_ID, ids, context=None):
model = action_rule.model_id.model
obj_pool = self.pool.get(model)
if not hasattr(obj_pool, 'base_action_ruled'):
- obj_pool.create = self._create(obj_pool.create, model, context=context)
- obj_pool.write = self._write(obj_pool.write, model, context=context)
+ obj_pool.create = self._create(obj_pool.create, model, context=None)
+ obj_pool.write = self._write(obj_pool.write, model, context=None)
obj_pool.base_action_ruled = True
return True
def create(self, cr, uid, vals, context=None):
res_id = super(base_action_rule, self).create(cr, uid, vals, context=context)
- self._register_hook(cr, uid, [res_id], context=context)
+ self._register_hook(cr)
return res_id
def write(self, cr, uid, ids, vals, context=None):
super(base_action_rule, self).write(cr, uid, ids, vals, context=context)
- self._register_hook(cr, uid, ids, context=context)
+ self._register_hook(cr)
return True
def _check(self, cr, uid, automatic=False, use_new_cursor=False, \
@@ -278,11 +280,14 @@ trigger date, like sending a reminder 15 minutes before a meeting."),
if old_records != None:
for record in old_records:
state_from = record.state
+ else:
+ state_from = "na"
#if we have an action that check the status
- if action.trg_state_from:
- ok = ok and action.trg_state_from==state_to
if action.trg_state_to:
- ok = state_from!=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
result_name = True
diff --git a/addons/base_action_rule/base_action_rule_view.xml b/addons/base_action_rule/base_action_rule_view.xml
index bb711be9928..4815baa9ebb 100644
--- a/addons/base_action_rule/base_action_rule_view.xml
+++ b/addons/base_action_rule/base_action_rule_view.xml
@@ -33,8 +33,8 @@
-
+
From 041e43967c7a705a4a487d02577b476ff8ad8b57 Mon Sep 17 00:00:00 2001
From: Arnaud Pineux
Date: Mon, 26 Nov 2012 12:44:05 +0100
Subject: [PATCH 08/87] [IMP] add comment
bzr revid: api@openerp.com-20121126114405-s3b3lo6qncho2n37
---
addons/base_action_rule/base_action_rule.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py
index 71b376f6946..8892bec34f2 100644
--- a/addons/base_action_rule/base_action_rule.py
+++ b/addons/base_action_rule/base_action_rule.py
@@ -281,7 +281,7 @@ trigger date, like sending a reminder 15 minutes before a meeting."),
for record in old_records:
state_from = record.state
else:
- state_from = "na"
+ 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:
From 8474567cdf5a0df9be8963abf6a8598eeabee17c Mon Sep 17 00:00:00 2001
From: Arnaud Pineux
Date: Mon, 26 Nov 2012 13:51:42 +0100
Subject: [PATCH 09/87] [FIX] Automated Action rule (register_hook)
bzr revid: api@openerp.com-20121126125142-9gpsupy7617vddya
---
addons/base_action_rule/base_action_rule.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py
index 8892bec34f2..693d46b9021 100644
--- a/addons/base_action_rule/base_action_rule.py
+++ b/addons/base_action_rule/base_action_rule.py
@@ -195,7 +195,7 @@ trigger date, like sending a reminder 15 minutes before a meeting."),
This Function is call by scheduler.
"""
rule_ids = self.search(cr, uid, [], context=context)
- self._register_hook(cr, uid, rule_ids, context=context)
+ self._register_hook(cr)
if context is None:
context = {}
for rule in self.browse(cr, uid, rule_ids, context=context):
From 41e9eadc6197b9082ac6e32c45330956ee469096 Mon Sep 17 00:00:00 2001
From: Arnaud Pineux
Date: Mon, 26 Nov 2012 14:10:17 +0100
Subject: [PATCH 10/87] [FIX] _register_hook refactored
bzr revid: api@openerp.com-20121126131017-55e2x4e75nmabt27
---
addons/base_action_rule/base_action_rule.py | 23 +++++++++++++++++----
1 file changed, 19 insertions(+), 4 deletions(-)
diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py
index 693d46b9021..11d62404502 100644
--- a/addons/base_action_rule/base_action_rule.py
+++ b/addons/base_action_rule/base_action_rule.py
@@ -169,7 +169,22 @@ trigger date, like sending a reminder 15 minutes before a meeting."),
the rules (given by `ids`).
"""
ids = self.search(cr,SUPERUSER_ID,[])
- for action_rule in self.browse(cr, SUPERUSER_ID, ids, context=None):
+ return self._register_hook_(cr,SUPERUSER_ID,ids,context=None)
+ #for action_rule in self.browse(cr, SUPERUSER_ID, ids, context=None):
+ # model = action_rule.model_id.model
+ # obj_pool = self.pool.get(model)
+ # if not hasattr(obj_pool, 'base_action_ruled'):
+ # obj_pool.create = self._create(obj_pool.create, model, context=None)
+ # obj_pool.write = self._write(obj_pool.write, model, context=None)
+ # obj_pool.base_action_ruled = True
+ #return True
+
+ def _register_hook_(self, cr, uid, ids, context=None):
+ """
+ Wrap every `create` and `write` methods of the models specified by
+ the rules (given by `ids`).
+ """
+ for action_rule in self.browse(cr, uid, ids, context=context):
model = action_rule.model_id.model
obj_pool = self.pool.get(model)
if not hasattr(obj_pool, 'base_action_ruled'):
@@ -181,12 +196,12 @@ trigger date, like sending a reminder 15 minutes before a meeting."),
def create(self, cr, uid, vals, context=None):
res_id = super(base_action_rule, self).create(cr, uid, vals, context=context)
- self._register_hook(cr)
+ self._register_hook_(cr, uid, res_id,context=context)
return res_id
def write(self, cr, uid, ids, vals, context=None):
super(base_action_rule, self).write(cr, uid, ids, vals, context=context)
- self._register_hook(cr)
+ self._register_hook_(cr, uid, ids, context=context)
return True
def _check(self, cr, uid, automatic=False, use_new_cursor=False, \
@@ -195,7 +210,7 @@ trigger date, like sending a reminder 15 minutes before a meeting."),
This Function is call by scheduler.
"""
rule_ids = self.search(cr, uid, [], context=context)
- self._register_hook(cr)
+ self._register_hook_(cr, uid, rule_ids, context=context)
if context is None:
context = {}
for rule in self.browse(cr, uid, rule_ids, context=context):
From 9137243cdc48d5f3e89b16b7ec56f95cb1ef894a Mon Sep 17 00:00:00 2001
From: Arnaud Pineux
Date: Mon, 26 Nov 2012 14:32:57 +0100
Subject: [PATCH 11/87] [FIX] _register_hook refactored (test)
bzr revid: api@openerp.com-20121126133257-f9j7vfgdfkqjewyn
---
addons/base_action_rule/base_action_rule.py | 16 ++++++----------
1 file changed, 6 insertions(+), 10 deletions(-)
diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py
index 11d62404502..14da227a367 100644
--- a/addons/base_action_rule/base_action_rule.py
+++ b/addons/base_action_rule/base_action_rule.py
@@ -170,28 +170,24 @@ trigger date, like sending a reminder 15 minutes before a meeting."),
"""
ids = self.search(cr,SUPERUSER_ID,[])
return self._register_hook_(cr,SUPERUSER_ID,ids,context=None)
- #for action_rule in self.browse(cr, SUPERUSER_ID, ids, context=None):
- # model = action_rule.model_id.model
- # obj_pool = self.pool.get(model)
- # if not hasattr(obj_pool, 'base_action_ruled'):
- # obj_pool.create = self._create(obj_pool.create, model, context=None)
- # obj_pool.write = self._write(obj_pool.write, model, context=None)
- # obj_pool.base_action_ruled = True
- #return True
def _register_hook_(self, cr, uid, ids, context=None):
"""
Wrap every `create` and `write` methods of the models specified by
the rules (given by `ids`).
"""
- for action_rule in self.browse(cr, uid, ids, context=context):
+ reg_ids = []
+ if not isinstance(ids, list):
+ reg_ids.append(ids)
+ else:
+ reg_ids.extend(ids)
+ for action_rule in self.browse(cr, uid, reg_ids, context=context):
model = action_rule.model_id.model
obj_pool = self.pool.get(model)
if not hasattr(obj_pool, 'base_action_ruled'):
obj_pool.create = self._create(obj_pool.create, model, context=None)
obj_pool.write = self._write(obj_pool.write, model, context=None)
obj_pool.base_action_ruled = True
-
return True
def create(self, cr, uid, vals, context=None):
From 100f3199e7120948dcc24aed555e8323ba0871aa Mon Sep 17 00:00:00 2001
From: Arnaud Pineux
Date: Mon, 26 Nov 2012 15:03:30 +0100
Subject: [PATCH 12/87] [IMP] Test for automated action rules
bzr revid: api@openerp.com-20121126140330-maut337p5q8fsglo
---
.../test/base_action_rule_test.py | 111 ++++++++++++++++++
1 file changed, 111 insertions(+)
create mode 100644 addons/base_action_rule/test/base_action_rule_test.py
diff --git a/addons/base_action_rule/test/base_action_rule_test.py b/addons/base_action_rule/test/base_action_rule_test.py
new file mode 100644
index 00000000000..41eb0e31746
--- /dev/null
+++ b/addons/base_action_rule/test/base_action_rule_test.py
@@ -0,0 +1,111 @@
+import tools
+from openerp.tests import common
+
+class myLead(orm.Model):
+
+ _name = name('base_action_rule.myLead')
+
+ _columns = {
+ 'partner_id': fields.many2one('res.partner', 'Partner', ondelete='set null',
+ select=True, help="Linked partner (optional). Usually created when converting the lead."),
+ 'id': fields.integer('ID', readonly=True),
+ 'name': fields.char('Subject', size=64, required=True, select=1),
+ 'active': fields.boolean('Active', required=False),
+ 'date_action_last': fields.datetime('Last Action', readonly=1),
+ 'date_action_next': fields.datetime('Next Action', readonly=1),
+ 'email_from': fields.char('Email', size=128, help="Email address of the contact", select=1),
+ 'section_id': fields.many2one('crm.case.section', 'Sales Team', \
+ select=True, help='When sending mails, the default email address is taken from the sales team.'),
+ 'create_date': fields.datetime('Creation Date' , readonly=True),
+ 'email_cc': fields.text('Global CC', size=252 , help="These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma"),
+ 'description': fields.text('Notes'),
+ 'write_date': fields.datetime('Update Date' , readonly=True),
+ 'categ_ids': fields.many2many('crm.case.categ', 'crm_lead_category_rel', 'lead_id', 'category_id', 'Categories', \
+ domain="['|',('section_id','=',section_id),('section_id','=',False), ('object_id.model', '=', 'crm.lead')]"),
+ 'type_id': fields.many2one('crm.case.resource.type', 'Campaign', \
+ domain="['|',('section_id','=',section_id),('section_id','=',False)]", help="From which campaign (seminar, marketing campaign, mass mailing, ...) did this contact come from?"),
+ 'channel_id': fields.many2one('crm.case.channel', 'Channel', help="Communication channel (mail, direct, phone, ...)"),
+ 'contact_name': fields.char('Contact Name', size=64),
+ 'partner_name': fields.char("Customer Name", size=64,help='The name of the future partner company that will be created while converting the lead into opportunity', select=1),
+ 'opt_out': fields.boolean('Opt-Out', oldname='optout', help="If opt-out is checked, this contact has refused to receive emails or unsubscribed to a campaign."),
+ 'type':fields.selection([ ('lead','Lead'), ('opportunity','Opportunity'), ],'Type', help="Type is used to separate Leads and Opportunities"),
+ 'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority', select=True),
+ 'date_closed': fields.datetime('Closed', readonly=True),
+ 'stage_id': fields.many2one('crm.case.stage', 'Stage',
+ domain="['&', ('fold', '=', False), '&', '|', ('section_ids', '=', section_id), ('case_default', '=', True), '|', ('type', '=', type), ('type', '=', 'both')]"),
+ 'user_id': fields.many2one('res.users', 'Salesperson'),
+ 'referred': fields.char('Referred By', size=64),
+ 'date_open': fields.datetime('Opened', readonly=True),
+ 'day_open': fields.function(_compute_day, string='Days to Open', \
+ multi='day_open', type="float", store=True),
+ 'day_close': fields.function(_compute_day, string='Days to Close', \
+ multi='day_close', type="float", store=True),
+ 'state': fields.related('stage_id', 'state', type="selection", store=True,
+ selection=crm.AVAILABLE_STATES, string="Status", readonly=True,
+ help='The Status is set to \'Draft\', when a case is created. If the case is in progress the Status is set to \'Open\'. When the case is over, the Status is set to \'Done\'. If the case needs to be reviewed then the Status is set to \'Pending\'.'),
+
+ # Only used for type opportunity
+ 'probability': fields.float('Success Rate (%)',group_operator="avg"),
+ 'planned_revenue': fields.float('Expected Revenue'),
+ 'ref': fields.reference('Reference', selection=crm._links_get, size=128),
+ 'ref2': fields.reference('Reference 2', selection=crm._links_get, size=128),
+ 'phone': fields.char("Phone", size=64),
+ 'date_deadline': fields.date('Expected Closing', help="Estimate of the date on which the opportunity will be won."),
+ 'date_action': fields.date('Next Action Date', select=True),
+ 'title_action': fields.char('Next Action', size=64),
+ 'color': fields.integer('Color Index'),
+ 'partner_address_name': fields.related('partner_id', 'name', type='char', string='Partner Contact Name', readonly=True),
+ 'partner_address_email': fields.related('partner_id', 'email', type='char', string='Partner Contact Email', readonly=True),
+ 'company_currency': fields.related('company_id', 'currency_id', type='many2one', string='Currency', readonly=True, relation="res.currency"),
+ 'user_email': fields.related('user_id', 'email', type='char', string='User Email', readonly=True),
+ 'user_login': fields.related('user_id', 'login', type='char', string='User Login', readonly=True),
+
+ # Fields for address, due to separation from crm and res.partner
+ 'street': fields.char('Street', size=128),
+ 'street2': fields.char('Street2', size=128),
+ 'zip': fields.char('Zip', change_default=True, size=24),
+ 'city': fields.char('City', size=128),
+ 'state_id': fields.many2one("res.country.state", 'State'),
+ 'country_id': fields.many2one('res.country', 'Country'),
+ 'phone': fields.char('Phone', size=64),
+ 'fax': fields.char('Fax', size=64),
+ 'mobile': fields.char('Mobile', size=64),
+ 'function': fields.char('Function', size=128),
+ 'title': fields.many2one('res.partner.title', 'Title'),
+ 'company_id': fields.many2one('res.company', 'Company', select=1),
+ 'payment_mode': fields.many2one('crm.payment.mode', 'Payment Mode', \
+ domain="[('section_id','=',section_id)]"),
+ 'planned_cost': fields.float('Planned Costs'),
+ }
+
+ _defaults = {
+ 'active': 1,
+ 'type': 'lead',
+ 'user_id': lambda s, cr, uid, c: s._get_default_user(cr, uid, c),
+ 'email_from': lambda s, cr, uid, c: s._get_default_email(cr, uid, c),
+ 'stage_id': lambda s, cr, uid, c: s._get_default_stage_id(cr, uid, c),
+ 'section_id': lambda s, cr, uid, c: s._get_default_section_id(cr, uid, c),
+ 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.lead', context=c),
+ 'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0],
+ 'color': 0,
+ }
+
+
+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.pool.get('crm.case.section')
+ self.team_1_id = self.team_pool.create(cr, uid, {
+
+ }, context=None)
+
+ self.myLead_pool = self.pool.get('base_action_rule.myLead')
+ self.new_myLead_id = self.myLead_pool.create(cr, uid, {
+ 'name' : "my_first_lead",
+ 'section_id' :
+ }, context=None)
From a8f351029755cfa4564c8c90931d28f3a3143420 Mon Sep 17 00:00:00 2001
From: Arnaud Pineux
Date: Tue, 27 Nov 2012 10:07:55 +0100
Subject: [PATCH 13/87] [TEST] automated action rule tests
bzr revid: api@openerp.com-20121127090755-03sfycnz8nw9ldnv
---
.../test/base_action_rule_test.py | 111 ------------------
addons/crm/tests/__init__.py | 27 +++++
addons/crm/tests/base_action_rule_test.py | 69 +++++++++++
3 files changed, 96 insertions(+), 111 deletions(-)
delete mode 100644 addons/base_action_rule/test/base_action_rule_test.py
create mode 100644 addons/crm/tests/__init__.py
create mode 100644 addons/crm/tests/base_action_rule_test.py
diff --git a/addons/base_action_rule/test/base_action_rule_test.py b/addons/base_action_rule/test/base_action_rule_test.py
deleted file mode 100644
index 41eb0e31746..00000000000
--- a/addons/base_action_rule/test/base_action_rule_test.py
+++ /dev/null
@@ -1,111 +0,0 @@
-import tools
-from openerp.tests import common
-
-class myLead(orm.Model):
-
- _name = name('base_action_rule.myLead')
-
- _columns = {
- 'partner_id': fields.many2one('res.partner', 'Partner', ondelete='set null',
- select=True, help="Linked partner (optional). Usually created when converting the lead."),
- 'id': fields.integer('ID', readonly=True),
- 'name': fields.char('Subject', size=64, required=True, select=1),
- 'active': fields.boolean('Active', required=False),
- 'date_action_last': fields.datetime('Last Action', readonly=1),
- 'date_action_next': fields.datetime('Next Action', readonly=1),
- 'email_from': fields.char('Email', size=128, help="Email address of the contact", select=1),
- 'section_id': fields.many2one('crm.case.section', 'Sales Team', \
- select=True, help='When sending mails, the default email address is taken from the sales team.'),
- 'create_date': fields.datetime('Creation Date' , readonly=True),
- 'email_cc': fields.text('Global CC', size=252 , help="These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma"),
- 'description': fields.text('Notes'),
- 'write_date': fields.datetime('Update Date' , readonly=True),
- 'categ_ids': fields.many2many('crm.case.categ', 'crm_lead_category_rel', 'lead_id', 'category_id', 'Categories', \
- domain="['|',('section_id','=',section_id),('section_id','=',False), ('object_id.model', '=', 'crm.lead')]"),
- 'type_id': fields.many2one('crm.case.resource.type', 'Campaign', \
- domain="['|',('section_id','=',section_id),('section_id','=',False)]", help="From which campaign (seminar, marketing campaign, mass mailing, ...) did this contact come from?"),
- 'channel_id': fields.many2one('crm.case.channel', 'Channel', help="Communication channel (mail, direct, phone, ...)"),
- 'contact_name': fields.char('Contact Name', size=64),
- 'partner_name': fields.char("Customer Name", size=64,help='The name of the future partner company that will be created while converting the lead into opportunity', select=1),
- 'opt_out': fields.boolean('Opt-Out', oldname='optout', help="If opt-out is checked, this contact has refused to receive emails or unsubscribed to a campaign."),
- 'type':fields.selection([ ('lead','Lead'), ('opportunity','Opportunity'), ],'Type', help="Type is used to separate Leads and Opportunities"),
- 'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority', select=True),
- 'date_closed': fields.datetime('Closed', readonly=True),
- 'stage_id': fields.many2one('crm.case.stage', 'Stage',
- domain="['&', ('fold', '=', False), '&', '|', ('section_ids', '=', section_id), ('case_default', '=', True), '|', ('type', '=', type), ('type', '=', 'both')]"),
- 'user_id': fields.many2one('res.users', 'Salesperson'),
- 'referred': fields.char('Referred By', size=64),
- 'date_open': fields.datetime('Opened', readonly=True),
- 'day_open': fields.function(_compute_day, string='Days to Open', \
- multi='day_open', type="float", store=True),
- 'day_close': fields.function(_compute_day, string='Days to Close', \
- multi='day_close', type="float", store=True),
- 'state': fields.related('stage_id', 'state', type="selection", store=True,
- selection=crm.AVAILABLE_STATES, string="Status", readonly=True,
- help='The Status is set to \'Draft\', when a case is created. If the case is in progress the Status is set to \'Open\'. When the case is over, the Status is set to \'Done\'. If the case needs to be reviewed then the Status is set to \'Pending\'.'),
-
- # Only used for type opportunity
- 'probability': fields.float('Success Rate (%)',group_operator="avg"),
- 'planned_revenue': fields.float('Expected Revenue'),
- 'ref': fields.reference('Reference', selection=crm._links_get, size=128),
- 'ref2': fields.reference('Reference 2', selection=crm._links_get, size=128),
- 'phone': fields.char("Phone", size=64),
- 'date_deadline': fields.date('Expected Closing', help="Estimate of the date on which the opportunity will be won."),
- 'date_action': fields.date('Next Action Date', select=True),
- 'title_action': fields.char('Next Action', size=64),
- 'color': fields.integer('Color Index'),
- 'partner_address_name': fields.related('partner_id', 'name', type='char', string='Partner Contact Name', readonly=True),
- 'partner_address_email': fields.related('partner_id', 'email', type='char', string='Partner Contact Email', readonly=True),
- 'company_currency': fields.related('company_id', 'currency_id', type='many2one', string='Currency', readonly=True, relation="res.currency"),
- 'user_email': fields.related('user_id', 'email', type='char', string='User Email', readonly=True),
- 'user_login': fields.related('user_id', 'login', type='char', string='User Login', readonly=True),
-
- # Fields for address, due to separation from crm and res.partner
- 'street': fields.char('Street', size=128),
- 'street2': fields.char('Street2', size=128),
- 'zip': fields.char('Zip', change_default=True, size=24),
- 'city': fields.char('City', size=128),
- 'state_id': fields.many2one("res.country.state", 'State'),
- 'country_id': fields.many2one('res.country', 'Country'),
- 'phone': fields.char('Phone', size=64),
- 'fax': fields.char('Fax', size=64),
- 'mobile': fields.char('Mobile', size=64),
- 'function': fields.char('Function', size=128),
- 'title': fields.many2one('res.partner.title', 'Title'),
- 'company_id': fields.many2one('res.company', 'Company', select=1),
- 'payment_mode': fields.many2one('crm.payment.mode', 'Payment Mode', \
- domain="[('section_id','=',section_id)]"),
- 'planned_cost': fields.float('Planned Costs'),
- }
-
- _defaults = {
- 'active': 1,
- 'type': 'lead',
- 'user_id': lambda s, cr, uid, c: s._get_default_user(cr, uid, c),
- 'email_from': lambda s, cr, uid, c: s._get_default_email(cr, uid, c),
- 'stage_id': lambda s, cr, uid, c: s._get_default_stage_id(cr, uid, c),
- 'section_id': lambda s, cr, uid, c: s._get_default_section_id(cr, uid, c),
- 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.lead', context=c),
- 'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0],
- 'color': 0,
- }
-
-
-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.pool.get('crm.case.section')
- self.team_1_id = self.team_pool.create(cr, uid, {
-
- }, context=None)
-
- self.myLead_pool = self.pool.get('base_action_rule.myLead')
- self.new_myLead_id = self.myLead_pool.create(cr, uid, {
- 'name' : "my_first_lead",
- 'section_id' :
- }, context=None)
diff --git a/addons/crm/tests/__init__.py b/addons/crm/tests/__init__.py
new file mode 100644
index 00000000000..a2de50226da
--- /dev/null
+++ b/addons/crm/tests/__init__.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Business Applications
+# Copyright (c) 2012-TODAY OpenERP S.A.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+from . import base_action_rule_test
+
+checks = [
+ base_action_rule_test,
+]
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/crm/tests/base_action_rule_test.py b/addons/crm/tests/base_action_rule_test.py
new file mode 100644
index 00000000000..caca0c635da
--- /dev/null
+++ b/addons/crm/tests/base_action_rule_test.py
@@ -0,0 +1,69 @@
+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
+
From 3df8c5fe9a048eac11d3c50e69bf3263f5b3b980 Mon Sep 17 00:00:00 2001
From: Arnaud Pineux
Date: Wed, 28 Nov 2012 10:29:28 +0100
Subject: [PATCH 14/87] [IMP] Base Action Rule (only filter conditions)
bzr revid: api@openerp.com-20121128092928-7kbs1d9g8ung4oul
---
addons/base_action_rule/__init__.py | 1 +
addons/base_action_rule/base_action_rule.py | 73 ++++-----
.../base_action_rule_view.xml | 32 ++--
addons/base_action_rule/test_models.py | 32 ++++
.../tests/__init__.py | 0
.../tests/base_action_rule_test.py | 142 ++++++++++++++++++
addons/crm/crm_action_rule.py | 4 +-
addons/crm/crm_action_rule_view.xml | 10 --
addons/crm/tests/base_action_rule_test.py | 69 ---------
9 files changed, 228 insertions(+), 135 deletions(-)
create mode 100644 addons/base_action_rule/test_models.py
rename addons/{crm => base_action_rule}/tests/__init__.py (100%)
create mode 100644 addons/base_action_rule/tests/base_action_rule_test.py
delete mode 100644 addons/crm/tests/base_action_rule_test.py
diff --git a/addons/base_action_rule/__init__.py b/addons/base_action_rule/__init__.py
index 207ed3cb30f..c8c827aaba2 100644
--- a/addons/base_action_rule/__init__.py
+++ b/addons/base_action_rule/__init__.py
@@ -20,5 +20,6 @@
##############################################################################
import base_action_rule
+import test_models
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py
index 14da227a367..2b6877a7c9c 100644
--- a/addons/base_action_rule/base_action_rule.py
+++ b/addons/base_action_rule/base_action_rule.py
@@ -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'\
\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)]
- '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),
}
@@ -110,7 +111,7 @@ trigger date, like sending a reminder 15 minutes before a meeting."),
_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
cr.execute("SELECT model.model, rule.id FROM base_action_rule rule \
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
# Otherwise we let the scheduler run the action
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
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:
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'):
- 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 wrapper
@@ -151,15 +161,22 @@ trigger date, like sending a reminder 15 minutes before a meeting."),
if isinstance(ids, (str, int, long)):
ids = [ids]
model_pool = self.pool.get(model)
- # get the old records (before the write)
- if model and ids:
- old_records = model_pool.browse(cr,uid,ids,context=context)
- # look at records' states to fill the record cache
- for record in old_records:
- record.state
+ # We check for the pre-filter. We must apply it before the write
+ precondition_ok = {}
+ for id in ids:
+ precondition_ok[id] = {}
+ for action in self.browse(cr, uid, self.search(cr, uid, [], context=context), context=context):
+ 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)
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 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 """
if context is None:
context = {}
- ok = True
- if action.filter_id and action.model_id.model == action.filter_id.model_id:
+ ok = precondition_ok
+ if action.filter_post_id and action.model_id.model == action.filter_post_id.model_id:
ctx = dict(context)
- ctx.update(eval(action.filter_id.context))
- obj_ids = self.pool.get(action.model_id.model).search(cr, uid, eval(action.filter_id.domain), context=ctx)
+ ctx.update(eval(action.filter_post_id.context))
+ 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
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)
@@ -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 []))
)
)
- #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
result_name = True
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)
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 """
if context is None:
context = {}
@@ -350,9 +351,11 @@ trigger date, like sending a reminder 15 minutes before a meeting."),
objects = [objects]
for action in self.browse(cr, uid, ids, context=context):
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)
-
context.update({'action': False})
return True
diff --git a/addons/base_action_rule/base_action_rule_view.xml b/addons/base_action_rule/base_action_rule_view.xml
index 4815baa9ebb..3bd90457828 100644
--- a/addons/base_action_rule/base_action_rule_view.xml
+++ b/addons/base_action_rule/base_action_rule_view.xml
@@ -15,36 +15,31 @@
-
-
-
+
+
+
+ Select a filter or a timer as condition.
+ To create a new filter,
+ - Go to your Model's page and set the filter parameters in the "Search" view
+ - In this same "Search" view, select the menu "Save Current Filter", enter the name and add the option "Share with all users"
+ The filter must therefore be available in this page.
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
+
-
-
-
@@ -63,7 +58,6 @@
-
diff --git a/addons/base_action_rule/test_models.py b/addons/base_action_rule/test_models.py
new file mode 100644
index 00000000000..9469b42c705
--- /dev/null
+++ b/addons/base_action_rule/test_models.py
@@ -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
diff --git a/addons/crm/tests/__init__.py b/addons/base_action_rule/tests/__init__.py
similarity index 100%
rename from addons/crm/tests/__init__.py
rename to addons/base_action_rule/tests/__init__.py
diff --git a/addons/base_action_rule/tests/base_action_rule_test.py b/addons/base_action_rule/tests/base_action_rule_test.py
new file mode 100644
index 00000000000..6f1c885df69
--- /dev/null
+++ b/addons/base_action_rule/tests/base_action_rule_test.py
@@ -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))
\ No newline at end of file
diff --git a/addons/crm/crm_action_rule.py b/addons/crm/crm_action_rule.py
index 72582a1f21e..74fc749f33c 100644
--- a/addons/crm/crm_action_rule.py
+++ b/addons/crm/crm_action_rule.py
@@ -43,8 +43,8 @@ class base_action_rule(osv.osv):
'act_categ_id': fields.many2one('crm.case.categ', 'Set Category to'),
}
- def do_check(self, cr, uid, action, obj, old_records=None, context=None):
- ok = super(base_action_rule, self).do_check(cr, uid, action, obj, old_records=old_records, context=context)
+ 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, precondition_ok=precondition_ok, context=context)
if hasattr(obj, 'section_id'):
ok = ok and (not action.trg_section_id or action.trg_section_id.id == obj.section_id.id)
diff --git a/addons/crm/crm_action_rule_view.xml b/addons/crm/crm_action_rule_view.xml
index e21b74de382..347e54c46ac 100644
--- a/addons/crm/crm_action_rule_view.xml
+++ b/addons/crm/crm_action_rule_view.xml
@@ -7,16 +7,6 @@
base.action.rule
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/crm/tests/base_action_rule_test.py b/addons/crm/tests/base_action_rule_test.py
deleted file mode 100644
index caca0c635da..00000000000
--- a/addons/crm/tests/base_action_rule_test.py
+++ /dev/null
@@ -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
-
From 7e8533d278979a3f31c13c342aa9eafe757df493 Mon Sep 17 00:00:00 2001
From: Arnaud Pineux
Date: Wed, 28 Nov 2012 13:01:00 +0100
Subject: [PATCH 15/87] [FIX] old actions based on filters still work
bzr revid: api@openerp.com-20121128120100-58xrtp54txigz0b5
---
addons/base_action_rule/base_action_rule.py | 18 +++++++++---------
.../base_action_rule/base_action_rule_view.xml | 2 +-
.../tests/base_action_rule_test.py | 12 ++++++------
3 files changed, 16 insertions(+), 16 deletions(-)
diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py
index 2b6877a7c9c..de3362c6644 100644
--- a/addons/base_action_rule/base_action_rule.py
+++ b/addons/base_action_rule/base_action_rule.py
@@ -97,8 +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'\
\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)]
- '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),
+ 'filter_id':fields.many2one('ir.filters', 'Postcondition Filter', required=False), #TODO: set domain [('model_id','=',model_id.model)]
+ 'filter_pre_id': fields.many2one('ir.filters', 'Precondition Filter', required=False),
'last_run': fields.datetime('Last Run', readonly=1),
}
@@ -140,7 +140,7 @@ trigger date, like sending a reminder 15 minutes before a meeting."),
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:
+ if action.filter_pre_id:
precondition_ok[new_id][action.id] = False
else:
precondition_ok[new_id][action.id] = True
@@ -167,12 +167,12 @@ trigger date, like sending a reminder 15 minutes before a meeting."),
precondition_ok[id] = {}
for action in self.browse(cr, uid, self.search(cr, uid, [], context=context), context=context):
precondition_ok[id][action.id] = True
- if action.filter_id and action.model_id.model == action.filter_id.model_id:
+ if action.filter_pre_id and action.model_id.model == action.filter_pre_id.model_id:
ctx = dict(context)
- ctx.update(eval(action.filter_id.context))
+ ctx.update(eval(action.filter_pre_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)
+ obj_ids = self.pool.get(action.model_id.model).search(cr, uid, eval(action.filter_pre_id.domain), context=ctx)
precondition_ok[id][action.id] = id in obj_ids
old_write(cr, uid, ids, vals, context=context)
if not context.get('action'):
@@ -285,10 +285,10 @@ trigger date, like sending a reminder 15 minutes before a meeting."),
if context is None:
context = {}
ok = precondition_ok
- if action.filter_post_id and action.model_id.model == action.filter_post_id.model_id:
+ if action.filter_id and action.model_id.model == action.filter_id.model_id:
ctx = dict(context)
- ctx.update(eval(action.filter_post_id.context))
- obj_ids = self.pool.get(action.model_id.model).search(cr, uid, eval(action.filter_post_id.domain), context=ctx)
+ ctx.update(eval(action.filter_id.context))
+ obj_ids = self.pool.get(action.model_id.model).search(cr, uid, eval(action.filter_id.domain), context=ctx)
ok = ok and obj.id in obj_ids
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)
diff --git a/addons/base_action_rule/base_action_rule_view.xml b/addons/base_action_rule/base_action_rule_view.xml
index 3bd90457828..257465b490a 100644
--- a/addons/base_action_rule/base_action_rule_view.xml
+++ b/addons/base_action_rule/base_action_rule_view.xml
@@ -31,8 +31,8 @@
+
-
diff --git a/addons/base_action_rule/tests/base_action_rule_test.py b/addons/base_action_rule/tests/base_action_rule_test.py
index 6f1c885df69..0452401ce23 100644
--- a/addons/base_action_rule/tests/base_action_rule_test.py
+++ b/addons/base_action_rule/tests/base_action_rule_test.py
@@ -39,7 +39,7 @@ class base_action_rule_test(common.TransactionCase):
'user_id': self.admin_user[1],
}, context=context)
- def create_rule(self, cr, uid, filter_id=None, filter_post_id=None, context=None):
+ def create_rule(self, cr, uid, filter_id=None, filter_pre_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"
"""
@@ -49,7 +49,7 @@ class base_action_rule_test(common.TransactionCase):
'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_pre_id' : filter_pre_id,
'filter_id' : filter_id,
'act_user_id': self.demo_user[1],
}, context=context)
@@ -61,7 +61,7 @@ class base_action_rule_test(common.TransactionCase):
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)
+ rule_1_id = self.create_rule(cr, uid, filter_pre_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')
@@ -74,7 +74,7 @@ class base_action_rule_test(common.TransactionCase):
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)
+ 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')
@@ -92,7 +92,7 @@ class base_action_rule_test(common.TransactionCase):
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)
+ self.create_rule(cr, uid, filter_pre_id=filter_draft, filter_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')
@@ -130,7 +130,7 @@ class base_action_rule_test(common.TransactionCase):
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)
+ self.create_rule(cr, uid, filter_pre_id=filter_draft, filter_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')
From 2b0a7a577654a8d9db973a139f91e1bb31054f65 Mon Sep 17 00:00:00 2001
From: "Nimesh (Open ERP)"
Date: Tue, 4 Dec 2012 15:31:06 +0530
Subject: [PATCH 16/87] [FIX] fix the problem of partial payment and purchase
invoice will go to paid status even if the price is 0 in product.
bzr revid: nco@tinyerp.com-20121204100106-64i2borf2dyj8jt8
---
addons/account/account_invoice.py | 7 +++++++
addons/account_voucher/account_voucher.py | 2 +-
2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/addons/account/account_invoice.py b/addons/account/account_invoice.py
index f89f7828269..75ce4daef5b 100644
--- a/addons/account/account_invoice.py
+++ b/addons/account/account_invoice.py
@@ -674,6 +674,9 @@ class account_invoice(osv.osv):
return super(account_invoice, self).copy(cr, uid, id, default, context)
def test_paid(self, cr, uid, ids, *args):
+ for inv in self.browse(cr, uid, ids):
+ if inv.amount_total == 0.0:
+ return True
res = self.move_line_id_payment_get(cr, uid, ids)
if not res:
return False
@@ -851,6 +854,8 @@ class account_invoice(osv.osv):
if context is None:
context = {}
for inv in self.browse(cr, uid, ids, context=context):
+ if inv.amount_total == 0.0:
+ continue
if not inv.journal_id.sequence_id:
raise osv.except_osv(_('Error!'), _('Please define sequence on the journal related to this invoice.'))
if not inv.invoice_line:
@@ -1037,6 +1042,8 @@ class account_invoice(osv.osv):
self.write(cr, uid, ids, {})
for obj_inv in self.browse(cr, uid, ids, context=context):
+ if obj_inv.amount_total == 0.0:
+ continue
id = obj_inv.id
invtype = obj_inv.type
number = obj_inv.number
diff --git a/addons/account_voucher/account_voucher.py b/addons/account_voucher/account_voucher.py
index ffc7cf1769d..f3a90b70f83 100644
--- a/addons/account_voucher/account_voucher.py
+++ b/addons/account_voucher/account_voucher.py
@@ -596,7 +596,7 @@ class account_voucher(osv.osv):
This function returns True if the line is considered as noise and should not be displayed
"""
if line.reconcile_partial_id:
- sign = 1 if ttype == 'receipt' else -1
+ sign = 1 if ttype in ['payment', 'receipt'] else -1
if currency_id == line.currency_id.id:
if line.amount_residual_currency * sign <= 0:
return True
From b04a134a16e2559b594a536276928ef9cc6f56a4 Mon Sep 17 00:00:00 2001
From: Christophe Matthieu
Date: Tue, 4 Dec 2012 11:23:04 +0100
Subject: [PATCH 17/87] [FIX] view_form: m2m_tags, when a user quick create a
tag, select a tags in the list or blurred and click on the arrow, the last
selection is persistant. Overwrite onSetInputData to reset suggestions if
input is empty.
bzr revid: chm@openerp.com-20121204102304-iru8pcjlca8zh5fj
---
addons/web/static/src/js/view_form.js | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js
index 21c3b3f43d9..5a573a1244e 100644
--- a/addons/web/static/src/js/view_form.js
+++ b/addons/web/static/src/js/view_form.js
@@ -3986,6 +3986,14 @@ instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(in
return item.name;
},
},
+ core: {
+ onSetInputData: function(e, data) {
+ if (data == '') {
+ this._plugins.autocomplete._suggestions = null;
+ }
+ this.input().val(data);
+ },
+ },
},
}).bind('getSuggestions', function(e, data) {
var _this = this;
From 638dcfce3488a6849a109a7220d8164e15bbd777 Mon Sep 17 00:00:00 2001
From: Arnaud Pineux
Date: Tue, 4 Dec 2012 16:36:17 +0100
Subject: [PATCH 18/87] [IMP] base_action_rule : delete unused fields and add
explanation
lp bug: https://launchpad.net/bugs/944197 fixed
bzr revid: api@openerp.com-20121204153617-ju3yyj2tt3d84jl9
---
addons/base_action_rule/base_action_rule.py | 57 -------------------
.../base_action_rule_view.xml | 8 +--
addons/crm/crm_action_rule.py | 32 -----------
addons/crm/crm_action_rule_demo.xml | 1 -
addons/crm/test/process/action_rule.yml | 20 -------
5 files changed, 4 insertions(+), 114 deletions(-)
delete mode 100644 addons/crm/test/process/action_rule.yml
diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py
index de3362c6644..00b179655f0 100644
--- a/addons/base_action_rule/base_action_rule.py
+++ b/addons/base_action_rule/base_action_rule.py
@@ -84,18 +84,9 @@ specifies you can put a negative number. If you need a delay before the \
trigger date, like sending a reminder 15 minutes before a meeting."),
'trg_date_range_type': fields.selection([('minutes', 'Minutes'), ('hour', 'Hours'), \
('day', 'Days'), ('month', 'Months')], 'Delay type'),
- 'trg_user_id': fields.many2one('res.users', 'Responsible'),
- 'trg_partner_id': fields.many2one('res.partner', 'Partner'),
- 'trg_partner_categ_id': fields.many2one('res.partner.category', 'Partner Category'),
- 'trg_state_from': fields.selection(_state_get, 'and previously was', size=16),
- 'trg_state_to': fields.selection(_state_get, 'Status changes to', size=16),
-
'act_user_id': fields.many2one('res.users', 'Set Responsible to'),
'act_state': fields.selection(_state_get, 'Set State to', size=16),
'act_followers': fields.many2many("res.partner", string="Set Followers"),
- 'regex_name': fields.char('Regex on Resource Name', size=128, help="Regular expression for matching name of the resource\
-\ne.g.: 'urgent.*' will search for records having name starting with the string 'urgent'\
-\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)]
'filter_id':fields.many2one('ir.filters', 'Postcondition Filter', required=False), #TODO: set domain [('model_id','=',model_id.model)]
'filter_pre_id': fields.many2one('ir.filters', 'Precondition Filter', required=False),
@@ -290,26 +281,6 @@ trigger date, like sending a reminder 15 minutes before a meeting."),
ctx.update(eval(action.filter_id.context))
obj_ids = self.pool.get(action.model_id.model).search(cr, uid, eval(action.filter_id.domain), context=ctx)
ok = ok and obj.id in obj_ids
- 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)
- if getattr(obj, 'partner_id', False):
- ok = ok and (not action.trg_partner_id.id or action.trg_partner_id.id==obj.partner_id.id)
- ok = ok and (
- not action.trg_partner_categ_id.id or
- (
- obj.partner_id.id and
- (action.trg_partner_categ_id.id in map(lambda x: x.id, obj.partner_id.category_id or []))
- )
- )
- reg_name = action.regex_name
- result_name = True
- if reg_name:
- ptrn = re.compile(ustr(reg_name))
- _result = ptrn.search(ustr(obj.name))
- if not _result:
- result_name = False
- regex_n = not reg_name or result_name
- ok = ok and regex_n
return ok
def do_action(self, cr, uid, action, obj, context=None):
@@ -359,38 +330,10 @@ trigger date, like sending a reminder 15 minutes before a meeting."),
context.update({'action': False})
return True
-base_action_rule()
-
class actions_server(osv.osv):
_inherit = 'ir.actions.server'
_columns = {
'action_rule_id': fields.many2one("base.action.rule", string="Action Rule")
}
-actions_server()
-
-class ir_cron(osv.osv):
- _inherit = 'ir.cron'
- _init_done = False
-
- def _poolJobs(self, db_name, check=False):
- if not self._init_done:
- self._init_done = True
- try:
- db = pooler.get_db(db_name)
- except:
- return False
- cr = db.cursor()
- try:
- next = datetime.now().strftime('%Y-%m-%d %H:00:00')
- # Putting nextcall always less than current time in order to call it every time
- cr.execute('UPDATE ir_cron set nextcall = \'%s\' where numbercall<>0 and active and model=\'base.action.rule\' ' % (next))
- finally:
- cr.commit()
- cr.close()
-
- super(ir_cron, self)._poolJobs(db_name, check=check)
-
-ir_cron()
-
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/base_action_rule/base_action_rule_view.xml b/addons/base_action_rule/base_action_rule_view.xml
index 257465b490a..aab9ce0000c 100644
--- a/addons/base_action_rule/base_action_rule_view.xml
+++ b/addons/base_action_rule/base_action_rule_view.xml
@@ -23,10 +23,10 @@
- Select a filter or a timer as condition.
- To create a new filter,
- - Go to your Model's page and set the filter parameters in the "Search" view
- - In this same "Search" view, select the menu "Save Current Filter", enter the name and add the option "Share with all users"
+ Select a filter or a timer as condition. An action rule is checked when you create or modify the "Related Document Model". The precondition filter is checked right before the modification while the postcondition filter is checked after the modification. A precondition filter will therefore not work during a creation.
+ To create a new filter:
+ - Go to your "Related Document Model" page and set the filter parameters in the "Search" view (Example of filter based on Leads/Opportunities: Creation Date "is equal to" 01/01/2012)
+ - In this same "Search" view, select the menu "Save Current Filter", enter the name (Ex: Create the 01/01/2012) and add the option "Share with all users"
The filter must therefore be available in this page.
diff --git a/addons/crm/crm_action_rule.py b/addons/crm/crm_action_rule.py
index 74fc749f33c..1344b170b9a 100644
--- a/addons/crm/crm_action_rule.py
+++ b/addons/crm/crm_action_rule.py
@@ -35,42 +35,10 @@ class base_action_rule(osv.osv):
_description = 'Action Rules'
_columns = {
- 'trg_section_id': fields.many2one('crm.case.section', 'Sales Team'),
- 'trg_max_history': fields.integer('Maximum Communication History'),
- 'trg_categ_id': fields.many2one('crm.case.categ', 'Category'),
- 'regex_history' : fields.char('Regular Expression on Case History', size=128),
'act_section_id': fields.many2one('crm.case.section', 'Set Team to'),
'act_categ_id': fields.many2one('crm.case.categ', 'Set Category to'),
}
- 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, precondition_ok=precondition_ok, context=context)
-
- if hasattr(obj, 'section_id'):
- ok = ok and (not action.trg_section_id or action.trg_section_id.id == obj.section_id.id)
- if hasattr(obj, 'categ_ids'):
- ok = ok and (not action.trg_categ_id or action.trg_categ_id.id in [x.id for x in obj.categ_ids])
-
- #Cheking for history
- regex = action.regex_history
- if regex:
- res = False
- ptrn = re.compile(ustr(regex))
- for history in obj.message_ids:
- _result = ptrn.search(ustr(history.subject))
- if _result:
- res = True
- break
- ok = ok and res
-
- if action.trg_max_history:
- res_count = False
- history_ids = filter(lambda x: x.email_from, obj.message_ids)
- if len(history_ids) <= action.trg_max_history:
- res_count = True
- ok = ok and res_count
- return ok
-
def do_action(self, cr, uid, action, obj, context=None):
res = super(base_action_rule, self).do_action(cr, uid, action, obj, context=context)
model_obj = self.pool.get(action.model_id.model)
diff --git a/addons/crm/crm_action_rule_demo.xml b/addons/crm/crm_action_rule_demo.xml
index 83f5681f311..31f07e59034 100644
--- a/addons/crm/crm_action_rule_demo.xml
+++ b/addons/crm/crm_action_rule_demo.xml
@@ -72,7 +72,6 @@ Thanks,
Set Auto Followers on leads which are urgent and come from USA.2
- urgent.*
diff --git a/addons/crm/test/process/action_rule.yml b/addons/crm/test/process/action_rule.yml
deleted file mode 100644
index f76e6c250bc..00000000000
--- a/addons/crm/test/process/action_rule.yml
+++ /dev/null
@@ -1,20 +0,0 @@
--
- I create a record rule.
--
- !python {model: base.action.rule}: |
- model_ids = self.pool.get("ir.model").search(cr, uid, [('model', '=', 'crm.lead')])
- from datetime import datetime
- new_id = self.create(cr, uid, {'name': 'New Rule', 'model_id': model_ids[0], 'trg_user_id': ref('base.user_root'), 'trg_partner_id': ref('base.res_partner_1'), 'act_user_id': ref('base.user_demo') })
- self._check(cr, uid)
--
- I create a new lead to check the record rule.
--
- !record {model: crm.lead, id: crm_lead_test_rules_id}:
- name: 'Test lead rules'
- partner_id: base.res_partner_1
--
- I check if the record rule is applied and the responsible is changed.
--
- !python {model: crm.lead}: |
- lead_user = self.browse(cr, uid, ref('crm_lead_test_rules_id'))
- assert lead_user.user_id.id == ref('base.user_demo'), "Responsible of lead is not changed."
From 5827f6c200dbf70e8b0e7aeb13facda32ac3c1bb Mon Sep 17 00:00:00 2001
From: Arnaud Pineux
Date: Tue, 4 Dec 2012 16:52:10 +0100
Subject: [PATCH 19/87] [IMP] base_action_rule : clear state and stage
lp bug: https://launchpad.net/bugs/944197 fixed
bzr revid: api@openerp.com-20121204155210-3cjh1153idyax1sh
---
addons/base_status/base_stage.py | 13 -------------
addons/base_status/base_state.py | 12 ------------
2 files changed, 25 deletions(-)
diff --git a/addons/base_status/base_stage.py b/addons/base_status/base_stage.py
index 5710a31d6a0..72d58553092 100644
--- a/addons/base_status/base_stage.py
+++ b/addons/base_status/base_stage.py
@@ -217,7 +217,6 @@ class base_stage(object):
self.write(cr, uid, [case.id], data, context=context)
case.case_escalate_send_note(case.section_id.parent_id, context=context)
cases = self.browse(cr, uid, ids, context=context)
- self._action(cr, uid, cases, 'escalate', context=context)
return True
def case_open(self, cr, uid, ids, context=None):
@@ -283,18 +282,6 @@ class base_stage(object):
self.write(cr, uid, ids, values_to_update, context=context)
return True
- def _action(self, cr, uid, cases, state_to, scrit=None, context=None):
- if context is None:
- context = {}
- context['state_to'] = state_to
- rule_obj = self.pool.get('base.action.rule')
- if not rule_obj:
- return True
- model_obj = self.pool.get('ir.model')
- model_ids = model_obj.search(cr, uid, [('model','=',self._name)], context=context)
- rule_ids = rule_obj.search(cr, uid, [('model_id','=',model_ids[0])], context=context)
- return rule_obj._action(cr, uid, rule_ids, cases, scrit=scrit, context=context)
-
def _check(self, cr, uid, ids=False, context=None):
""" Function called by the scheduler to process cases for date actions.
diff --git a/addons/base_status/base_state.py b/addons/base_status/base_state.py
index 97d70268bdf..e8a1dcc6b9f 100644
--- a/addons/base_status/base_state.py
+++ b/addons/base_status/base_state.py
@@ -107,7 +107,6 @@ class base_state(object):
raise osv.except_osv(_('Error !'), _('You can not escalate, you are already at the top level regarding your sales-team category.'))
self.write(cr, uid, [case.id], data, context=context)
case.case_escalate_send_note(parent_id, context=context)
- self._action(cr, uid, cases, 'escalate', context=context)
return True
def case_open(self, cr, uid, ids, context=None):
@@ -163,17 +162,6 @@ class base_state(object):
update_values = {}
update_values['state'] = state_name
self.write(cr, uid, ids, update_values, context=context)
- #self._action(cr, uid, cases, state_name, context=context)
-
- def _action(self, cr, uid, cases, state_to, scrit=None, context=None):
- if context is None:
- context = {}
- context['state_to'] = state_to
- rule_obj = self.pool.get('base.action.rule')
- model_obj = self.pool.get('ir.model')
- model_ids = model_obj.search(cr, uid, [('model','=',self._name)])
- rule_ids = rule_obj.search(cr, uid, [('model_id','=',model_ids[0])])
- return rule_obj._action(cr, uid, rule_ids, cases, scrit=scrit, context=context)
# ******************************
# Notifications
From 8381a0c92f5dd5d6c01e1dc53481509ad1ee7a1a Mon Sep 17 00:00:00 2001
From: Arnaud Pineux
Date: Tue, 4 Dec 2012 17:20:52 +0100
Subject: [PATCH 20/87] [FIX] base_stage & state
bzr revid: api@openerp.com-20121204162052-u3t2vvf4ttcppdic
---
addons/base_status/base_stage.py | 3 ---
1 file changed, 3 deletions(-)
diff --git a/addons/base_status/base_stage.py b/addons/base_status/base_stage.py
index 6e2313be4ac..f88a25c57b4 100644
--- a/addons/base_status/base_stage.py
+++ b/addons/base_status/base_stage.py
@@ -277,9 +277,6 @@ class base_stage(object):
def write(self, cr, uid, ids, vals, context=None):
res = super(base_stage,self).write(cr, uid, ids, vals, context)
- if vals.get('stage_id'):
- for case in self.browse(cr, uid, ids, context=context):
- self._action(cr, uid, case, case.stage_id.state, context=context)
return res
From 0ca480a80d4e87883941f84508c58d36c97d6329 Mon Sep 17 00:00:00 2001
From: Arnaud Pineux
Date: Tue, 4 Dec 2012 17:23:27 +0100
Subject: [PATCH 21/87] [FIX] base_stage & state
bzr revid: api@openerp.com-20121204162327-uu941ixjjojgc15v
---
addons/crm/__openerp__.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/addons/crm/__openerp__.py b/addons/crm/__openerp__.py
index 63d5d3da84b..a438dd2858e 100644
--- a/addons/crm/__openerp__.py
+++ b/addons/crm/__openerp__.py
@@ -111,7 +111,6 @@ Dashboard for CRM will include:
'test/process/lead2opportunity2win.yml',
'test/process/merge_opportunity.yml',
'test/process/cancel_lead.yml',
- 'test/process/action_rule.yml',
'test/process/segmentation.yml',
'test/process/phonecalls.yml',
'test/ui/crm_demo.yml',
From 87a7c9f8dd810f07a45202cde05de4a7d3e54340 Mon Sep 17 00:00:00 2001
From: Raphael Collet
Date: Wed, 19 Dec 2012 14:14:59 +0100
Subject: [PATCH 22/87] [IMP] crm: remove unused import
bzr revid: rco@openerp.com-20121219131459-xtbfvfll740dq9rg
---
addons/crm/crm_lead.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py
index 309ef14efc6..11776f55756 100644
--- a/addons/crm/crm_lead.py
+++ b/addons/crm/crm_lead.py
@@ -29,7 +29,6 @@ from openerp.tools.translate import _
from openerp.tools import html2plaintext
from base.res.res_partner import format_address
-import pdb
CRM_LEAD_FIELDS_TO_MERGE = ['name',
'partner_id',
From 95b41b40e984e80a4228b761554fcacab39e8425 Mon Sep 17 00:00:00 2001
From: Raphael Collet
Date: Wed, 19 Dec 2012 14:21:38 +0100
Subject: [PATCH 23/87] [IMP] register_hook: improve a bit the code
bzr revid: rco@openerp.com-20121219132138-npf12n5l5ug6ujvx
---
openerp/modules/loading.py | 7 +++----
openerp/osv/orm.py | 2 +-
2 files changed, 4 insertions(+), 5 deletions(-)
diff --git a/openerp/modules/loading.py b/openerp/modules/loading.py
index aa756d51a57..fb68642f137 100644
--- a/openerp/modules/loading.py
+++ b/openerp/modules/loading.py
@@ -426,10 +426,9 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
else:
_logger.info('Modules loaded.')
- # STEP 7: for every model call _register_hook
- for models in pool.models:
- pool_mod = pool.get(models)
- pool_mod._register_hook(cr)
+ # STEP 7: call _register_hook on every model
+ for model in pool.models.values():
+ model._register_hook(cr)
finally:
cr.close()
diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py
index 7fbf2d90ad0..875723b0989 100644
--- a/openerp/osv/orm.py
+++ b/openerp/osv/orm.py
@@ -5229,7 +5229,7 @@ class BaseModel(object):
resolve_o2m_commands_to_record_dicts = resolve_2many_commands
def _register_hook(self, cr):
- """ This method is called right after the registry is filled """
+ """ stuff to do right after the registry is built """
pass
# keep this import here, at top it will cause dependency cycle errors
From 080761cb276136dbcc3c1c8a5b2a68c3ca91f3d0 Mon Sep 17 00:00:00 2001
From: Raphael Collet
Date: Wed, 19 Dec 2012 15:20:21 +0100
Subject: [PATCH 24/87] [FIX] base_action_rule: fix tests
bzr revid: rco@openerp.com-20121219142021-15h9an87hhhkbhi8
---
.../tests/base_action_rule_test.py | 32 +++++++++----------
1 file changed, 16 insertions(+), 16 deletions(-)
diff --git a/addons/base_action_rule/tests/base_action_rule_test.py b/addons/base_action_rule/tests/base_action_rule_test.py
index 0452401ce23..ebefebe3ff4 100644
--- a/addons/base_action_rule/tests/base_action_rule_test.py
+++ b/addons/base_action_rule/tests/base_action_rule_test.py
@@ -1,4 +1,4 @@
-import tools
+from openerp import SUPERUSER_ID
from openerp.tests import common
from .. import test_models
@@ -8,8 +8,8 @@ class base_action_rule_test(common.TransactionCase):
"""*****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')
+ self.demo = self.registry('ir.model.data').get_object(cr, uid, 'base', 'user_demo').id
+ self.admin = SUPERUSER_ID
def create_filter_done(self, cr, uid, context=None):
filter_pool = self.registry('ir.filters')
@@ -36,12 +36,12 @@ class base_action_rule_test(common.TransactionCase):
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],
+ 'user_id': self.admin,
}, context=context)
def create_rule(self, cr, uid, filter_id=None, filter_pre_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"
+ The "Rule 1" says that when a lead goes to the 'draft' state, the responsible for that lead changes to user "demo"
"""
self.action_pool = self.registry('base.action.rule')
return self.action_pool.create(cr,uid,{
@@ -51,7 +51,7 @@ class base_action_rule_test(common.TransactionCase):
'trg_date_type' : 'none',
'filter_pre_id' : filter_pre_id,
'filter_id' : filter_id,
- 'act_user_id': self.demo_user[1],
+ 'act_user_id': self.demo,
}, context=context)
def test_00_check_to_state_draft_pre(self):
@@ -65,7 +65,7 @@ class base_action_rule_test(common.TransactionCase):
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))
+ self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin, context=context))
def test_01_check_to_state_draft_post(self):
"""
@@ -78,7 +78,7 @@ class base_action_rule_test(common.TransactionCase):
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))
+ self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.demo, context=context))
def test_02_check_from_draft_to_done_with_steps(self):
"""
@@ -96,27 +96,27 @@ class base_action_rule_test(common.TransactionCase):
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))
+ self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin, 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))
+ self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin, 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))
+ self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin, 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))
+ self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin, 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))
+ self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin, context=context))
def test_02_check_from_draft_to_done_without_steps(self):
"""
@@ -124,7 +124,7 @@ class base_action_rule_test(common.TransactionCase):
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"
+ The responsible in that test will change to user "demo"
"""
cr, uid = self.cr, self.uid
context = {}
@@ -134,9 +134,9 @@ class base_action_rule_test(common.TransactionCase):
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))
+ self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin, 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))
\ No newline at end of file
+ self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.demo, context=context))
From b35e1cb3217cdbb432d9bf93dc49ae214b4d9da9 Mon Sep 17 00:00:00 2001
From: Raphael Collet
Date: Wed, 19 Dec 2012 15:22:35 +0100
Subject: [PATCH 25/87] [IMP] base_action_rule: remove unused stuff
bzr revid: rco@openerp.com-20121219142235-uqs7m7jmeqh38bdt
---
addons/base_action_rule/base_action_rule.py | 12 +-----------
1 file changed, 1 insertion(+), 11 deletions(-)
diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py
index adcc0f25023..c401cf076d0 100644
--- a/addons/base_action_rule/base_action_rule.py
+++ b/addons/base_action_rule/base_action_rule.py
@@ -21,16 +21,11 @@
from datetime import datetime
from datetime import timedelta
-import re
import time
from openerp import SUPERUSER_ID
-from openerp.osv import fields, osv, orm
+from openerp.osv import fields, osv
from openerp.tools.translate import _
-from openerp.tools.safe_eval import safe_eval
-from openerp.tools import ustr
-from openerp import pooler
-from openerp import tools
def get_datetime(date_field):
@@ -57,10 +52,6 @@ class base_action_rule(osv.osv):
""" Get State """
return [('', ''), ('na','N/A (No previous state)')]
- def priority_get(self, cr, uid, context=None):
- """ Get Priority """
- return [('', '')]
-
_columns = {
'name': fields.char('Rule Name', size=64, required=True),
'model_id': fields.many2one('ir.model', 'Related Document Model', required=True, domain=[('osv_memory','=', False)]),
@@ -101,7 +92,6 @@ trigger date, like sending a reminder 15 minutes before a meeting."),
_order = 'sequence'
-
def post_action(self, cr, uid, ids, model, precondition_ok=None, context=None):
# Searching for action rules
cr.execute("SELECT model.model, rule.id FROM base_action_rule rule \
From e6cfbc5807dfa36154298c500e09f13480268e1c Mon Sep 17 00:00:00 2001
From: Raphael Collet
Date: Wed, 19 Dec 2012 15:23:45 +0100
Subject: [PATCH 26/87] [IMP] base_action_rule: improve field definitions, add
missing domains and restrictions
bzr revid: rco@openerp.com-20121219142345-pya9tjhd9ilepxy7
---
addons/base_action_rule/base_action_rule.py | 38 +++++++++++++--------
1 file changed, 24 insertions(+), 14 deletions(-)
diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py
index c401cf076d0..3b310519ed2 100644
--- a/addons/base_action_rule/base_action_rule.py
+++ b/addons/base_action_rule/base_action_rule.py
@@ -50,17 +50,18 @@ class base_action_rule(osv.osv):
def state_get(self, cr, uid, context=None):
""" Get State """
- return [('', ''), ('na','N/A (No previous state)')]
+ return [('', '')]
_columns = {
'name': fields.char('Rule Name', size=64, required=True),
- 'model_id': fields.many2one('ir.model', 'Related Document Model', required=True, domain=[('osv_memory','=', False)]),
+ 'model_id': fields.many2one('ir.model', 'Related Document Model',
+ required=True, domain=[('osv_memory', '=', False)]),
'model': fields.related('model_id', 'model', type="char", size=256, string='Model'),
'create_date': fields.datetime('Create Date', readonly=1),
- 'active': fields.boolean('Active', help="If the active field is set to False,\
- it will allow you to hide the rule without removing it."),
- 'sequence': fields.integer('Sequence', help="Gives the sequence order \
-when displaying a list of rules."),
+ 'active': fields.boolean('Active',
+ help="When unchecked, the rule is hidden and will not be executed."),
+ 'sequence': fields.integer('Sequence',
+ help="Gives the sequence order when displaying a list of rules."),
'trg_date_type': fields.selection([
('none', 'None'),
('create', 'Creation Date'),
@@ -69,18 +70,27 @@ when displaying a list of rules."),
('date', 'Date'),
('deadline', 'Deadline'),
], 'Trigger Date', size=16),
- 'trg_date_range': fields.integer('Delay after trigger date', \
- help="Delay After Trigger Date,\
-specifies you can put a negative number. If you need a delay before the \
-trigger date, like sending a reminder 15 minutes before a meeting."),
- 'trg_date_range_type': fields.selection([('minutes', 'Minutes'), ('hour', 'Hours'), \
+ 'trg_date_range': fields.integer('Delay after trigger date',
+ help="Delay after the trigger date." \
+ "You can put a negative number if you need a delay before the" \
+ "trigger date, like sending a reminder 15 minutes before a meeting."),
+ 'trg_date_range_type': fields.selection([('minutes', 'Minutes'), ('hour', 'Hours'),
('day', 'Days'), ('month', 'Months')], 'Delay type'),
'act_user_id': fields.many2one('res.users', 'Set Responsible to'),
'act_state': fields.selection(_state_get, 'Set State to', size=16),
'act_followers': fields.many2many("res.partner", string="Set Followers"),
- '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', 'Postcondition Filter', required=False), #TODO: set domain [('model_id','=',model_id.model)]
- 'filter_pre_id': fields.many2one('ir.filters', 'Precondition Filter', required=False),
+ 'server_action_ids': fields.one2many('ir.actions.server', 'action_rule_id',
+ domain="[('model_id', '=', model_id)]",
+ string='Server Action',
+ help="Example: email reminders, call object service, etc."),
+ 'filter_pre_id': fields.many2one('ir.filters', string='Before Filter',
+ ondelete='restrict',
+ domain="[('model_id', '=', model_id.model)]",
+ help="If present, this condition must be satisfied before the update of the record."),
+ 'filter_id': fields.many2one('ir.filters', string='After Filter',
+ ondelete='restrict',
+ domain="[('model_id', '=', model_id.model)]",
+ help="If present, this condition must be satisfied after the update of the record."),
'last_run': fields.datetime('Last Run', readonly=1),
}
From 07a3cf28a017d6e7353d3e7e5245d14cd7776f29 Mon Sep 17 00:00:00 2001
From: Raphael Collet
Date: Wed, 19 Dec 2012 16:05:33 +0100
Subject: [PATCH 27/87] [IMP] base_action_rule: improve method _register_hook
of base.action.rule
bzr revid: rco@openerp.com-20121219150533-t513zasagd4a73sx
---
addons/base_action_rule/base_action_rule.py | 45 ++++++++-------------
1 file changed, 17 insertions(+), 28 deletions(-)
diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py
index 3b310519ed2..0017a8d0794 100644
--- a/addons/base_action_rule/base_action_rule.py
+++ b/addons/base_action_rule/base_action_rule.py
@@ -117,7 +117,7 @@ class base_action_rule(osv.osv):
self._action(cr, uid, [rule_id], model_pool.browse(cr, uid, ids, context=context), precondition_ok=precondition_ok, context=context)
return True
- def _create(self, old_create, model, context=None):
+ def _wrap_create(self, old_create, model):
"""
Return a wrapper around `old_create` calling both `old_create` and
`post_action`, in that order.
@@ -140,7 +140,7 @@ class base_action_rule(osv.osv):
return new_id
return wrapper
- def _write(self, old_write, model, context=None):
+ def _wrap_write(self, old_write, model):
"""
Return a wrapper around `old_write` calling both `old_write` and
`post_action`, in that order.
@@ -171,41 +171,31 @@ class base_action_rule(osv.osv):
return True
return wrapper
- def _register_hook(self, cr):
+ def _register_hook(self, cr, ids=None):
+ """ Wrap the methods `create` and `write` of the models specified by
+ the rules given by `ids` (or all existing rules if `ids` is `Ǹone`.)
"""
- Wrap every `create` and `write` methods of the models specified by
- the rules (given by `ids`).
- """
- ids = self.search(cr,SUPERUSER_ID,[])
- return self._register_hook_(cr,SUPERUSER_ID,ids,context=None)
-
- def _register_hook_(self, cr, uid, ids, context=None):
- """
- Wrap every `create` and `write` methods of the models specified by
- the rules (given by `ids`).
- """
- reg_ids = []
- if not isinstance(ids, list):
- reg_ids.append(ids)
- else:
- reg_ids.extend(ids)
- for action_rule in self.browse(cr, uid, reg_ids, context=context):
+ if ids is None:
+ ids = self.search(cr, SUPERUSER_ID, [])
+ for action_rule in self.browse(cr, SUPERUSER_ID, ids):
model = action_rule.model_id.model
- obj_pool = self.pool.get(model)
- if not hasattr(obj_pool, 'base_action_ruled'):
- obj_pool.create = self._create(obj_pool.create, model, context=None)
- obj_pool.write = self._write(obj_pool.write, model, context=None)
- obj_pool.base_action_ruled = True
+ model_obj = self.pool.get(model)
+ if not hasattr(model_obj, 'base_action_ruled'):
+ model_obj.create = self._wrap_create(model_obj.create, model)
+ model_obj.write = self._wrap_write(model_obj.write, model)
+ model_obj.base_action_ruled = True
return True
def create(self, cr, uid, vals, context=None):
res_id = super(base_action_rule, self).create(cr, uid, vals, context=context)
- self._register_hook_(cr, uid, res_id,context=context)
+ self._register_hook(cr, [res_id])
return res_id
def write(self, cr, uid, ids, vals, context=None):
+ if isinstance(ids, (int, long)):
+ ids = [ids]
super(base_action_rule, self).write(cr, uid, ids, vals, context=context)
- self._register_hook_(cr, uid, ids, context=context)
+ self._register_hook(cr, ids)
return True
def _check(self, cr, uid, automatic=False, use_new_cursor=False, \
@@ -214,7 +204,6 @@ class base_action_rule(osv.osv):
This Function is call by scheduler.
"""
rule_ids = self.search(cr, uid, [], context=context)
- self._register_hook_(cr, uid, rule_ids, context=context)
if context is None:
context = {}
for rule in self.browse(cr, uid, rule_ids, context=context):
From 0228273ffb0e6bef4d8f1cbbf1b38d82508148cc Mon Sep 17 00:00:00 2001
From: Raphael Collet
Date: Wed, 19 Dec 2012 16:07:23 +0100
Subject: [PATCH 28/87] [FIX] base_action_rule: fix in wrapper method
bzr revid: rco@openerp.com-20121219150723-n25qqmczh3oau1cn
---
addons/base_action_rule/base_action_rule.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py
index 0017a8d0794..ed630248957 100644
--- a/addons/base_action_rule/base_action_rule.py
+++ b/addons/base_action_rule/base_action_rule.py
@@ -122,7 +122,7 @@ class base_action_rule(osv.osv):
Return a wrapper around `old_create` calling both `old_create` and
`post_action`, in that order.
"""
- def wrapper(cr, uid, vals, context=context):
+ def wrapper(cr, uid, vals, context=None):
if context is None:
context = {}
new_id = old_create(cr, uid, vals, context=context)
@@ -145,7 +145,7 @@ class base_action_rule(osv.osv):
Return a wrapper around `old_write` calling both `old_write` and
`post_action`, in that order.
"""
- def wrapper(cr, uid, ids, vals, context=context):
+ def wrapper(cr, uid, ids, vals, context=None):
old_records = {}
if context is None:
context = {}
From b0fd6e6ee744bb5a24739a60538b0f18ea5edada Mon Sep 17 00:00:00 2001
From: Raphael Collet
Date: Thu, 20 Dec 2012 10:09:19 +0100
Subject: [PATCH 29/87] [IMP] base_action_rule: tests
bzr revid: rco@openerp.com-20121220090919-eelugxam0kmyn7s2
---
.../tests/base_action_rule_test.py | 102 +++++++++---------
1 file changed, 49 insertions(+), 53 deletions(-)
diff --git a/addons/base_action_rule/tests/base_action_rule_test.py b/addons/base_action_rule/tests/base_action_rule_test.py
index ebefebe3ff4..b0f169b3bdb 100644
--- a/addons/base_action_rule/tests/base_action_rule_test.py
+++ b/addons/base_action_rule/tests/base_action_rule_test.py
@@ -10,6 +10,7 @@ class base_action_rule_test(common.TransactionCase):
cr, uid = self.cr, self.uid
self.demo = self.registry('ir.model.data').get_object(cr, uid, 'base', 'user_demo').id
self.admin = SUPERUSER_ID
+ self.model = self.registry('base.action.rule.lead.test')
def create_filter_done(self, cr, uid, context=None):
filter_pool = self.registry('ir.filters')
@@ -33,13 +34,12 @@ class base_action_rule_test(common.TransactionCase):
"""
Create a new lead_test
"""
- lead_pool = self.registry('base.action.rule.lead.test')
- return lead_pool.create(cr, uid, {
+ return self.model.create(cr, uid, {
'name': "Lead Test 1",
'user_id': self.admin,
}, context=context)
- def create_rule(self, cr, uid, filter_id=None, filter_pre_id=None, context=None):
+ def create_rule(self, cr, uid, filter_id=False, filter_pre_id=False, context=None):
"""
The "Rule 1" says that when a lead goes to the 'draft' state, the responsible for that lead changes to user "demo"
"""
@@ -59,26 +59,24 @@ class base_action_rule_test(common.TransactionCase):
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_pre_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, context=context))
+ filter_draft = self.create_filter_draft(cr, uid)
+ rule_1_id = self.create_rule(cr, uid, filter_pre_id=filter_draft)
+ new_lead_id = self.create_lead_test_1(cr, uid)
+ new_lead = self.model.browse(cr, uid, new_lead_id)
+ self.assertEquals(new_lead.state, 'draft')
+ self.assertEquals(new_lead.user_id.id, self.admin)
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_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, context=context))
+ filter_draft = self.create_filter_draft(cr, uid)
+ rule_1_id = self.create_rule(cr, uid, filter_id=filter_draft)
+ new_lead_id = self.create_lead_test_1(cr, uid)
+ new_lead = self.model.browse(cr, uid, new_lead_id)
+ self.assertEquals(new_lead.state, 'draft')
+ self.assertEquals(new_lead.user_id.id, self.demo)
def test_02_check_from_draft_to_done_with_steps(self):
"""
@@ -89,34 +87,33 @@ class base_action_rule_test(common.TransactionCase):
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_pre_id=filter_draft, filter_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, context=context))
+ filter_draft = self.create_filter_draft(cr, uid)
+ filter_done = self.create_filter_done(cr, uid)
+ self.create_rule(cr, uid, filter_pre_id=filter_draft, filter_id=filter_done)
+ new_lead_id = self.create_lead_test_1(cr, uid)
+ new_lead = self.model.browse(cr, uid, new_lead_id)
+ self.assertEquals(new_lead.state, 'draft')
+ self.assertEquals(new_lead.user_id.id, self.admin)
""" 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, context=context))
+ new_lead.write({'state': 'open'})
+ new_lead = self.model.browse(cr, uid, new_lead_id)
+ self.assertEquals(new_lead.state, 'open')
+ self.assertEquals(new_lead.user_id.id, self.admin)
""" 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, context=context))
+ new_lead.write({'state': 'pending'})
+ new_lead = self.model.browse(cr, uid, new_lead_id)
+ self.assertEquals(new_lead.state, 'pending')
+ self.assertEquals(new_lead.user_id.id, self.admin)
""" 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, context=context))
+ new_lead.write({'state': 'cancel'})
+ new_lead = self.model.browse(cr, uid, new_lead_id)
+ self.assertEquals(new_lead.state, 'cancel')
+ self.assertEquals(new_lead.user_id.id, self.admin)
""" 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, context=context))
+ new_lead.write({'state': 'done'})
+ new_lead = self.model.browse(cr, uid, new_lead_id)
+ self.assertEquals(new_lead.state, 'done')
+ self.assertEquals(new_lead.user_id.id, self.admin)
def test_02_check_from_draft_to_done_without_steps(self):
"""
@@ -127,16 +124,15 @@ class base_action_rule_test(common.TransactionCase):
The responsible in that test will change to user "demo"
"""
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_pre_id=filter_draft, filter_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, context=context))
+ filter_draft = self.create_filter_draft(cr, uid)
+ filter_done = self.create_filter_done(cr, uid)
+ self.create_rule(cr, uid, filter_pre_id=filter_draft, filter_id=filter_done)
+ new_lead_id = self.create_lead_test_1(cr, uid)
+ new_lead = self.model.browse(cr, uid, new_lead_id)
+ self.assertEquals(new_lead.state, 'draft')
+ self.assertEquals(new_lead.user_id.id, self.admin)
""" 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, context=context))
+ new_lead.write({'state': 'done'})
+ new_lead = self.model.browse(cr, uid, new_lead_id)
+ self.assertEquals(new_lead.state, 'done')
+ self.assertEquals(new_lead.user_id.id, self.demo)
From da4226d8072dfb46c8b4d7d3d00fda14096e146c Mon Sep 17 00:00:00 2001
From: Raphael Collet
Date: Thu, 20 Dec 2012 10:44:50 +0100
Subject: [PATCH 30/87] [FIX] base_action_rule: tests
bzr revid: rco@openerp.com-20121220094450-wwkk1beszml5y9z6
---
.../tests/base_action_rule_test.py | 17 +++++++++++++----
1 file changed, 13 insertions(+), 4 deletions(-)
diff --git a/addons/base_action_rule/tests/base_action_rule_test.py b/addons/base_action_rule/tests/base_action_rule_test.py
index b0f169b3bdb..cc679a6b1a2 100644
--- a/addons/base_action_rule/tests/base_action_rule_test.py
+++ b/addons/base_action_rule/tests/base_action_rule_test.py
@@ -11,6 +11,7 @@ class base_action_rule_test(common.TransactionCase):
self.demo = self.registry('ir.model.data').get_object(cr, uid, 'base', 'user_demo').id
self.admin = SUPERUSER_ID
self.model = self.registry('base.action.rule.lead.test')
+ self.base_action_rule = self.registry('base.action.rule')
def create_filter_done(self, cr, uid, context=None):
filter_pool = self.registry('ir.filters')
@@ -43,8 +44,7 @@ class base_action_rule_test(common.TransactionCase):
"""
The "Rule 1" says that when a lead goes to the 'draft' state, the responsible for that lead changes to user "demo"
"""
- self.action_pool = self.registry('base.action.rule')
- return self.action_pool.create(cr,uid,{
+ return self.base_action_rule.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,
@@ -54,17 +54,23 @@ class base_action_rule_test(common.TransactionCase):
'act_user_id': self.demo,
}, context=context)
+ def delete_rules(self, cr, uid, context=None):
+ """ delete all the rules on model 'base.action.rule.lead.test' """
+ action_ids = self.base_action_rule.search(cr, uid, [('model', '=', self.model._name)], context=context)
+ return self.base_action_rule.unlink(cr, uid, action_ids, 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
filter_draft = self.create_filter_draft(cr, uid)
- rule_1_id = self.create_rule(cr, uid, filter_pre_id=filter_draft)
+ self.create_rule(cr, uid, filter_pre_id=filter_draft)
new_lead_id = self.create_lead_test_1(cr, uid)
new_lead = self.model.browse(cr, uid, new_lead_id)
self.assertEquals(new_lead.state, 'draft')
self.assertEquals(new_lead.user_id.id, self.admin)
+ self.delete_rules(cr, uid)
def test_01_check_to_state_draft_post(self):
"""
@@ -72,11 +78,12 @@ class base_action_rule_test(common.TransactionCase):
"""
cr, uid = self.cr, self.uid
filter_draft = self.create_filter_draft(cr, uid)
- rule_1_id = self.create_rule(cr, uid, filter_id=filter_draft)
+ self.create_rule(cr, uid, filter_id=filter_draft)
new_lead_id = self.create_lead_test_1(cr, uid)
new_lead = self.model.browse(cr, uid, new_lead_id)
self.assertEquals(new_lead.state, 'draft')
self.assertEquals(new_lead.user_id.id, self.demo)
+ self.delete_rules(cr, uid)
def test_02_check_from_draft_to_done_with_steps(self):
"""
@@ -114,6 +121,7 @@ class base_action_rule_test(common.TransactionCase):
new_lead = self.model.browse(cr, uid, new_lead_id)
self.assertEquals(new_lead.state, 'done')
self.assertEquals(new_lead.user_id.id, self.admin)
+ self.delete_rules(cr, uid)
def test_02_check_from_draft_to_done_without_steps(self):
"""
@@ -136,3 +144,4 @@ class base_action_rule_test(common.TransactionCase):
new_lead = self.model.browse(cr, uid, new_lead_id)
self.assertEquals(new_lead.state, 'done')
self.assertEquals(new_lead.user_id.id, self.demo)
+ self.delete_rules(cr, uid)
From 40b1c95e1a0932f4d1f87bf9894a169b2c9e57ff Mon Sep 17 00:00:00 2001
From: Christophe Matthieu
Date: Thu, 20 Dec 2012 13:31:22 +0100
Subject: [PATCH 31/87] [IMP]mail: display checkbox for allow to add email
(from email_from value) for creating partner and send email to this new
partners.
bzr revid: chm@openerp.com-20121220123122-qsqn8w97t1tre3cq
---
addons/mail/mail_thread.py | 13 ++++--
addons/mail/res_users.py | 10 +++--
addons/mail/static/src/css/mail.css | 8 ++++
addons/mail/static/src/js/mail.js | 70 +++++++++++++++++++++++------
addons/mail/static/src/xml/mail.xml | 9 +++-
5 files changed, 88 insertions(+), 22 deletions(-)
diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py
index 48bdef508d0..cb07cb91a6f 100644
--- a/addons/mail/mail_thread.py
+++ b/addons/mail/mail_thread.py
@@ -749,8 +749,8 @@ class mail_thread(osv.AbstractModel):
return mail_message.create(cr, uid, values, context=context)
def message_post_user_api(self, cr, uid, thread_id, body='', subject=False, parent_id=False,
- attachment_ids=None, context=None, content_subtype='plaintext',
- extra_email=[], **kwargs):
+ attachment_ids=None, extra_email=[],
+ context=None, content_subtype='plaintext', **kwargs):
""" Wrapper on message_post, used for user input :
- mail gateway
- quick reply in Chatter (refer to mail.js), not
@@ -771,13 +771,20 @@ class mail_thread(osv.AbstractModel):
if content_subtype == 'plaintext':
body = tools.plaintext2html(body)
+ partner_ids = kwargs.pop('partner_ids', [])
+
for partner in extra_email:
part_ids = self.pool.get('res.partner').search(cr, uid, [('email', '=', partner)], context=context)
if not part_ids:
part_ids = [self.pool.get('res.partner').name_create(cr, uid, partner, context=context)[0]]
self.message_subscribe(cr, uid, [thread_id], part_ids, context=context)
- partner_ids = kwargs.pop('partner_ids', [])
+ message_ids = mail_message.search(cr, uid, [('email_from', '=', partner)], context=context)
+ if part_ids and message_ids:
+ mail_message.write(cr, uid, message_ids, {'email_from': None, 'author_id': part_ids[0]}, context=context)
+
+ partner_ids = set(partner_ids) + set(part_ids)
+
if parent_id:
parent_message = self.pool.get('mail.message').browse(cr, uid, parent_id, context=context)
partner_ids += [(4, partner.id) for partner in parent_message.partner_ids]
diff --git a/addons/mail/res_users.py b/addons/mail/res_users.py
index 4c6bf6e150d..24c9c185d4d 100644
--- a/addons/mail/res_users.py
+++ b/addons/mail/res_users.py
@@ -116,8 +116,10 @@ class res_users(osv.Model):
alias_pool.unlink(cr, uid, alias_ids, context=context)
return res
+
def message_post_user_api(self, cr, uid, thread_id, body='', subject=False, parent_id=False,
- attachment_ids=None, context=None, content_subtype='plaintext', **kwargs):
+ attachment_ids=None, extra_email=[],
+ context=None, content_subtype='plaintext', **kwargs):
""" Redirect the posting of message on res.users to the related partner.
This is done because when giving the context of Chatter on the
various mailboxes, we do not have access to the current partner_id.
@@ -128,8 +130,10 @@ class res_users(osv.Model):
if isinstance(thread_id, (list, tuple)):
thread_id = thread_id[0]
partner_id = self.browse(cr, uid, thread_id).partner_id.id
- return self.pool.get('res.partner').message_post_user_api(cr, uid, partner_id, body=body, subject=subject,
- parent_id=parent_id, attachment_ids=attachment_ids, context=context, content_subtype=content_subtype, **kwargs)
+ return self.pool.get('res.partner').message_post_user_api(cr, uid,
+ partner_id, body=body, subject=subject, parent_id=parent_id,
+ attachment_ids=attachment_ids, extra_email=extra_email,
+ context=context, content_subtype=content_subtype, **kwargs)
def message_post(self, cr, uid, thread_id, context=None, **kwargs):
""" Redirect the posting of message on res.users to the related partner.
diff --git a/addons/mail/static/src/css/mail.css b/addons/mail/static/src/css/mail.css
index e154e285f39..886199a668b 100644
--- a/addons/mail/static/src/css/mail.css
+++ b/addons/mail/static/src/css/mail.css
@@ -253,6 +253,14 @@
.openerp .oe_mail .oe_msg_composer .oe_msg_attachment_list{
display: block;
}
+.openerp .oe_mail .oe_msg_composer .oe_emails_from label{
+ vertical-align: middle;
+ display: block;
+ line-height: 16px;
+}
+.openerp .oe_mail .oe_msg_composer .oe_emails_from input{
+ vertical-align: middle;
+}
.openerp .oe_mail .oe_attachment{
display: inline-block;
width: 100px;
diff --git a/addons/mail/static/src/js/mail.js b/addons/mail/static/src/js/mail.js
index f873daff9c3..a770b07be17 100644
--- a/addons/mail/static/src/js/mail.js
+++ b/addons/mail/static/src/js/mail.js
@@ -369,6 +369,7 @@ openerp.mail = function (session) {
this._super(parent, datasets, options);
this.show_compact_message = false;
this.show_delete_attachment = true;
+ this.emails_from = [];
},
start: function () {
@@ -478,9 +479,11 @@ openerp.mail = function (session) {
this.$('.oe_cancel').on('click', _.bind( this.on_cancel, this) );
this.$('.oe_post').on('click', _.bind( this.on_message_post, this) );
this.$('.oe_full').on('click', _.bind( this.on_compose_fullmail, this, this.id ? 'reply' : 'comment') );
-
+
/* stack for don't close the compose form if the user click on a button */
- this.$('.oe_msg_footer').on('mousedown', _.bind( function () { this.stay_open = true; }, this));
+ this.$('.oe_msg_left, .oe_msg_center').on('mousedown', _.bind( function () { this.stay_open = true; }, this));
+ this.$('.oe_msg_left, .oe_msg_content').on('mouseup', _.bind( function () { this.$('textarea').focus(); }, this));
+
var ev_stay = {};
ev_stay.mouseup = ev_stay.keydown = ev_stay.focus = function () { self.stay_open = false; };
this.$('textarea:not(.oe_compact)').on(ev_stay);
@@ -491,6 +494,8 @@ openerp.mail = function (session) {
// event: delete child attachments off the oe_msg_attachment_list box
this.$(".oe_msg_attachment_list").on('click', '.oe_delete', this.on_attachment_delete);
+
+ this.$(".oe_emails_from").on('change', 'input', this.on_checked_email_from);
},
on_compose_fullmail: function (default_composition_mode) {
@@ -568,21 +573,23 @@ openerp.mail = function (session) {
if (this.do_check_attachment_upload() && (this.attachment_ids.length || body.match(/\S+/))) {
//session.web.blockUI();
- this.parent_thread.ds_thread.call('message_post_user_api', [
- this.context.default_res_id,
- body,
- false,
- this.context.default_parent_id,
- _.map(this.attachment_ids, function (file) {return file.id;}),
- this.parent_thread.context
- ]).done(function (record) {
+ var values = [
+ this.context.default_res_id, //thread_id
+ body, //body
+ false, //subject
+ this.context.default_parent_id, //parent_id
+ _.map(this.attachment_ids, function (file) {return file.id;}), //attachment_ids
+ _.map(_.filter(this.emails_from, function (f) {return f[1]}), function (f) {return f[0]}), //extra_email
+ this.parent_thread.context, // context
+ ];
+ this.parent_thread.ds_thread.call('message_post_user_api', values).done(function (record) {
var thread = self.parent_thread;
var root = thread == self.options.root_thread;
if (self.options.display_indented_thread < self.thread_level && thread.parent_message) {
var thread = thread.parent_message.parent_thread;
}
// create object and attach to the thread object
- thread.message_fetch([['id', 'child_of', [self.id]]], false, [record], function (arg, data) {
+ thread.message_fetch([["id", "=", record]], false, [record], function (arg, data) {
var message = thread.create_message_object( data[0] );
// insert the message on dom
thread.insert_message( message, root ? undefined : self.$el, root );
@@ -597,8 +604,8 @@ openerp.mail = function (session) {
/* convert the compact mode into the compose message
*/
on_compose_expandable: function (event) {
-
- if ((!this.stay_open || (event && event.type == 'click')) && (!this.show_composer || !this.$('textarea:not(.oe_compact)').val().match(/\S+/))) {
+ this.get_emails_from();
+ if ((!this.stay_open || (event && event.type == 'click')) && (!this.show_composer || !this.$('textarea:not(.oe_compact)').val().match(/\S+/) && !this.attachment_ids.length)) {
this.show_composer = !this.show_composer || this.stay_open;
this.reinit();
}
@@ -620,6 +627,41 @@ openerp.mail = function (session) {
if (!this.show_composer) {
this.reinit();
}
+ },
+
+ get_emails_from: function () {
+ var self = this;
+ var messages = [];
+
+ if (this.parent_thread.parent_message) {
+ // go to the parented message
+ var message = this.parent_thread.parent_message;
+ var parent_message = message.parent_id ? message.parent_thread.parent_message : message;
+ var messages = [parent_message].concat(parent_message.get_childs());
+ } else {
+ // get all wall messages
+ _.each(this.options.root_thread.messages, function (msg) {messages.push(msg); messages.concat(msg.get_childs());});
+ }
+
+ var emails_from = _.map(_.filter(messages,
+ function (thread) {return thread.author_id && !thread.author_id[0];}),
+ function (thread) {return thread.author_id[1];});
+
+ return _.each(emails_from, function (email_from) {
+ if (!_.find(self.emails_from, function (from) {return from[0] == email_from;})) {
+ self.emails_from.push([email_from, true]);
+ }
+ });
+ },
+
+ on_checked_email_from: function (event) {
+ var $input = $(event.target);
+ var email = $input.attr("data");
+ _.each(this.emails_from, function (email_from) {
+ if (email_from[0] == email) {
+ email_from[1] = $input.is(":checked");
+ }
+ });
}
});
@@ -1477,7 +1519,7 @@ openerp.mail = function (session) {
$(window).resize( _.bind(this.thread.on_scroll, this.thread) );
this.$el.resize( _.bind(this.thread.on_scroll, this.thread) );
window.setTimeout( _.bind(this.thread.on_scroll, this.thread), 500 );
- }
+ },
});
diff --git a/addons/mail/static/src/xml/mail.xml b/addons/mail/static/src/xml/mail.xml
index 3c510813029..168f32a9d7e 100644
--- a/addons/mail/static/src/xml/mail.xml
+++ b/addons/mail/static/src/xml/mail.xml
@@ -27,6 +27,11 @@
- Add this contact's email on recipient and follower :
-
+
From 84bef2751cc669e503746537891ec78224182349 Mon Sep 17 00:00:00 2001
From: "dle@openerp.com" <>
Date: Fri, 21 Dec 2012 13:44:41 +0100
Subject: [PATCH 64/87] [FIX]Wrong condition and message
bzr revid: dle@openerp.com-20121221124441-lklcl8b2bmp5wo27
---
addons/account/account_invoice.py | 3 ---
addons/account_voucher/account_voucher.py | 7 ++++---
2 files changed, 4 insertions(+), 6 deletions(-)
diff --git a/addons/account/account_invoice.py b/addons/account/account_invoice.py
index 62ba9c179da..af26d1f4aed 100644
--- a/addons/account/account_invoice.py
+++ b/addons/account/account_invoice.py
@@ -690,9 +690,6 @@ class account_invoice(osv.osv):
return super(account_invoice, self).copy(cr, uid, id, default, context)
def test_paid(self, cr, uid, ids, *args):
- for inv in self.browse(cr, uid, ids):
- if inv.amount_total == 0.0:
- return True
res = self.move_line_id_payment_get(cr, uid, ids)
if not res:
return False
diff --git a/addons/account_voucher/account_voucher.py b/addons/account_voucher/account_voucher.py
index 96a431e0a79..d95235b3803 100644
--- a/addons/account_voucher/account_voucher.py
+++ b/addons/account_voucher/account_voucher.py
@@ -26,6 +26,7 @@ from openerp import netsvc
from openerp.osv import fields, osv
import openerp.addons.decimal_precision as dp
from openerp.tools.translate import _
+from openerp.tools import float_compare
class res_company(osv.osv):
_inherit = "res.company"
@@ -1083,15 +1084,15 @@ class account_voucher(osv.osv):
ctx.update({'date': voucher_brw.date})
for line in voucher_brw.line_ids:
#create one move line per voucher line where amount is not 0.0
- if not line.amount:
+ if not line.amount and not float_compare(line.move_line_id.invoice.amount_total,0.0,precision_rounding=0.0):
continue
# convert the amount set on the voucher line into the currency of the voucher's company
amount = self._convert_amount(cr, uid, line.untax_amount or line.amount, voucher_brw.id, context=ctx)
# if the amount encoded in voucher is equal to the amount unreconciled, we need to compute the
# currency rate difference
if line.amount == line.amount_unreconciled:
- if not line.move_line_id.amount_residual:
- raise osv.except_osv(_('Wrong bank statement line'),_("You have to delete the bank statement line which the payment was reconciled to manually. Please check the payment of the partner %s by the amount of %s.")%(line.voucher_id.partner_id.name, line.voucher_id.amount))
+ if not line.move_line_id:
+ raise osv.except_osv(_('Wrong voucher line'),_("The invoice you are willing to pay is not valid anymore."))
sign = voucher_brw.type in ('payment', 'purchase') and -1 or 1
currency_rate_difference = sign * (line.move_line_id.amount_residual - amount)
else:
From e9f9e1191ca4e782d8014a4ba13a1feedf3fc4d7 Mon Sep 17 00:00:00 2001
From: "Quentin (OpenERP)"
Date: Fri, 21 Dec 2012 14:03:02 +0100
Subject: [PATCH 65/87] [FIX] account_voucher: bug fixed in the supplier
partial payment. There is no need to take care of the sign as the residual
amount of account.move.line is already taking care of it
bzr revid: qdp-launchpad@openerp.com-20121221130302-23v55ygcqekdlywi
---
addons/account_voucher/account_voucher.py | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/addons/account_voucher/account_voucher.py b/addons/account_voucher/account_voucher.py
index 4d510853660..842e50ebff1 100644
--- a/addons/account_voucher/account_voucher.py
+++ b/addons/account_voucher/account_voucher.py
@@ -599,12 +599,11 @@ class account_voucher(osv.osv):
This function returns True if the line is considered as noise and should not be displayed
"""
if line.reconcile_partial_id:
- sign = 1 if ttype == 'receipt' else -1
if currency_id == line.currency_id.id:
- if line.amount_residual_currency * sign <= 0:
+ if line.amount_residual_currency <= 0:
return True
else:
- if line.amount_residual * sign <= 0:
+ if line.amount_residual <= 0:
return True
return False
From 63d993e45d34e2250734b861d5900fa74cb75ae8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?=
Date: Fri, 21 Dec 2012 14:39:28 +0100
Subject: [PATCH 66/87] [CLEAN] invite wizard: removed unused method
onchange_partner_ids. Cleaned a bit code in invite_view.
bzr revid: tde@openerp.com-20121221133928-lytc76vbs0bdnhuz
---
addons/mail/wizard/invite.py | 11 -----------
addons/mail/wizard/invite_view.xml | 5 +++--
2 files changed, 3 insertions(+), 13 deletions(-)
diff --git a/addons/mail/wizard/invite.py b/addons/mail/wizard/invite.py
index 0b8b7bc7895..ee3053014af 100644
--- a/addons/mail/wizard/invite.py
+++ b/addons/mail/wizard/invite.py
@@ -49,17 +49,6 @@ class invite_wizard(osv.osv_memory):
'message': fields.html('Message'),
}
- def onchange_partner_ids(self, cr, uid, ids, value, context=None):
- """ onchange_partner_ids (value format: [[6, 0, [3, 4]]]). The
- basic purpose of this method is to check that destination partners
- effectively have email addresses. Otherwise a warning is thrown.
- """
- res = {'value': {}}
- if not value or not value[0] or not value[0][0] == 6:
- return
- res.update(self.pool.get('mail.message').check_partners_email(cr, uid, value[0][2], context=context))
- return res
-
def add_followers(self, cr, uid, ids, context=None):
for wizard in self.browse(cr, uid, ids, context=context):
model_obj = self.pool.get(wizard.res_model)
diff --git a/addons/mail/wizard/invite_view.xml b/addons/mail/wizard/invite_view.xml
index 65463529948..5b3b679ae9f 100644
--- a/addons/mail/wizard/invite_view.xml
+++ b/addons/mail/wizard/invite_view.xml
@@ -11,8 +11,9 @@
-
+
-
+
-
-
+
+
From 31d8bdaa3429ffdefe5278e9e17d638f2baf6171 Mon Sep 17 00:00:00 2001
From: "dle@openerp.com" <>
Date: Fri, 21 Dec 2012 15:36:37 +0100
Subject: [PATCH 71/87] [FIX]payment/receipt of amount 0, not just invoices : )
bzr revid: dle@openerp.com-20121221143637-wqmjg91g185s1val
---
addons/account_voucher/account_voucher.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/addons/account_voucher/account_voucher.py b/addons/account_voucher/account_voucher.py
index d95235b3803..50e458975a7 100644
--- a/addons/account_voucher/account_voucher.py
+++ b/addons/account_voucher/account_voucher.py
@@ -1082,9 +1082,11 @@ class account_voucher(osv.osv):
voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context)
ctx = context.copy()
ctx.update({'date': voucher_brw.date})
+ prec = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
for line in voucher_brw.line_ids:
#create one move line per voucher line where amount is not 0.0
- if not line.amount and not float_compare(line.move_line_id.invoice.amount_total,0.0,precision_rounding=0.0):
+ #The second part of the condition handle cases were payment/receipt has an amount of 0
+ if not line.amount and not (line.move_line_id and not float_compare(line.move_line_id.debit, line.move_line_id.credit, precision_rounding=prec) and not float_compare(line.move_line_id.debit, 0.0, precision_rounding=prec)):
continue
# convert the amount set on the voucher line into the currency of the voucher's company
amount = self._convert_amount(cr, uid, line.untax_amount or line.amount, voucher_brw.id, context=ctx)
From f922d9ea1f8124fe7785d8817397c7284e9ee0fb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?=
Date: Fri, 21 Dec 2012 15:40:14 +0100
Subject: [PATCH 72/87] [IMP] mail: refactored a bit the code in
message_post_user_api, as well as overrides in res_partner and res_users, to
use kwargs. Updated mail.js to use kwargs. Updated mail.xml and css to tweak
the display.
bzr revid: tde@openerp.com-20121221144014-otg03akq4c9grjmz
---
addons/mail/mail_thread.py | 55 +++++++++++++++--------------
addons/mail/res_partner.py | 6 ++--
addons/mail/res_users.py | 30 ++++++----------
addons/mail/static/src/css/mail.css | 1 +
addons/mail/static/src/js/mail.js | 22 +++++-------
addons/mail/static/src/xml/mail.xml | 21 +++++------
6 files changed, 60 insertions(+), 75 deletions(-)
diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py
index a62ac73101f..6925128f689 100644
--- a/addons/mail/mail_thread.py
+++ b/addons/mail/mail_thread.py
@@ -886,8 +886,8 @@ class mail_thread(osv.AbstractModel):
return mail_message.create(cr, uid, values, context=context)
- def message_post_user_api(self, cr, uid, thread_id, body='', subject=False, parent_id=False,
- attachment_ids=None, extra_email=[],
+ def message_post_user_api(self, cr, uid, thread_id, body='', subject=False,
+ parent_id=False, attachment_ids=None, extra_emails=None,
context=None, content_subtype='plaintext', **kwargs):
""" Wrapper on message_post, used for user input :
- mail gateway
@@ -902,40 +902,41 @@ class mail_thread(osv.AbstractModel):
to the related document. Should only be set by Chatter.
- extra_email: [ 'Fabien ', 'al@openerp.com' ]
"""
+ partner_obj = self.pool.get('res.partner')
+ mail_message_obj = self.pool.get('mail.message')
ir_attachment = self.pool.get('ir.attachment')
+ extra_emails = extra_emails or []
- # 1. Pre-processing: body, partner_ids, type and subtype
- if content_subtype == 'plaintext':
- body = tools.plaintext2html(body)
-
- new_partner_ids = set([])
- for partner in extra_email:
- part_ids = self.pool.get('res.partner').search(cr, uid, [('email', '=', partner)], context=context)
- # create a new partner if not exists
- if not part_ids:
- part_ids = [self.pool.get('res.partner').name_create(cr, uid, partner, context=dict())[0]]
+ # 1.A.1: pre-process partners and incoming extra_emails
+ partner_ids = set([])
+ for email in extra_emails:
+ partner_id = partner_obj.find_or_create(cr, uid, email, context=context)
# link mail with this from mail to the new partner id
- message_ids = mail_message.search(cr, uid, [('email_from', '=', partner)], context=context)
- if part_ids and message_ids:
- mail_message.write(cr, uid, message_ids, {'email_from': None, 'author_id': part_ids[0]}, context=context)
- new_partner_ids |= set(part_ids)
-
- if new_partner_ids:
- self.message_subscribe(cr, uid, [thread_id], list(new_partner_ids), context=context)
-
- partner_ids = set(kwargs.pop('partner_ids', [])) | set(new_partner_ids)
+ partner_msg_ids = mail_message_obj.search(cr, SUPERUSER_ID, [('email_from', '=', email), ('author_id', '=', False)], context=context)
+ if partner_id and partner_msg_ids:
+ mail_message_obj.write(cr, SUPERUSER_ID, partner_msg_ids, {'email_from': None, 'author_id': partner_id}, context=context)
+ partner_ids.add((4, partner_id))
+ if partner_ids:
+ self.message_subscribe(cr, uid, [thread_id], list(partner_ids), context=context)
+ # 1.A.2: add recipients of parent message
if parent_id:
- parent_message = self.pool.get('mail.message').browse(cr, uid, parent_id, context=context)
+ parent_message = mail_message_obj.browse(cr, uid, parent_id, context=context)
partner_ids |= set([(4, partner.id) for partner in parent_message.partner_ids])
# TDE FIXME HACK: mail.thread -> private message
if self._name == 'mail.thread' and parent_message.author_id.id:
partner_ids.add((4, parent_message.author_id.id))
+ # 1.A.3: add specified recipients
+ partner_ids |= set(kwargs.pop('partner_ids', []))
+
+ # 1.B: handle body, message_type and message_subtype
+ if content_subtype == 'plaintext':
+ body = tools.plaintext2html(body)
message_type = kwargs.pop('type', 'comment')
message_subtype = kwargs.pop('subtype', 'mail.mt_comment')
- # 2. Pre-processing: free attachments linked to the model
+ # 2. Pre-processing: attachments
# HACK TDE FIXME: Chatter: attachments linked to the document (not done JS-side), load the message
if attachment_ids:
# TDE FIXME (?): when posting a private message, we use mail.thread as a model
@@ -955,10 +956,10 @@ class mail_thread(osv.AbstractModel):
attachment_ids = []
# 3. Post message
- new_message_id = self.message_post(cr, uid, thread_id=thread_id, body=body, subject=subject, type=message_type,
- subtype=message_subtype, parent_id=parent_id, attachment_ids=[(4, id) for id in attachment_ids],
- context=context, partner_ids=partner_ids, **kwargs)
-
+ new_message_id = self.message_post(cr, uid, thread_id=thread_id, body=body, subject=subject,
+ type=message_type, subtype=message_subtype, parent_id=parent_id,
+ attachment_ids=[(4, id) for id in attachment_ids], partner_ids=partner_ids,
+ context=context, **kwargs)
return new_message_id
#------------------------------------------------------
diff --git a/addons/mail/res_partner.py b/addons/mail/res_partner.py
index cf9a2272a98..cfcb1106afc 100644
--- a/addons/mail/res_partner.py
+++ b/addons/mail/res_partner.py
@@ -42,8 +42,7 @@ class res_partner_mail(osv.Model):
'notification_email_send': lambda *args: 'comment'
}
- def message_post(self, cr, uid, thread_id, body='', subject=None, type='notification',
- subtype=None, parent_id=False, attachments=None, context=None, **kwargs):
+ def message_post(self, cr, uid, thread_id, **kwargs):
""" Override related to res.partner. In case of email message, set it as
private:
- add the target partner in the message partner_ids
@@ -58,7 +57,6 @@ class res_partner_mail(osv.Model):
partner_ids.append((4, thread_id))
kwargs['partner_ids'] = partner_ids
thread_id = False
- return super(res_partner_mail, self).message_post(cr, uid, thread_id, body=body, subject=subject,
- type=type, subtype=subtype, parent_id=parent_id, attachments=attachments, context=context, **kwargs)
+ return super(res_partner_mail, self).message_post(cr, uid, thread_id, **kwargs)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/mail/res_users.py b/addons/mail/res_users.py
index 24c9c185d4d..6c7fe712652 100644
--- a/addons/mail/res_users.py
+++ b/addons/mail/res_users.py
@@ -116,36 +116,28 @@ class res_users(osv.Model):
alias_pool.unlink(cr, uid, alias_ids, context=context)
return res
-
- def message_post_user_api(self, cr, uid, thread_id, body='', subject=False, parent_id=False,
- attachment_ids=None, extra_email=[],
- context=None, content_subtype='plaintext', **kwargs):
- """ Redirect the posting of message on res.users to the related partner.
- This is done because when giving the context of Chatter on the
- various mailboxes, we do not have access to the current partner_id.
- We therefore post on the user and redirect on its partner. """
+ def _message_post_get_pid(self, cr, uid, thread_id, context=None):
assert thread_id, "res.users does not support posting global messages"
if context and 'thread_model' in context:
context['thread_model'] = 'res.partner'
if isinstance(thread_id, (list, tuple)):
thread_id = thread_id[0]
- partner_id = self.browse(cr, uid, thread_id).partner_id.id
- return self.pool.get('res.partner').message_post_user_api(cr, uid,
- partner_id, body=body, subject=subject, parent_id=parent_id,
- attachment_ids=attachment_ids, extra_email=extra_email,
- context=context, content_subtype=content_subtype, **kwargs)
+ return self.browse(cr, uid, thread_id).partner_id.id
+
+ def message_post_user_api(self, cr, uid, thread_id, body='', context=None, **kwargs):
+ """ Redirect the posting of message on res.users to the related partner.
+ This is done because when giving the context of Chatter on the
+ various mailboxes, we do not have access to the current partner_id.
+ We therefore post on the user and redirect on its partner. """
+ partner_id = self._message_post_get_pid(cr, uid, thread_id, context=context)
+ return self.pool.get('res.partner').message_post_user_api(cr, uid, partner_id, **kwargs)
def message_post(self, cr, uid, thread_id, context=None, **kwargs):
""" Redirect the posting of message on res.users to the related partner.
This is done because when giving the context of Chatter on the
various mailboxes, we do not have access to the current partner_id.
We therefore post on the user and redirect on its partner. """
- assert thread_id, "res.users does not support posting global messages"
- if context and 'thread_model' in context:
- context['thread_model'] = 'res.partner'
- if isinstance(thread_id, (list, tuple)):
- thread_id = thread_id[0]
- partner_id = self.browse(cr, uid, thread_id).partner_id.id
+ partner_id = self._message_post_get_pid(cr, uid, thread_id, context=context)
return self.pool.get('res.partner').message_post(cr, uid, partner_id, context=context, **kwargs)
def message_update(self, cr, uid, ids, msg_dict, update_vals=None, context=None):
diff --git a/addons/mail/static/src/css/mail.css b/addons/mail/static/src/css/mail.css
index dbeb882d570..1efbcfcfcf1 100644
--- a/addons/mail/static/src/css/mail.css
+++ b/addons/mail/static/src/css/mail.css
@@ -256,6 +256,7 @@
.openerp .oe_mail .oe_msg_composer .oe_emails_from{
font-size: 12px;
margin-left: 20px;
+ margin-bottom: 2px;
}
.openerp .oe_mail .oe_msg_composer .oe_emails_from label{
vertical-align: middle;
diff --git a/addons/mail/static/src/js/mail.js b/addons/mail/static/src/js/mail.js
index 50d24b93ca4..f9ef9ef371b 100644
--- a/addons/mail/static/src/js/mail.js
+++ b/addons/mail/static/src/js/mail.js
@@ -609,17 +609,14 @@ openerp.mail = function (session) {
/*do post a message and fetch the message*/
do_send_message_post: function () {
var self = this;
- //session.web.blockUI();
- var values = [
- this.context.default_res_id, //thread_id
- this.$('textarea').val(), //body
- false, //subject
- this.context.default_parent_id, //parent_id
- _.map(this.attachment_ids, function (file) {return file.id;}), //attachment_ids
- _.map(_.filter(this.emails_from, function (f) {return f[1]}), function (f) {return f[0]}), //extra_email
- this.parent_thread.context, // context
- ];
- this.parent_thread.ds_thread.call('message_post_user_api', values).done(function (message_id) {
+ this.parent_thread.ds_thread._model.call('message_post_user_api', [this.context.default_res_id], {
+ 'body': this.$('textarea').val(),
+ 'subject': false,
+ 'parent_id': this.context.default_parent_id,
+ 'attachment_ids': _.map(this.attachment_ids, function (file) {return file.id;}),
+ 'extra_emails': _.map(_.filter(this.emails_from, function (f) {return f[1]}), function (f) {return f[0]}),
+ 'context': this.parent_thread.context,
+ }).done(function (message_id) {
var thread = self.parent_thread;
var root = thread == self.options.root_thread;
if (self.options.display_indented_thread < self.thread_level && thread.parent_message) {
@@ -632,7 +629,6 @@ openerp.mail = function (session) {
thread.insert_message( message, root ? undefined : self.$el, root );
});
self.on_cancel();
- //session.web.unblockUI();
});
},
@@ -1060,7 +1056,7 @@ openerp.mail = function (session) {
this.author_id = datasets.author_id || false;
this.thread_level = (datasets.thread_level+1) || 0;
this.partner_ids = datasets.partner_ids || [];
- if (datasets.author_id)
+ if (datasets.author_id && datasets.author_id[0])
this.partner_ids.push(datasets.author_id);
this.messages = [];
diff --git a/addons/mail/static/src/xml/mail.xml b/addons/mail/static/src/xml/mail.xml
index 41de6d6b40f..5401675b0f1 100644
--- a/addons/mail/static/src/xml/mail.xml
+++ b/addons/mail/static/src/xml/mail.xml
@@ -114,26 +114,23 @@
and
-
-
-
-
-
-
-
+
-
+
-
- , others...
+
+ , others...<<<
-
+ Select a filter or a timer as condition. An action rule is checked when you create or modify the "Related Document Model". The precondition filter is checked right before the modification while the postcondition filter is checked after the modification. A precondition filter will therefore not work during a creation.
+ To create a new filter:
+ - Go to your "Related Document Model" page and set the filter parameters in the "Search" view (Example of filter based on Leads/Opportunities: Creation Date "is equal to" 01/01/2012)
+ - In this same "Search" view, select the menu "Save Current Filter", enter the name (Ex: Create the 01/01/2012) and add the option "Share with all users"
+ The filter must therefore be available in this page.
+
-
+
@@ -63,24 +66,22 @@
-
+
-
-
-
- base.action.rule.tree
+
+
+ base.action.rule.treebase.action.rule
-
+
-
-
-
+
+
Automated Actionsbase.action.rule
@@ -103,6 +104,5 @@
-
From 9e580d355b5831086dfbc3677044d68eb3c6cf18 Mon Sep 17 00:00:00 2001
From: "Quentin (OpenERP)"
Date: Fri, 21 Dec 2012 15:49:45 +0100
Subject: [PATCH 74/87] [IMP] hr_expense: raise an error when no journal can be
found at the time of the expense accounting entries creation
bzr revid: qdp-launchpad@openerp.com-20121221144945-gy67wln705ewgkxw
---
addons/hr_expense/hr_expense.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/addons/hr_expense/hr_expense.py b/addons/hr_expense/hr_expense.py
index 6381c507f97..7557bb1abfe 100644
--- a/addons/hr_expense/hr_expense.py
+++ b/addons/hr_expense/hr_expense.py
@@ -168,6 +168,8 @@ class hr_expense_expense(osv.osv):
journal_id = voucher_obj._get_journal(cr, uid, context={'type': 'purchase', 'company_id': company_id})
if journal_id:
journal = account_journal.browse(cr, uid, journal_id, context=context)
+ if not journal:
+ raise osv.except_osv(_('Error!'), _("No expense journal found. Please make sure you have a journal with type 'purchase' configured."))
for line in exp.line_ids:
if line.product_id:
acc = line.product_id.property_account_expense
From 1ab87ddd677d230f254dcfe8075df1a655e3efc4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?=
Date: Fri, 21 Dec 2012 15:52:47 +0100
Subject: [PATCH 75/87] [CLEAN] Misc code cleaning.
bzr revid: tde@openerp.com-20121221145247-qqd1a1morv99ooow
---
addons/mail/mail_thread.py | 19 +++++++++----------
addons/mail/res_users.py | 6 ++----
2 files changed, 11 insertions(+), 14 deletions(-)
diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py
index 6925128f689..943e5a011ea 100644
--- a/addons/mail/mail_thread.py
+++ b/addons/mail/mail_thread.py
@@ -886,9 +886,9 @@ class mail_thread(osv.AbstractModel):
return mail_message.create(cr, uid, values, context=context)
- def message_post_user_api(self, cr, uid, thread_id, body='', subject=False,
- parent_id=False, attachment_ids=None, extra_emails=None,
- context=None, content_subtype='plaintext', **kwargs):
+ def message_post_user_api(self, cr, uid, thread_id, body='', parent_id=False,
+ attachment_ids=None, extra_emails=None, content_subtype='plaintext',
+ context=None, **kwargs):
""" Wrapper on message_post, used for user input :
- mail gateway
- quick reply in Chatter (refer to mail.js), not
@@ -933,8 +933,8 @@ class mail_thread(osv.AbstractModel):
# 1.B: handle body, message_type and message_subtype
if content_subtype == 'plaintext':
body = tools.plaintext2html(body)
- message_type = kwargs.pop('type', 'comment')
- message_subtype = kwargs.pop('subtype', 'mail.mt_comment')
+ msg_type = kwargs.pop('type', 'comment')
+ msg_subtype = kwargs.pop('subtype', 'mail.mt_comment')
# 2. Pre-processing: attachments
# HACK TDE FIXME: Chatter: attachments linked to the document (not done JS-side), load the message
@@ -954,13 +954,12 @@ class mail_thread(osv.AbstractModel):
ir_attachment.write(cr, SUPERUSER_ID, attachment_ids, {'res_model': model, 'res_id': thread_id}, context=context)
else:
attachment_ids = []
+ attachment_ids = [(4, id) for id in attachment_ids]
# 3. Post message
- new_message_id = self.message_post(cr, uid, thread_id=thread_id, body=body, subject=subject,
- type=message_type, subtype=message_subtype, parent_id=parent_id,
- attachment_ids=[(4, id) for id in attachment_ids], partner_ids=partner_ids,
- context=context, **kwargs)
- return new_message_id
+ return self.message_post(cr, uid, thread_id=thread_id, body=body,
+ type=msg_type, subtype=msg_subtype, parent_id=parent_id,
+ attachment_ids=attachment_ids, partner_ids=partner_ids, context=context, **kwargs)
#------------------------------------------------------
# Followers API
diff --git a/addons/mail/res_users.py b/addons/mail/res_users.py
index 6c7fe712652..4f87b9d51dc 100644
--- a/addons/mail/res_users.py
+++ b/addons/mail/res_users.py
@@ -127,16 +127,14 @@ class res_users(osv.Model):
def message_post_user_api(self, cr, uid, thread_id, body='', context=None, **kwargs):
""" Redirect the posting of message on res.users to the related partner.
This is done because when giving the context of Chatter on the
- various mailboxes, we do not have access to the current partner_id.
- We therefore post on the user and redirect on its partner. """
+ various mailboxes, we do not have access to the current partner_id. """
partner_id = self._message_post_get_pid(cr, uid, thread_id, context=context)
return self.pool.get('res.partner').message_post_user_api(cr, uid, partner_id, **kwargs)
def message_post(self, cr, uid, thread_id, context=None, **kwargs):
""" Redirect the posting of message on res.users to the related partner.
This is done because when giving the context of Chatter on the
- various mailboxes, we do not have access to the current partner_id.
- We therefore post on the user and redirect on its partner. """
+ various mailboxes, we do not have access to the current partner_id. """
partner_id = self._message_post_get_pid(cr, uid, thread_id, context=context)
return self.pool.get('res.partner').message_post(cr, uid, partner_id, context=context, **kwargs)
From 8ead65d82a47afde1d162597244a9a2089ee473c Mon Sep 17 00:00:00 2001
From: Raphael Collet
Date: Fri, 21 Dec 2012 16:29:14 +0100
Subject: [PATCH 76/87] [IMP] orm: fix vacuum of transient model
bzr revid: rco@openerp.com-20121221152914-qs1s9h1zwnyzft1m
---
openerp/osv/orm.py | 25 ++++++++++---------------
1 file changed, 10 insertions(+), 15 deletions(-)
diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py
index ca2a4fb6268..dfb4fdd0fa7 100644
--- a/openerp/osv/orm.py
+++ b/openerp/osv/orm.py
@@ -5064,24 +5064,20 @@ class BaseModel(object):
def _transient_clean_rows_older_than(self, cr, seconds):
assert self._transient, "Model %s is not transient, it cannot be vacuumed!" % self._name
- '''Never delete rows used in last 5 minutes'''
+ # Never delete rows used in last 5 minutes
seconds = max(seconds, 300)
- now_str = "(now() at time zone 'UTC')"
- cr.execute(
- "SELECT id FROM " + self._table + " WHERE"
- " COALESCE(write_date, create_date, " + now_str + ")::timestamp"
- "< (" + now_str + " - interval %s)", ("%s seconds" % seconds,))
+ query = ("SELECT id FROM " + self._table + " WHERE"
+ " COALESCE(write_date, create_date, (now() at time zone 'UTC'))::timestamp"
+ " < ((now() at time zone 'UTC') - interval %s)")
+ cr.execute(query, ("%s seconds" % seconds,))
ids = [x[0] for x in cr.fetchall()]
- if ids:
- self.unlink(cr, SUPERUSER_ID, ids)
+ self.unlink(cr, SUPERUSER_ID, ids)
def _transient_clean_old_rows(self, cr, max_count):
# Check how many rows we have in the table
- sql_statement = "SELECT count(*) as row_count FROM %s" % (self._table, )
- cr.execute(sql_statement)
+ cr.execute("SELECT count(*) AS row_count FROM " + self._table)
res = cr.fetchall()
- row_count = res[0][0]
- if row_count <= max_count:
+ if res[0][0] <= max_count:
return # max not reached, nothing to do
self._transient_clean_rows_older_than(cr, 300)
@@ -5103,10 +5099,9 @@ class BaseModel(object):
- the 10 rows that have been created/changed the last 5 minutes will NOT be deleted
"""
assert self._transient, "Model %s is not transient, it cannot be vacuumed!" % self._name
- _transient_check_time = 20 # arbitrary limit on vacuum executions
+ _transient_check_time = 20 # arbitrary limit on vacuum executions
self._transient_check_count += 1
- if ((not force)
- and (self._transient_check_count < _transient_check_time)):
+ if not force and (self._transient_check_count < _transient_check_time):
return True # no vacuum cleaning this time
self._transient_check_count = 0
From d28418f6e42a6dbaec81ef7d37e82baa5e8b11b6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?=
Date: Fri, 21 Dec 2012 16:29:56 +0100
Subject: [PATCH 77/87] [FIX] message_post_user_api: fixed subscribe.
bzr revid: tde@openerp.com-20121221152956-h400n3w8hgxleumm
---
addons/mail/mail_thread.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py
index 943e5a011ea..f9406699190 100644
--- a/addons/mail/mail_thread.py
+++ b/addons/mail/mail_thread.py
@@ -917,7 +917,7 @@ class mail_thread(osv.AbstractModel):
mail_message_obj.write(cr, SUPERUSER_ID, partner_msg_ids, {'email_from': None, 'author_id': partner_id}, context=context)
partner_ids.add((4, partner_id))
if partner_ids:
- self.message_subscribe(cr, uid, [thread_id], list(partner_ids), context=context)
+ self.message_subscribe(cr, uid, [thread_id], [item[1] for item in partner_ids], context=context)
# 1.A.2: add recipients of parent message
if parent_id:
From f9763f5d05166b91962a1a88e97b4a401349e3fd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?=
Date: Fri, 21 Dec 2012 16:30:07 +0100
Subject: [PATCH 78/87] [IMP] mail.js, xml: misc cleaning. I hate JS.
bzr revid: tde@openerp.com-20121221153007-5o220hdqg9sbt3f3
---
addons/mail/static/src/js/mail.js | 8 +++++---
addons/mail/static/src/xml/mail.xml | 4 +++-
2 files changed, 8 insertions(+), 4 deletions(-)
diff --git a/addons/mail/static/src/js/mail.js b/addons/mail/static/src/js/mail.js
index f9ef9ef371b..29a2e076b19 100644
--- a/addons/mail/static/src/js/mail.js
+++ b/addons/mail/static/src/js/mail.js
@@ -1055,9 +1055,11 @@ openerp.mail = function (session) {
this.is_private = datasets.is_private || false;
this.author_id = datasets.author_id || false;
this.thread_level = (datasets.thread_level+1) || 0;
- this.partner_ids = datasets.partner_ids || [];
- if (datasets.author_id && datasets.author_id[0])
- this.partner_ids.push(datasets.author_id);
+ datasets.partner_ids = datasets.partner_ids || [];
+ if (datasets.author_id && ! _.contains(datasets.partner_ids, datasets.author_id) && datasets.author_id[0]) {
+ datasets.partner_ids.push(datasets.author_id);
+ }
+ this.partner_ids = datasets.partner_ids;
this.messages = [];
this.options.flat_mode = !!(this.options.display_indented_thread > this.thread_level ? this.options.display_indented_thread - this.thread_level : 0);
diff --git a/addons/mail/static/src/xml/mail.xml b/addons/mail/static/src/xml/mail.xml
index 5401675b0f1..4b3c31525d4 100644
--- a/addons/mail/static/src/xml/mail.xml
+++ b/addons/mail/static/src/xml/mail.xml
@@ -114,11 +114,13 @@
and
+
-
+
+ , others...
From e29796f4264a08e2bf3d1a76106816002c139229 Mon Sep 17 00:00:00 2001
From: Antony Lesuisse
Date: Fri, 21 Dec 2012 16:46:06 +0100
Subject: [PATCH 79/87] packaging jinja2
bzr revid: al@openerp.com-20121221154606-jffr8i7ebs0n1n58
---
setup.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/setup.py b/setup.py
index 92f10d839f4..b144924d022 100755
--- a/setup.py
+++ b/setup.py
@@ -62,7 +62,7 @@ def py2exe_options():
"skip_archive": 1,
"optimize": 2,
"dist_dir": 'dist',
- "packages": [ "DAV", "HTMLParser", "PIL", "asynchat", "asyncore", "commands", "dateutil", "decimal", "docutils", "email", "encodings", "imaplib", "Jinja2", "lxml", "lxml._elementpath", "lxml.builder", "lxml.etree", "lxml.objectify", "mako", "openerp", "poplib", "pychart", "pydot", "pyparsing", "pytz", "reportlab", "select", "simplejson", "smtplib", "uuid", "vatnumber", "vobject", "xml", "xml.dom", "yaml", ],
+ "packages": [ "DAV", "HTMLParser", "PIL", "asynchat", "asyncore", "commands", "dateutil", "decimal", "docutils", "email", "encodings", "imaplib", "jinja2", "lxml", "lxml._elementpath", "lxml.builder", "lxml.etree", "lxml.objectify", "mako", "openerp", "poplib", "pychart", "pydot", "pyparsing", "pytz", "reportlab", "select", "simplejson", "smtplib", "uuid", "vatnumber", "vobject", "xml", "xml.dom", "yaml", ],
"excludes" : ["Tkconstants","Tkinter","tcl"],
}
}
From 94cfed4d178f194633b6c68ffbecc1db33a2d316 Mon Sep 17 00:00:00 2001
From: Olivier Dony
Date: Fri, 21 Dec 2012 16:44:26 +0100
Subject: [PATCH 80/87] [FIX] res.company: default company info position
incorrect in US Letter format
bzr revid: odo@openerp.com-20121221154426-4ligi2c9vwdht3a4
---
openerp/addons/base/res/res_company.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/openerp/addons/base/res/res_company.py b/openerp/addons/base/res/res_company.py
index 5917b76aee2..6b90a612fb9 100644
--- a/openerp/addons/base/res/res_company.py
+++ b/openerp/addons/base/res/res_company.py
@@ -345,7 +345,7 @@ class res_company(osv.osv):
"""
_header_a4 = _header_main % ('23.0cm', '27.6cm', '27.7cm', '27.7cm', '27.8cm', '27.4cm', '25.8cm', '26.0cm', '26.0cm', '25.6cm', '25.6cm', '25.5cm', '25.5cm')
- _header_letter = _header_main % ('21.3cm', '25.9cm', '26.0cm', '26.0cm', '26.1cm', '25.5cm', '25.1cm', '24.3cm', '24.3cm', '23.9cm', '23.9cm', '23.8cm', '23.8cm')
+ _header_letter = _header_main % ('21.3cm', '25.9cm', '26.0cm', '26.0cm', '26.1cm', '25.7cm', '24.1cm', '24.3cm', '24.3cm', '23.9cm', '23.9cm', '23.8cm', '23.8cm')
def onchange_paper_format(self, cr, uid, ids, paper_format, context=None):
if paper_format == 'us_letter':
From 65405e956c93286aaffd88e35a10a12481e8fbaa Mon Sep 17 00:00:00 2001
From: Olivier Dony
Date: Fri, 21 Dec 2012 16:49:06 +0100
Subject: [PATCH 81/87] [FIX] rml reports: no hardcoded template pageSize - is
set by company header/footer to A4 or US Letter
bzr revid: odo@openerp.com-20121221154906-urv7cxsudhmu8nl5
---
openerp/addons/base/module/report/ir_module_reference.rml | 2 +-
openerp/addons/base/report/custom_rml_printscreen.xsl | 2 +-
openerp/addons/base/report/preview_report.rml | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/openerp/addons/base/module/report/ir_module_reference.rml b/openerp/addons/base/module/report/ir_module_reference.rml
index 00030a10596..0fd3c492110 100644
--- a/openerp/addons/base/module/report/ir_module_reference.rml
+++ b/openerp/addons/base/module/report/ir_module_reference.rml
@@ -1,6 +1,6 @@
-
+
diff --git a/openerp/addons/base/report/custom_rml_printscreen.xsl b/openerp/addons/base/report/custom_rml_printscreen.xsl
index 38e3a207b6f..43e3fbecefe 100644
--- a/openerp/addons/base/report/custom_rml_printscreen.xsl
+++ b/openerp/addons/base/report/custom_rml_printscreen.xsl
@@ -6,7 +6,7 @@
-
+
diff --git a/openerp/addons/base/report/preview_report.rml b/openerp/addons/base/report/preview_report.rml
index f41319f2261..8643f04db75 100644
--- a/openerp/addons/base/report/preview_report.rml
+++ b/openerp/addons/base/report/preview_report.rml
@@ -1,6 +1,6 @@
-
+
From be6dd64e1e2192f12281f13e6f85af0d9bdf8387 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?=
Date: Fri, 21 Dec 2012 17:05:23 +0100
Subject: [PATCH 82/87] [FIX] Fixed argument of wrapper.
bzr revid: tde@openerp.com-20121221160523-q11rzna9jz7zspyu
---
addons/mail/res_users.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/addons/mail/res_users.py b/addons/mail/res_users.py
index 4f87b9d51dc..e58e97a3fd8 100644
--- a/addons/mail/res_users.py
+++ b/addons/mail/res_users.py
@@ -124,7 +124,7 @@ class res_users(osv.Model):
thread_id = thread_id[0]
return self.browse(cr, uid, thread_id).partner_id.id
- def message_post_user_api(self, cr, uid, thread_id, body='', context=None, **kwargs):
+ def message_post_user_api(self, cr, uid, thread_id, context=None, **kwargs):
""" Redirect the posting of message on res.users to the related partner.
This is done because when giving the context of Chatter on the
various mailboxes, we do not have access to the current partner_id. """
From dcd9f1d35e2e898cf7835b88d7adbbb7646cbc66 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?=
Date: Fri, 21 Dec 2012 17:11:59 +0100
Subject: [PATCH 83/87] [IMP] mail: fixed a forgottent context.
bzr revid: tde@openerp.com-20121221161159-267ezrwo3bux3879
---
addons/mail/res_users.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/addons/mail/res_users.py b/addons/mail/res_users.py
index e58e97a3fd8..160335fba2a 100644
--- a/addons/mail/res_users.py
+++ b/addons/mail/res_users.py
@@ -129,7 +129,7 @@ class res_users(osv.Model):
This is done because when giving the context of Chatter on the
various mailboxes, we do not have access to the current partner_id. """
partner_id = self._message_post_get_pid(cr, uid, thread_id, context=context)
- return self.pool.get('res.partner').message_post_user_api(cr, uid, partner_id, **kwargs)
+ return self.pool.get('res.partner').message_post_user_api(cr, uid, partner_id, context=context, **kwargs)
def message_post(self, cr, uid, thread_id, context=None, **kwargs):
""" Redirect the posting of message on res.users to the related partner.
From ca2855bb19e869fa1b9ec2fa4c43635204614d80 Mon Sep 17 00:00:00 2001
From: Fabien Pinckaers
Date: Fri, 21 Dec 2012 17:12:07 +0100
Subject: [PATCH 84/87] [IMP] simplified name_search on analytic
bzr revid: fp@tinyerp.com-20121221161207-doqu92tu4vxct6q6
---
addons/analytic/analytic.py | 13 +------------
1 file changed, 1 insertion(+), 12 deletions(-)
diff --git a/addons/analytic/analytic.py b/addons/analytic/analytic.py
index c9454c340f0..39754268a49 100644
--- a/addons/analytic/analytic.py
+++ b/addons/analytic/analytic.py
@@ -299,18 +299,7 @@ class account_analytic_account(osv.osv):
if name:
account_ids = self.search(cr, uid, [('code', '=', name)] + args, limit=limit, context=context)
if not account_ids:
- names=map(lambda i : i.strip(),name.split('/'))
- for i in range(len(names)):
- dom=[('name', operator, names[i])]
- if i>0:
- dom+=[('id','child_of',account_ids)]
- account_ids = self.search(cr, uid, dom, limit=limit, context=context)
- newacc = account_ids
- while newacc:
- newacc = self.search(cr, uid, [('parent_id', 'in', newacc)], limit=limit, context=context)
- account_ids += newacc
- if args:
- account_ids = self.search(cr, uid, [('id', 'in', account_ids)] + args, limit=limit, context=context)
+ account_ids = self.search(cr, uid, [('name', 'ilike', name)] + args, limit=limit, context=context)
else:
account_ids = self.search(cr, uid, args, limit=limit, context=context)
return self.name_get(cr, uid, account_ids, context=context)
From a8b88a2aec107455ae37ae738ebab8cd6a150974 Mon Sep 17 00:00:00 2001
From: Fabien Pinckaers
Date: Fri, 21 Dec 2012 17:30:03 +0100
Subject: [PATCH 85/87] [IMP] analytic account name_search improved
bzr revid: fp@tinyerp.com-20121221163003-7mvf3948rmuxwh4w
---
addons/analytic/analytic.py | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/addons/analytic/analytic.py b/addons/analytic/analytic.py
index 39754268a49..07552172d9b 100644
--- a/addons/analytic/analytic.py
+++ b/addons/analytic/analytic.py
@@ -299,7 +299,12 @@ class account_analytic_account(osv.osv):
if name:
account_ids = self.search(cr, uid, [('code', '=', name)] + args, limit=limit, context=context)
if not account_ids:
- account_ids = self.search(cr, uid, [('name', 'ilike', name)] + args, limit=limit, context=context)
+ dom = []
+ for name2 in name.split('/'):
+ name = name2.strip()
+ account_ids = self.search(cr, uid, dom + [('name', 'ilike', name)] + args, limit=limit, context=context)
+ if not account_ids: break
+ dom = [('parent_id','in',account_ids)]
else:
account_ids = self.search(cr, uid, args, limit=limit, context=context)
return self.name_get(cr, uid, account_ids, context=context)
From 940fc2fa858a938eab9c16a6a10ec2477db3da70 Mon Sep 17 00:00:00 2001
From: Raphael Collet
Date: Fri, 21 Dec 2012 17:33:59 +0100
Subject: [PATCH 86/87] [IMP] ir.actions.server: improve code of email actions
bzr revid: rco@openerp.com-20121221163359-bfv218b315r3z9wr
---
openerp/addons/base/ir/ir_actions.py | 30 +++++++++++++---------------
1 file changed, 14 insertions(+), 16 deletions(-)
diff --git a/openerp/addons/base/ir/ir_actions.py b/openerp/addons/base/ir/ir_actions.py
index 77eeb8d58f3..908dd86fa19 100644
--- a/openerp/addons/base/ir/ir_actions.py
+++ b/openerp/addons/base/ir/ir_actions.py
@@ -660,19 +660,6 @@ class actions_server(osv.osv):
if action.state == 'email':
email_from = config['email_from']
- address = str(action.email)
- try:
- address = eval(str(action.email), cxt)
- except:
- pass
-
- if not address:
- _logger.info('No partner email address specified, not sending any email.')
- continue
-
- if not isinstance(address, tuple) and not isinstance(address, list):
- address = [address]
-
if not email_from:
_logger.debug('--email-from command line option is not specified, using a fallback value instead.')
if user.user_email:
@@ -680,16 +667,27 @@ class actions_server(osv.osv):
else:
email_from = "%s@%s" % (user.login, gethostname())
+ try:
+ address = eval(str(action.email), cxt)
+ except:
+ address = str(action.email)
+
+ if not address:
+ _logger.info('No partner email address specified, not sending any email.')
+ continue
+
+ # handle single and multiple recipient addresses
+ addresses = address if isinstance(address, (tuple, list)) else [address]
subject = self.merge_message(cr, uid, action.subject, action, context)
body = self.merge_message(cr, uid, action.message, action, context)
ir_mail_server = self.pool.get('ir.mail_server')
- msg = ir_mail_server.build_email(email_from, address, subject, body)
+ msg = ir_mail_server.build_email(email_from, addresses, subject, body)
res_email = ir_mail_server.send_email(cr, uid, msg)
if res_email:
- _logger.info('Email successfully sent to: %s', address)
+ _logger.info('Email successfully sent to: %s', addresses)
else:
- _logger.warning('Failed to send email to: %s', address)
+ _logger.warning('Failed to send email to: %s', addresses)
if action.state == 'trigger':
wf_service = netsvc.LocalService("workflow")
From 22454532d1911c899862b6b4986dad3377065f92 Mon Sep 17 00:00:00 2001
From: Fabien Pinckaers
Date: Fri, 21 Dec 2012 17:42:55 +0100
Subject: [PATCH 87/87] [IMP] very small fix in contracts, always show sale
orders
bzr revid: fp@tinyerp.com-20121221164255-2mys887v0uee0f0z
---
.../account_analytic_analysis_view.xml | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/addons/account_analytic_analysis/account_analytic_analysis_view.xml b/addons/account_analytic_analysis/account_analytic_analysis_view.xml
index 1f1117aec1b..eaadbe0fa83 100644
--- a/addons/account_analytic_analysis/account_analytic_analysis_view.xml
+++ b/addons/account_analytic_analysis/account_analytic_analysis_view.xml
@@ -68,11 +68,12 @@
No order to invoice, create
+
+ />