diff --git a/addons/base_status/__init__.py b/addons/base_status/__init__.py new file mode 100644 index 00000000000..e4107f90a37 --- /dev/null +++ b/addons/base_status/__init__.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2012-today OpenERP SA () +# +# 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_state +import base_stage + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/base_status/__openerp__.py b/addons/base_status/__openerp__.py new file mode 100644 index 00000000000..fcb8f54a7ee --- /dev/null +++ b/addons/base_status/__openerp__.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2012-today OpenERP SA () +# +# 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': 'State/Stage Management', + 'version': '1.0', + 'category': 'Hidden', + 'description': """ +This module handles state and stage. It is derived from the crm_base and +crm_case classes from crm. + + * ``base_state``: state management + * ``base_stage``: stage management + """, + 'author': 'OpenERP SA', + 'website': 'http://www.openerp.com', + 'depends': ['base'], + 'init_xml': [], + 'update_xml': [], + 'demo_xml': [], + 'installable': True, + 'auto_install': False, +} +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/base_status/base_stage.py b/addons/base_status/base_stage.py new file mode 100644 index 00000000000..cc6dda19cdf --- /dev/null +++ b/addons/base_status/base_stage.py @@ -0,0 +1,432 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-today OpenERP SA () +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +from osv import fields +from tools.translate import _ + +class base_stage(object): + """ Base utility mixin class for objects willing to manage their stages. + Object that inherit from this class should inherit from mailgate.thread + to have access to the mail gateway, as well as Chatter. Objects + subclassing this class should define the following colums: + - ``date_open`` (datetime field) + - ``date_closed`` (datetime field) + - ``user_id`` (many2one to res.users) + - ``partner_id`` (many2one to res.partner) + - ``stage_id`` (many2one to a stage definition model) + - ``state`` (selection field, related to the stage_id.state) + """ + + def _get_default_partner(self, cr, uid, context=None): + """ Gives id of partner for current user + :param context: if portal not in context returns False + """ + if context is None: + context = {} + if not context or not context.get('portal'): + return False + 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 user.company_id.partner_id.id + + def _get_default_email(self, cr, uid, context=None): + """ Gives default email address for current user + :param context: if portal not in context returns False + """ + if context is None: + context = {} + if not context or not context.get('portal'): + return False + user = self.pool.get('res.users').browse(cr, uid, uid, context=context) + return user.user_email + + def _get_default_user(self, cr, uid, context=None): + """ Gives current user id + :param context: if portal not in context returns False + """ + if context is None: + context = {} + if not context or not context.get('portal'): + return False + return uid + + def onchange_partner_address_id(self, cr, uid, ids, add, email=False): + """ This function returns value of partner email based on Partner Address + :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').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 part: Partner's id + :param email: Partner's email ID + """ + data={} + if part: + addr = self.pool.get('res.partner').address_get(cr, uid, [part], ['contact']) + data.update(self.onchange_partner_address_id(cr, uid, ids, addr['contact'])['value']) + return {'value': data} + + def _get_default_section_id(self, cr, uid, context=None): + """ Gives default section """ + return False + + def _get_default_stage_id(self, cr, uid, context=None): + """ Gives default stage_id """ + return self.stage_find(cr, uid, [], None, [('state', '=', 'draft')], context=context) + + def stage_find(self, cr, uid, cases, section_id, domain=[], order='sequence', context=None): + """ Find stage, with a given (optional) domain on the search, + ordered by the order parameter. If several stages match the + search criterions, the first one will be returned, according + to the requested search order. + This method is meant to be overriden by subclasses. That way + specific behaviors can be achieved for every class inheriting + from base_stage. + + :param cases: browse_record of cases + :param section_id: section limitating the search, given for + a generic search (for example default search). + A section models concepts such as Sales team + (for CRM), ou departments (for HR). + :param domain: a domain on the search of stages + :param order: order of the search + """ + return False + + def stage_set_with_state_name(self, cr, uid, cases, state_name, context=None): + """ Set a new stage, with a state_name instead of a stage_id + :param cases: browse_record of cases + """ + if isinstance(cases, (int, long)): + cases = self.browse(cr, uid, cases, context=context) + for case in cases: + stage_id = self.stage_find(cr, uid, [case], None, [('state', '=', state_name)], context=context) + if stage_id: + self.stage_set(cr, uid, [case.id], stage_id, context=context) + return True + + def stage_set(self, cr, uid, ids, stage_id, context=None): + """ Set the new stage. This methods is the right method to call + when changing states. It also checks whether an onchange is + defined, and execute it. + """ + value = {} + if hasattr(self, 'onchange_stage_id'): + value = self.onchange_stage_id(cr, uid, ids, stage_id, context=context)['value'] + value['stage_id'] = stage_id + self.stage_set_send_note(cr, uid, ids, stage_id, context=context) + return self.write(cr, uid, ids, value, context=context) + + def stage_change(self, cr, uid, ids, op, order, context=None): + """ Change the stage and take the next one, based on a condition + writen for the 'sequence' field and an operator. This methods + checks whether the case has a current stage, and takes its + sequence. Otherwise, a default 0 sequence is chosen and this + method will therefore choose the first available stage. + For example if op is '>' and current stage has a sequence of + 10, this will call stage_find, with [('sequence', '>', '10')]. + """ + for case in self.browse(cr, uid, ids, context=context): + seq = 0 + if case.stage_id: + seq = case.stage_id.sequence or 0 + section_id = None + next_stage_id = self.stage_find(cr, uid, [case], None, [('sequence', op, seq)],order, context=context) + 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 = {} + + 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(base_stage, self).copy(cr, uid, id, default, context=context) + + def case_escalate(self, cr, uid, ids, context=None): + """ Escalates case to parent level """ + cases = self.browse(cr, uid, ids, context=context) + cases[0].state # fill browse record cache, for _action having old and new values + 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: + if case.section_id.parent_id.user_id: + data['user_id'] = case.section_id.parent_id.user_id.id + 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) + case.case_escalate_send_note(case.section_id.parent_id, context=context) + cases = self.browse(cr, uid, ids, context=context) + self._action(cr, uid, cases, 'escalate', context=context) + return True + + def case_open(self, cr, uid, ids, context=None): + """ Opens case """ + cases = self.browse(cr, uid, ids, context=context) + for case in cases: + data = {'active': True} + if case.stage_id and case.stage_id.state == 'draft': + data['date_open'] = fields.datetime.now() + if not case.user_id: + data['user_id'] = uid + self.case_set(cr, uid, [case.id], 'open', data, context=context) + self.case_open_send_note(cr, uid, [case.id], context=context) + return True + + def case_close(self, cr, uid, ids, context=None): + """ Closes case """ + self.case_set(cr, uid, ids, 'done', {'active': True, 'date_closed': fields.datetime.now()}, context=context) + self.case_close_send_note(cr, uid, ids, context=context) + return True + + def case_cancel(self, cr, uid, ids, context=None): + """ Cancels case """ + self.case_set(cr, uid, ids, 'cancel', {'active': True}, context=context) + self.case_cancel_send_note(cr, uid, ids, context=context) + return True + + def case_pending(self, cr, uid, ids, context=None): + """ Set case as pending """ + self.case_set(cr, uid, ids, 'pending', {'active': True}, context=context) + self.case_pending_send_note(cr, uid, ids, context=context) + return True + + def case_reset(self, cr, uid, ids, context=None): + """ Resets case as draft """ + self.case_set(cr, uid, ids, 'draft', {'active': True}, context=context) + self.case_reset_send_note(cr, uid, ids, context=context) + return True + + def case_set(self, cr, uid, ids, new_state_name=None, values_to_update=None, new_stage_id=None, context=None): + """ Generic method for setting case. This methods wraps the update + of the record, as well as call to _action and browse_record + case setting to fill the cache. + + :params new_state_name: the new state of the record; this method + will call ``stage_set_with_state_name`` + that will find the stage matching the + new state, using the ``stage_find`` method. + :params new_stage_id: alternatively, you may directly give the + new stage of the record + :params state_name: the new value of the state, such as + 'draft' or 'close'. + :params update_values: values that will be added with the state + update when writing values to the record. + """ + cases = self.browse(cr, uid, ids, context=context) + cases[0].state # fill browse record cache, for _action having old and new values + # 1. update the stage + if new_state_name: + self.stage_set_with_state_name(cr, uid, cases, new_state_name, context=context) + elif not (new_stage_id is None): + self.stage_set(cr, uid, ids, new_stage_id, context=context) + # 2. update values + if values_to_update: + self.write(cr, uid, ids, values_to_update, context=context) + # 3. call _action for base action rule + if new_state_name: + self._action(cr, uid, cases, new_state_name, context=context) + elif not (new_stage_id is None): + new_state_name = self.read(cr, uid, ids, ['state'], context=context)[0]['state'] + self._action(cr, uid, cases, new_state_name, context=context) + return True + + def _action(self, cr, uid, cases, state_to, scrit=None, context=None): + 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 remind_partner(self, cr, uid, ids, context=None, attach=False): + return self.remind_user(cr, uid, ids, context, attach, + destination=False) + + def remind_user(self, cr, uid, ids, context=None, attach=False, destination=True): + mail_message = self.pool.get('mail.message') + for case in self.browse(cr, uid, ids, context=context): + 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 and message.body_text: + body = message.body_text + 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=self._name, + 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. + Must be overriden by inheriting classes. + """ + return True + + 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_thread_followers(self, cr, uid, ids, context=None): + res = {} + for case in self.browse(cr, uid, ids, context=context): + l=[] + if case.email_cc: + l.append(case.email_cc) + if case.user_id and case.user_id.user_email: + l.append(case.user_id.user_email) + res[case.id] = l + return res + + # ****************************** + # Notifications + # ****************************** + + def case_get_note_msg_prefix(self, cr, uid, id, context=None): + """ Default prefix for notifications. For example: "%s has been + closed.". As several models will inherit from base_stage, + this method returns a void string. Class using base_stage + will have to override this method to define the prefix they + want to display. + """ + return '' + + def stage_set_send_note(self, cr, uid, ids, stage_id, context=None): + """ Send a notification when the stage changes. This method has + to be overriden, because each document will have its particular + behavior and/or stage model (such as project.task.type or + crm.case.stage). + """ + return True + + def case_open_send_note(self, cr, uid, ids, context=None): + for id in ids: + msg = _('%s has been opened.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context)) + self.message_append_note(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 closed.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context)) + self.message_append_note(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 canceled.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context)) + self.message_append_note(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 pending.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context)) + self.message_append_note(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 renewed.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context)) + self.message_append_note(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 escalated to %s.' % (self.case_get_note_msg_prefix(cr, uid, id, context=context), new_section.name) + else: + msg = '%s has been escalated.' % (self.case_get_note_msg_prefix(cr, uid, id, context=context)) + self.message_append_note(cr, uid, [id], 'System Notification', msg, context=context) + return True diff --git a/addons/base_status/base_state.py b/addons/base_status/base_state.py new file mode 100644 index 00000000000..5baf71a01b6 --- /dev/null +++ b/addons/base_status/base_state.py @@ -0,0 +1,194 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-today OpenERP SA () +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +from osv import fields +from tools.translate import _ + +class base_state(object): + """ Base utility mixin class for objects willing to manage their state. + Object subclassing this class should define the following colums: + - ``date_open`` (datetime field) + - ``date_closed`` (datetime field) + - ``user_id`` (many2one to res.users) + - ``partner_id`` (many2one to res.partner) + - ``state`` (selection field) + """ + + def _get_default_partner(self, cr, uid, context=None): + """ Gives id of partner for current user + :param context: if portal not in context returns False + """ + if context is None: + context = {} + if not context or not context.get('portal'): + return False + 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 user.company_id.partner_id.id + + def _get_default_email(self, cr, uid, context=None): + """ Gives default email address for current user + :param context: if portal not in context returns False + """ + if context is None: + context = {} + if not context or not context.get('portal'): + return False + user = self.pool.get('res.users').browse(cr, uid, uid, context=context) + return user.user_email + + def _get_default_user(self, cr, uid, context=None): + """ Gives current user id + :param context: if portal not in context returns False + """ + if context is None: + context = {} + if not context or not context.get('portal'): + return False + return uid + + def onchange_partner_address_id(self, cr, uid, ids, add, email=False): + """ This function returns value of partner email based on Partner Address + :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').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 part: Partner's id + :param email: Partner's email ID + """ + data={} + if part: + addr = self.pool.get('res.partner').address_get(cr, uid, [part], ['contact']) + data.update(self.onchange_partner_address_id(cr, uid, ids, addr['contact'])['value']) + return {'value': data} + + def case_open(self, cr, uid, ids, context=None): + """ Opens case """ + cases = self.browse(cr, uid, ids, context=context) + for case in cases: + values = {'active': True} + if case.state == 'draft': + values['date_open'] = fields.datetime.now() + if not case.user_id: + values['user_id'] = uid + self.case_set(cr, uid, [case.id], 'open', values, context=context) + self.case_open_send_note(cr, uid, [case.id], context=context) + return True + + def case_close(self, cr, uid, ids, context=None): + """ Closes case """ + self.case_set(cr, uid, ids, 'done', {'date_closed': fields.datetime.now()}, context=context) + self.case_close_send_note(cr, uid, ids, context=context) + return True + + def case_cancel(self, cr, uid, ids, context=None): + """ Cancels case """ + self.case_set(cr, uid, ids, 'cancel', {'active': True}, context=context) + self.case_cancel_send_note(cr, uid, ids, context=context) + return True + + def case_pending(self, cr, uid, ids, context=None): + """ Sets case as pending """ + self.case_set(cr, uid, ids, 'pending', {'active': True}, context=context) + self.case_pending_send_note(cr, uid, ids, context=context) + return True + + def case_reset(self, cr, uid, ids, context=None): + """ Resets case as draft """ + self.case_set(cr, uid, ids, 'draft', {'active': True}, context=context) + self.case_reset_send_note(cr, uid, ids, context=context) + return True + + def case_set(self, cr, uid, ids, state_name, update_values=None, context=None): + """ Generic method for setting case. This methods wraps the update + of the record, as well as call to _action and browse_record + case setting to fill the cache. + + :params: state_name: the new value of the state, such as + 'draft' or 'close'. + :params: update_values: values that will be added with the state + update when writing values to the record. + """ + cases = self.browse(cr, uid, ids, context=context) + cases[0].state # fill browse record cache, for _action having old and new values + if update_values is None: + update_values = {} + update_values['state'] = state_name + self.write(cr, uid, ids, update_values, context=context) + self._action(cr, uid, cases, state_name, context=context) + + def _action(self, cr, uid, cases, state_to, scrit=None, context=None): + if context is None: + context = {} + context['state_to'] = state_to + rule_obj = self.pool.get('base.action.rule') + model_obj = self.pool.get('ir.model') + model_ids = model_obj.search(cr, uid, [('model','=',self._name)]) + rule_ids = rule_obj.search(cr, uid, [('model_id','=',model_ids[0])]) + return rule_obj._action(cr, uid, rule_ids, cases, scrit=scrit, context=context) + + # ****************************** + # Notifications + # ****************************** + + def case_get_note_msg_prefix(self, cr, uid, id, context=None): + return '' + + def case_open_send_note(self, cr, uid, ids, context=None): + for id in ids: + msg = _('%s has been opened.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context)) + self.message_append_note(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 closed.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context)) + self.message_append_note(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 canceled.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context)) + self.message_append_note(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 pending.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context)) + self.message_append_note(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 renewed.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context)) + self.message_append_note(cr, uid, [id], body=msg, context=context) + return True diff --git a/addons/crm/__openerp__.py b/addons/crm/__openerp__.py index 2049b3a8cc9..0c9b75b4d41 100644 --- a/addons/crm/__openerp__.py +++ b/addons/crm/__openerp__.py @@ -57,6 +57,7 @@ Creates a dashboard for CRM that includes: 'depends': [ 'base_action_rule', 'base_setup', + 'base_status', 'process', 'mail', 'base_calendar', @@ -66,7 +67,6 @@ Creates a dashboard for CRM that includes: ], 'init_xml': [ 'crm_data.xml', - 'crm_meeting_data.xml', 'crm_lead_data.xml', 'crm_meeting_data.xml', 'crm_phonecall_data.xml', diff --git a/addons/crm/board_crm_view.xml b/addons/crm/board_crm_view.xml index b8e05b68cf5..524a49ef025 100644 --- a/addons/crm/board_crm_view.xml +++ b/addons/crm/board_crm_view.xml @@ -19,19 +19,13 @@ - -