X-Git-Url: http://git.inspyration.org/?a=blobdiff_plain;f=addons%2Fcrm%2Fcrm.py;h=bee1e48ada201df1e4f35e22b21b4b1b9adc0781;hb=a8c70fd477623bd044271a62c3986d9f0f0c1d7b;hp=d91062b581692e1ac3013065ef3e545009ab68eb;hpb=81f52634b36a42c16a67f2b514986ca79c49c46a;p=odoo%2Fodoo.git diff --git a/addons/crm/crm.py b/addons/crm/crm.py index d91062b..bee1e48 100644 --- a/addons/crm/crm.py +++ b/addons/crm/crm.py @@ -29,8 +29,8 @@ from tools.translate import _ MAX_LEVEL = 15 AVAILABLE_STATES = [ - ('draft', 'Draft'), - ('open', 'Open'), + ('draft', 'New'), + ('open', 'In Progress'), ('cancel', 'Cancelled'), ('done', 'Closed'), ('pending', 'Pending'), @@ -44,146 +44,181 @@ AVAILABLE_PRIORITIES = [ ('5', 'Lowest'), ] -class crm_case(object): - """A simple python class to be used for common functions """ +class crm_case_channel(osv.osv): + _name = "crm.case.channel" + _description = "Channels" + _order = 'name' + _columns = { + 'name': fields.char('Channel Name', size=64, required=True), + 'active': fields.boolean('Active'), + } + _defaults = { + 'active': lambda *a: 1, + } - def _find_lost_stage(self, cr, uid, type, section_id): - return self._find_percent_stage(cr, uid, 0.0, type, section_id) - - def _find_won_stage(self, cr, uid, type, section_id): - return self._find_percent_stage(cr, uid, 100.0, type, section_id) +class crm_case_stage(osv.osv): + """ Stage of case """ - def _find_percent_stage(self, cr, uid, percent, type, section_id): - """ - Return the first stage with a probability == percent - """ - stage_pool = self.pool.get('crm.case.stage') - if section_id : - ids = stage_pool.search(cr, uid, [("probability", '=', percent), ("type", 'like', type), ("section_ids", 'in', [section_id])]) - else : - ids = stage_pool.search(cr, uid, [("probability", '=', percent), ("type", 'like', type)]) - - if ids: - return ids[0] - return False - - - def _find_first_stage(self, cr, uid, type, section_id): - """ - return the first stage that has a sequence number equal or higher than sequence - """ - stage_pool = self.pool.get('crm.case.stage') - if section_id : - ids = stage_pool.search(cr, uid, [("sequence", '>', 0), ("type", 'like', type), ("section_ids", 'in', [section_id])]) - else : - ids = stage_pool.search(cr, uid, [("sequence", '>', 0), ("type", 'like', type)]) - - if ids: - stages = stage_pool.browse(cr, uid, ids) - stage_min = stages[0] - for stage in stages: - if stage_min.sequence > stage.sequence: - stage_min = stage - return stage_min.id - else : - return False - - def onchange_stage_id(self, cr, uid, ids, stage_id, context={}): + _name = "crm.case.stage" + _description = "Stage of case" + _rec_name = 'name' + _order = "sequence" + + _columns = { + 'name': fields.char('Stage Name', size=64, required=True, translate=True), + 'sequence': fields.integer('Sequence', help="Used to order stages."), + 'probability': fields.float('Probability (%)', required=True, help="This percentage depicts the default/average probability of the Case for this stage to be a success"), + 'on_change': fields.boolean('Change Probability Automatically', help="Setting this stage will change the probability automatically on the opportunity."), + 'requirements': fields.text('Requirements'), + 'section_ids':fields.many2many('crm.case.section', 'section_stage_rel', 'stage_id', 'section_id', 'Sections'), + 'case_default': fields.boolean('Common to All Teams', help="If you check this field, this stage will be proposed by default on each sales team. It will not assign this stage to existing teams."), + } - """ @param self: The object pointer - @param cr: the current row, from the database cursor, - @param uid: the current user’s ID for security checks, - @param ids: List of stage’s IDs - @stage_id: change state id on run time """ - - if not stage_id: - return {'value':{}} + _defaults = { + 'sequence': lambda *args: 1, + 'probability': lambda *args: 0.0, + } - stage = self.pool.get('crm.case.stage').browse(cr, uid, stage_id, context) +class crm_case_section(osv.osv): + """Sales Team""" - if not stage.on_change: - return {'value':{}} - return {'value':{'probability': stage.probability}} + _name = "crm.case.section" + _description = "Sales Teams" + _order = "complete_name" - def _get_default_partner_address(self, cr, uid, context=None): + def get_full_name(self, cr, uid, ids, field_name, arg, context=None): + return dict(self.name_get(cr, uid, ids, context=context)) + + _columns = { + 'name': fields.char('Sales Team', size=64, required=True, translate=True), + 'complete_name': fields.function(get_full_name, type='char', size=256, readonly=True, store=True), + 'code': fields.char('Code', size=8), + 'active': fields.boolean('Active', help="If the active field is set to "\ + "true, it will allow you to hide the sales team without removing it."), + 'allow_unlink': fields.boolean('Allow Delete', help="Allows to delete non draft cases"), + 'change_responsible': fields.boolean('Reassign Escalated', help="When escalating to this team override the saleman with the team leader."), + 'user_id': fields.many2one('res.users', 'Team Leader'), + 'member_ids':fields.many2many('res.users', 'sale_member_rel', 'section_id', 'member_id', 'Team Members'), + 'reply_to': fields.char('Reply-To', size=64, help="The email address put in the 'Reply-To' of all emails sent by OpenERP about cases in this sales team"), + 'parent_id': fields.many2one('crm.case.section', 'Parent Team'), + 'child_ids': fields.one2many('crm.case.section', 'parent_id', 'Child Teams'), + 'resource_calendar_id': fields.many2one('resource.calendar', "Working Time", help="Used to compute open days"), + 'note': fields.text('Description'), + 'working_hours': fields.float('Working Hours', digits=(16,2 )), + 'stage_ids': fields.many2many('crm.case.stage', 'section_stage_rel', 'section_id', 'stage_id', 'Stages'), + } + def _get_stage_common(self, cr, uid, context): + ids = self.pool.get('crm.case.stage').search(cr, uid, [('case_default','=',1)], context=context) + return ids + + _defaults = { + 'active': lambda *a: 1, + 'allow_unlink': lambda *a: 1, + 'stage_ids': _get_stage_common + } + + _sql_constraints = [ + ('code_uniq', 'unique (code)', 'The code of the sales team must be unique !') + ] + + _constraints = [ + (osv.osv._check_recursion, 'Error ! You cannot create recursive Sales team.', ['parent_id']) + ] + + def name_get(self, cr, uid, ids, context=None): + """Overrides orm name_get method""" + if not isinstance(ids, list) : + ids = [ids] + res = [] + if not ids: + return res + reads = self.read(cr, uid, ids, ['name', 'parent_id'], context) + + for record in reads: + name = record['name'] + if record['parent_id']: + name = record['parent_id'][1] + ' / ' + name + res.append((record['id'], name)) + return res + +class crm_case_categ(osv.osv): + """ Category of Case """ + _name = "crm.case.categ" + _description = "Category of Case" + _columns = { + 'name': fields.char('Name', size=64, required=True, translate=True), + 'section_id': fields.many2one('crm.case.section', 'Sales Team'), + 'object_id': fields.many2one('ir.model', 'Object Name'), + } + + def _find_object_id(self, cr, uid, context=None): + """Finds id for case object""" + object_id = context and context.get('object_id', False) or False + ids = self.pool.get('ir.model').search(cr, uid, [('model', '=', object_id)]) + return ids and ids[0] or False + + _defaults = { + 'object_id' : _find_object_id + } + +class crm_case_resource_type(osv.osv): + """ Resource Type of case """ + _name = "crm.case.resource.type" + _description = "Campaign" + _rec_name = "name" + _columns = { + 'name': fields.char('Campaign Name', size=64, required=True, translate=True), + 'section_id': fields.many2one('crm.case.section', 'Sales Team'), + } +class crm_base(object): + """ Base utility mixin class for crm objects, + Object subclassing this should define colums: + date_open + date_closed + user_id + partner_id + partner_address_id + """ + def _get_default_partner_address(self, cr, uid, context=None): """Gives id of default address for current user - @param self: The object pointer - @param cr: the current row, from the database cursor, - @param uid: the current user’s ID for security checks, - @param context: A standard dictionary for contextual values + :param context: if portal in context is false return false anyway """ if context is None: context = {} - if not context.get('portal', False): + if not context.get('portal'): return False - return self.pool.get('res.users').browse(cr, uid, uid, context).address_id.id + # was user.address_id.id, but address_id has been removed + user = self.pool.get('res.users').browse(cr, uid, uid, context=context) + if hasattr(user, 'partner_address_id') and user.partner_address_id: + return user.partner_address_id + return False def _get_default_partner(self, cr, uid, context=None): """Gives id of partner for current user - @param self: The object pointer - @param cr: the current row, from the database cursor, - @param uid: the current user’s ID for security checks, - @param context: A standard dictionary for contextual values + :param context: if portal in context is false return false anyway """ if context is None: context = {} if not context.get('portal', False): return False user = self.pool.get('res.users').browse(cr, uid, uid, context=context) - if not user.address_id: - return False - return user.address_id.partner_id.id - - def copy(self, cr, uid, id, default=None, context=None): - """ - Overrides orm copy method. - @param self: the object pointer - @param cr: the current row, from the database cursor, - @param uid: the current user’s ID for security checks, - @param id: Id of mailgate thread - @param default: Dictionary of default values for copy. - @param context: A standard dictionary for contextual values - """ - if context is None: - context = {} - if default is None: - default = {} + if hasattr(user, 'partner_address_id') and user.partner_address_id: + return user.partner_address_id + return user.company_id.partner_id.id - default.update({ - 'message_ids': [], - }) - if hasattr(self, '_columns'): - if self._columns.get('date_closed'): - default.update({ - 'date_closed': False, - }) - if self._columns.get('date_open'): - default.update({ - 'date_open': False - }) - return super(osv.osv, self).copy(cr, uid, id, default, context=context) - def _get_default_email(self, cr, uid, context=None): """Gives default email address for current user - @param self: The object pointer - @param cr: the current row, from the database cursor, - @param uid: the current user’s ID for security checks, - @param context: A standard dictionary for contextual values + :param context: if portal in context is false return false anyway """ if not context.get('portal', False): return False user = self.pool.get('res.users').browse(cr, uid, uid, context=context) - if not user.address_id: - return False - return user.address_id.email + return user.user_email def _get_default_user(self, cr, uid, context=None): """Gives current user id - @param self: The object pointer - @param cr: the current row, from the database cursor, - @param uid: the current user’s ID for security checks, - @param context: A standard dictionary for contextual values + :param context: if portal in context is false return false anyway """ if context and context.get('portal', False): return False @@ -191,160 +226,187 @@ class crm_case(object): def _get_section(self, cr, uid, context=None): """Gives section id for current User - @param self: The object pointer - @param cr: the current row, from the database cursor, - @param uid: the current user’s ID for security checks, - @param context: A standard dictionary for contextual values """ user = self.pool.get('res.users').browse(cr, uid, uid, context=context) return user.context_section_id.id or False - def _find_next_stage(self, cr, uid, stage_list, index, current_seq, stage_pool, context=None): - if index + 1 == len(stage_list): - return False - next_stage_id = stage_list[index + 1] - next_stage = stage_pool.browse(cr, uid, next_stage_id, context=context) - if not next_stage: - return False - next_seq = next_stage.sequence - if not current_seq : - current_seq = 0 - - if (abs(next_seq - current_seq)) >= 1: - return next_stage - else : - return self._find_next_stage(cr, uid, stage_list, index + 1, current_seq, stage_pool) - - def stage_change(self, cr, uid, ids, context=None, order='sequence'): - if context is None: - context = {} - stage_pool = self.pool.get('crm.case.stage') - stage_type = context and context.get('stage_type','') - current_seq = False - next_stage_id = False - - for case in self.browse(cr, uid, ids, context=context): - next_stage = False - value = {} - if case.section_id.id : - domain = [('type', '=', stage_type),('section_ids', '=', case.section_id.id)] - else : - domain = [('type', '=', stage_type)] - - - stages = stage_pool.search(cr, uid, domain, order=order) - current_seq = case.stage_id.sequence - index = -1 - if case.stage_id and case.stage_id.id in stages: - index = stages.index(case.stage_id.id) - - next_stage = self._find_next_stage(cr, uid, stages, index, current_seq, stage_pool, context=context) - - if next_stage: - next_stage_id = next_stage.id - value.update({'stage_id': next_stage.id}) - if next_stage.on_change: - value.update({'probability': next_stage.probability}) - self.write(cr, uid, [case.id], value, context=context) - - - return next_stage_id #FIXME should return a list of all id - - - def stage_next(self, cr, uid, ids, context=None): - """This function computes next stage for case from its current stage - using available stage for that case type - @param self: The object pointer - @param cr: the current row, from the database cursor, - @param uid: the current user’s ID for security checks, - @param ids: List of case IDs - @param context: A standard dictionary for contextual values""" - - return self.stage_change(cr, uid, ids, context=context, order='sequence') - - def stage_previous(self, cr, uid, ids, context=None): - """This function computes previous stage for case from its current stage - using available stage for that case type - @param self: The object pointer - @param cr: the current row, from the database cursor, - @param uid: the current user’s ID for security checks, - @param ids: List of case IDs - @param context: A standard dictionary for contextual values""" - return self.stage_change(cr, uid, ids, context=context, order='sequence desc') + def onchange_partner_address_id(self, cr, uid, ids, add, email=False): + """This function returns value of partner email based on Partner Address + :param ids: List of case IDs + :param add: Id of Partner's address + :param email: Partner's email ID + """ + data = {'value': {'email_from': False, 'phone':False}} + if add: + address = self.pool.get('res.partner.address').browse(cr, uid, add) + data['value'] = {'email_from': address and address.email or False , + 'phone': address and address.phone or False} + if 'phone' not in self._columns: + del data['value']['phone'] + return data def onchange_partner_id(self, cr, uid, ids, part, email=False): """This function returns value of partner address based on partner - @param self: The object pointer - @param cr: the current row, from the database cursor, - @param uid: the current user’s ID for security checks, - @param ids: List of case IDs - @param part: Partner's id - @email: Partner's email ID + :param ids: List of case IDs + :param part: Partner's id + :param email: Partner's email ID """ - if not part: - return {'value': {'partner_address_id': False, - 'email_from': False, - 'phone': False - }} - addr = self.pool.get('res.partner').address_get(cr, uid, [part], ['contact']) - data = {'partner_address_id': addr['contact']} - data.update(self.onchange_partner_address_id(cr, uid, ids, addr['contact'])['value']) + data={} + if part: + addr = self.pool.get('res.partner').address_get(cr, uid, [part], ['contact']) + data = {'partner_address_id': addr['contact']} + data.update(self.onchange_partner_address_id(cr, uid, ids, addr['contact'])['value']) return {'value': data} - def onchange_partner_address_id(self, cr, uid, ids, add, email=False): - """This function returns value of partner email based on Partner Address - @param self: The object pointer - @param cr: the current row, from the database cursor, - @param uid: the current user’s ID for security checks, - @param ids: List of case IDs - @param add: Id of Partner's address - @email: Partner's email ID - """ - if not add: - return {'value': {'email_from': False}} - address = self.pool.get('res.partner.address').browse(cr, uid, add) - return {'value': {'email_from': address.email, 'phone': address.phone}} - - def _history(self, cr, uid, cases, keyword, history=False, subject=None, email=False, details=None, email_from=False, message_id=False, attach=[], context=None): - mailgate_pool = self.pool.get('mailgate.thread') - return mailgate_pool.history(cr, uid, cases, keyword, history=history,\ - subject=subject, email=email, \ - details=details, email_from=email_from,\ - message_id=message_id, attach=attach, \ - context=context) - def case_open(self, cr, uid, ids, *args): """Opens Case - @param self: The object pointer - @param cr: the current row, from the database cursor, - @param uid: the current user’s ID for security checks, - @param ids: List of case Ids - @param *args: Tuple Value for additional Params + :param ids: List of case Ids """ - cases = self.browse(cr, uid, ids) - self._history(cr, uid, cases, _('Open')) for case in cases: data = {'state': 'open', 'active': True} if not case.user_id: data['user_id'] = uid self.write(cr, uid, case.id, data) - - - self._action(cr, uid, cases, 'open') + + self._action(cr, uid, cases, 'open') return True def case_close(self, cr, uid, ids, *args): """Closes Case - @param self: The object pointer - @param cr: the current row, from the database cursor, - @param uid: the current user’s ID for security checks, - @param ids: List of case Ids - @param *args: Tuple Value for additional Params + :param ids: List of case Ids + """ + cases = self.browse(cr, uid, ids) + cases[0].state # to fill the browse record cache + self.write(cr, uid, ids, {'state': 'done', 'date_closed': time.strftime('%Y-%m-%d %H:%M:%S'), }) + # We use the cache of cases to keep the old case state + self._action(cr, uid, cases, 'done') + return True + + def case_cancel(self, cr, uid, ids, *args): + """Cancels Case + :param ids: List of case Ids + """ + cases = self.browse(cr, uid, ids) + cases[0].state # to fill the browse record cache + self.write(cr, uid, ids, {'state': 'cancel', 'active': True}) + # We use the cache of cases to keep the old case state + self._action(cr, uid, cases, 'cancel') + return True + + def case_pending(self, cr, uid, ids, *args): + """Marks case as pending + :param ids: List of case Ids + """ + cases = self.browse(cr, uid, ids) + cases[0].state # to fill the browse record cache + self.write(cr, uid, ids, {'state': 'pending', 'active': True}) + self._action(cr, uid, cases, 'pending') + return True + + def case_reset(self, cr, uid, ids, *args): + """Resets case as draft + :param ids: List of case Ids + """ + cases = self.browse(cr, uid, ids) + cases[0].state # to fill the browse record cache + self.write(cr, uid, ids, {'state': 'draft', 'active': True}) + self._action(cr, uid, cases, 'draft') + return True + + def _action(self, cr, uid, cases, state_to, scrit=None, context=None): + if context is None: + context = {} + context['state_to'] = state_to + rule_obj = self.pool.get('base.action.rule') + 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) + +class crm_case(crm_base): + """ A simple python class to be used for common functions + Object that inherit from this class should inherit from mailgate.thread + And need a stage_id field + And object that inherit (orm inheritance) from a class the overwrite copy + """ + + def stage_find(self, cr, uid, section_id, domain=[], order='sequence'): + domain = list(domain) + if section_id: + domain.append(('section_ids', '=', section_id)) + stage_ids = self.pool.get('crm.case.stage').search(cr, uid, domain, order=order) + if stage_ids: + return stage_ids[0] + return False + + def stage_set(self, cr, uid, ids, stage_id, context=None): + value = {} + if hasattr(self,'onchange_stage_id'): + value = self.onchange_stage_id(cr, uid, ids, stage_id)['value'] + value['stage_id'] = stage_id + return self.write(cr, uid, ids, value, context=context) + + def stage_change(self, cr, uid, ids, op, order, context=None): + if context is None: + context = {} + for case in self.browse(cr, uid, ids, context=context): + seq = 0 + if case.stage_id: + seq = case.stage_id.sequence + section_id = None + if case.section_id: + section_id = case.section_id.id + next_stage_id = self.stage_find(cr, uid, section_id, [('sequence',op,seq)],order) + if next_stage_id: + return self.stage_set(cr, uid, [case.id], next_stage_id, context=context) + return False + + def stage_next(self, cr, uid, ids, context=None): + """This function computes next stage for case from its current stage + using available stage for that case type + """ + return self.stage_change(cr, uid, ids, '>','sequence', context) + + def stage_previous(self, cr, uid, ids, context=None): + """This function computes previous stage for case from its current + stage using available stage for that case type """ + return self.stage_change(cr, uid, ids, '<', 'sequence desc', context) + + def copy(self, cr, uid, id, default=None, context=None): + """Overrides orm copy method to avoid copying messages, + as well as date_closed and date_open columns if they + exist.""" + if default is None: + default = {} + + default.update({ 'message_ids': [], }) + if hasattr(self, '_columns'): + if self._columns.get('date_closed'): + default.update({ 'date_closed': False, }) + if self._columns.get('date_open'): + default.update({ 'date_open': False }) + return super(crm_case, self).copy(cr, uid, id, default, context=context) + + + def case_open(self, cr, uid, ids, *args): + """Opens Case""" + cases = self.browse(cr, uid, ids) + self.message_append(cr, uid, cases, _('Open')) + for case in cases: + data = {'state': 'open', 'active': True } + if not case.user_id: + data['user_id'] = uid + self.write(cr, uid, case.id, data) + self._action(cr, uid, cases, 'open') + return True + + def case_close(self, cr, uid, ids, *args): + """Closes Case""" cases = self.browse(cr, uid, ids) cases[0].state # to fill the browse record cache - self._history(cr, uid, cases, _('Close')) + self.message_append(cr, uid, cases, _('Close')) self.write(cr, uid, ids, {'state': 'done', 'date_closed': time.strftime('%Y-%m-%d %H:%M:%S'), }) @@ -355,17 +417,10 @@ class crm_case(object): return True def case_escalate(self, cr, uid, ids, *args): - """Escalates case to top level - @param self: The object pointer - @param cr: the current row, from the database cursor, - @param uid: the current user’s ID for security checks, - @param ids: List of case Ids - @param *args: Tuple Value for additional Params - """ + """Escalates case to parent level""" cases = self.browse(cr, uid, ids) for case in cases: data = {'active': True} - if case.section_id.parent_id: data['section_id'] = case.section_id.parent_id.id if case.section_id.parent_id.change_responsible: @@ -375,21 +430,15 @@ class crm_case(object): raise osv.except_osv(_('Error !'), _('You can not escalate, You are already at the top level regarding your sales-team category.')) self.write(cr, uid, [case.id], data) cases = self.browse(cr, uid, ids) - self._history(cr, uid, cases, _('Escalate')) + self.message_append(cr, uid, cases, _('Escalate')) self._action(cr, uid, cases, 'escalate') return True def case_cancel(self, cr, uid, ids, *args): - """Cancels Case - @param self: The object pointer - @param cr: the current row, from the database cursor, - @param uid: the current user’s ID for security checks, - @param ids: List of case Ids - @param *args: Tuple Value for additional Params - """ + """Cancels Case""" cases = self.browse(cr, uid, ids) cases[0].state # to fill the browse record cache - self._history(cr, uid, cases, _('Cancel')) + self.message_append(cr, uid, cases, _('Cancel')) self.write(cr, uid, ids, {'state': 'cancel', 'active': True}) self._action(cr, uid, cases, 'cancel') @@ -399,108 +448,87 @@ class crm_case(object): return True def case_pending(self, cr, uid, ids, *args): - """Marks case as pending - @param self: The object pointer - @param cr: the current row, from the database cursor, - @param uid: the current user’s ID for security checks, - @param ids: List of case Ids - @param *args: Tuple Value for additional Params - """ + """Marks case as pending""" cases = self.browse(cr, uid, ids) cases[0].state # to fill the browse record cache - self._history(cr, uid, cases, _('Pending')) + self.message_append(cr, uid, cases, _('Pending')) self.write(cr, uid, ids, {'state': 'pending', 'active': True}) self._action(cr, uid, cases, 'pending') return True def case_reset(self, cr, uid, ids, *args): - """Resets case as draft - @param self: The object pointer - @param cr: the current row, from the database cursor, - @param uid: the current user’s ID for security checks, - @param ids: List of case Ids - @param *args: Tuple Value for additional Params - """ + """Resets case as draft""" + state = 'draft' + if 'crm.phonecall' in args: + state = 'open' cases = self.browse(cr, uid, ids) 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') + self.message_append(cr, uid, cases, _('Draft')) + self.write(cr, uid, ids, {'state': state, 'active': True}) + self._action(cr, uid, cases, state) return True + #DEAD Code def remind_partner(self, cr, uid, ids, context=None, attach=False): - - """ - @param self: The object pointer - @param cr: the current row, from the database cursor, - @param uid: the current user’s ID for security checks, - @param ids: List of Remind Partner's IDs - @param context: A standard dictionary for contextual values - - """ return self.remind_user(cr, uid, ids, context, attach, destination=False) + #DEAD Code def remind_user(self, cr, uid, ids, context=None, attach=False, destination=True): - """ - @param self: The object pointer - @param cr: the current row, from the database cursor, - @param uid: the current user’s ID for security checks, - @param ids: List of Remind user's IDs - @param context: A standard dictionary for contextual values - - """ + mail_message = self.pool.get('mail.message') for case in self.browse(cr, uid, ids, context=context): - if not case.section_id.reply_to: - raise osv.except_osv(_('Error!'), ("Reply To is not specified in the sales team")) - 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.description or "" - if case.message_ids: - body = case.message_ids[0].description or "" - if not destination: - src, dest = dest, src - if body and case.user_id.signature: - if body: - body += '\n\n%s' % (case.user_id.signature) - else: - body = '\n\n%s' % (case.user_id.signature) - - body = self.format_body(body) - - attach_to_send = None - - if attach: - attach_ids = self.pool.get('ir.attachment').search(cr, uid, [('res_model', '=', self._name), ('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 - subject = "Reminder: [%s] %s" % (str(case.id), case.name, ) - tools.email_send( - src, - [dest], - subject, - body, - reply_to=case.section_id.reply_to, - openobject_id=str(case.id), - attach=attach_to_send - ) - self._history(cr, uid, [case], _('Send'), history=True, subject=subject, email=dest, details=body, email_from=src) + if not destination and not case.email_from: + return False + if not case.user_id.user_email: + return False + if destination and case.section_id.user_id: + case_email = case.section_id.user_id.user_email + else: + case_email = case.user_id.user_email + + src = case_email + dest = case.user_id.user_email or "" + body = case.description or "" + for message in case.message_ids: + if message.email_from: + body = message.description + break + + if not destination: + src, dest = dest, case.email_from + if body and case.user_id.signature: + if body: + body += '\n\n%s' % (case.user_id.signature) + else: + body = '\n\n%s' % (case.user_id.signature) + + body = self.format_body(body) + + attach_to_send = {} + + if attach: + attach_ids = self.pool.get('ir.attachment').search(cr, uid, [('res_model', '=', self._name), ('res_id', '=', case.id)]) + attach_to_send = self.pool.get('ir.attachment').read(cr, uid, attach_ids, ['datas_fname', 'datas']) + attach_to_send = dict(map(lambda x: (x['datas_fname'], base64.decodestring(x['datas'])), attach_to_send)) + + # Send an email + subject = "Reminder: [%s] %s" % (str(case.id), case.name, ) + mail_message.schedule_with_attach(cr, uid, + src, + [dest], + subject, + body, + model='crm.case', + reply_to=case.section_id.reply_to, + res_id=case.id, + attachments=attach_to_send, + context=context + ) return True def _check(self, cr, uid, ids=False, context=None): - """ - Function called by the scheduler to process cases for date actions - Only works on not done and cancelled cases - - @param self: The object pointer - @param cr: the current row, from the database cursor, - @param uid: the current user’s ID for security checks, - @param context: A standard dictionary for contextual values + """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) \ @@ -513,25 +541,13 @@ class crm_case(object): cases = self.browse(cr, uid, ids2, context=context) return self._action(cr, uid, cases, False, context=context) - def _action(self, cr, uid, cases, state_to, scrit=None, context=None): - if context is None: - context = {} - context['state_to'] = state_to - rule_obj = self.pool.get('base.action.rule') - model_obj = self.pool.get('ir.model') - model_ids = model_obj.search(cr, uid, [('model','=',self._name)]) - rule_ids = rule_obj.search(cr, uid, [('model_id','=',model_ids[0])]) - return rule_obj._action(cr, uid, rule_ids, cases, scrit=scrit, context=context) - 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) - def message_followers(self, cr, uid, ids, context=None): - """ Get a list of emails of the people following this thread - """ + def message_thread_followers(self, cr, uid, ids, context=None): res = {} for case in self.browse(cr, uid, ids, context=context): l=[] @@ -542,195 +558,8 @@ class crm_case(object): res[case.id] = l return res - -class crm_case_stage(osv.osv): - """ Stage of case """ - - _name = "crm.case.stage" - _description = "Stage of case" - _rec_name = 'name' - _order = "sequence" - - - - def _get_type_value(self, cr, user, context): - return [('lead','Lead'),('opportunity','Opportunity')] - - - _columns = { - 'name': fields.char('Stage Name', size=64, required=True, translate=True), - 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of case stages."), - 'probability': fields.float('Probability (%)', required=True, help="This percentage depicts the default/average probability of the Case for this stage to be a success"), - 'on_change': fields.boolean('Change Probability Automatically', \ - help="Change Probability on next and previous stages."), - 'requirements': fields.text('Requirements'), - 'type': fields.selection(_get_type_value, 'Type'), - } - - - def _find_stage_type(self, cr, uid, context=None): - """Finds type of stage according to object. - @param self: The object pointer - @param cr: the current row, from the database cursor, - @param uid: the current user’s ID for security checks, - @param context: A standard dictionary for contextual values - """ - type = context and context.get('type', '') or '' - return type - - _defaults = { - 'sequence': lambda *args: 1, - 'probability': lambda *args: 0.0, - 'type': _find_stage_type, - } - -crm_case_stage() - - -class crm_case_section(osv.osv): - """Sales Team""" - - _name = "crm.case.section" - _description = "Sales Teams" - _order = "complete_name" - - def get_full_name(self, cr, uid, ids, field_name, arg, context=None): - return dict(self.name_get(cr, uid, ids, context=context)) - - _columns = { - 'name': fields.char('Sales Team', size=64, required=True, translate=True), - 'complete_name': fields.function(get_full_name, method=True, type='char', size=256, readonly=True, store=True), - 'code': fields.char('Code', size=8), - 'active': fields.boolean('Active', help="If the active field is set to "\ - "true, it will allow you to hide the sales team without removing it."), - 'allow_unlink': fields.boolean('Allow Delete', help="Allows to delete non draft cases"), - 'change_responsible': fields.boolean('Change Responsible', help="Thick this box if you want that on escalation, the responsible of this sale team automatically becomes responsible of the lead/opportunity escaladed"), - 'user_id': fields.many2one('res.users', 'Responsible User'), - 'member_ids':fields.many2many('res.users', 'sale_member_rel', 'section_id', 'member_id', 'Team Members'), - 'reply_to': fields.char('Reply-To', size=64, help="The email address put in the 'Reply-To' of all emails sent by OpenERP about cases in this sales team"), - 'parent_id': fields.many2one('crm.case.section', 'Parent Team'), - 'child_ids': fields.one2many('crm.case.section', 'parent_id', 'Child Teams'), - 'resource_calendar_id': fields.many2one('resource.calendar', "Working Time"), - 'note': fields.text('Description'), - 'working_hours': fields.float('Working Hours', digits=(16,2 )), - 'stage_ids': fields.many2many('crm.case.stage', 'section_stage_rel', 'section_id', 'stage_id', 'Stages'), - } - - _defaults = { - 'active': lambda *a: 1, - 'allow_unlink': lambda *a: 1, - } - - _sql_constraints = [ - ('code_uniq', 'unique (code)', 'The code of the sales team must be unique !') - ] - - def _check_recursion(self, cr, uid, ids, context=None): - - """ - Checks for recursion level for sales team - @param self: The object pointer - @param cr: the current row, from the database cursor, - @param uid: the current user’s ID for security checks, - @param ids: List of Sales team ids - """ - level = 100 - - while len(ids): - cr.execute('select distinct parent_id from crm_case_section where id IN %s', (tuple(ids),)) - ids = filter(None, map(lambda x: x[0], cr.fetchall())) - if not level: - return False - level -= 1 - - return True - - _constraints = [ - (_check_recursion, 'Error ! You cannot create recursive Sales team.', ['parent_id']) - ] - - def name_get(self, cr, uid, ids, context=None): - """Overrides orm name_get method - @param self: The object pointer - @param cr: the current row, from the database cursor, - @param uid: the current user’s ID for security checks, - @param ids: List of sales team ids - """ - if context is None: - context = {} - - res = [] - if not ids: - return res - reads = self.read(cr, uid, ids, ['name', 'parent_id'], context) - - for record in reads: - name = record['name'] - if record['parent_id']: - name = record['parent_id'][1] + ' / ' + name - res.append((record['id'], name)) - return res - -crm_case_section() - - -class crm_case_categ(osv.osv): - """ Category of Case """ - _name = "crm.case.categ" - _description = "Category of Case" - _columns = { - 'name': fields.char('Name', size=64, required=True, translate=True), - 'section_id': fields.many2one('crm.case.section', 'Sales Team'), - 'object_id': fields.many2one('ir.model', 'Object Name'), - } - - def _find_object_id(self, cr, uid, context=None): - """Finds id for case object - @param self: The object pointer - @param cr: the current row, from the database cursor, - @param uid: the current user’s ID for security checks, - @param context: A standard dictionary for contextual values - """ - - object_id = context and context.get('object_id', False) or False - ids = self.pool.get('ir.model').search(cr, uid, [('model', '=', object_id)]) - return ids and ids[0] - - _defaults = { - 'object_id' : _find_object_id - - } -crm_case_categ() - - -class crm_case_stage(osv.osv): - _inherit = "crm.case.stage" - _columns = { - 'section_ids':fields.many2many('crm.case.section', 'section_stage_rel', 'stage_id', 'section_id', 'Sections'), - } - -crm_case_stage() - - -class crm_case_resource_type(osv.osv): - """ Resource Type of case """ - _name = "crm.case.resource.type" - _description = "Campaign" - _rec_name = "name" - _columns = { - 'name': fields.char('Campaign Name', size=64, required=True, translate=True), - 'section_id': fields.many2one('crm.case.section', 'Sales Team'), - } -crm_case_resource_type() - - def _links_get(self, cr, uid, context=None): - """Gets links value for reference field - @param self: The object pointer - @param cr: the current row, from the database cursor, - @param uid: the current user’s ID for security checks, - @param context: A standard dictionary for contextual values - """ + """Gets links value for reference field""" obj = self.pool.get('res.request.link') ids = obj.search(cr, uid, []) res = obj.read(cr, uid, ids, ['object', 'name'], context) @@ -742,20 +571,14 @@ class users(osv.osv): _columns = { 'context_section_id': fields.many2one('crm.case.section', 'Sales Team'), } - def write(self, cr, uid, ids, vals, context=None): - res = super(users, self).write(cr, uid, ids, vals, context=context) - section_obj=self.pool.get('crm.case.section') - for user in self.browse(cr, uid, ids, context): - if user.context_section_id: - section_obj.write(cr, uid, [user.context_section_id.id], {'member_ids':[(4, user.id)]},context) - return res -users() + def create(self, cr, uid, vals, context=None): + res = super(users, self).create(cr, uid, vals, context=context) + section_obj=self.pool.get('crm.case.section') + if vals.get('context_section_id'): + section_obj.write(cr, uid, [vals['context_section_id']], {'member_ids':[(4, res)]}, context) + return res -class res_partner(osv.osv): - _inherit = 'res.partner' - _columns = { - 'section_id': fields.many2one('crm.case.section', 'Sales Team'), - } -res_partner() +users() +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: