[MERGE] from trunk
bzr revid: chm@openerp.com-20121221170432-6t90xtnxx27bw2ux
This commit is contained in:
commit
09dd7f22a0
|
@ -68,11 +68,12 @@
|
|||
|
||||
<span attrs="{'invisible': ['|',('fix_price_to_invoice','<>',0.0 ),('partner_id','=',False)]}" class="oe_grey">
|
||||
No order to invoice, create
|
||||
</span>
|
||||
<button name="%(action_sales_order)d" string="Sale Orders"
|
||||
type="action"
|
||||
class="oe_link"
|
||||
context="{'default_partner_id': [partner_id], 'search_default_partner_id': [partner_id],'search_default_project_id': [active_id],'default_project_id': [active_id]}"
|
||||
/></span>
|
||||
/>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td class="oe_timesheet_grey">
|
||||
|
|
|
@ -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"
|
||||
|
@ -599,12 +600,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
|
||||
|
||||
|
@ -1083,17 +1083,19 @@ 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 (second part of the clause) only if the original move line was not having debit = credit = 0 (which is a legal value)
|
||||
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)
|
||||
# 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:
|
||||
|
|
|
@ -299,18 +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:
|
||||
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)
|
||||
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)
|
||||
|
|
|
@ -20,5 +20,6 @@
|
|||
##############################################################################
|
||||
|
||||
import base_action_rule
|
||||
import test_models
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -19,27 +19,30 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
import re
|
||||
from datetime import datetime, timedelta
|
||||
import time
|
||||
import logging
|
||||
|
||||
from openerp.osv import fields, osv, orm
|
||||
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
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
def get_datetime(date_field):
|
||||
DATE_RANGE_FUNCTION = {
|
||||
'minutes': lambda interval: timedelta(minutes=interval),
|
||||
'hour': lambda interval: timedelta(hours=interval),
|
||||
'day': lambda interval: timedelta(days=interval),
|
||||
'month': lambda interval: timedelta(months=interval),
|
||||
False: lambda interval: timedelta(0),
|
||||
}
|
||||
|
||||
def get_datetime(date_str):
|
||||
'''Return a datetime from a date string or a datetime string'''
|
||||
#complete date time if date_field contains only a date
|
||||
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')
|
||||
# complete date time if date_str contains only a date
|
||||
if ' ' not in date_str:
|
||||
date_str = date_str + " 00:00:00"
|
||||
return datetime.strptime(date_str, DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
|
||||
|
||||
class base_action_rule(osv.osv):
|
||||
|
@ -48,320 +51,208 @@ class base_action_rule(osv.osv):
|
|||
_name = 'base.action.rule'
|
||||
_description = 'Action Rules'
|
||||
|
||||
def _state_get(self, cr, uid, context=None):
|
||||
""" Get State """
|
||||
return self.state_get(cr, uid, context=context)
|
||||
|
||||
def state_get(self, cr, uid, context=None):
|
||||
""" Get State """
|
||||
return [('', '')]
|
||||
|
||||
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)]),
|
||||
'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."),
|
||||
'trg_date_type': fields.selection([
|
||||
('none', 'None'),
|
||||
('create', 'Creation Date'),
|
||||
('write', 'Last Modified Date'),
|
||||
('action_last', 'Last Action Date'),
|
||||
('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'), \
|
||||
'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_id': fields.many2one('ir.model.fields', string='Trigger Date',
|
||||
domain="[('model_id', '=', model_id), ('ttype', 'in', ('date', 'datetime'))]"),
|
||||
'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'),
|
||||
'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),
|
||||
|
||||
'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', 'Filter', required=False), #TODO: set domain [('model_id','=',model_id.model)]
|
||||
'act_user_id': fields.many2one('res.users', 'Set Responsible'),
|
||||
'act_followers': fields.many2many("res.partner", string="Add Followers"),
|
||||
'server_action_ids': fields.many2many('ir.actions.server', string='Server Actions',
|
||||
domain="[('model_id', '=', model_id)]",
|
||||
help="Examples: email reminders, call object service, etc."),
|
||||
'filter_pre_id': fields.many2one('ir.filters', string='Before Update 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 Update 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),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'active': True,
|
||||
'trg_date_type': 'none',
|
||||
'trg_date_range_type': 'day',
|
||||
}
|
||||
|
||||
_order = 'sequence'
|
||||
|
||||
def _filter(self, cr, uid, action, action_filter, record_ids, context=None):
|
||||
""" filter the list record_ids that satisfy the action filter """
|
||||
if record_ids and action_filter:
|
||||
assert action.model == action_filter.model_id, "Filter model different from action rule model"
|
||||
model = self.pool.get(action_filter.model_id)
|
||||
domain = [('id', 'in', record_ids)] + eval(action_filter.domain)
|
||||
ctx = dict(context or {})
|
||||
ctx.update(eval(action_filter.context))
|
||||
record_ids = model.search(cr, uid, domain, context=ctx)
|
||||
return record_ids
|
||||
|
||||
def _process(self, cr, uid, action, record_ids, context=None):
|
||||
""" process the given action on the records """
|
||||
# execute server actions
|
||||
model = self.pool.get(action.model_id.model)
|
||||
if action.server_action_ids:
|
||||
server_action_ids = map(int, action.server_action_ids)
|
||||
for record in model.browse(cr, uid, record_ids, context):
|
||||
action_server_obj = self.pool.get('ir.actions.server')
|
||||
ctx = dict(context, active_model=model._name, active_ids=[record.id], active_id=record.id)
|
||||
action_server_obj.run(cr, uid, server_action_ids, context=ctx)
|
||||
|
||||
# modify records
|
||||
values = {}
|
||||
if 'date_action_last' in model._all_columns:
|
||||
values['date_action_last'] = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
if action.act_user_id and 'user_id' in model._all_columns:
|
||||
values['user_id'] = action.act_user_id.id
|
||||
if values:
|
||||
model.write(cr, uid, record_ids, values, context=context)
|
||||
|
||||
if action.act_followers and hasattr(model, 'message_subscribe'):
|
||||
follower_ids = map(int, action.act_followers)
|
||||
model.message_subscribe(cr, uid, record_ids, follower_ids, context=context)
|
||||
|
||||
def post_action(self, cr, uid, ids, model, 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) \
|
||||
WHERE active and model = %s", (model,))
|
||||
res = cr.fetchall()
|
||||
# Check if any rule matching with current object
|
||||
for obj_name, rule_id in res:
|
||||
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':
|
||||
self._action(cr, uid, [rule_id], obj.browse(cr, uid, ids, context=context), 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
|
||||
`_process`, in that order.
|
||||
"""
|
||||
Return a wrapper around `old_create` calling both `old_create` and
|
||||
`post_action`, in that order.
|
||||
"""
|
||||
def wrapper(cr, uid, vals, context=context):
|
||||
if context is None:
|
||||
context = {}
|
||||
def wrapper(cr, uid, vals, context=None):
|
||||
# avoid loops or cascading actions
|
||||
if context and context.get('action'):
|
||||
return old_create(cr, uid, vals, context=context)
|
||||
|
||||
context = dict(context or {}, action=True)
|
||||
new_id = old_create(cr, uid, vals, context=context)
|
||||
if not context.get('action'):
|
||||
self.post_action(cr, uid, [new_id], model, context=context)
|
||||
|
||||
# as it is a new record, we do not consider the actions that have a prefilter
|
||||
action_dom = [('model', '=', model), ('trg_date_id', '=', False), ('filter_pre_id', '=', False)]
|
||||
action_ids = self.search(cr, uid, action_dom, context=context)
|
||||
|
||||
# check postconditions, and execute actions on the records that satisfy them
|
||||
for action in self.browse(cr, uid, action_ids, context=context):
|
||||
if self._filter(cr, uid, action, action.filter_id, [new_id], context=context):
|
||||
self._process(cr, uid, action, [new_id], context=context)
|
||||
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
|
||||
`_process`, in that order.
|
||||
"""
|
||||
Return a wrapper around `old_write` calling both `old_write` and
|
||||
`post_action`, in that order.
|
||||
"""
|
||||
def wrapper(cr, uid, ids, vals, context=context):
|
||||
if context is None:
|
||||
context = {}
|
||||
if isinstance(ids, (str, int, long)):
|
||||
ids = [ids]
|
||||
def wrapper(cr, uid, ids, vals, context=None):
|
||||
# avoid loops or cascading actions
|
||||
if context and context.get('action'):
|
||||
return old_write(cr, uid, ids, vals, context=context)
|
||||
|
||||
context = dict(context or {}, action=True)
|
||||
ids = [ids] if isinstance(ids, (int, long, str)) else ids
|
||||
|
||||
# retrieve the action rules to possibly execute
|
||||
action_dom = [('model', '=', model), ('trg_date_id', '=', False)]
|
||||
action_ids = self.search(cr, uid, action_dom, context=context)
|
||||
actions = self.browse(cr, uid, action_ids, context=context)
|
||||
|
||||
# check preconditions
|
||||
pre_ids = {}
|
||||
for action in actions:
|
||||
pre_ids[action] = self._filter(cr, uid, action, action.filter_pre_id, ids, context=context)
|
||||
|
||||
# execute write
|
||||
old_write(cr, uid, ids, vals, context=context)
|
||||
if not context.get('action'):
|
||||
self.post_action(cr, uid, ids, model, context=context)
|
||||
|
||||
# check postconditions, and execute actions on the records that satisfy them
|
||||
for action in actions:
|
||||
post_ids = self._filter(cr, uid, action, action.filter_id, pre_ids[action], context=context)
|
||||
if post_ids:
|
||||
self._process(cr, uid, action, post_ids, context=context)
|
||||
return True
|
||||
|
||||
return wrapper
|
||||
|
||||
def _register_hook(self, cr, uid, ids, context=None):
|
||||
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 `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):
|
||||
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=context)
|
||||
obj_pool.write = self._write(obj_pool.write, model, context=context)
|
||||
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, \
|
||||
context=None):
|
||||
"""
|
||||
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):
|
||||
model = rule.model_id.model
|
||||
model_pool = self.pool.get(model)
|
||||
last_run = False
|
||||
if rule.last_run:
|
||||
last_run = get_datetime(rule.last_run)
|
||||
def _check(self, cr, uid, automatic=False, use_new_cursor=False, context=None):
|
||||
""" This Function is called by scheduler. """
|
||||
context = context or {}
|
||||
# retrieve all the action rules that have a trg_date_id and no precondition
|
||||
action_dom = [('trg_date_id', '!=', False), ('filter_pre_id', '=', False)]
|
||||
action_ids = self.search(cr, uid, action_dom, context=context)
|
||||
for action in self.browse(cr, uid, action_ids, context=context):
|
||||
now = datetime.now()
|
||||
ctx = dict(context)
|
||||
if rule.filter_id and rule.model_id.model == rule.filter_id.model_id:
|
||||
ctx.update(eval(rule.filter_id.context))
|
||||
obj_ids = model_pool.search(cr, uid, eval(rule.filter_id.domain), context=ctx)
|
||||
else:
|
||||
obj_ids = model_pool.search(cr, uid, [], context=ctx)
|
||||
for obj in model_pool.browse(cr, uid, obj_ids, context=ctx):
|
||||
# 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=='write' and hasattr(obj, 'write_date'):
|
||||
base = obj.write_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 base:
|
||||
fnct = {
|
||||
'minutes': lambda interval: timedelta(minutes=interval),
|
||||
'day': lambda interval: timedelta(days=interval),
|
||||
'hour': lambda interval: timedelta(hours=interval),
|
||||
'month': lambda interval: timedelta(months=interval),
|
||||
}
|
||||
base = get_datetime(base)
|
||||
delay = fnct[rule.trg_date_range_type](rule.trg_date_range)
|
||||
action_date = base + delay
|
||||
if (not last_run or (last_run <= action_date < now)):
|
||||
try:
|
||||
self._action(cr, uid, [rule.id], obj, context=ctx)
|
||||
self.write(cr, uid, [rule.id], {'last_run': now}, context=context)
|
||||
except Exception, e:
|
||||
import traceback
|
||||
print traceback.format_exc()
|
||||
|
||||
|
||||
last_run = get_datetime(action.last_run) if action.last_run else False
|
||||
|
||||
def do_check(self, cr, uid, action, obj, 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:
|
||||
# retrieve all the records that satisfy the action's condition
|
||||
model = self.pool.get(action.model_id.model)
|
||||
domain = []
|
||||
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)
|
||||
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 []))
|
||||
)
|
||||
)
|
||||
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
|
||||
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
|
||||
if action.filter_id:
|
||||
domain = eval(action.filter_id.domain)
|
||||
ctx.update(eval(action.filter_id.context))
|
||||
record_ids = model.search(cr, uid, domain, context=ctx)
|
||||
|
||||
def do_action(self, cr, uid, action, obj, context=None):
|
||||
""" Do Action """
|
||||
if context is None:
|
||||
context = {}
|
||||
ctx = dict(context)
|
||||
model_obj = self.pool.get(action.model_id.model)
|
||||
action_server_obj = self.pool.get('ir.actions.server')
|
||||
if action.server_action_ids:
|
||||
ctx.update({'active_model': action.model_id.model, 'active_id':obj.id, 'active_ids':[obj.id]})
|
||||
action_server_obj.run(cr, uid, [x.id for x in action.server_action_ids], context=ctx)
|
||||
# determine when action should occur for the records
|
||||
date_field = action.trg_date_id.name
|
||||
if date_field == 'date_action_last' and 'create_date' in model._all_columns:
|
||||
get_record_dt = lambda record: record[date_field] or record.create_date
|
||||
else:
|
||||
get_record_dt = lambda record: record[date_field]
|
||||
|
||||
write = {}
|
||||
if hasattr(obj, 'user_id') and action.act_user_id:
|
||||
write['user_id'] = action.act_user_id.id
|
||||
if hasattr(obj, 'date_action_last'):
|
||||
write['date_action_last'] = time.strftime('%Y-%m-%d %H:%M:%S')
|
||||
if hasattr(obj, 'state') and action.act_state:
|
||||
write['state'] = action.act_state
|
||||
delay = DATE_RANGE_FUNCTION[action.trg_date_range_type](action.trg_date_range)
|
||||
|
||||
model_obj.write(cr, uid, [obj.id], write, context)
|
||||
if hasattr(obj, 'state') and hasattr(obj, 'message_post') and action.act_state:
|
||||
model_obj.message_post(cr, uid, [obj], _(action.act_state), context=context)
|
||||
|
||||
if hasattr(obj, 'message_subscribe') and action.act_followers:
|
||||
exits_followers = [x.id for x in obj.message_follower_ids]
|
||||
new_followers = [x.id for x in action.act_followers if x.id not in exits_followers]
|
||||
if new_followers:
|
||||
model_obj.message_subscribe(cr, uid, [obj.id], new_followers, context=context)
|
||||
return True
|
||||
# process action on the records that should be executed
|
||||
for record in model.browse(cr, uid, record_ids, context=context):
|
||||
record_dt = get_record_dt(record)
|
||||
if not record_dt:
|
||||
continue
|
||||
action_dt = get_datetime(record_dt) + delay
|
||||
if last_run and (last_run <= action_dt < now) or (action_dt < now):
|
||||
try:
|
||||
self._process(cr, uid, action, [record.id], context=context)
|
||||
except Exception:
|
||||
import traceback
|
||||
_logger.error(traceback.format_exc())
|
||||
|
||||
def _action(self, cr, uid, ids, objects, scrit=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):
|
||||
self.do_action(cr, uid, action, obj, context=context)
|
||||
|
||||
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:
|
||||
action.write({'last_run': now.strftime(DEFAULT_SERVER_DATETIME_FORMAT)})
|
||||
|
|
|
@ -4,57 +4,55 @@
|
|||
<menuitem id="base.menu_base_action_rule_admin" name="Automated Actions"
|
||||
parent="base.menu_custom" />
|
||||
|
||||
<!--
|
||||
Action Rule Form View
|
||||
-->
|
||||
<record id="view_base_action_rule_form" model="ir.ui.view">
|
||||
<field name="name">base.action.rule.form</field>
|
||||
<!-- Action Rule Form View -->
|
||||
<record id="view_base_action_rule_form" model="ir.ui.view">
|
||||
<field name="name">base.action.rule.form</field>
|
||||
<field name="model">base.action.rule</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Action Rule" version="7.0">
|
||||
<sheet>
|
||||
<group col="4">
|
||||
<field name="name"/>
|
||||
<field name="model_id"/>
|
||||
<field name="filter_id" domain="[('model_id','=',model), ('user_id', '=', False)]" context="{'default_model_id': model}"/>
|
||||
<field name="sequence"/>
|
||||
<field name="active"/>
|
||||
<field name="model" invisible="1"/>
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1><field name="name"/></h1>
|
||||
<group>
|
||||
<group>
|
||||
<field name="model_id"/>
|
||||
<field name="model" invisible="1"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="active"/>
|
||||
<field name="sequence"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Conditions">
|
||||
<group>
|
||||
<group name="model" string="Conditions on Model Fields">
|
||||
<field name="regex_name"/>
|
||||
<field name="trg_user_id"/>
|
||||
<group name="filter" string="Filter Condition">
|
||||
<field name="filter_pre_id" domain="[('model_id','=',model), ('user_id', '=', False)]" context="{'default_model_id': model}"/>
|
||||
<field name="filter_id" domain="[('model_id','=',model), ('user_id', '=', False)]" context="{'default_model_id': model}"/>
|
||||
</group>
|
||||
<group name="partner" string="Conditions on Model Partner">
|
||||
<field name="trg_partner_id"/>
|
||||
<field name="trg_partner_categ_id"/>
|
||||
</group>
|
||||
<group name="state" string="Conditions on Status">
|
||||
<field name="trg_state_from"/>
|
||||
<field name="trg_state_to"/>
|
||||
</group>
|
||||
<group name="timing" string="Conditions on Timing">
|
||||
<field name="trg_date_type"/>
|
||||
<field name="trg_date_range" string="Delay After Trigger Date" attrs="{'invisible': [('trg_date_type', '=', 'none')]}"/>
|
||||
<field name="trg_date_range_type" attrs="{'invisible': [('trg_date_type', '=', 'none')]}"/>
|
||||
<group name="timing" string="Timer">
|
||||
<field name="trg_date_id"/>
|
||||
<label for="trg_date_range" string="Delay After Trigger Date" attrs="{'invisible': [('trg_date_id','=',False)]}"/>
|
||||
<div attrs="{'invisible': [('trg_date_id','=',False)]}">
|
||||
<field name="trg_date_range" class="oe_inline"/>
|
||||
<field name="trg_date_range_type" class="oe_inline" attrs="{'required': [('trg_date_id','!=',False)]}"/>
|
||||
</div>
|
||||
</group>
|
||||
</group>
|
||||
<group string="Note">
|
||||
<label string="The rule uses the AND operator. The model must match all non-empty fields so that the rule executes the action described in the 'Actions' tab." />
|
||||
</group>
|
||||
<p class="oe_grey">
|
||||
<b>Select a filter or a timer as condition.</b><br/> 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.<br/>
|
||||
<b>To create a new filter:</b><br/>
|
||||
- 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)<br/>
|
||||
- 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"<br/>
|
||||
The filter must therefore be available in this page.
|
||||
</p>
|
||||
</page>
|
||||
<page string="Actions">
|
||||
<group name="action_followers">
|
||||
<field name="act_followers" widget="many2many_tags"/>
|
||||
</group>
|
||||
<group name="action_field" col="4" string="Fields to Change">
|
||||
<field name="act_user_id"/>
|
||||
<field name="act_state"/>
|
||||
<field name="act_followers" widget="many2many_tags"/>
|
||||
</group>
|
||||
<group name="action_server" string="Server Actions to be Triggered (eg. Email Reminder, Call Object Method, etc...)" >
|
||||
<group name="action_server" string="Server actions to run">
|
||||
<field name="server_action_ids" domain="[('model_id','=', model_id)]" nolabel="1" context="{'default_model_id': model_id}">
|
||||
<tree string="Server Actions">
|
||||
<field name="sequence"/>
|
||||
|
@ -63,30 +61,27 @@
|
|||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
</record>
|
||||
|
||||
<!-- Action Rule Tree View -->
|
||||
|
||||
<record id="view_base_action_rule_tree" model="ir.ui.view">
|
||||
<field name="name">base.action.rule.tree</field>
|
||||
<!-- Action Rule Tree View -->
|
||||
<record id="view_base_action_rule_tree" model="ir.ui.view">
|
||||
<field name="name">base.action.rule.tree</field>
|
||||
<field name="model">base.action.rule</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Action Rule">
|
||||
<field name="sequence"/>
|
||||
<field name="name" colspan="4"/>
|
||||
<field name="name"/>
|
||||
<field name="filter_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Action Rule Action -->
|
||||
</record>
|
||||
|
||||
<!-- Action Rule Action -->
|
||||
<record id="base_action_rule_act" model="ir.actions.act_window">
|
||||
<field name="name">Automated Actions</field>
|
||||
<field name="res_model">base.action.rule</field>
|
||||
|
@ -109,6 +104,5 @@
|
|||
<menuitem id="menu_base_action_rule_form"
|
||||
parent="base.menu_base_action_rule_admin" action="base_action_rule_act" sequence="1"/>
|
||||
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -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
|
|
@ -0,0 +1,27 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (c) 2012-TODAY OpenERP S.A. <http://openerp.com>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
from . import base_action_rule_test
|
||||
|
||||
checks = [
|
||||
base_action_rule_test,
|
||||
]
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -0,0 +1,146 @@
|
|||
from openerp import SUPERUSER_ID
|
||||
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 = 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')
|
||||
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
|
||||
"""
|
||||
return self.model.create(cr, uid, {
|
||||
'name': "Lead Test 1",
|
||||
'user_id': self.admin,
|
||||
}, context=context)
|
||||
|
||||
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"
|
||||
"""
|
||||
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,
|
||||
'filter_pre_id' : filter_pre_id,
|
||||
'filter_id' : filter_id,
|
||||
'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)
|
||||
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):
|
||||
"""
|
||||
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
|
||||
filter_draft = self.create_filter_draft(cr, uid)
|
||||
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):
|
||||
"""
|
||||
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
|
||||
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'})
|
||||
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'})
|
||||
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'})
|
||||
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'})
|
||||
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):
|
||||
"""
|
||||
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 user "demo"
|
||||
"""
|
||||
cr, uid = self.cr, self.uid
|
||||
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'})
|
||||
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)
|
|
@ -262,28 +262,3 @@ class base_stage(object):
|
|||
if values_to_update:
|
||||
self.write(cr, uid, ids, values_to_update, context=context)
|
||||
return True
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
Must be overriden by inheriting classes.
|
||||
"""
|
||||
return True
|
||||
|
|
|
@ -106,7 +106,7 @@ class base_state(object):
|
|||
else:
|
||||
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)
|
||||
self._action(cr, uid, cases, 'escalate', context=context)
|
||||
case.case_escalate_send_note(parent_id, context=context)
|
||||
return True
|
||||
|
||||
def case_open(self, cr, uid, ids, context=None):
|
||||
|
@ -152,16 +152,50 @@ class base_state(object):
|
|||
if update_values is None:
|
||||
update_values = {}
|
||||
update_values['state'] = state_name
|
||||
res = self.write(cr, uid, ids, update_values, context=context)
|
||||
self._action(cr, uid, cases, state_name, context=context)
|
||||
return res
|
||||
return self.write(cr, uid, ids, update_values, context=context)
|
||||
|
||||
# ******************************
|
||||
# Notifications
|
||||
# ******************************
|
||||
|
||||
def case_get_note_msg_prefix(self, cr, uid, id, context=None):
|
||||
return ''
|
||||
|
||||
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)
|
||||
def case_open_send_note(self, cr, uid, ids, context=None):
|
||||
for id in ids:
|
||||
msg = _('%s has been <b>opened</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
|
||||
self.message_post(cr, uid, [id], body=msg, context=context)
|
||||
return True
|
||||
|
||||
def case_escalate_send_note(self, cr, uid, ids, new_section=None, context=None):
|
||||
for id in ids:
|
||||
if new_section:
|
||||
msg = '%s has been <b>escalated</b> to <b>%s</b>.' % (self.case_get_note_msg_prefix(cr, uid, id, context=context), new_section.name)
|
||||
else:
|
||||
msg = '%s has been <b>escalated</b>.' % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
|
||||
self.message_post(cr, uid, [id], body=msg, context=context)
|
||||
return True
|
||||
|
||||
def case_close_send_note(self, cr, uid, ids, context=None):
|
||||
for id in ids:
|
||||
msg = _('%s has been <b>closed</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
|
||||
self.message_post(cr, uid, [id], body=msg, context=context)
|
||||
return True
|
||||
|
||||
def case_cancel_send_note(self, cr, uid, ids, context=None):
|
||||
for id in ids:
|
||||
msg = _('%s has been <b>canceled</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
|
||||
self.message_post(cr, uid, [id], body=msg, context=context)
|
||||
return True
|
||||
|
||||
def case_pending_send_note(self, cr, uid, ids, context=None):
|
||||
for id in ids:
|
||||
msg = _('%s is now <b>pending</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
|
||||
self.message_post(cr, uid, [id], body=msg, context=context)
|
||||
return True
|
||||
|
||||
def case_reset_send_note(self, cr, uid, ids, context=None):
|
||||
for id in ids:
|
||||
msg = _('%s has been <b>renewed</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
|
||||
self.message_post(cr, uid, [id], body=msg, context=context)
|
||||
return True
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
##############################################################################
|
||||
|
||||
import crm
|
||||
import crm_action_rule
|
||||
import crm_segmentation
|
||||
import crm_lead
|
||||
import crm_meeting
|
||||
|
|
|
@ -77,7 +77,6 @@ Dashboard for CRM will include:
|
|||
|
||||
'crm_view.xml',
|
||||
|
||||
'crm_action_rule_view.xml',
|
||||
'crm_lead_view.xml',
|
||||
'crm_lead_menu.xml',
|
||||
|
||||
|
@ -108,7 +107,6 @@ Dashboard for CRM will include:
|
|||
'test/process/lead2opportunity_assign_salesmen.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',
|
||||
|
|
|
@ -1,96 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import re
|
||||
from openerp import tools
|
||||
|
||||
from openerp.tools.translate import _
|
||||
from openerp.tools import ustr
|
||||
from openerp.osv import fields
|
||||
from openerp.osv import osv
|
||||
|
||||
import crm
|
||||
|
||||
class base_action_rule(osv.osv):
|
||||
""" Base Action Rule """
|
||||
_inherit = 'base.action.rule'
|
||||
_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, context=None):
|
||||
ok = super(base_action_rule, self).do_check(cr, uid, action, obj, 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)
|
||||
write = {}
|
||||
if hasattr(action, 'act_section_id') and action.act_section_id:
|
||||
write['section_id'] = action.act_section_id.id
|
||||
|
||||
if hasattr(action, 'act_categ_id') and action.act_categ_id:
|
||||
write['categ_ids'] = [(4, action.act_categ_id.id)]
|
||||
|
||||
model_obj.write(cr, uid, [obj.id], write, context)
|
||||
return res
|
||||
|
||||
def state_get(self, cr, uid, context=None):
|
||||
"""Gets available states for crm"""
|
||||
res = super(base_action_rule, self).state_get(cr, uid, context=context)
|
||||
return res + crm.AVAILABLE_STATES
|
||||
|
||||
def priority_get(self, cr, uid, context=None):
|
||||
res = super(base_action_rule, self).priority_get(cr, uid, context=context)
|
||||
return res + crm.AVAILABLE_PRIORITIES
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -27,7 +27,7 @@ Description: [[object.description]]
|
|||
<field name="model_id" ref="model_crm_lead"/>
|
||||
<field name="sequence">1</field>
|
||||
<field name="filter_id" ref="filter_draft_lead"/>
|
||||
<field name="trg_date_type">create</field>
|
||||
<field name="trg_date_id" ref="field_crm_lead_create_date"/>
|
||||
<field name="trg_date_range">5</field>
|
||||
<field name="trg_date_range_type">day</field>
|
||||
<field name="server_action_ids" eval="[(6,0,[ref('action_email_reminder_lead')])]"/>
|
||||
|
@ -40,18 +40,27 @@ Description: [[object.description]]
|
|||
<field name="domain">[('country_id','=','United States')]</field>
|
||||
<field name="user_id" eval="False"/>
|
||||
</record>
|
||||
<record id="action_set_team_sales_department" model="ir.actions.server">
|
||||
<field name="name">Set team to Sales Department</field>
|
||||
<field name="model_id" ref="model_crm_lead"/>
|
||||
<field name="condition">True</field>
|
||||
<field name="type">ir.actions.server</field>
|
||||
<field name="state">code</field>
|
||||
<field name="code">sales_team = self.pool.get('ir.model.data').get_object(cr, uid, 'crm', 'section_sales_department')
|
||||
object.write({'section_id': sales_team.id})
|
||||
</field>
|
||||
</record>
|
||||
<record id="rule_set_followers_lead" model="base.action.rule">
|
||||
<field name="name">Set Auto Followers on leads which are urgent and come from USA.</field>
|
||||
<field name="model_id" ref="model_crm_lead"/>
|
||||
<field name="sequence">2</field>
|
||||
<field name="regex_name">urgent.*</field>
|
||||
<field name="filter_id" ref="filter_usa_lead"/>
|
||||
<field name="act_followers" eval="[(6,0,[ref('base.res_partner_4'), ref('base.res_partner_5'), ref('base.res_partner_6')])]"/>
|
||||
<field name="act_user_id" ref="base.user_root"/>
|
||||
<field name="act_section_id" ref="section_sales_department"/>
|
||||
<field name="trg_date_type">create</field>
|
||||
<field name="trg_date_id" ref="field_crm_lead_create_date"/>
|
||||
<field name="trg_date_range">0</field>
|
||||
<field name="trg_date_range_type">minutes</field>
|
||||
<field name="server_action_ids" eval="[(6,0,[ref('action_set_team_sales_department')])]"/>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<!-- Case rules -->
|
||||
<record id="view_base_action_rule_line_form1" model="ir.ui.view">
|
||||
<field name="name">base.action.rule.form.inherit</field>
|
||||
<field name="model">base.action.rule</field>
|
||||
<field name="inherit_id" ref="base_action_rule.view_base_action_rule_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//group[@name='partner']" position="after">
|
||||
<group name="case" string="Conditions on Case Fields">
|
||||
<field name="trg_section_id" widget="selection"/>
|
||||
<field name="trg_categ_id"/>
|
||||
</group>
|
||||
<group name="communication" string="Conditions on Communication History">
|
||||
<field name="regex_history"/>
|
||||
<field name="trg_max_history"/>
|
||||
</group>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='act_user_id']" position="after">
|
||||
<field name="act_section_id"/>
|
||||
<field name="act_categ_id"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
|
@ -30,7 +30,7 @@ access_res_partner,res.partner.crm.user,base.model_res_partner,base.group_sale_s
|
|||
access_res_partner_category,res.partner.category.crm.user,base.model_res_partner_category,base.group_sale_salesman,1,1,1,0
|
||||
mail_mailgate_thread,mail.thread,mail.model_mail_thread,base.group_sale_salesman,1,1,1,1
|
||||
access_crm_case_categ_manager,crm.case.categ manager,model_crm_case_categ,base.group_sale_manager,1,1,1,1
|
||||
access_base_action_rule_manager,base.action.rule manager,model_base_action_rule,base.group_sale_manager,1,1,1,1
|
||||
access_base_action_rule_manager,base.action.rule manager,base_action_rule.model_base_action_rule,base.group_sale_manager,1,1,1,1
|
||||
access_crm_lead_report_user,crm.lead.report user,model_crm_lead_report,base.group_sale_salesman,1,1,1,1
|
||||
access_res_partner_bank_type_crm_user,res.partner.bank.type.crm.user,base.model_res_partner_bank_type,base.group_sale_salesman,1,0,0,0
|
||||
access_crm_lead_partner_manager,crm.lead.partner.manager,model_crm_lead,base.group_partner_manager,1,0,0,0
|
||||
|
|
|
|
@ -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."
|
|
@ -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
|
||||
|
|
|
@ -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, context=None, content_subtype='plaintext',
|
||||
extra_email=[], **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
|
||||
|
@ -902,30 +902,41 @@ class mail_thread(osv.AbstractModel):
|
|||
to the related document. Should only be set by Chatter.
|
||||
- extra_email: [ 'Fabien <fpi@openerp.com>', '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)
|
||||
# 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
|
||||
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], [item[1] for item in partner_ids], context=context)
|
||||
|
||||
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', [])
|
||||
# 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)
|
||||
partner_ids += [(4, partner.id) for partner in parent_message.partner_ids]
|
||||
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.append((4, parent_message.author_id.id))
|
||||
partner_ids.add((4, parent_message.author_id.id))
|
||||
|
||||
message_type = kwargs.pop('type', 'comment')
|
||||
message_subtype = kwargs.pop('subtype', 'mail.mt_comment')
|
||||
# 1.A.3: add specified recipients
|
||||
partner_ids |= set(kwargs.pop('partner_ids', []))
|
||||
|
||||
# 2. Pre-processing: free attachments linked to the model
|
||||
# 1.B: handle body, message_type and message_subtype
|
||||
if content_subtype == 'plaintext':
|
||||
body = tools.plaintext2html(body)
|
||||
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
|
||||
if attachment_ids:
|
||||
# TDE FIXME (?): when posting a private message, we use mail.thread as a model
|
||||
|
@ -943,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],
|
||||
context=context, partner_ids=partner_ids, **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
|
||||
|
@ -969,7 +979,12 @@ class mail_thread(osv.AbstractModel):
|
|||
|
||||
def message_subscribe(self, cr, uid, ids, partner_ids, subtype_ids=None, context=None):
|
||||
""" Add partners to the records followers. """
|
||||
self.check_access_rights(cr, uid, 'read')
|
||||
user_pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
|
||||
if set(partner_ids) == set([user_pid]):
|
||||
self.check_access_rights(cr, uid, 'read')
|
||||
else:
|
||||
self.check_access_rights(cr, uid, 'write')
|
||||
|
||||
self.write(cr, SUPERUSER_ID, ids, {'message_follower_ids': [(4, pid) for pid in partner_ids]}, context=context)
|
||||
# if subtypes are not specified (and not set to a void list), fetch default ones
|
||||
if subtype_ids is None:
|
||||
|
@ -991,7 +1006,11 @@ class mail_thread(osv.AbstractModel):
|
|||
|
||||
def message_unsubscribe(self, cr, uid, ids, partner_ids, context=None):
|
||||
""" Remove partners from the records followers. """
|
||||
self.check_access_rights(cr, uid, 'read')
|
||||
user_pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
|
||||
if set(partner_ids) == set([user_pid]):
|
||||
self.check_access_rights(cr, uid, 'read')
|
||||
else:
|
||||
self.check_access_rights(cr, uid, 'write')
|
||||
return self.write(cr, SUPERUSER_ID, ids, {'message_follower_ids': [(3, pid) for pid in partner_ids]}, context=context)
|
||||
|
||||
def message_subscribe_from_parent(self, cr, uid, ids, updated_fields, context=None):
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -116,32 +116,26 @@ 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):
|
||||
""" 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, 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, 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. """
|
||||
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, 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.
|
||||
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
|
||||
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)
|
||||
|
||||
def message_update(self, cr, uid, ids, msg_dict, update_vals=None, context=None):
|
||||
|
|
|
@ -271,6 +271,19 @@
|
|||
.openerp .oe_mail .oe_msg_composer .oe_msg_attachment_list{
|
||||
display: block;
|
||||
}
|
||||
.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;
|
||||
display: block;
|
||||
line-height: 14px;
|
||||
}
|
||||
.openerp .oe_mail .oe_msg_composer .oe_emails_from input{
|
||||
vertical-align: middle;
|
||||
}
|
||||
.openerp .oe_mail .oe_attachment{
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
|
@ -483,8 +496,8 @@
|
|||
}
|
||||
.openerp .oe_mail .oe_msg_content .oe_mail_list_recipients{
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
margin-bottom: 4px;
|
||||
margin: 4px 0 4px 20px;
|
||||
text-indent: -20px;
|
||||
}
|
||||
|
||||
/* ---------------- HIDDEN MESSAGES ------------------ */
|
||||
|
@ -596,6 +609,14 @@
|
|||
margin-right:4px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.openerp .oe_followers .oe_remove_follower{
|
||||
cursor: pointer;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.openerp .oe_followers .oe_show_more{
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* ---------------- MESSAGES BODY ------------------ */
|
||||
.openerp .oe_mail .oe_msg_content .oe_blockquote,
|
||||
|
|
|
@ -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,9 @@ 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, .oe_mail_list_recipients').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').on(ev_stay);
|
||||
|
@ -491,6 +492,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) {
|
||||
|
@ -559,46 +562,79 @@ openerp.mail = function (session) {
|
|||
}
|
||||
},
|
||||
|
||||
/*post a message and fetch the message*/
|
||||
check_recipient_partners: function (emails) {
|
||||
var self = this;
|
||||
var deferreds = [];
|
||||
var ds_partner = new session.web.DataSetSearch(this, 'res.partner');
|
||||
_.each(emails, function (email) {
|
||||
ds_partner.call('search', [[['email', '=', email]]]).then(function (partner_ids) {
|
||||
if (!partner_ids.length) {
|
||||
var deferred = $.Deferred();
|
||||
var pop = new session.web.form.FormOpenPopup(this);
|
||||
pop.show_element(
|
||||
'res.partner',
|
||||
0,
|
||||
{
|
||||
'default_email': email,
|
||||
'force_email': true,
|
||||
'ref': "compound_context",
|
||||
},
|
||||
{
|
||||
title: _t("Please complete partner's informations"),
|
||||
}
|
||||
);
|
||||
pop.on('write_completed, closed', self, function () {
|
||||
deferred.resolve();
|
||||
});
|
||||
deferreds.push(deferred);
|
||||
}
|
||||
});
|
||||
});
|
||||
return $.when.apply( $, deferreds );
|
||||
},
|
||||
|
||||
on_message_post: function (event) {
|
||||
var self = this;
|
||||
|
||||
var comment_node = this.$('textarea');
|
||||
var body = comment_node.val();
|
||||
|
||||
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 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) {
|
||||
var message = thread.create_message_object( data[0] );
|
||||
// insert the message on dom
|
||||
thread.insert_message( message, root ? undefined : self.$el, root );
|
||||
});
|
||||
self.on_cancel();
|
||||
//session.web.unblockUI();
|
||||
});
|
||||
return true;
|
||||
if (this.do_check_attachment_upload() && (this.attachment_ids.length || this.$('textarea').val().match(/\S+/))) {
|
||||
// create list of new partners
|
||||
var extra_email = _.map(_.filter(this.emails_from, function (f) {return f[1]}), function (f) {return f[0]});
|
||||
this.check_recipient_partners(extra_email).done(function () {
|
||||
self.do_send_message_post();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/*do post a message and fetch the message*/
|
||||
do_send_message_post: function () {
|
||||
var self = this;
|
||||
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) {
|
||||
var thread = thread.parent_message.parent_thread;
|
||||
}
|
||||
// create object and attach to the thread object
|
||||
thread.message_fetch([["id", "=", message_id]], false, [message_id], 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 );
|
||||
});
|
||||
self.on_cancel();
|
||||
});
|
||||
},
|
||||
|
||||
/* 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 +656,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 if (this.options.emails_from_on_composer) {
|
||||
// get all wall messages if is not a mail.Wall
|
||||
_.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");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -982,9 +1053,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)
|
||||
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);
|
||||
|
@ -1450,6 +1523,7 @@ openerp.mail = function (session) {
|
|||
'message_ids': undefined,
|
||||
'compose_as_todo' : false,
|
||||
'readonly' : false,
|
||||
'emails_from_on_composer': true,
|
||||
}, this.action.params);
|
||||
|
||||
this.action.params.help = this.action.help || false;
|
||||
|
@ -1490,7 +1564,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 );
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
|
@ -1527,6 +1601,10 @@ openerp.mail = function (session) {
|
|||
}
|
||||
|
||||
this.domain = this.node.params && this.node.params.domain || [];
|
||||
|
||||
if (!this.__parentedParent.is_action_enabled('edit')) {
|
||||
this.node.params.show_link = false;
|
||||
}
|
||||
},
|
||||
|
||||
start: function () {
|
||||
|
@ -1615,6 +1693,7 @@ openerp.mail = function (session) {
|
|||
'show_record_name': true,
|
||||
'show_compact_message': this.action.params.view_mailbox ? false : 1,
|
||||
'view_inbox': false,
|
||||
'emails_from_on_composer': false,
|
||||
}, this.action.params);
|
||||
},
|
||||
|
||||
|
|
|
@ -26,12 +26,16 @@ openerp_mail_followers = function(session, mail) {
|
|||
this._super.apply(this, arguments);
|
||||
this.image = this.node.attrs.image || 'image_small';
|
||||
this.comment = this.node.attrs.help || false;
|
||||
this.displayed_nb = this.node.attrs.displayed_nb || 10;
|
||||
this.displayed_limit = this.node.attrs.displayed_nb || 10;
|
||||
this.displayed_nb = this.displayed_limit;
|
||||
this.ds_model = new session.web.DataSetSearch(this, this.view.model);
|
||||
this.ds_follow = new session.web.DataSetSearch(this, this.field.relation);
|
||||
this.ds_users = new session.web.DataSetSearch(this, 'res.users');
|
||||
|
||||
this.value = [];
|
||||
this.followers = [];
|
||||
|
||||
this.view_is_editable = this.__parentedParent.is_action_enabled('edit');
|
||||
},
|
||||
|
||||
start: function() {
|
||||
|
@ -69,29 +73,47 @@ openerp_mail_followers = function(session, mail) {
|
|||
// event: click on a subtype, that (un)subscribe for this subtype
|
||||
this.$el.on('click', '.oe_subtype_list input', self.do_update_subscription);
|
||||
// event: click on 'invite' button, that opens the invite wizard
|
||||
this.$('.oe_invite').on('click', function (event) {
|
||||
action = {
|
||||
type: 'ir.actions.act_window',
|
||||
res_model: 'mail.wizard.invite',
|
||||
view_mode: 'form',
|
||||
view_type: 'form',
|
||||
views: [[false, 'form']],
|
||||
target: 'new',
|
||||
context: {
|
||||
'default_res_model': self.view.dataset.model,
|
||||
'default_res_id': self.view.datarecord.id,
|
||||
},
|
||||
}
|
||||
self.do_action(action, {
|
||||
on_close: function() {
|
||||
self.read_value();
|
||||
},
|
||||
});
|
||||
this.$('.oe_invite').on('click', self.on_invite_follower);
|
||||
this.$el.on('click', '.oe_remove_follower', self.on_remove_follower);
|
||||
this.$el.on('click', '.oe_show_more', self.on_show_more_followers)
|
||||
},
|
||||
|
||||
on_invite_follower: function (event) {
|
||||
var self = this;
|
||||
var action = {
|
||||
type: 'ir.actions.act_window',
|
||||
res_model: 'mail.wizard.invite',
|
||||
view_mode: 'form',
|
||||
view_type: 'form',
|
||||
views: [[false, 'form']],
|
||||
target: 'new',
|
||||
context: {
|
||||
'default_res_model': this.view.dataset.model,
|
||||
'default_res_id': this.view.datarecord.id,
|
||||
},
|
||||
}
|
||||
this.do_action(action, {
|
||||
on_close: function() {
|
||||
self.read_value();
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
on_show_more_followers: function (event) {
|
||||
this.displayed_nb += this.displayed_limit;
|
||||
this.display_followers(false);
|
||||
},
|
||||
|
||||
on_remove_follower: function (event) {
|
||||
var partner_id = $(event.target).data('id');
|
||||
var context = new session.web.CompoundContext(this.build_context(), {});
|
||||
return this.ds_model.call('message_unsubscribe', [[this.view.datarecord.id], [partner_id], context])
|
||||
.then(this.proxy('read_value'));
|
||||
},
|
||||
|
||||
read_value: function () {
|
||||
var self = this;
|
||||
this.displayed_nb = this.displayed_limit;
|
||||
return this.ds_model.read_ids([this.view.datarecord.id], ['message_follower_ids']).then(function (results) {
|
||||
self.value = results[0].message_follower_ids;
|
||||
self.render_value();
|
||||
|
@ -145,20 +167,20 @@ openerp_mail_followers = function(session, mail) {
|
|||
/** Display the followers */
|
||||
display_followers: function (records) {
|
||||
var self = this;
|
||||
records = records || [];
|
||||
this.message_is_follower = this.set_is_follower(records);
|
||||
this.followers = records || this.followers;
|
||||
this.message_is_follower = this.set_is_follower(this.followers);
|
||||
// clean and display title
|
||||
var node_user_list = this.$('.oe_follower_list').empty();
|
||||
this.$('.oe_follower_title').html(this._format_followers(records.length));
|
||||
this.$('.oe_follower_title').html(this._format_followers(this.followers.length));
|
||||
// truncate number of displayed followers
|
||||
truncated = records.splice(0, this.displayed_nb);
|
||||
var truncated = this.followers.slice(0, this.displayed_nb);
|
||||
_(truncated).each(function (record) {
|
||||
record.avatar_url = mail.ChatterUtils.get_image(self.session, 'res.partner', 'image_small', record.id);
|
||||
$(session.web.qweb.render('mail.followers.partner', {'record': record})).appendTo(node_user_list);
|
||||
$(session.web.qweb.render('mail.followers.partner', {'record': record, 'widget': self})).appendTo(node_user_list);
|
||||
});
|
||||
// FVA note: be sure it is correctly translated
|
||||
if (truncated.length < records.length) {
|
||||
$('<div class="oe_partner">And ' + (records.length - truncated.length) + ' more.</div>').appendTo(node_user_list);
|
||||
if (truncated.length < this.followers.length) {
|
||||
$(session.web.qweb.render('mail.followers.show_more', {'number': (this.followers.length - truncated.length)} )).appendTo(node_user_list);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -196,18 +218,23 @@ openerp_mail_followers = function(session, mail) {
|
|||
var subtype_list_ul = this.$('.oe_subtype_list');
|
||||
subtype_list_ul.empty();
|
||||
var records = data[this.view.datarecord.id || this.view.dataset.ids[0]].message_subtype_data;
|
||||
_(records).each(function (record, record_name) {
|
||||
record.name = record_name;
|
||||
record.followed = record.followed || undefined;
|
||||
$(session.web.qweb.render('mail.followers.subtype', {'record': record})).appendTo( self.$('.oe_subtype_list') );
|
||||
});
|
||||
if (records.length > 1) {
|
||||
_(records).each(function (record, record_name) {
|
||||
record.name = record_name;
|
||||
record.followed = record.followed || undefined;
|
||||
$(session.web.qweb.render('mail.followers.subtype', {'record': record})).appendTo( self.$('.oe_subtype_list') );
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
do_follow: function () {
|
||||
_(this.$('.oe_msg_subtype_check')).each(function (record) {
|
||||
var context = new session.web.CompoundContext(this.build_context(), {});
|
||||
this.ds_model.call('message_subscribe_users', [[this.view.datarecord.id], [this.session.uid], undefined, context])
|
||||
.then(this.proxy('read_value'));
|
||||
|
||||
_.each(this.$('.oe_subtype_list input'), function (record) {
|
||||
$(record).attr('checked', 'checked');
|
||||
});
|
||||
this.do_update_subscription();
|
||||
},
|
||||
|
||||
do_unfollow: function () {
|
||||
|
@ -229,9 +256,13 @@ openerp_mail_followers = function(session, mail) {
|
|||
}
|
||||
});
|
||||
|
||||
var context = new session.web.CompoundContext(this.build_context(), {});
|
||||
return this.ds_model.call('message_subscribe_users', [[this.view.datarecord.id], [this.session.uid], this.message_is_follower ? checklist : undefined, context])
|
||||
.then(this.proxy('read_value'));
|
||||
if (!checklist.length) {
|
||||
this.do_unfollow();
|
||||
} else {
|
||||
var context = new session.web.CompoundContext(this.build_context(), {});
|
||||
return this.ds_model.call('message_subscribe_users', [[this.view.datarecord.id], [this.session.uid], checklist, context])
|
||||
.then(this.proxy('read_value'));
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
@ -81,7 +81,7 @@
|
|||
<img t-att-src="'/mail/static/src/img/mimetypes/' + attachment.filetype + '.png'"></img>
|
||||
<div class='oe_name'><t t-raw='attachment.name' /></div>
|
||||
</a>
|
||||
<div class='oe_delete oe_e' title="Delete this attachment" t-attf-data-id="{attachment.id}">[</div>
|
||||
<div class='oe_delete oe_e' title="Delete this attachment" t-att-data-id="attachment.id">[</div>
|
||||
<div class='oe_progress_bar'>
|
||||
uploading
|
||||
</div>
|
||||
|
@ -93,7 +93,7 @@
|
|||
<img t-att-src="widget.attachments_resize_image(attachment.id, [100,80])"></img>
|
||||
<div class='oe_name'><t t-raw='attachment.name' /></div>
|
||||
</a>
|
||||
<div class='oe_delete oe_e'>[</div>
|
||||
<div class='oe_delete oe_e' title="Delete this attachment" t-att-data-id="attachment.id">[</div>
|
||||
<div class='oe_progress_bar'>
|
||||
uploading
|
||||
</div>
|
||||
|
@ -115,19 +115,28 @@
|
|||
<t t-if="!widget.parent_thread.parent_message.record_name">this document</t>
|
||||
</span>
|
||||
</t>
|
||||
<t t-if="!widget.is_private and widget.partner_ids.length"> and </t>
|
||||
<t t-if="!widget.is_private and (widget.partner_ids.length or (widget.author_id and widget.author_id[0]))"> and </t>
|
||||
<t t-set="inc" t-value="0"/>
|
||||
<t t-if="widget.partner_ids.length" t-foreach="widget.partner_ids" t-as="partner">
|
||||
<t t-foreach="widget.partner_ids" t-as="partner">
|
||||
<span t-attf-class="oe_partner_follower #{inc>=3?'oe_hidden':''}"><t t-if="inc" t-raw="', '"/>
|
||||
<a t-if="widget.options.show_link" t-attf-href="#model=res.partner&id=#{partner[0]}"><t t-raw="partner[1]"/></a>
|
||||
<t t-if="!widget.options.show_link" t-raw="partner[1]"/>
|
||||
</span><t t-set="inc" t-value="inc+1"/>
|
||||
</span>
|
||||
<t t-set="inc" t-value="inc+1"/>
|
||||
</t>
|
||||
<t t-if="widget.partner_ids.length>=3">
|
||||
<span class="oe_more">, <a><t t-raw="widget.partner_ids.length-3"/> others...</a></span>
|
||||
<t t-if="widget.partner_ids.length > 3">
|
||||
<span class="oe_more">, <a><t t-raw="widget.partner_ids.length - 3"/> others...</a></span>
|
||||
<a class="oe_more_hidden"><<<</a>
|
||||
</t>
|
||||
</div>
|
||||
<div class="oe_emails_from">
|
||||
<t t-foreach='widget.emails_from' t-as='email_from'>
|
||||
<label title="Add them into recipients and followers">
|
||||
<input type="checkbox" t-att-checked="email_from[1] ? 'checked' : undefind" t-att-data="email_from[0]"/>
|
||||
<t t-raw="email_from[0]"/>
|
||||
</label>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<!--
|
||||
|
@ -234,8 +243,8 @@
|
|||
<t t-if="widget.attachment_ids.length > 0">
|
||||
<div class="oe_msg_attachment_list"></div>
|
||||
</t>
|
||||
<a t-if="widget.author_id and widget.options.show_link" t-attf-href="#model=res.partner&id=#{widget.author_id[0]}"><t t-raw="widget.author_id[1]"/></a>
|
||||
<span t-if="widget.author_id and !widget.options.show_link"><t t-raw="widget.author_id[1]"/></span>
|
||||
<a t-if="widget.author_id and widget.options.show_link and widget.author_id[0]" t-attf-href="#model=res.partner&id=#{widget.author_id[0]}"><t t-raw="widget.author_id[1]"/></a>
|
||||
<span t-if="widget.author_id and (!widget.options.show_link or !widget.author_id[0])"><t t-raw="widget.author_id[1]"/></span>
|
||||
<span class='oe_subtle'>•</span>
|
||||
<span t-att-title="widget.date"><t t-raw="widget.timerelative"/></span>
|
||||
<span t-if="!widget.options.readonly" class='oe_subtle'>•</span>
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<hr size="2"></hr>
|
||||
<div class='oe_follower_title_box'>
|
||||
<h4 class='oe_follower_title'>Followers</h4>
|
||||
<a href='#' class="oe_invite">Invite others</a>
|
||||
<a t-if="widget.view_is_editable" href='#' class="oe_invite">Add others</a>
|
||||
</div>
|
||||
<div class="oe_follower_list"></div>
|
||||
</div>
|
||||
|
@ -32,6 +32,7 @@
|
|||
<div t-name="mail.followers.partner" class='oe_partner'>
|
||||
<img class="oe_mail_thumbnail oe_mail_frame" t-attf-src="{record.avatar_url}"/>
|
||||
<a t-attf-href="#model=res.partner&id=#{record.id}"><t t-raw="record.name"/></a>
|
||||
<span t-if="widget.view_is_editable" class="oe_remove_follower oe_e" title="Remove this follower" t-att-data-id="record.id">X</span>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
|
@ -47,4 +48,8 @@
|
|||
</table>
|
||||
</t>
|
||||
|
||||
<t t-name="mail.followers.show_more">
|
||||
<div class="oe_partner oe_show_more">And <t t-raw="number"/> more.</div>
|
||||
</t>
|
||||
|
||||
</template>
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -11,8 +11,9 @@
|
|||
<group>
|
||||
<field name="res_model" invisible="1"/>
|
||||
<field name="res_id" invisible="1"/>
|
||||
<field name="partner_ids" widget="many2many_tags"
|
||||
on_change="onchange_partner_ids(partner_ids)" />
|
||||
<field name="partner_ids" widget="many2many_tags_email"
|
||||
placeholder="Add contacts to notify..."
|
||||
context="{'force_email':True}"/>
|
||||
<field name="message"/>
|
||||
</group>
|
||||
<footer>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
text-decoration: line-through;
|
||||
}
|
||||
.openerp .oe_form .oe_form_field.oe_memo {
|
||||
margin: 0 -16px 0 -16px;
|
||||
margin: 0;
|
||||
padding: 0px;
|
||||
width: 100%;
|
||||
min-height: 200px;
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
.oe_pad.oe_memo
|
||||
width: auto
|
||||
.oe_form_field.oe_memo
|
||||
margin: 0 -16px 0 -16px
|
||||
margin: 0
|
||||
padding: 0px
|
||||
width: 100%
|
||||
min-height: 200px
|
||||
|
|
Loading…
Reference in New Issue