diff --git a/addons/base_action_rule/__init__.py b/addons/base_action_rule/__init__.py new file mode 100644 index 00000000000..207ed3cb30f --- /dev/null +++ b/addons/base_action_rule/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). +# +# 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 . +# +############################################################################## + +import base_action_rule + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/base_action_rule/__terp__.py b/addons/base_action_rule/__terp__.py new file mode 100644 index 00000000000..ab1ae6c965b --- /dev/null +++ b/addons/base_action_rule/__terp__.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). +# +# 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 . +# +############################################################################## + +{ + 'name': 'Action Rule', + 'version': '1.0', + 'category': 'Generic Modules/Others', + 'description': "This module allows to implement action rules for any object.", + 'author': 'Tiny', + 'website': 'http://www.openerp.com', + 'depends': ['base'], + 'init_xml': [], + 'update_xml': [ + 'base_action_rule_view.xml', + 'security/ir.model.access.csv', + ], + 'demo_xml': [], + 'installable': True, + 'active': False, +} +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py new file mode 100644 index 00000000000..554f7a9edf3 --- /dev/null +++ b/addons/base_action_rule/base_action_rule.py @@ -0,0 +1,305 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). +# +# 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 . +# +############################################################################## + +import time +import mx.DateTime +import re + +import tools +from osv import fields, osv, orm +from osv.orm import except_orm +from tools.translate import _ + +class base_action_rule(osv.osv): + _name = 'base.action.rule' + _description = 'Action Rules' + + + _columns = { + 'name': fields.many2one('ir.model', 'Model', required=True), + 'max_level': fields.integer('Max Level', help='Specifies maximum level.'), + 'rule_lines': fields.one2many('base.action.rule.line','rule_id','Rule Lines'), + 'create_date': fields.datetime('Create Date', readonly=1), + 'active': fields.boolean('Active') + } + + _defaults = { + 'active': lambda *a: True, + 'max_level': lambda *a: 15, + } + + def format_body(self, body): + return body and tools.ustr(body) or '' + + def format_mail(self, obj, body): + data = { + 'object_id': obj.id, + 'object_subject': hasattr(obj, 'name') and obj.name or False, + 'object_date': hasattr(obj, 'date') and obj.date or False, + 'object_description': hasattr(obj, 'description') and obj.description or False, + 'object_user': hasattr(obj, 'user_id') and (obj.user_id and obj.user_id.name) or '/', + 'object_user_email': hasattr(obj, 'user_id') and (obj.user_id and obj.user_id.address_id and obj.user_id.address_id.email) or '/', + 'object_user_phone': hasattr(obj, 'user_id') and (obj.user_id and obj.user_id.address_id and obj.user_id.address_id.phone) or '/', + 'partner': hasattr(obj, 'partner_id') and (obj.partner_id and obj.partner_id.name) or '/', + 'partner_email': hasattr(obj, 'partner_address_id') and (obj.partner_address_id and obj.partner_address_id.email) or '/', + } + return format_body(body % data) + + def email_send(self, cr, uid, obj, emails, body, emailfrom=tools.config.get('email_from',False), context={}): + body = self.format_mail(obj, body) + if not emailfrom: + if hasattr(obj, 'user_id') and obj.user_id and obj.user_id.address_id and obj.user_id.address_id.email: + emailfrom = obj.user_id.address_id.email + + name = '[%d] %s' % (obj.id, tools.ustr(obj.name)) + emailfrom = tools.ustr(emailfrom) + reply_to = emailfrom + if not emailfrom: + raise osv.except_osv(_('Error!'), + _("No E-Mail ID Found for your Company address!")) + return tools.email_send(emailfrom, emails, name, body, reply_to=reply_to, openobject_id=str(obj.id)) + + + def do_check(self, cr, uid, action, obj, context={}): + ok = True + if hasattr(obj, 'user_id'): + ok = ok and (not action.trg_user_id.id or action.trg_user_id.id==obj.user_id.id) + if hasattr(obj, 'partner_id'): + 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) + if hasattr(obj, 'state'): + ok = ok and (not action.trg_state_from or action.trg_state_from==obj.state) + if state_to: + ok = ok and (not action.trg_state_to or action.trg_state_to==state_to) + + if hasattr(obj, 'priority'): + ok = ok and (not action.trg_priority_from or action.trg_priority_from>=obj.priority) + ok = ok and (not action.trg_priority_to or action.trg_priority_to<=obj.priority) + + reg_name = action.regex_name + result_name = True + if reg_name: + ptrn = re.compile(str(reg_name)) + _result = ptrn.search(str(obj.name)) + if not _result: + result_name = False + regex_n = not reg_name or result_name + ok = ok and regex_n + return ok + + def do_action(self, cr, uid, action, model_obj, obj, context={}): + if action.server_action_id: + context.update({'active_id':obj.id,'active_ids':[obj.id]}) + self.pool.get('ir.actions.server').run(cr, uid, [action.server_action_id.id], context) + write = {} + if hasattr(obj, 'user_id') and action.act_user_id: + obj.user_id = 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: + obj.state = action.act_state + write['state'] = action.act_state + + if hasattr(obj, 'priority') and action.act_priority: + obj.priority = action.act_priority + write['priority'] = action.act_priority + + model_obj.write(cr, uid, [obj.id], write, context) + + if hasattr(model_obj, 'remind_user') and action.act_remind_user: + model_obj.remind_user(cr, uid, [obj.id], context, attach=action.act_remind_attach) + if hasattr(model_obj, 'remind_partner') and action.act_remind_partner: + model_obj.remind_partner(cr, uid, [obj.id], context, attach=action.act_remind_attach) + if action.act_method: + getattr(model_obj, 'act_method')(cr, uid, [obj.id], action, context) + emails = [] + if hasattr(obj, 'user_id') and action.act_mail_to_user: + if obj.user_id and obj.user_id.address_id: + emails.append(obj.user_id.address_id.email) + + if action.act_mail_to_watchers: + emails += (action.act_email_cc or '').split(',') + if action.act_mail_to_email: + emails += (action.act_mail_to_email or '').split(',') + emails = filter(None, emails) + if len(emails) and action.act_mail_body: + emails = list(set(emails)) + self.email_send(cr, uid, obj, emails, action.act_mail_body) + return True + + def _action(self, cr, uid, ids, objects, scrit=None, context={}): + if not scrit: + scrit = [] + rule_line_obj = self.pool.get('base.action.rule.line') + for rule in self.browse(cr, uid, ids): + level = rule.max_level + if not level: + break + newactions = [] + scrit += [('rule_id','=',rule.id)] + line_ids = rule_line_obj.search(cr, uid, scrit) + actions = rule_line_obj.browse(cr, uid, line_ids, context=context) + model_obj = self.pool.get(rule.name.model) + for obj in objects: + for action in actions: + ok = self.do_check(cr, uid, action, obj, context=context) + if not ok: + continue + + base = False + if hasattr(obj, 'create_date') and action.trg_date_type=='create': + base = mx.DateTime.strptime(obj.create_date[:19], '%Y-%m-%d %H:%M:%S') + elif hasattr(obj, 'create_date') and action.trg_date_type=='action_last': + if hasattr(obj, 'date_action_last') and obj.date_action_last: + base = mx.DateTime.strptime(obj.date_action_last, '%Y-%m-%d %H:%M:%S') + else: + base = mx.DateTime.strptime(obj.create_date[:19], '%Y-%m-%d %H:%M:%S') + elif hasattr(obj, 'date_deadline') and action.trg_date_type=='deadline' and obj.date_deadline: + base = mx.DateTime.strptime(obj.date_deadline, '%Y-%m-%d %H:%M:%S') + elif hasattr(obj, 'date') and action.trg_date_type=='date' and obj.date: + base = mx.DateTime.strptime(obj.date, '%Y-%m-%d %H:%M:%S') + if base: + fnct = { + 'minutes': lambda interval: mx.DateTime.RelativeDateTime(minutes=interval), + 'day': lambda interval: mx.DateTime.RelativeDateTime(days=interval), + 'hour': lambda interval: mx.DateTime.RelativeDateTime(hours=interval), + 'month': lambda interval: mx.DateTime.RelativeDateTime(months=interval), + } + d = base + fnct[action.trg_date_range_type](action.trg_date_range) + dt = d.strftime('%Y-%m-%d %H:%M:%S') + ok = False + if hasattr(obj, 'date_action_last') and hasattr(obj, 'date_action_next'): + ok = (dt <= time.strftime('%Y-%m-%d %H:%M:%S')) and \ + ((not obj.date_action_next) or \ + (dt >= obj.date_action_next and \ + obj.date_action_last < obj.date_action_next)) + if not ok: + if not obj.date_action_next or dt < obj.date_action_next: + obj.date_action_next = dt + model_obj.write(cr, uid, [obj.id], {'date_action_next': dt}, context) + else: + ok = action.trg_date_type=='none' + + if ok: + self.do_action(cr, uid, action, model_obj, obj, context) + break + level -= 1 + return True +base_action_rule() + +class base_action_rule_line(osv.osv): + _name = 'base.action.rule.line' + _description = 'Action Rule Lines' + + def _state_get(self, cr, uid, context={}): + return self.state_get(cr, uid, context=context) + def _priority_get(self, cr, uid, context={}): + return self.priority_get(cr, uid, context=context) + + def state_get(self, cr, uid, context={}): + return [('','')] + def priority_get(self, cr, uid, context={}): + return [('','')] + + _columns = { + 'name': fields.char('Rule Name',size=64, required=True), + 'rule_id': fields.many2one('base.action.rule','Rule'), + 'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the rule without removing it."), + 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of rules."), + + 'trg_date_type': fields.selection([ + ('none','None'), + ('create','Creation Date'), + ('action_last','Last Action Date'), + ('date','Date'), + ], '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'),('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, 'State', size=16), + 'trg_state_to': fields.selection(_state_get, 'Button Pressed', size=16), + 'trg_priority_from': fields.selection(_priority_get, 'Minimum Priority'), + 'trg_priority_to': fields.selection(_priority_get, 'Maximum Priority'), + + 'act_method': fields.char('Call Object Method', size=64), + 'act_user_id': fields.many2one('res.users', 'Set responsible to'), + 'act_state': fields.selection(_state_get, 'Set state to', size=16), + 'act_priority': fields.selection(_priority_get, 'Set priority to'), + 'act_email_cc': fields.char('Add watchers (Cc)', size=250, help="These people will receive a copy of the future communication between partner and users by email"), + + 'act_remind_partner': fields.boolean('Remind Partner', help="Check this if you want the rule to send a reminder by email to the partner."), + 'act_remind_user': fields.boolean('Remind responsible', help="Check this if you want the rule to send a reminder by email to the user."), + 'act_reply_to': fields.char('Reply-To', size=64), + 'act_remind_attach': fields.boolean('Remind with attachment', help="Check this if you want that all documents attached to the object be attached to the reminder email sent."), + + 'act_mail_to_user': fields.boolean('Mail to responsible',help="Check this if you want the rule to send an email to the responsible person."), + 'act_mail_to_watchers': fields.boolean('Mail to watchers (CC)',help="Check this if you want the rule to mark CC(mail to any other person defined in actions)."), + 'act_mail_to_email': fields.char('Mail to these emails', size=128,help="Email-id of the persons whom mail is to be sent"), + 'act_mail_body': fields.text('Mail body',help="Content of mail"), + 'regex_name': fields.char('Regular Expression on Model Name', size=128), + 'server_action_id': fields.many2one('ir.actions.server','Server Action',help="Describes the action name." \ + "eg:on which object which action to be taken on basis of which condition"), + } + + _defaults = { + 'active': lambda *a: 1, + 'trg_date_type': lambda *a: 'none', + 'trg_date_range_type': lambda *a: 'day', + 'act_mail_to_user': lambda *a: 0, + 'act_remind_partner': lambda *a: 0, + 'act_remind_user': lambda *a: 0, + 'act_mail_to_watchers': lambda *a: 0, + } + + _order = 'sequence' + + + def _check_mail(self, cr, uid, ids, context=None): + empty = orm.browse_null() + rule_obj = self.pool.get('base.action.rule') + for rule in self.browse(cr, uid, ids): + if rule.act_mail_body: + try: + rule_obj.format_mail(empty, rule.act_mail_body) + except (ValueError, KeyError, TypeError): + return False + return True + + _constraints = [ + (_check_mail, 'Error: The mail is not well formated', ['act_mail_body']), + ] + +base_action_rule_line() +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/base_action_rule/base_action_rule_view.xml b/addons/base_action_rule/base_action_rule_view.xml new file mode 100644 index 00000000000..71c84e3eaaa --- /dev/null +++ b/addons/base_action_rule/base_action_rule_view.xml @@ -0,0 +1,130 @@ + + + + + + + + base.action.rule.form + base.action.rule + form + +
+ + + + + + + +
+ + + base.action.rule.tree + base.action.rule + tree + + + + + + + + + + Action Rules + base.action.rule + form + tree,form + + + + + + + base.action.rule.line.form + base.action.rule.line + form + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + base.action.rule.line.tree + base.action.rule.line + tree + + + + + + + +
+
diff --git a/addons/base_action_rule/security/ir.model.access.csv b/addons/base_action_rule/security/ir.model.access.csv new file mode 100644 index 00000000000..982dfdebfb5 --- /dev/null +++ b/addons/base_action_rule/security/ir.model.access.csv @@ -0,0 +1,3 @@ +"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" +"access_base_action_rule","base.action.rule","model_base_action_rule",,1,1,1,1 +"access_base_action_rule_line","base.action.rule.line","model_base_action_rule_line",,1,1,1,1 diff --git a/addons/base_calendar/base_calendar_view.xml b/addons/base_calendar/base_calendar_view.xml index 87dabe5c45b..7bdaaf2a88f 100644 --- a/addons/base_calendar/base_calendar_view.xml +++ b/addons/base_calendar/base_calendar_view.xml @@ -240,12 +240,12 @@ - + parent="base.menu_calendar_configuration" /> diff --git a/addons/caldav/__terp__.py b/addons/caldav/__terp__.py index 6178fcb3e1c..8bdb50bdf8c 100644 --- a/addons/caldav/__terp__.py +++ b/addons/caldav/__terp__.py @@ -24,7 +24,7 @@ "name" : "Share Calendar using Caldav", "version" : "1.0", "depends" : [ - "base_calendar", + "base", ], 'description': """ Contains basic functionality for caldav system like: diff --git a/addons/caldav/caldav_view.xml b/addons/caldav/caldav_view.xml index 598254f3cbb..a578ad560bd 100644 --- a/addons/caldav/caldav_view.xml +++ b/addons/caldav/caldav_view.xml @@ -50,10 +50,11 @@ tree,form - + diff --git a/addons/crm/__init__.py b/addons/crm/__init__.py index e1f19604f16..05d2d54113f 100644 --- a/addons/crm/__init__.py +++ b/addons/crm/__init__.py @@ -21,6 +21,7 @@ import crm import crm_mailgate +import crm_action_rule import crm_segmentation import crm_meeting import crm_lead diff --git a/addons/crm/__terp__.py b/addons/crm/__terp__.py index 5ea22566483..8302d67f0a9 100644 --- a/addons/crm/__terp__.py +++ b/addons/crm/__terp__.py @@ -44,7 +44,7 @@ The CRM module has a email gateway for the synchronisation interface between mails and Open ERP.""", 'author': 'Tiny', 'website': 'http://www.openerp.com', - 'depends': ['base', + 'depends': ['base', 'base_action_rule', 'process', 'mail_gateway', 'base_calendar', @@ -63,6 +63,7 @@ between mails and Open ERP.""", 'update_xml': [ 'crm_wizard.xml', 'crm_view.xml', + 'crm_action_rule_view.xml', 'crm_lead_wizard.xml', 'crm_lead_view.xml', 'crm_lead_menu.xml', diff --git a/addons/crm/crm.py b/addons/crm/crm.py index a0509e35c12..2b4075e4f7f 100644 --- a/addons/crm/crm.py +++ b/addons/crm/crm.py @@ -163,108 +163,6 @@ class crm_case_stage(osv.osv): crm_case_stage() - -class crm_case_rule(osv.osv): - _name = "crm.case.rule" - _description = "Case Rule" - _columns = { - 'name': fields.char('Rule Name',size=64, required=True), - 'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the case rule without removing it."), - 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of case rules."), - - 'trg_state_from': fields.selection([('',''),('escalate','Escalate')]+AVAILABLE_STATES, 'Case State', size=16), - 'trg_state_to': fields.selection([('',''),('escalate','Escalate')]+AVAILABLE_STATES, 'Button Pressed', size=16), - - 'trg_date_type': fields.selection([ - ('none','None'), - ('create','Creation Date'), - ('action_last','Last Action Date'), - ('deadline','Deadline'), - ('date','Date'), - ], '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'),('day','Days'),('month','Months')], 'Delay type'), - - 'trg_section_id': fields.many2one('crm.case.section', 'Section'), - - 'trg_categ_id': fields.many2one('crm.case.categ', 'Category', domain="[('section_id','=',trg_section_id)]"), - '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_priority_from': fields.selection([('','')] + AVAILABLE_PRIORITIES, 'Minimum Priority'), - 'trg_priority_to': fields.selection([('','')] + AVAILABLE_PRIORITIES, 'Maximim Priority'), - 'trg_max_history': fields.integer('Maximum Communication History'), - - 'act_method': fields.char('Call Object Method', size=64), - 'act_state': fields.selection([('','')]+AVAILABLE_STATES, 'Set state to', size=16), - 'act_section_id': fields.many2one('crm.case.section', 'Set section to'), - 'act_user_id': fields.many2one('res.users', 'Set responsible to'), - 'act_priority': fields.selection([('','')] + AVAILABLE_PRIORITIES, 'Set priority to'), - 'act_email_cc': fields.char('Add watchers (Cc)', size=250, help="These people will receive a copy of the future communication between partner and users by email"), - - 'act_remind_partner': fields.boolean('Remind Partner', help="Check this if you want the rule to send a reminder by email to the partner."), - 'act_remind_user': fields.boolean('Remind responsible', help="Check this if you want the rule to send a reminder by email to the user."), - 'act_remind_attach': fields.boolean('Remind with attachment', help="Check this if you want that all documents attached to the case be attached to the reminder email sent."), - - 'act_mail_to_user': fields.boolean('Mail to responsible',help="Check this if you want the rule to send an email to the responsible person."), - 'act_mail_to_partner': fields.boolean('Mail to partner',help="Check this if you want the rule to send an email to the partner."), - 'act_mail_to_watchers': fields.boolean('Mail to watchers (CC)',help="Check this if you want the rule to mark CC(mail to any other person defined in actions)."), - 'act_mail_to_email': fields.char('Mail to these emails', size=128,help="Email-id of the persons whom mail is to be sent"), - 'act_mail_body': fields.text('Mail body',help="Content of mail"), - 'regex_name' : fields.char('Regular Expression on Case Name', size=128), - 'regex_history' : fields.char('Regular Expression on Case History', size=128), - 'server_action_id' : fields.many2one('ir.actions.server','Server Action',help="Describes the action name." \ - "eg:on which object which ation to be taken on basis of which condition"), - } - _defaults = { - 'active': lambda *a: 1, - 'trg_date_type': lambda *a: 'none', - 'trg_date_range_type': lambda *a: 'day', - 'act_mail_to_user': lambda *a: 0, - 'act_remind_partner': lambda *a: 0, - 'act_remind_user': lambda *a: 0, - 'act_mail_to_partner': lambda *a: 0, - 'act_mail_to_watchers': lambda *a: 0, - } - _order = 'sequence' - - def _check(self, cr, uid, ids=False, context={}): - ''' - Function called by the scheduler to process cases for date actions - Only works on not done and cancelled cases - ''' - cr.execute('select * from crm_case \ - where (date_action_last<%s or date_action_last is null) \ - and (date_action_next<=%s or date_action_next is null) \ - and state not in (\'cancel\',\'done\')', - (time.strftime("%Y-%m-%d %H:%M:%S"), - time.strftime('%Y-%m-%d %H:%M:%S'))) - ids2 = map(lambda x: x[0], cr.fetchall() or []) - case_obj = self.pool.get('crm.case') - cases = case_obj.browse(cr, uid, ids2, context) - return case_obj._action(cr, uid, cases, False, context=context) - - - def _check_mail(self, cr, uid, ids, context=None): - caseobj = self.pool.get('crm.case') - emptycase = orm.browse_null() - for rule in self.browse(cr, uid, ids): - if rule.act_mail_body: - try: - caseobj.format_mail(emptycase, rule.act_mail_body) - except (ValueError, KeyError, TypeError): - return False - return True - - _constraints = [ - (_check_mail, 'Error: The mail is not well formated', ['act_mail_body']), - ] - -crm_case_rule() - def _links_get(self, cr, uid, context={}): obj = self.pool.get('res.request.link') ids = obj.search(cr, uid, []) @@ -335,9 +233,6 @@ class crm_case(osv.osv): \nIf the case is in progress the state is set to \'Open\'.\ \nWhen the case is over, the state is set to \'Done\'.\ \nIf the case needs to be reviewed then the state is set to \'Pending\'.'), - - 'date_action_last': fields.datetime('Last Action', readonly=1), - 'date_action_next': fields.datetime('Next Action', readonly=1), 'company_id': fields.many2one('res.company','Company'), } def _get_default_partner_address(self, cr, uid, context): @@ -427,164 +322,6 @@ class crm_case(osv.osv): value['email_from'] = case.email_from return {'value': value} - def _action(self, cr, uid, cases, state_to, scrit=None, context={}): - if not scrit: - scrit = [] - action_ids = self.pool.get('crm.case.rule').search(cr, uid, scrit) - level = MAX_LEVEL - while len(action_ids) and level: - newactions = [] - actions = self.pool.get('crm.case.rule').browse(cr, uid, action_ids, context) - for case in cases: - for action in actions: - ok = True - ok = ok and (not action.trg_state_from or action.trg_state_from==case.state) - ok = ok and (not action.trg_state_to or action.trg_state_to==state_to) - ok = ok and (not action.trg_section_id or action.trg_section_id.id==case.section_id.id) - ok = ok and (not action.trg_categ_id or action.trg_categ_id.id==case.categ_id.id) - ok = ok and (not action.trg_user_id.id or action.trg_user_id.id==case.user_id.id) - ok = ok and (not action.trg_partner_id.id or action.trg_partner_id.id==case.partner_id.id) - ok = ok and (not action.trg_max_history or action.trg_max_history<=(len(case.history_line)+1)) - ok = ok and ( - not action.trg_partner_categ_id.id or - ( - case.partner_id.id and - (action.trg_partner_categ_id.id in map(lambda x: x.id, case.partner_id.category_id or [])) - ) - ) - ok = ok and (not action.trg_priority_from or action.trg_priority_from>=case.priority) - ok = ok and (not action.trg_priority_to or action.trg_priority_to<=case.priority) - - reg_name = action.regex_name - result_name = True - if reg_name: - ptrn = re.compile(str(reg_name)) - _result = ptrn.search(str(case.name)) - if not _result: - result_name = False - regex_n = not reg_name or result_name - ok = ok and regex_n - - reg_history = action.regex_history - result_history = True - if reg_history: - ptrn = re.compile(str(reg_history)) - if case.history_line: - _result = ptrn.search(str(case.history_line[0].description)) - if not _result: - result_history = False - regex_h = not reg_history or result_history - ok = ok and regex_h - - if not ok: - continue - - base = False - if action.trg_date_type=='create': - base = mx.DateTime.strptime(case.create_date[:19], '%Y-%m-%d %H:%M:%S') - elif action.trg_date_type=='action_last': - if case.date_action_last: - base = mx.DateTime.strptime(case.date_action_last, '%Y-%m-%d %H:%M:%S') - else: - base = mx.DateTime.strptime(case.create_date[:19], '%Y-%m-%d %H:%M:%S') - elif action.trg_date_type=='deadline' and case.date_deadline: - base = mx.DateTime.strptime(case.date_deadline, '%Y-%m-%d %H:%M:%S') - elif action.trg_date_type=='date' and case.date: - base = mx.DateTime.strptime(case.date, '%Y-%m-%d %H:%M:%S') - if base: - fnct = { - 'minutes': lambda interval: mx.DateTime.RelativeDateTime(minutes=interval), - 'day': lambda interval: mx.DateTime.RelativeDateTime(days=interval), - 'hour': lambda interval: mx.DateTime.RelativeDateTime(hours=interval), - 'month': lambda interval: mx.DateTime.RelativeDateTime(months=interval), - } - d = base + fnct[action.trg_date_range_type](action.trg_date_range) - dt = d.strftime('%Y-%m-%d %H:%M:%S') - ok = (dt <= time.strftime('%Y-%m-%d %H:%M:%S')) and \ - ((not case.date_action_next) or \ - (dt >= case.date_action_next and \ - case.date_action_last < case.date_action_next)) - if not ok: - if not case.date_action_next or dt < case.date_action_next: - case.date_action_next = dt - self.write(cr, uid, [case.id], {'date_action_next': dt}, context) - - else: - ok = action.trg_date_type=='none' - - if ok: - if action.server_action_id: - context.update({'active_id':case.id,'active_ids':[case.id]}) - self.pool.get('ir.actions.server').run(cr, uid, [action.server_action_id.id], context) - write = {} - if action.act_state: - case.state = action.act_state - write['state'] = action.act_state - if action.act_section_id: - case.section_id = action.act_section_id - write['section_id'] = action.act_section_id.id - if action.act_user_id: - case.user_id = action.act_user_id - write['user_id'] = action.act_user_id.id - if action.act_priority: - case.priority = action.act_priority - write['priority'] = action.act_priority - if action.act_email_cc: - if '@' in (case.email_cc or ''): - emails = case.email_cc.split(",") - if action.act_email_cc not in emails:# and '<'+str(action.act_email_cc)+">" not in emails: - write['email_cc'] = case.email_cc+','+action.act_email_cc - else: - write['email_cc'] = action.act_email_cc - write['date_action_last'] = time.strftime('%Y-%m-%d %H:%M:%S') - self.write(cr, uid, [case.id], write, context) - caseobj = self.pool.get('crm.case') - if action.act_remind_user: - caseobj.remind_user(cr, uid, [case.id], context, attach=action.act_remind_attach) - if action.act_remind_partner: - caseobj.remind_partner(cr, uid, [case.id], context, attach=action.act_remind_attach) - if action.act_method: - getattr(caseobj, 'act_method')(cr, uid, [case.id], action, context) - emails = [] - if action.act_mail_to_user: - if case.user_id and case.user_id.address_id: - emails.append(case.user_id.address_id.email) - if action.act_mail_to_partner: - emails.append(case.email_from) - if action.act_mail_to_watchers: - emails += (action.act_email_cc or '').split(',') - if action.act_mail_to_email: - emails += (action.act_mail_to_email or '').split(',') - emails = filter(None, emails) - if len(emails) and action.act_mail_body: - emails = list(set(emails)) - self.email_send(cr, uid, case, emails, action.act_mail_body) - break - action_ids = newactions - level -= 1 - return True - - def format_body(self, body): - return body and tools.ustr(body) or '' - - def format_mail(self, case, body): - data = { - 'case_id': case.id, - 'case_subject': case.name, - 'case_date': case.date, - 'case_description': case.description, - - 'case_user': (case.user_id and case.user_id.name) or '/', - 'case_user_email': (case.user_id and case.user_id.address_id and case.user_id.address_id.email) or '/', - 'case_user_phone': (case.user_id and case.user_id.address_id and case.user_id.address_id.phone) or '/', - - 'email_from': case.email_from, - 'partner': (case.partner_id and case.partner_id.name) or '/', - 'partner_email': (case.partner_address_id and case.partner_address_id.email) or '/', - } - return self.format_body(body % data) - - def __history(self, cr, uid, cases, keyword, history=False, email=False, details=None, context={}): model_obj = self.pool.get('ir.model') for case in cases: @@ -615,50 +352,6 @@ class crm_case(osv.osv): self._action(cr,uid, cases, 'draft') return res - def remind_partner(self, cr, uid, ids, context={}, attach=False): - return self.remind_user(cr, uid, ids, context, attach, - destination=False) - - def remind_user(self, cr, uid, ids, context={}, attach=False, - destination=True): - for case in self.browse(cr, uid, ids): - if not case.section_id.reply_to: - raise osv.except_osv(_('Error!'), ("Reply To is not specified in Section")) - if not case.email_from: - raise osv.except_osv(_('Error!'), ("Partner Email is not specified in Case")) - if case.section_id.reply_to and case.email_from: - src = case.email_from - dest = case.section_id.reply_to - body = case.email_last or case.description - if not destination: - src, dest = dest, src - if case.user_id.signature: - body += '\n\n%s' % (case.user_id.signature or '') - dest = [dest] - - attach_to_send = None - - if attach: - attach_ids = self.pool.get('ir.attachment').search(cr, uid, [('res_model', '=', 'crm.case'), ('res_id', '=', case.id)]) - attach_to_send = self.pool.get('ir.attachment').read(cr, uid, attach_ids, ['datas_fname','datas']) - attach_to_send = map(lambda x: (x['datas_fname'], base64.decodestring(x['datas'])), attach_to_send) - - # Send an email - flag = tools.email_send( - src, - dest, - "Reminder: [%s] %s" % (str(case.id), case.name, ), - self.format_body(body), - reply_to=case.section_id.reply_to, - openobject_id=str(case.id), - attach=attach_to_send - ) - if flag: - raise osv.except_osv(_('Email!'),("Email Successfully Sent")) - else: - raise osv.except_osv(_('Email Fail!'),("Email is not sent successfully")) - return True - def add_reply(self, cursor, user, ids, context=None): for case in self.browse(cursor, user, ids, context=context): if case.email_last: @@ -757,7 +450,7 @@ class crm_case(osv.osv): self.write(cr, uid, ids, data) cases = self.browse(cr, uid, ids) self.__history(cr, uid, cases, _('Escalate')) - self._action(cr, uid, cases, 'escalate') + self._action(cr,uid, cases, 'escalate') return True @@ -794,8 +487,9 @@ class crm_case(osv.osv): cases[0].state # to fill the browse record cache self.__history(cr, uid, cases, _('Draft')) self.write(cr, uid, ids, {'state':'draft', 'active':True}) - self._action(cr, uid, cases, 'draft') - return True + self._action(cr,uid, cases, 'draft') + return True + crm_case() @@ -893,13 +587,6 @@ class crm_email_add_cc_wizard(osv.osv_memory): crm_email_add_cc_wizard() -def _section_get(self, cr, uid, context={}): - obj = self.pool.get('crm.case.section') - ids = obj.search(cr, uid, []) - res = obj.read(cr, uid, ids, ['id','name'], context) - res = [(str(r['id']),r['name']) for r in res] - return res - class users(osv.osv): _inherit = 'res.users' _description = "Users" @@ -907,6 +594,3 @@ class users(osv.osv): 'context_section_id': fields.many2one('crm.case.section', 'Sales Section'), } users() - - - diff --git a/addons/crm/crm_action_rule.py b/addons/crm/crm_action_rule.py new file mode 100644 index 00000000000..a07b7b77801 --- /dev/null +++ b/addons/crm/crm_action_rule.py @@ -0,0 +1,193 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). +# +# 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 . +# +############################################################################## + +import time +import re +import os +import base64 +import tools +import mx.DateTime + +from tools.translate import _ +from osv import fields +from osv import osv +from osv import orm +from osv.orm import except_orm + +import crm + +class case(osv.osv): + _inherit = 'crm.case' + _columns = { + 'date_action_last': fields.datetime('Last Action', readonly=1), + 'date_action_next': fields.datetime('Next Action', readonly=1), + } + + def remind_partner(self, cr, uid, ids, context={}, attach=False): + return self.remind_user(cr, uid, ids, context, attach, + destination=False) + + def remind_user(self, cr, uid, ids, context={}, attach=False, + destination=True): + for case in self.browse(cr, uid, ids): + if not case.section_id.reply_to: + raise osv.except_osv(_('Error!'), ("Reply To is not specified in Section")) + if not case.email_from: + raise osv.except_osv(_('Error!'), ("Partner Email is not specified in Case")) + if case.section_id.reply_to and case.email_from: + src = case.email_from + dest = case.section_id.reply_to + body = case.email_last or case.description + if not destination: + src, dest = dest, src + if case.user_id.signature: + body += '\n\n%s' % (case.user_id.signature or '') + dest = [dest] + + attach_to_send = None + + if attach: + attach_ids = self.pool.get('ir.attachment').search(cr, uid, [('res_model', '=', 'crm.case'), ('res_id', '=', case.id)]) + attach_to_send = self.pool.get('ir.attachment').read(cr, uid, attach_ids, ['datas_fname','datas']) + attach_to_send = map(lambda x: (x['datas_fname'], base64.decodestring(x['datas'])), attach_to_send) + + # Send an email + flag = tools.email_send( + src, + dest, + "Reminder: [%s] %s" % (str(case.id), case.name, ), + self.format_body(body), + reply_to=case.section_id.reply_to, + openobject_id=str(case.id), + attach=attach_to_send + ) + if flag: + raise osv.except_osv(_('Email!'),("Email Successfully Sent")) + else: + raise osv.except_osv(_('Email Fail!'),("Email is not sent successfully")) + return True + + def _check(self, cr, uid, ids=False, context={}): + ''' + Function called by the scheduler to process cases for date actions + Only works on not done and cancelled cases + ''' + cr.execute('select * from crm_case \ + where (date_action_last<%s or date_action_last is null) \ + and (date_action_next<=%s or date_action_next is null) \ + and state not in (\'cancel\',\'done\')', + (time.strftime("%Y-%m-%d %H:%M:%S"), + time.strftime('%Y-%m-%d %H:%M:%S'))) + ids2 = map(lambda x: x[0], cr.fetchall() or []) + cases = self.browse(cr, uid, ids2, context) + return self._action(cr, uid, cases, False, context=context) + + def _action(self, cr, uid, cases, state_to, scrit=None, context={}): + if not context: + 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, [('name','=',model_ids[0])]) + return rule_obj._action(cr, uid, rule_ids, cases, scrit=scrit, context=context) + + def format_body(self, body): + return self.pool.get('base.action.rule').format_body(body) + + def format_mail(self, obj, body): + return self.pool.get('base.action.rule').format_mail(obj, body) +case() + +class base_action_rule(osv.osv): + _inherit = 'base.action.rule' + _description = 'Action Rules' + + def do_check(self, cr, uid, action, obj, context={}): + 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_id'): + ok = ok and (not action.trg_categ_id or action.trg_categ_id.id==obj.categ_id.id) + if hasattr(obj, 'history_line'): + ok = ok and (not action.trg_max_history or action.trg_max_history<=(len(obj.history_line)+1)) + reg_history = action.regex_history + result_history = True + if reg_history: + ptrn = re.compile(str(reg_history)) + if obj.history_line: + _result = ptrn.search(str(obj.history_line[0].description)) + if not _result: + result_history = False + regex_h = not reg_history or result_history + ok = ok and regex_h + return ok + + def do_action(self, cr, uid, action, model_obj, obj, context={}): + res = super(base_action_rule, self).do_action(cr, uid, action, model_obj, obj, context=context) + write = {} + + if action.act_section_id: + obj.section_id = action.act_section_id + write['section_id'] = action.act_section_id.id + + if hasattr(obj, 'email_cc') and action.act_email_cc: + if '@' in (obj.email_cc or ''): + emails = obj.email_cc.split(",") + if obj.act_email_cc not in emails:# and '<'+str(action.act_email_cc)+">" not in emails: + write['email_cc'] = obj.email_cc+','+obj.act_email_cc + else: + write['email_cc'] = obj.act_email_cc + + model_obj.write(cr, uid, [obj.id], write, context) + if hasattr(obj, 'email_from') and action.act_mail_to_partner: + emails.append(obj.email_from) + emails = filter(None, emails) + if len(emails) and action.act_mail_body: + emails = list(set(emails)) + self.email_send(cr, uid, obj, emails, action.act_mail_body) + return True + + +base_action_rule() + +class base_action_rule_line(osv.osv): + _inherit = 'base.action.rule.line' + + def state_get(self, cr, uid, context={}): + res = super(base_action_rule_line, self).state_get(cr, uid, context=context) + return res + [('escalate','Escalate')] + crm.AVAILABLE_STATES + + def priority_get(self, cr, uid, context={}): + res = super(base_action_rule_line, self).priority_get(cr, uid, context=context) + return res + crm.AVAILABLE_PRIORITIES + + _columns = { + 'trg_section_id': fields.many2one('crm.case.section', 'Section'), + '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 section to'), + 'act_mail_to_partner': fields.boolean('Mail to partner',help="Check this if you want the rule to send an email to the partner."), + } + +base_action_rule_line() diff --git a/addons/crm/crm_action_rule_view.xml b/addons/crm/crm_action_rule_view.xml new file mode 100644 index 00000000000..0cca1b22416 --- /dev/null +++ b/addons/crm/crm_action_rule_view.xml @@ -0,0 +1,45 @@ + + + + + + base.action.rule.line.form.inherit + base.action.rule.line + + form + + + + + + + + + + + base.action.rule.line.form2.inherit + base.action.rule.line + + form + + + + + + + + + + base.action.rule.line.form3.inherit + base.action.rule.line + + form + + + + + + + + + diff --git a/addons/crm/crm_claims_view.xml b/addons/crm/crm_claims_view.xml index fecf8ae8ee3..02407e4f207 100644 --- a/addons/crm/crm_claims_view.xml +++ b/addons/crm/crm_claims_view.xml @@ -110,8 +110,6 @@ - -
diff --git a/addons/crm/crm_data.xml b/addons/crm/crm_data.xml index 8bc30fbd26e..6dd53747c54 100644 --- a/addons/crm/crm_data.xml +++ b/addons/crm/crm_data.xml @@ -66,7 +66,7 @@ hours -1 - + diff --git a/addons/crm/crm_fund_view.xml b/addons/crm/crm_fund_view.xml index 7260ed1e513..8d552ff3320 100644 --- a/addons/crm/crm_fund_view.xml +++ b/addons/crm/crm_fund_view.xml @@ -109,8 +109,6 @@ - - diff --git a/addons/crm/crm_helpdesk_view.xml b/addons/crm/crm_helpdesk_view.xml index 09d53676339..9a8bcbe5679 100644 --- a/addons/crm/crm_helpdesk_view.xml +++ b/addons/crm/crm_helpdesk_view.xml @@ -70,8 +70,6 @@ - - diff --git a/addons/crm/crm_opportunity_view.xml b/addons/crm/crm_opportunity_view.xml index 4a74b9c970c..ab370e4ae08 100644 --- a/addons/crm/crm_opportunity_view.xml +++ b/addons/crm/crm_opportunity_view.xml @@ -99,8 +99,6 @@ - - diff --git a/addons/crm/crm_view.xml b/addons/crm/crm_view.xml index 80d10591c85..a5fa9e1a305 100644 --- a/addons/crm/crm_view.xml +++ b/addons/crm/crm_view.xml @@ -129,109 +129,8 @@ - - - - - - crm.case.rule.form - crm.case.rule - form - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - crm.case.rule.tree - crm.case.rule - tree - - - - - - - - - Rules - crm.case.rule - form - - - - + + Add CC crm.email.add.cc @@ -377,8 +276,6 @@ - -
diff --git a/addons/crm/security/ir.model.access.csv b/addons/crm/security/ir.model.access.csv index 7339832b083..48d09804d1b 100644 --- a/addons/crm/security/ir.model.access.csv +++ b/addons/crm/security/ir.model.access.csv @@ -3,7 +3,6 @@ "access_crm_segmentation_line","crm.segmentation.line","model_crm_segmentation_line","crm.group_crm_manager",1,1,1,1 "access_crm_case_section","crm.case.section","model_crm_case_section","crm.group_crm_user",1,0,0,0 "access_crm_case_categ","crm.case.categ","model_crm_case_categ","crm.group_crm_user",1,0,0,0 -"access_crm_case_rule","crm.case.rule","model_crm_case_rule","crm.group_crm_user",1,0,0,0 "access_crm_case_manger","crm.case manager","model_crm_case","crm.group_crm_user",1,1,1,1 "access_crm_case","crm.case","model_crm_case","crm.group_crm_manager",1,1,1,1 "access_crm_claim","crm.claim","model_crm_claim","crm.group_crm_manager",1,1,1,1 @@ -17,7 +16,6 @@ "access_crm_case_history","crm.case.history","model_crm_case_history","crm.group_crm_user",1,1,1,1 "access_crm_case_section_manager","crm.case.section.manager","model_crm_case_section","crm.group_crm_manager",1,1,1,1 "access_crm_case_categ_manager","crm.case.categ.manager","model_crm_case_categ","crm.group_crm_manager",1,1,1,1 -"access_crm_case_rule_manager","crm.case.rule.manager","model_crm_case_rule","crm.group_crm_manager",1,1,1,1 "access_crm_case_log_manager","crm.case.log manager","model_crm_case_log","crm.group_crm_manager",1,1,1,1 "access_crm_case_history_manager","crm.case.history manager","model_crm_case_history","crm.group_crm_manager",1,1,1,1 "access_crm_email_add_cc_manager","crm.email.add.cc","model_crm_email_add_cc","crm.group_crm_manager",1,1,1,1 diff --git a/addons/crm_hr/crm_hr.py b/addons/crm_hr/crm_hr.py index 7fe2f2c4878..b122cc6b9e9 100644 --- a/addons/crm_hr/crm_hr.py +++ b/addons/crm_hr/crm_hr.py @@ -19,22 +19,9 @@ # ############################################################################## -import time -import re -import os - -import mx.DateTime -import base64 - -from tools.translate import _ - -import tools from osv import fields,osv,orm -from osv.orm import except_orm - from crm import crm - class crm_job(osv.osv): _name = "crm.job" _description = "Job Cases" @@ -63,13 +50,6 @@ class crm_job(osv.osv): "the partner mentality in relation to our services.The scale has" \ "to be created with a factor for each level from 0 (Very dissatisfied) to 10 (Extremely satisfied)."), 'phonecall_id':fields.many2one ('crm.phonecall', 'Phonecall'), - - } - def onchange_categ_id(self, cr, uid, ids, categ, context={}): - if not categ: - return {'value':{}} - cat = self.pool.get('crm.case.categ').browse(cr, uid, categ, context).probability - return {'value':{'probability':cat}} crm_job() diff --git a/addons/crm_hr/crm_hr_view.xml b/addons/crm_hr/crm_hr_view.xml index f4a46ec9621..9771a25309c 100644 --- a/addons/crm_hr/crm_hr_view.xml +++ b/addons/crm_hr/crm_hr_view.xml @@ -127,8 +127,6 @@ - - diff --git a/addons/crm_project/crm_bugs_view.xml b/addons/crm_project/crm_bugs_view.xml index 0d84723f11e..3ff37426770 100644 --- a/addons/crm_project/crm_bugs_view.xml +++ b/addons/crm_project/crm_bugs_view.xml @@ -80,8 +80,6 @@ - - diff --git a/addons/event/event_view.xml b/addons/event/event_view.xml index 58a1c4ea997..10008add118 100644 --- a/addons/event/event_view.xml +++ b/addons/event/event_view.xml @@ -216,8 +216,6 @@ - -