diff options
57 files changed, 2290 insertions, 1633 deletions
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 (<http://openerp.com>) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +############################################################################## + +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 (<http://openerp.com>) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +############################################################################## + +{ + '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 (<http://www.openerp.com>) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +############################################################################## + +from 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 + <b>closed</b>.". 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 <b>opened</b>.') % (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 <b>closed</b>.') % (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 <b>canceled</b>.') % (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 <b>pending</b>.') % (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 <b>renewed</b>.') % (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 <b>escalated</b> to <b>%s</b>.' % (self.case_get_note_msg_prefix(cr, uid, id, context=context), new_section.name) + else: + msg = '%s has been <b>escalated</b>.' % (self.case_get_note_msg_prefix(cr, uid, id, context=context)) + self.message_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 (<http://www.openerp.com>) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +############################################################################## + +from 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 <b>opened</b>.') % (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 <b>closed</b>.') % (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 <b>canceled</b>.') % (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 <b>pending</b>.') % (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 <b>renewed</b>.') % (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 @@ <field name="planned_revenue" sum="Total of Planned Revenue"/> <field name="probability" widget="progressbar" avg="Avg. of Probability"/> <field name="date_deadline" invisible="1"/> - <field name="state"/> - <button name="case_open" string="Open" - states="draft,pending" type="object" - icon="gtk-go-forward" /> - <button name="case_pending" string="Pending" - states="open,draft" type="object" - icon="gtk-media-pause" /> - <button name="case_mark_lost" string="Lost" - states="open,pending" type="object" - icon="gtk-cancel" /> - <button name="case_close" string="Won" - states="open,draft,pending" type="object" - icon="gtk-apply" /> + <field name="state" groups="base.group_no_one"/> + <button name="case_open" string="Open" type="object" + states="draft,pending" icon="gtk-go-forward"/> + <button name="case_mark_won" string="Won" type="object" + states="open,pending" icon="gtk-apply"/> + <button name="case_mark_lost" string="Lost" type="object" + states="open,pending" icon="gtk-cancel"/> </tree> </field> </record> diff --git a/addons/crm/crm.py b/addons/crm/crm.py index eb6314c5103..65af3f9e530 100644 --- a/addons/crm/crm.py +++ b/addons/crm/crm.py @@ -2,7 +2,7 @@ ############################################################################## # # OpenERP, Open Source Management Solution -# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>). +# Copyright (C) 2004-today OpenERP SA (<http://www.openerp.com>) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -19,12 +19,11 @@ # ############################################################################## -import time import base64 -import tools - +import time from osv import fields from osv import osv +import tools from tools.translate import _ MAX_LEVEL = 15 @@ -57,8 +56,11 @@ class crm_case_channel(osv.osv): } class crm_case_stage(osv.osv): - """ Stage of case """ - + """ Model for case stages. This models the main stages of a document + management flow. Main CRM objects (leads, opportunities, project + issues, ...) will now use only stages, instead of state and stages. + Stages are for example used to display the kanban view of records. + """ _name = "crm.case.stage" _description = "Stage of case" _rec_name = 'name' @@ -66,22 +68,34 @@ class crm_case_stage(osv.osv): _columns = { 'name': fields.char('Stage Name', size=64, required=True, translate=True), - 'sequence': fields.integer('Sequence', help="Used to order stages."), + 'sequence': fields.integer('Sequence', help="Used to order stages. Lower is better."), '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."), + 'section_ids':fields.many2many('crm.case.section', 'section_stage_rel', 'stage_id', 'section_id', string='Sections', + help="Link between stages and sales teams. When set, this limitate the current stage to the selected sales teams."), + 'state': fields.selection(AVAILABLE_STATES, 'State', required=True, help="The related state for the stage. The state of your document will automatically change regarding the selected stage. For example, if a stage is related to the state 'Close', when your document reaches this stage, it will be automatically have the 'closed' state."), + '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."), + 'fold': fields.boolean('Hide in Views when Empty', + help="This stage is not visible, for example in status bar or kanban view, when there are no records in that stage to display."), + 'type': fields.selection([ ('lead','Lead'), + ('opportunity', 'Opportunity'), + ('both', 'Both')], + string='Type', size=16, required=True, + help="This field is used to distinguish stages related to Leads from stages related to Opportunities, or to specify stages available for both types."), } _defaults = { 'sequence': lambda *args: 1, 'probability': lambda *args: 0.0, + 'state': 'draft', + 'fold': False, + 'type': 'both', } class crm_case_section(osv.osv): - """Sales Team""" - + """ Model for sales teams. """ _name = "crm.case.section" _description = "Sales Teams" _order = "complete_name" @@ -107,6 +121,7 @@ class crm_case_section(osv.osv): '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 @@ -171,429 +186,6 @@ class crm_case_resource_type(osv.osv): '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 - """ - def _get_default_partner_address(self, cr, uid, context=None): - """Gives id of default address for current user - :param context: if portal in context is false return false anyway - """ - if context is None: - context = {} - if not context.get('portal'): - return False - # 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 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 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 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) - return user.user_email - - def _get_default_user(self, cr, uid, context=None): - """Gives current user id - :param context: if portal in context is false return false anyway - """ - if context and context.get('portal', False): - return False - return uid - - def _get_section(self, cr, uid, context=None): - return False - - 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').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 ids: List of case IDs - :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_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 <b>opened</b>.' % (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 <b>closed</b>.'% (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 <b>canceled</b>.' % (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 <b>pending</b>.' % (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 <b>renewed</b>.' % (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_open(self, cr, uid, ids, context=None): - """Opens Case - :param ids: List of case Ids - """ - cases = self.browse(cr, uid, ids) - 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.case_open_send_note(cr, uid, ids, context=context) - self._action(cr, uid, cases, 'open') - - return True - - def case_close(self, cr, uid, ids, context=None): - """Closes 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': 'done', 'date_closed': time.strftime('%Y-%m-%d %H:%M:%S'), }) - # We use the cache of cases to keep the old case state - self.case_close_send_note(cr, uid, ids, context=context) - self._action(cr, uid, cases, 'done') - return True - - def case_cancel(self, cr, uid, ids, context=None): - """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.case_cancel_send_note(cr, uid, ids, context=context) - self._action(cr, uid, cases, 'cancel') - return True - - def case_pending(self, cr, uid, ids, context=None): - """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.case_pending_send_note(cr, uid, ids, context=context) - self._action(cr, uid, cases, 'pending') - return True - - def case_reset(self, cr, uid, ids, context=None): - """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.case_reset_send_note(cr, uid, ids, context=context) - 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_escalate_send_note(self, cr, uid, ids, new_section=None, context=None): - for id in ids: - if new_section: - msg = '%s has been <b>escalated</b> to <b>%s</b>.' % (self.case_get_note_msg_prefix(cr, uid, id, context=context), new_section.name) - else: - msg = '%s has been <b>escalated</b>.' % (self.case_get_note_msg_prefix(cr, uid, id, context=context)) - self.message_append_note(cr, uid, [id], 'System Notification', msg, context=context) - return True - - def case_get_note_msg_prefix(self, cr, uid, id, context=None): - return '' - - def case_open(self, cr, uid, ids, context=None): - """Opens Case""" - cases = self.browse(cr, uid, ids) - 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.case_open_send_note(cr, uid, ids, context=context) - self._action(cr, uid, cases, 'open') - return True - - def case_close(self, cr, uid, ids, context=None): - """Closes Case""" - 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.case_close_send_note(cr, uid, ids, context=context) - self._action(cr, uid, cases, 'done') - return True - - def case_escalate(self, cr, uid, ids, context=None): - """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: - 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) - case.case_escalate_send_note(case.section_id.parent_id) - cases = self.browse(cr, uid, ids) - self._action(cr, uid, cases, 'escalate') - return True - - def case_cancel(self, cr, uid, ids, context=None): - """Cancels Case""" - cases = self.browse(cr, uid, ids) - cases[0].state # to fill the browse record cache - self.write(cr, uid, ids, {'state': 'cancel', - 'active': True}) - self.case_cancel_send_note(cr, uid, ids, context=context) - self._action(cr, uid, cases, 'cancel') - return True - - def case_pending(self, cr, uid, ids, context=None): - """Marks case as pending""" - 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.case_pending_send_note(cr, uid, ids, context=context) - self._action(cr, uid, cases, 'pending') - return True - - def case_reset(self, cr, uid, ids, context=None): - """Resets case as draft""" - state = 'draft' - cases = self.browse(cr, uid, ids) - cases[0].state # to fill the browse record cache - self.write(cr, uid, ids, {'state': state, 'active': True}) - self.case_reset_send_note(cr, uid, ids, context=context) - self._action(cr, uid, cases, state) - return True - - 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 - 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=context) - return self._action(cr, uid, cases, False, 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_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 def _links_get(self, cr, uid, context=None): """Gets links value for reference field""" diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index bf20deb0ce5..ee6b9835f02 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -2,7 +2,7 @@ ############################################################################## # # OpenERP, Open Source Management Solution -# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>). +# Copyright (C) 2004-today OpenERP SA (<http://www.openerp.com>) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -19,15 +19,15 @@ # ############################################################################## -from osv import fields, osv -from datetime import datetime +import binascii +from base_status.base_stage import base_stage import crm +from datetime import datetime +from mail.mail_message import to_email +from osv import fields, osv import time -from tools.translate import _ -from crm import crm_case -import binascii import tools -from mail.mail_message import to_email +from tools.translate import _ CRM_LEAD_PENDING_STATES = ( crm.AVAILABLE_STATES[2][0], # Cancelled @@ -35,22 +35,68 @@ CRM_LEAD_PENDING_STATES = ( crm.AVAILABLE_STATES[4][0], # Pending ) -class crm_lead(crm_case, osv.osv): +class crm_lead(base_stage, osv.osv): """ CRM Lead Case """ _name = "crm.lead" _description = "Lead/Opportunity" _order = "priority,date_action,id desc" _inherit = ['ir.needaction_mixin', 'mail.thread','res.partner'] + def _get_default_section_id(self, cr, uid, context=None): + """ Gives default section by checking if present in the context """ + return (self._resolve_section_id_from_context(cr, uid, context=context) or False) + + def _get_default_stage_id(self, cr, uid, context=None): + """ Gives default stage_id """ + section_id = self._get_default_section_id(cr, uid, context=context) + return self.stage_find(cr, uid, [], section_id, [('state', '=', 'draft'), ('type', '=', 'both')], context=context) + + def _resolve_section_id_from_context(self, cr, uid, context=None): + """ Returns ID of section based on the value of 'section_id' + context key, or None if it cannot be resolved to a single + Sales Team. + """ + if context is None: + context = {} + if type(context.get('default_section_id')) in (int, long): + return context.get('default_section_id') + if isinstance(context.get('default_section_id'), basestring): + section_name = context['default_section_id'] + section_ids = self.pool.get('crm.case.section').name_search(cr, uid, name=section_name, context=context) + if len(section_ids) == 1: + return int(section_ids[0][0]) + return None + + def _resolve_type_from_context(self, cr, uid, context=None): + """ Returns the type (lead or opportunity) from the type context + key. Returns None if it cannot be resolved. + """ + if context is None: + context = {} + return context.get('default_type') + def _read_group_stage_ids(self, cr, uid, ids, domain, read_group_order=None, access_rights_uid=None, context=None): access_rights_uid = access_rights_uid or uid stage_obj = self.pool.get('crm.case.stage') order = stage_obj._order + # lame hack to allow reverting search, should just work in the trivial case if read_group_order == 'stage_id desc': - # lame hack to allow reverting search, should just work in the trivial case order = "%s desc" % order - stage_ids = stage_obj._search(cr, uid, ['|', ('id','in',ids),('case_default','=',1)], order=order, - access_rights_uid=access_rights_uid, context=context) + # retrieve section_id from the context and write the domain + # - ('id', 'in', 'ids'): add columns that should be present + # - OR ('case_default', '=', True), ('fold', '=', False): add default columns that are not folded + # - OR ('section_ids', '=', section_id), ('fold', '=', False) if section_id: add section columns that are not folded + search_domain = [] + section_id = self._resolve_section_id_from_context(cr, uid, context=context) + if section_id: + search_domain += ['|', '&', ('section_ids', '=', section_id), ('fold', '=', False)] + search_domain += ['|', ('id', 'in', ids), '&', ('case_default', '=', True), ('fold', '=', False)] + # retrieve type from the context (if set: choose 'type' or 'both') + type = self._resolve_type_from_context(cr, uid, context=context) + if type: + search_domain += ['|', ('type', '=', type), ('type', '=', 'both')] + # perform search + stage_ids = stage_obj._search(cr, uid, search_domain, order=order, access_rights_uid=access_rights_uid, context=context) result = stage_obj.name_get(cr, access_rights_uid, stage_ids, context=context) # restore order of the search result.sort(lambda x,y: cmp(stage_ids.index(x[0]), stage_ids.index(y[0]))) @@ -154,7 +200,6 @@ class crm_lead(crm_case, osv.osv): 'email_cc': fields.text('Global CC', size=252 , help="These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma"), 'description': fields.text('Notes'), 'write_date': fields.datetime('Update Date' , readonly=True), - 'categ_id': fields.many2one('crm.case.categ', 'Category', \ domain="['|',('section_id','=',section_id),('section_id','=',False), ('object_id.model', '=', 'crm.lead')]"), 'type_id': fields.many2one('crm.case.resource.type', 'Campaign', \ @@ -167,7 +212,8 @@ class crm_lead(crm_case, osv.osv): 'type':fields.selection([ ('lead','Lead'), ('opportunity','Opportunity'), ],'Type', help="Type is used to separate Leads and Opportunities"), 'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority', select=True), 'date_closed': fields.datetime('Closed', readonly=True), - 'stage_id': fields.many2one('crm.case.stage', 'Stage', domain="[('section_ids', '=', section_id)]"), + 'stage_id': fields.many2one('crm.case.stage', 'Stage', + domain="['&', '|', ('section_ids', '=', section_id), ('case_default', '=', True), '|', ('type', '=', type), ('type', '=', 'both')]"), 'user_id': fields.many2one('res.users', 'Salesperson'), 'referred': fields.char('Referred By', size=64), 'date_open': fields.datetime('Opened', readonly=True), @@ -175,11 +221,13 @@ class crm_lead(crm_case, osv.osv): multi='day_open', type="float", store=True), 'day_close': fields.function(_compute_day, string='Days to Close', \ multi='day_close', type="float", store=True), - 'state': fields.selection(crm.AVAILABLE_STATES, 'Status', size=16, readonly=True, - help='The state is set to \'Draft\', when a case is created.\ - \nIf the case is in progress the state is set to \'In progress\'.\ - \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\'.'), + 'state': fields.related('stage_id', 'state', type="selection", store=True, + selection=crm.AVAILABLE_STATES, string="State", readonly=True, + help='The state is set to \'Draft\', when a case is created.\ + If the case is in progress the state is set to \'Open\'.\ + When the case is over, the state is set to \'Done\'.\ + If the case needs to be reviewed then the state is \ + set to \'Pending\'.'), 'message_ids': fields.one2many('mail.message', 'res_id', 'Messages', domain=[('model','=',_name)]), 'subjects': fields.function(_get_email_subject, fnct_search=_history_search, string='Subject of Email', type='char', size=64), @@ -192,23 +240,21 @@ class crm_lead(crm_case, osv.osv): 'date_deadline': fields.date('Expected Closing'), 'date_action': fields.date('Next Action Date', select=True), 'title_action': fields.char('Next Action', size=64), - 'stage_id': fields.many2one('crm.case.stage', 'Stage', domain="[('section_ids', '=', section_id)]"), 'color': fields.integer('Color Index'), 'partner_address_name': fields.related('partner_id', 'name', type='char', string='Partner Contact Name', readonly=True), 'partner_address_email': fields.related('partner_id', 'email', type='char', string='Partner Contact Email', readonly=True), 'company_currency': fields.related('company_id', 'currency_id', 'symbol', type='char', string='Company Currency', readonly=True), 'user_email': fields.related('user_id', 'user_email', type='char', string='User Email', readonly=True), 'user_login': fields.related('user_id', 'login', type='char', string='User Login', readonly=True), - } _defaults = { - 'active': lambda *a: 1, - 'user_id': crm_case._get_default_user, - 'email_from': crm_case._get_default_email, - 'state': lambda *a: 'draft', - 'type': lambda *a: 'lead', - 'section_id': crm_case._get_section, + 'active': 1, + 'type': 'lead', + 'user_id': lambda s, cr, uid, c: s._get_default_user(cr, uid, c), + 'email_from': lambda s, cr, uid, c: s._get_default_email(cr, uid, c), + 'stage_id': lambda s, cr, uid, c: s._get_default_stage_id(cr, uid, c), + 'section_id': lambda s, cr, uid, c: s._get_default_section_id(cr, uid, c), 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.lead', context=c), 'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0], 'color': 0, @@ -241,95 +287,104 @@ class crm_lead(crm_case, osv.osv): return {'value':{}} return {'value':{'probability': stage.probability}} - def stage_find_percent(self, cr, uid, percent, section_id): - """ Return the first stage with a probability == percent + def _check(self, cr, uid, ids=False, context=None): + """ Override of the base.stage method. + 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=context) + return self._action(cr, uid, cases, False, context=context) + + def stage_find(self, cr, uid, cases, section_id, domain=[], order='sequence', context=None): + """ Override of the base.stage method + Parameter of the stage search taken from the lead: + - type: stage type must be the same or 'both' + - section_id: if set, stages must belong to this section or + be a default stage; if not set, stages must be default + stages """ - stage_pool = self.pool.get('crm.case.stage') - if section_id : - ids = stage_pool.search(cr, uid, [("probability", '=', percent), ("section_ids", 'in', [section_id])]) - else : - ids = stage_pool.search(cr, uid, [("probability", '=', percent)]) - - if ids: - return ids[0] + if isinstance(cases, (int, long)): + cases = self.browse(cr, uid, cases, context=context) + # collect all section_ids + section_ids = [] + types = ['both'] + if section_id: + section_ids.append(section_id) + for lead in cases: + if lead.section_id: + section_ids.append(lead.section_id.id) + if lead.type not in types: + types.append(lead.type) + # OR all section_ids and OR with case_default + search_domain = [] + if section_ids: + search_domain += [('|')] * len(section_ids) + for section_id in section_ids: + search_domain.append(('section_ids', '=', section_id)) + search_domain.append(('case_default', '=', True)) + # AND with cases types + search_domain.append(('type', 'in', types)) + # AND with the domain in parameter + search_domain += list(domain) + # perform search, return the first found + stage_ids = self.pool.get('crm.case.stage').search(cr, uid, search_domain, order=order, context=context) + if stage_ids: + return stage_ids[0] return False - def stage_find_lost(self, cr, uid, section_id): - return self.stage_find_percent(cr, uid, 0.0, section_id) - - def stage_find_won(self, cr, uid, section_id): - return self.stage_find_percent(cr, uid, 100.0, section_id) - - def case_open(self, cr, uid, ids, context=None): - for lead in self.browse(cr, uid, ids, context=context): - if lead.state == 'draft': - value = {'date_open': time.strftime('%Y-%m-%d %H:%M:%S')} - self.write(cr, uid, [lead.id], value) - if lead.type == 'opportunity' and not lead.stage_id: - stage_id = self.stage_find(cr, uid, lead.section_id.id or False, [('sequence','>',0)]) - if stage_id: - self.stage_set(cr, uid, [lead.id], stage_id) - res = super(crm_lead, self).case_open(cr, uid, ids, context) - return res - - def case_close(self, cr, uid, ids, context=None): - res = super(crm_lead, self).case_close(cr, uid, ids, context) - self.write(cr, uid, ids, {'date_closed': time.strftime('%Y-%m-%d %H:%M:%S')}) - return res - def case_cancel(self, cr, uid, ids, context=None): - """Overrides cancel for crm_case for setting probability - """ - res = super(crm_lead, self).case_cancel(cr, uid, ids, context) - self.write(cr, uid, ids, {'probability' : 0.0}) + """ Overrides case_cancel from base_stage to set probability """ + res = super(crm_lead, self).case_cancel(cr, uid, ids, context=context) + self.write(cr, uid, ids, {'probability' : 0.0}, context=context) return res def case_reset(self, cr, uid, ids, context=None): - """Overrides reset as draft in order to set the stage field as empty - """ - res = super(crm_lead, self).case_reset(cr, uid, ids, context) - self.write(cr, uid, ids, {'stage_id': False, 'probability': 0.0}) + """ Overrides case_reset from base_stage to set probability """ + res = super(crm_lead, self).case_reset(cr, uid, ids, context=context) + self.write(cr, uid, ids, {'probability': 0.0}, context=context) return res def case_mark_lost(self, cr, uid, ids, context=None): - """Mark the case as lost: state = done and probability = 0% - """ - res = super(crm_lead, self).case_close(cr, uid, ids, context) - self.write(cr, uid, ids, {'probability' : 0.0}) + """ Mark the case as lost: state=cancel and probability=0 """ for lead in self.browse(cr, uid, ids): - stage_id = self.stage_find_lost(cr, uid, lead.section_id.id or False) + stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, [('probability', '=', 0.0)], context=context) if stage_id: - self.stage_set(cr, uid, [lead.id], stage_id) - return res + self.case_set(cr, uid, [lead.id], values_to_update={'probability': 0.0}, new_stage_id=stage_id, context=context) + self.case_mark_lost_send_note(cr, uid, ids, context=context) + return True def case_mark_won(self, cr, uid, ids, context=None): - """Mark the case as lost: state = done and probability = 0% - """ - res = super(crm_lead, self).case_close(cr, uid, ids, context=None) - self.write(cr, uid, ids, {'probability' : 100.0}) + """ Mark the case as lost: state=done and probability=100 """ for lead in self.browse(cr, uid, ids): - stage_id = self.stage_find_won(cr, uid, lead.section_id.id or False) + stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, [('probability', '=', 100.0)], context=context) if stage_id: - self.stage_set(cr, uid, [lead.id], stage_id) - self.case_mark_won_send_note(cr, uid, [lead.id], context=context) - return res + self.case_set(cr, uid, [lead.id], values_to_update={'probability': 100.0}, new_stage_id=stage_id, context=context) + self.case_mark_won_send_note(cr, uid, ids, context=context) + return True def set_priority(self, cr, uid, ids, priority): - """Set lead priority + """ Set lead priority """ return self.write(cr, uid, ids, {'priority' : priority}) def set_high_priority(self, cr, uid, ids, context=None): - """Set lead priority to high + """ Set lead priority to high """ return self.set_priority(cr, uid, ids, '1') def set_normal_priority(self, cr, uid, ids, context=None): - """Set lead priority to normal + """ Set lead priority to normal """ return self.set_priority(cr, uid, ids, '3') - def _merge_data(self, cr, uid, ids, oldest, fields, context=None): # prepare opportunity data into dictionary for merging opportunities = self.browse(cr, uid, ids, context=context) @@ -807,22 +862,12 @@ class crm_lead(crm_case, osv.osv): "You should better cancel it, instead of deleting it.") % lead.name) return super(crm_lead, self).unlink(cr, uid, ids, context) - def write(self, cr, uid, ids, vals, context=None): - if not context: - context = {} - - if 'date_closed' in vals: - return super(crm_lead,self).write(cr, uid, ids, vals, context=context) - - if vals.get('stage_id'): - stage = self.pool.get('crm.case.stage').browse(cr, uid, vals['stage_id'], context=context) + if vals.get('stage_id') and not vals.get('probability'): # change probability of lead(s) if required by stage - if not vals.get('probability') and stage.on_change: + stage = self.pool.get('crm.case.stage').browse(cr, uid, vals['stage_id'], context=context) + if stage.on_change: vals['probability'] = stage.probability - for case in self.browse(cr, uid, ids, context=context): - message = _("Stage changed to <b>%s</b>.") % (stage.name) - case.message_append_note(body=message) return super(crm_lead,self).write(cr, uid, ids, vals, context) # ---------------------------------------- @@ -837,6 +882,11 @@ class crm_lead(crm_case, osv.osv): sub_ids.append(obj.user_id.id) return self.pool.get('res.users').read(cr, uid, sub_ids, context=context) + def stage_set_send_note(self, cr, uid, ids, stage_id, context=None): + """ Override of the (void) default notification method. """ + stage_name = self.pool.get('crm.case.stage').name_get(cr, uid, [stage_id], context=context)[0][1] + return self.message_append_note(cr, uid, ids, body= _("Stage changed to <b>%s</b>.") % (stage_name), context=context) + def case_get_note_msg_prefix(self, cr, uid, lead, context=None): if isinstance(lead, (int, long)): lead = self.browse(cr, uid, [lead], context=context)[0] diff --git a/addons/crm/crm_lead_data.xml b/addons/crm/crm_lead_data.xml index aaa00307bfe..306a35924a9 100644 --- a/addons/crm/crm_lead_data.xml +++ b/addons/crm/crm_lead_data.xml @@ -3,47 +3,81 @@ <data noupdate="1"> <!-- Crm stages --> - <record model="crm.case.stage" id="stage_lead6"> - <field name="name">Lost</field> - <field eval="1" name="case_default"/> - <field eval="'0'" name="probability"/> - <field eval="'0'" name="sequence"/> - </record> <record model="crm.case.stage" id="stage_lead1"> <field name="name">New</field> <field eval="1" name="case_default"/> + <field name="state">draft</field> <field eval="'10'" name="probability"/> - <field eval="'11'" name="sequence"/> + <field eval="'10'" name="sequence"/> + <field name="type">both</field> </record> <record model="crm.case.stage" id="stage_lead2"> + <field name="name">Opportunity</field> + <field eval="1" name="case_default"/> + <field name="state">open</field> + <field eval="'20'" name="probability"/> + <field eval="'11'" name="sequence"/> + <field name="type">lead</field> + </record> + <record model="crm.case.stage" id="stage_lead3"> <field name="name">Qualification</field> <field eval="1" name="case_default"/> + <field name="state">open</field> <field eval="'20'" name="probability"/> <field eval="'12'" name="sequence"/> + <field name="type">opportunity</field> </record> - <record model="crm.case.stage" id="stage_lead3"> + <record model="crm.case.stage" id="stage_lead4"> <field name="name">Proposition</field> <field eval="1" name="case_default"/> + <field name="state">open</field> <field eval="'40'" name="probability"/> <field eval="'13'" name="sequence"/> + <field name="type">opportunity</field> </record> - <record model="crm.case.stage" id="stage_lead4"> + <record model="crm.case.stage" id="stage_lead5"> <field name="name">Negotiation</field> <field eval="1" name="case_default"/> + <field name="state">open</field> <field eval="'60'" name="probability"/> <field eval="'14'" name="sequence"/> + <field name="type">opportunity</field> </record> - <record model="crm.case.stage" id="stage_lead5"> + <record model="crm.case.stage" id="stage_lead6"> <field name="name">Won</field> <field eval="1" name="case_default"/> + <field name="state">done</field> <field eval="'100'" name="probability"/> <field eval="'15'" name="sequence"/> <field eval="1" name="on_change"/> + <field name="type">opportunity</field> + </record> + <record model="crm.case.stage" id="stage_lead7"> + <field name="name">Dead</field> + <field eval="1" name="case_default"/> + <field eval="True" name="fold"/> + <field name="state">cancel</field> + <field eval="'0'" name="probability"/> + <field eval="'16'" name="sequence"/> + <field name="type">lead</field> + </record> + <record model="crm.case.stage" id="stage_lead8"> + <field name="name">Lost</field> + <field eval="1" name="case_default"/> + <field eval="True" name="fold"/> + <field name="state">cancel</field> + <field eval="'0'" name="probability"/> + <field eval="'17'" name="sequence"/> + <field name="type">opportunity</field> </record> + <record model="crm.case.section" id="section_sales_department"> <field name="name">Sales Department</field> <field name="code">Sales</field> - <field name="stage_ids" eval="[(4, ref('stage_lead1')), (4, ref('stage_lead2')), (4, ref('stage_lead3')), (4, ref('stage_lead4')), (4, ref('stage_lead5')), (4, ref('stage_lead6'))]"/> + <field name="stage_ids" eval="[ (4, ref('stage_lead1')), (4, ref('stage_lead2')), + (4, ref('stage_lead3')), (4, ref('stage_lead4')), + (4, ref('stage_lead5')), (4, ref('stage_lead6')), + (4, ref('stage_lead7')), (4, ref('stage_lead8'))]"/> </record> <!-- Crm campain --> diff --git a/addons/crm/crm_lead_demo.xml b/addons/crm/crm_lead_demo.xml index 19987f8f61a..21c7f9e31ab 100644 --- a/addons/crm/crm_lead_demo.xml +++ b/addons/crm/crm_lead_demo.xml @@ -8,14 +8,13 @@ <field name="type">lead</field> <field name="user_id" ref="base.user_root"/> <field eval="'The Oil Company'" name="partner_name"/> - <field eval="'draft'" name="state"/> <field name="section_id" ref="crm.section_sales_marketing_department"/> <field eval="'Luc Latour'" name="contact_name"/> <field name="title" ref="base.res_partner_title_sir"/> - <field eval="'Training Manager'" name="function"/> - <field eval="'Paris'" name="city"/> - <field name="country_id" ref="base.fr"/> - <field eval="'luc.latour@oilcompany.fr'" name="email_from"/> + <field eval="'Training Manager'" name="function"/> + <field eval="'Paris'" name="city"/> + <field name="country_id" ref="base.fr"/> + <field eval="'luc.latour@oilcompany.fr'" name="email_from"/> <field eval="'0033 621 782-0636'" name="mobile"/> <field eval="1" name="active"/> <field name="stage_id" ref="crm.stage_lead1"/> @@ -29,18 +28,17 @@ <field name="type">lead</field> <field name="user_id" ref="base.user_root"/> <field eval="'Le Club SARL'" name="partner_name"/> - <field eval="'Marc Dufour'" name="contact_name"/> + <field eval="'Marc Dufour'" name="contact_name"/> <field name="title" ref="base.res_partner_title_sir"/> - <field eval="'Purchase Manager'" name="function"/> - <field eval="'Bordeaux'" name="city"/> - <field name="country_id" ref="base.fr"/> - <field eval="'md@leclub.fr'" name="email_from"/> - <field eval="'draft'" name="state"/> + <field eval="'Purchase Manager'" name="function"/> + <field eval="'Bordeaux'" name="city"/> + <field name="country_id" ref="base.fr"/> + <field eval="'md@leclub.fr'" name="email_from"/> <field name="section_id" ref="crm.section_sales_department"/> <field eval="'(392) 895-7917'" name="mobile"/> <field eval="1" name="active"/> <field name="categ_id" ref="crm.categ_oppor2"/> - <field name="stage_id" ref="crm.stage_lead2"/> + <field name="stage_id" ref="crm.stage_lead1"/> <field eval="'Interest in Your New Product'" name="name"/> <field eval="'(956) 293-2595'" name="phone"/> </record> @@ -50,15 +48,14 @@ <field name="type">lead</field> <field name="user_id" ref="base.user_demo"/> <field eval="'The Kompany'" name="partner_name"/> - <field eval="'John Miller'" name="contact_name"/> - <field eval="'New-York'" name="city"/> - <field name="country_id" ref="base.us"/> - <field eval="'draft'" name="state"/> + <field eval="'John Miller'" name="contact_name"/> + <field eval="'New-York'" name="city"/> + <field name="country_id" ref="base.us"/> <field name="section_id" ref="crm.section_sales_department"/> <field eval="'(820) 167-3208'" name="mobile"/> <field eval="1" name="active"/> <field name="categ_id" ref="crm.categ_oppor4"/> - <field name="stage_id" ref="crm.stage_lead2"/> + <field name="stage_id" ref="crm.stage_lead1"/> <field eval="'Need Info about Web Design'" name="name"/> <field eval="'(079) 681-2139'" name="phone"/> <field eval="'contact@thkompany.com'" name="email_from"/> @@ -68,10 +65,9 @@ <field eval="'3'" name="priority"/> <field name="type">lead</field> <field eval="'The Gas Company'" name="partner_name"/> - <field eval="'Henry Mc Coy'" name="contact_name"/> - <field eval="'London'" name="city"/> - <field name="country_id" ref="base.uk"/> - <field eval="'draft'" name="state"/> + <field eval="'Henry Mc Coy'" name="contact_name"/> + <field eval="'London'" name="city"/> + <field name="country_id" ref="base.uk"/> <field name="section_id" ref="crm.section_sales_department"/> <field eval="'(077) 582-4035'" name="mobile"/> <field eval="1" name="active"/> @@ -87,16 +83,15 @@ <field name="type">lead</field> <field name="user_id" ref="base.user_root"/> <field eval="'Stonage IT'" name="partner_name"/> - <field eval="'Carrie Helle'" name="contact_name"/> - <field eval="'Purchase Manager'" name="function"/> - <field eval="'Bruxelles'" name="city"/> - <field name="country_id" ref="base.be"/> - <field eval="'draft'" name="state"/> + <field eval="'Carrie Helle'" name="contact_name"/> + <field eval="'Purchase Manager'" name="function"/> + <field eval="'Bruxelles'" name="city"/> + <field name="country_id" ref="base.be"/> <field name="section_id" ref="crm.section_sales_marketing_department"/> <field eval="'(333) 715-1450'" name="mobile"/> <field eval="1" name="active"/> <field name="categ_id" ref="crm.categ_oppor1"/> - <field name="stage_id" ref="crm.stage_lead2"/> + <field name="stage_id" ref="crm.stage_lead1"/> <field eval="'Need a Quotation for PC1'" name="name"/> <field eval="'(855) 924-4364'" name="phone"/> <field eval="'helle@stonageit.be'" name="email_from"/> @@ -110,15 +105,14 @@ <field name="type">lead</field> <field name="user_id" ref="base.user_root"/> <field eval="'Opensides'" name="partner_name"/> - <field eval="'Tina Pinero'" name="contact_name"/> - <field eval="'Consultant'" name="function"/> - <field eval="'Roma'" name="city"/> - <field name="country_id" ref="base.it"/> - <field eval="'draft'" name="state"/> + <field eval="'Tina Pinero'" name="contact_name"/> + <field eval="'Consultant'" name="function"/> + <field eval="'Roma'" name="city"/> + <field name="country_id" ref="base.it"/> <field name="section_id" ref="crm.section_sales_department"/> <field eval="'(468) 017-2684'" name="mobile"/> <field name="categ_id" ref="crm.categ_oppor8"/> - <field name="stage_id" ref="crm.stage_lead6"/> + <field name="stage_id" ref="crm.stage_lead1"/> <field eval="'Need Info about your Services'" name="name"/> <field eval="'(373) 907-1009'" name="phone"/> <field eval="'info@opensides.be'" name="email_from"/> @@ -130,15 +124,14 @@ <field name="type">lead</field> <field name="user_id" ref="base.user_demo"/> <field eval="'Gardner Group'" name="partner_name"/> - <field eval="'Wendi Baltz'" name="contact_name"/> - <field eval="'Journalist'" name="function"/> - <field eval="'Kiev'" name="city"/> - <field name="country_id" ref="base.ua"/> - <field eval="'draft'" name="state"/> + <field eval="'Wendi Baltz'" name="contact_name"/> + <field eval="'Journalist'" name="function"/> + <field eval="'Kiev'" name="city"/> + <field name="country_id" ref="base.ua"/> <field name="section_id" ref="crm.section_sales_department"/> <field eval="'(463) 014-1208'" name="mobile"/> <field name="categ_id" ref="crm.categ_oppor4"/> - <field name="stage_id" ref="crm.stage_lead2"/> + <field name="stage_id" ref="crm.stage_lead1"/> <field eval="'Info about Your Company ?'" name="name"/> <field eval="'(282) 603-7489'" name="phone"/> </record> @@ -149,17 +142,16 @@ <field name="type">lead</field> <field name="user_id" ref="base.user_demo"/> <field eval="'Survey'" name="name"/> - <field eval="'open'" name="state"/> <field name="section_id" ref="crm.section_sales_department"/> <field name="categ_id" ref="crm.categ_oppor6"/> <field name="stage_id" ref="crm.stage_lead1"/> <field eval="'Survey Expert'" name="partner_name"/> - <field eval="'John Smith'" name="contact_name"/> - <field eval="'Sales'" name="function"/> - <field eval="'Cambridge'" name="city"/> - <field name="country_id" ref="base.uk"/> + <field eval="'John Smith'" name="contact_name"/> + <field eval="'Sales'" name="function"/> + <field eval="'Cambridge'" name="city"/> + <field name="country_id" ref="base.uk"/> <field eval="'smith_john@gmail.com'" name="email_from"/> - <field eval="'(282) 596-8584'" name="phone"/> + <field eval="'(282) 596-8584'" name="phone"/> </record> <record id="crm_case_business_card0" model="crm.lead"> <field eval="1" name="active"/> @@ -168,17 +160,16 @@ <field name="type">lead</field> <field name="user_id" ref="base.user_root"/> <field eval="'Partnership'" name="name"/> - <field eval="'open'" name="state"/> <field name="section_id" ref="crm.section_sales_department"/> <field name="categ_id" ref="crm.categ_oppor5"/> <field name="stage_id" ref="crm.stage_lead1"/> <field eval="'Marketing Business'" name="partner_name"/> - <field eval="'Laure Smith'" name="contact_name"/> - <field eval="'Sales'" name="function"/> - <field eval="'Oxford'" name="city"/> - <field name="country_id" ref="base.uk"/> + <field eval="'Laure Smith'" name="contact_name"/> + <field eval="'Sales'" name="function"/> + <field eval="'Oxford'" name="city"/> + <field name="country_id" ref="base.uk"/> <field eval="'l.smith@marketing-business.com'" name="email_from"/> - <field eval="'(252) 578-7894'" name="phone"/> + <field eval="'(252) 578-7894'" name="phone"/> </record> <record id="crm_case_imported_contact0" model="crm.lead"> <field eval="1" name="active"/> @@ -186,9 +177,8 @@ <field eval="'2'" name="priority"/> <field name="type">lead</field> <field eval="'Info'" name="name"/> - <field eval="'cancel'" name="state"/> <field name="section_id" ref="crm.section_sales_department"/> - <field name="stage_id" ref="crm.stage_lead2"/> + <field name="stage_id" ref="crm.stage_lead1"/> <field eval="'Business Group'" name="partner_name"/> </record> <record id="crm_case_employee0" model="crm.lead"> @@ -198,12 +188,11 @@ <field name="type">lead</field> <field name="user_id" ref="base.user_demo"/> <field eval="'Need Info about Onsite Intervention'" name="name"/> - <field eval="'draft'" name="state"/> <field name="section_id" ref="crm.section_sales_department"/> <field name="categ_id" ref="crm.categ_oppor3"/> - <field name="stage_id" ref="crm.stage_lead2"/> + <field name="stage_id" ref="crm.stage_lead1"/> <field eval="'Agrolait'" name="partner_name"/> - <field eval="'Sylvie Lelitre'" name="contact_name"/> + <field eval="'Sylvie Lelitre'" name="contact_name"/> </record> <record id="crm_case_company_partnership0" model="crm.lead"> <field eval="1" name="active"/> @@ -212,28 +201,15 @@ <field name="type">lead</field> <field name="user_id" ref="base.user_demo"/> <field eval="'Need Quotation for 100 PC and 100 Keyboards'" name="name"/> - <field eval="'done'" name="state"/> <field name="section_id" ref="crm.section_sales_department"/> <field name="categ_id" ref="crm.categ_oppor1"/> <field name="stage_id" ref="crm.stage_lead1"/> <field eval="'Centrale d\'achats 1'" name="partner_name"/> </record> - <!-- Call Function to Open the leads--> - <function model="crm.lead" name="case_open" - eval="[ref('crm_case_electonicgoodsdealer0'), ref('crm_case_company_partnership0'), ref('crm_case_webvisitor0'), ref('crm_case_business_card0'), ref('crm.crm_case_employee0')], {'install_mode': True}" - /> - <!-- Call Function to mark the lead as Pending--> - <function model="crm.lead" name="case_pending" - eval="[ref('crm_case_itdeveloper0')], {'install_mode': True}" - /> - <!-- Call Function to Close the leads--> - <function model="crm.lead" name="case_close" - eval="[ref('crm_case_vpoperations0'), ref('crm_case_developingwebapplications0'), ref('crm_case_webvisitor0')], {'install_mode': True}" - /> - <!-- Call Function to Cancel the leads--> + <!-- Call Function to Cancel the leads (set as Dead) --> <function model="crm.lead" name="case_cancel" - eval="[ref('crm_case_mgroperations0'), ref('crm_case_imported_contact0')], {'install_mode': True}" + eval="[ref('crm_case_company_partnership0'), ref('crm_case_vpoperations0'), ref('crm_case_developingwebapplications0'), ref('crm_case_webvisitor0')], {'install_mode': True}" /> <!-- Demo Opportunities --> @@ -245,11 +221,10 @@ <field name="partner_id" ref="base.res_partner_3"/> <field eval="'3'" name="priority"/> <field name="user_id" ref="base.user_root"/> - <field eval="'open'" name="state"/> <field eval="'150000'" name="planned_revenue"/> <field name="section_id" ref="crm.section_sales_department"/> <field name="categ_id" ref="crm.categ_oppor1"/> - <field name="stage_id" ref="crm.stage_lead3"/> + <field name="stage_id" ref="crm.stage_lead4"/> <field eval="'Plan to buy 200 PC2'" name="name"/> <field eval="'Conf call with purchase manager'" name="title_action"/> </record> @@ -260,12 +235,11 @@ <field name="partner_id" ref="base.res_partner_9"/> <field eval="'3'" name="priority"/> <field name="user_id" ref="base.user_demo"/> - <field eval="'draft'" name="state"/> <field eval="45000.0" name="planned_revenue"/> <field eval="35" name="probability"/> <field name="section_id" ref="crm.section_sales_department"/> <field name="categ_id" ref="crm.categ_oppor3"/> - <field name="stage_id" ref="crm.stage_lead2"/> + <field name="stage_id" ref="crm.stage_lead3"/> <field eval="'Pricing Information of Onsite Intervention'" name="name"/> <field eval="'Send price list regarding our interventions'" name="title_action"/> <field eval="time.strftime('%Y-%m-03')" name="date_action"/> @@ -283,12 +257,11 @@ <field name="partner_id" ref="base.res_partner_lucievonck0"/> <field eval="'3'" name="priority"/> <field name="user_id" ref="base.user_demo"/> - <field eval="'draft'" name="state"/> <field eval="30000.0" name="planned_revenue"/> <field eval="30" name="probability"/> <field name="section_id" ref="crm.section_sales_department"/> <field name="categ_id" ref="crm.categ_oppor3"/> - <field name="stage_id" ref="crm.stage_lead2"/> + <field name="stage_id" ref="crm.stage_lead3"/> <field eval="'Interest in your Kitchen Design Project'" name="name"/> <field eval="'Send Catalogue by E-Mail'" name="title_action"/> <field eval="time.strftime('%Y-01-10')" name="date_action"/> @@ -299,19 +272,18 @@ <field eval="1367" name="zip"/> <field name="country_id" ref="base.be"/> </record> - <record id="crm_case_unifliege" model="crm.lead"> + <record id="crm_case_unifliege" model="crm.lead"> <field eval="1" name="active"/> <field name="type">opportunity</field> <field name="type_id" ref="crm.type_lead2"/> <field name="partner_id" ref="base.res_partner_accent"/> <field eval="'3'" name="priority"/> <field name="user_id" ref="base.user_root"/> - <field eval="'open'" name="state"/> <field eval="2500.0" name="planned_revenue"/> <field eval="25" name="probability"/> <field name="section_id" ref="crm.section_sales_department"/> <field name="categ_id" ref="crm.categ_oppor6"/> - <field name="stage_id" ref="crm.stage_lead2"/> + <field name="stage_id" ref="crm.stage_lead3"/> <field eval="'Plan train our students on your product'" name="name"/> <field eval="'Call to define real needs about training'" name="title_action"/> <field eval="time.strftime('%Y-%m-04')" name="date_action"/> @@ -323,19 +295,18 @@ <field eval="75016" name="zip"/> <field name="country_id" ref="base.be"/> </record> - <record id="crm_case_bankwealthy2" model="crm.lead"> + <record id="crm_case_bankwealthy2" model="crm.lead"> <field eval="1" name="active"/> <field name="type">opportunity</field> <field name="type_id" ref="crm.type_lead2"/> <field name="partner_id" ref="base.res_partner_2"/> <field eval="'3'" name="priority"/> <field name="user_id" ref="base.user_root"/> - <field eval="'open'" name="state"/> <field eval="462.0" name="planned_revenue"/> <field eval="40" name="probability"/> <field name="section_id" ref="crm.section_sales_department"/> <field name="categ_id" ref="crm.categ_oppor2"/> - <field name="stage_id" ref="crm.stage_lead2"/> + <field name="stage_id" ref="crm.stage_lead3"/> <field eval="'Plan to buy 66 keyboards and 66 mouses'" name="name"/> <field eval="'Propose the kit keyboard+mouse'" name="title_action"/> <field eval="time.strftime('%Y-04-12')" name="date_action"/> @@ -354,12 +325,11 @@ <field name="partner_id" ref="base.res_partner_8"/> <field eval="'3'" name="priority"/> <field name="user_id" ref="base.user_root"/> - <field eval="'done'" name="state"/> <field eval="55000.0" name="planned_revenue"/> <field eval="90" name="probability"/> <field name="section_id" ref="crm.section_sales_department"/> <field name="categ_id" ref="crm.categ_oppor7"/> - <field name="stage_id" ref="crm.stage_lead5"/> + <field name="stage_id" ref="crm.stage_lead6"/> <field eval="'Need 20 Days of Consultancy'" name="name"/> <field eval="time.strftime('%Y-%m-%d')" name="date_deadline"/> <field eval="'info@mycompany.net'" name="email_from"/> @@ -372,11 +342,10 @@ <field name="partner_id" ref="base.res_partner_duboissprl0"/> <field eval="'3'" name="priority"/> <field name="user_id" ref="base.user_demo"/> - <field eval="'open'" name="state"/> <field eval="45000.0" name="planned_revenue"/> <field name="section_id" ref="crm.section_sales_department"/> <field name="categ_id" ref="crm.categ_oppor5"/> - <field name="stage_id" ref="crm.stage_lead4"/> + <field name="stage_id" ref="crm.stage_lead5"/> <field eval="'Need new design for my website'" name="name"/> <field eval="time.strftime('%Y-05-01')" name="date_action"/> <field eval="time.strftime('%Y-06-30')" name="date_deadline"/> @@ -395,40 +364,37 @@ <field name="partner_id" ref="base.res_partner_maxtor"/> <field eval="'3'" name="priority"/> <field name="user_id" ref="base.user_demo"/> - <field eval="'done'" name="state"/> <field eval="42000.0" name="planned_revenue"/> <field name="section_id" ref="crm.section_sales_department"/> <field name="categ_id" ref="crm.categ_oppor2"/> - <field name="stage_id" ref="crm.stage_lead6"/> + <field name="stage_id" ref="crm.stage_lead8"/> <field eval="'Want to subscribe to your online solution'" name="name"/> </record> - <record id="crm_case_dirtminingltdunits0" model="crm.lead"> + <record id="crm_case_dirtminingltdunits0" model="crm.lead"> <field eval="30" name="probability"/> <field eval="1" name="active"/> <field name="type">opportunity</field> <field name="partner_id" ref="base.res_partner_tinyatwork"/> <field eval="'3'" name="priority"/> <field name="user_id" ref="base.user_root"/> - <field eval="'done'" name="state"/> <field eval="25000.0" name="planned_revenue"/> <field name="section_id" ref="crm.section_sales_department"/> <field name="categ_id" ref="crm.categ_oppor6"/> - <field name="stage_id" ref="crm.stage_lead6"/> + <field name="stage_id" ref="crm.stage_lead8"/> <field eval="'Interest in your Partnership Contract'" name="name"/> </record> - <record id="crm_case_dirtminingltdunits10" model="crm.lead"> + <record id="crm_case_dirtminingltdunits10" model="crm.lead"> <field eval="30" name="probability"/> <field eval="1" name="active"/> <field name="type">opportunity</field> <field name="partner_id" ref="base.res_partner_desertic_hispafuentes"/> <field eval="'3'" name="priority"/> <field name="user_id" ref="base.user_root"/> - <field eval="'open'" name="state"/> <field eval="5000" name="planned_revenue"/> <field eval="30" name="probability"/> <field name="section_id" ref="crm.section_sales_department"/> <field name="categ_id" ref="crm.categ_oppor2"/> - <field name="stage_id" ref="crm.stage_lead2"/> + <field name="stage_id" ref="crm.stage_lead3"/> <field eval="'Plan to attend a training'" name="name"/> <field eval="time.strftime('%Y-04-10')" name="date_action"/> <field eval="time.strftime('%Y-06-12')" name="date_deadline"/> @@ -439,7 +405,7 @@ <field eval="77420" name="zip"/> <field name="country_id" ref="base.fr"/> </record> - <record id="crm_case_construstazunits0" model="crm.lead"> + <record id="crm_case_construstazunits0" model="crm.lead"> <field eval="60" name="probability"/> <field eval="1" name="active"/> <field name="type">opportunity</field> @@ -447,11 +413,10 @@ <field name="partner_id" ref="base.res_partner_thymbra"/> <field eval="'3'" name="priority"/> <field name="user_id" ref="base.user_root"/> - <field eval="'open'" name="state"/> <field eval="'150000'" name="planned_revenue"/> <field name="section_id" ref="crm.section_sales_department"/> <field name="categ_id" ref="crm.categ_oppor1"/> - <field name="stage_id" ref="crm.stage_lead3"/> + <field name="stage_id" ref="crm.stage_lead4"/> <field eval="'Need customize the solution'" name="name"/> <field eval="'Conf call with technical service'" name="title_action"/> <field name="partner_name">Thymbra</field> @@ -461,7 +426,7 @@ <field eval="1659" name="zip"/> <field name="country_id" ref="base.ar"/> </record> - <record id="crm_case_ericdubois4" model="crm.lead"> + <record id="crm_case_ericdubois4" model="crm.lead"> <field eval="65" name="probability"/> <field eval="1" name="active"/> <field name="type">opportunity</field> @@ -469,11 +434,10 @@ <field name="partner_id" ref="base.res_partner_ericdubois0"/> <field eval="'3'" name="priority"/> <field name="user_id" ref="base.user_root"/> - <field eval="'open'" name="state"/> <field eval="'1200'" name="planned_revenue"/> <field name="section_id" ref="crm.section_sales_department"/> <field name="categ_id" ref="crm.categ_oppor1"/> - <field name="stage_id" ref="crm.stage_lead3"/> + <field name="stage_id" ref="crm.stage_lead4"/> <field eval="'Interest in your customizable PC'" name="name"/> <field eval="time.strftime('%Y-08-05')" name="date_action"/> <field eval="time.strftime('%Y-10-10')" name="date_deadline"/> @@ -484,26 +448,24 @@ <field eval="7000" name="zip"/> <field name="country_id" ref="base.be"/> </record> - <record id="crm_case_fabiendupont" model="crm.lead"> + <record id="crm_case_fabiendupont" model="crm.lead"> <field eval="1" name="active"/> <field name="type">opportunity</field> <field name="type_id" ref="crm.type_lead1"/> <field name="partner_id" ref="base.res_partner_fabiendupont0"/> <field eval="'3'" name="priority"/> <field name="user_id" ref="base.user_root"/> - <field eval="'draft'" name="state"/> <field name="categ_id" ref="crm.categ_oppor4"/> <field name="stage_id" ref="crm.stage_lead1"/> <field eval="'Need more info about the onsite intervention'" name="name"/> </record> - <record id="crm_case_shelvehouse" model="crm.lead"> + <record id="crm_case_shelvehouse" model="crm.lead"> <field eval="1" name="active"/> <field name="type">opportunity</field> <field name="type_id" ref="crm.type_lead1"/> <field name="partner_id" ref="base.res_partner_theshelvehouse0"/> <field eval="'3'" name="priority"/> <field name="user_id" ref="base.user_demo"/> - <field eval="'draft'" name="state"/> <field name="categ_id" ref="crm.categ_oppor4"/> <field name="stage_id" ref="crm.stage_lead1"/> <field eval="'Need more info about your pc2'" name="name"/> diff --git a/addons/crm/crm_lead_view.xml b/addons/crm/crm_lead_view.xml index 3c2b8f48eb6..7f921a35bfe 100644 --- a/addons/crm/crm_lead_view.xml +++ b/addons/crm/crm_lead_view.xml @@ -1,35 +1,41 @@ <?xml version="1.0"?> <openerp> -<data> - - <!-- Stage Search view --> - <record id="crm_lead_stage_search" model="ir.ui.view"> - <field name="name">Stage - Search</field> - <field name="model">crm.case.stage</field> - <field name="type">search</field> - <field name="arch" type="xml"> - <search string="Stage Search"> - <field name="name"/> - </search> - </field> - </record> - - <!--Lead Stage Form view --> - <record id="crm_lead_stage_act" model="ir.actions.act_window"> - <field name="name">Stages</field> - <field name="res_model">crm.case.stage</field> - <field name="view_type">form</field> - <field name="view_id" ref="crm.crm_case_stage_tree"/> - <field name="help">Add specific stages to leads and opportunities allowing your sales to better organise their sales pipeline. Stages will allow them to easily track how a specific lead or opportunity is positioned in the sales cycle.</field> - </record> + <data> + + <!-- + CRM CASE STAGE + --> + + <!-- Stage Search view --> + <record id="crm_lead_stage_search" model="ir.ui.view"> + <field name="name">Stage - Search</field> + <field name="model">crm.case.stage</field> + <field name="type">search</field> + <field name="arch" type="xml"> + <search string="Stage Search"> + <field name="name"/> + <field name="state"/> + <field name="type"/> + </search> + </field> + </record> - <menuitem action="crm_lead_stage_act" id="menu_crm_lead_stage_act" name="Stages" - groups="base.group_no_one" sequence="0" - parent="base.menu_crm_config_lead" /> + <!-- Stage Form view --> + <record id="crm_lead_stage_act" model="ir.actions.act_window"> + <field name="name">Stages</field> + <field name="res_model">crm.case.stage</field> + <field name="view_type">form</field> + <field name="view_id" ref="crm.crm_case_stage_tree"/> + <field name="help">Add specific stages to leads and opportunities allowing your sales to better organise their sales pipeline. Stages will allow them to easily track how a specific lead or opportunity is positioned in the sales cycle.</field> + </record> + <menuitem action="crm_lead_stage_act" id="menu_crm_lead_stage_act" name="Stages" sequence="0" parent="base.menu_crm_config_lead" /> - <!-- Lead/Opportunity Categories Action --> + <!-- + LEADS/OPPORTUNITIES CATEGORIES + --> + <!-- Categories Form View --> <record id="crm_lead_categ_action" model="ir.actions.act_window"> <field name="name">Categories</field> <field name="res_model">crm.case.categ</field> @@ -44,6 +50,9 @@ id="menu_crm_lead_categ" name="Categories" parent="base.menu_crm_config_lead" sequence="1" groups="base.group_no_one"/> + <!-- + LEADS + --> <!-- CRM Lead Form View --> <record model="ir.ui.view" id="crm_case_form_view_leads"> @@ -53,14 +62,17 @@ <field name="arch" type="xml"> <form string="Leads Form" layout="manual"> <div class="oe_form_topbar"> - <button name="case_open" string="Open" states="draft,pending" type="object" /> - <button name="case_mark_lost" string="Close" states="open,pending" type="object" /> - <button name="case_pending" string="Pending" states="open" type="object" /> - <button name="case_escalate" string="Escalate" states="open,pending" type="object" /> - <button name="case_reset" string="Reset to Draft" states="done,cancel" type="object" /> - <button name="case_cancel" string="Cancel" states="draft,open,pending" type="object" /> + <button name="%(crm.action_crm_lead2opportunity_partner)d" string="Convert to Opportunity" type="action" + states="draft,open,pending" help="Convert to Opportunity"/> + <button name="case_escalate" string="Escalate" type="object" + states="draft,open,pending"/> + <button name="case_reset" string="Reset" type="object" + states="cancel"/> + <button name="case_cancel" string="Cancel" type="object" + states="draft,open,pending"/> <div class="oe_right"> - <field name="state" nolabel="1" widget="statusbar" statusbar_visible="draft,open,done" statusbar_colors='{"pending":"blue"}'/> + <field name="stage_id" nolabel="1" widget="statusbar" + on_change="onchange_stage_id(stage_id)"/> </div> <div class="oe_clear"/> </div> @@ -71,24 +83,9 @@ <field name="categ_id" widget="selection" domain="[('object_id.model','=','crm.lead')]"/> - <button - name="%(crm.action_crm_lead2opportunity_partner)d" - string="Convert to Opportunity" - help="Convert to Opportunity" icon="gtk-go-forward" - type="action" - /> <newline /> <field name="user_id" /> <field name="section_id" widget="selection" /> - <field name="stage_id" domain="section_id and [('section_ids', '=', section_id)] or []" /> - <group col="2" colspan="1"> - <button name="stage_previous" string="" - states="open,pending,draft" type="object" - icon="gtk-go-back" context="{'stage_type': 'lead'}" /> - <button name="stage_next" string="" - states="open,pending,draft" type="object" - icon="gtk-go-forward" context="{'stage_type': 'lead'}" /> - </group> <field name="type" invisible="1"/> </group> <notebook colspan="4"> @@ -142,6 +139,7 @@ <field name="type_id" select="1" widget="selection"/> <field name="channel_id" select="1" widget="selection"/> <field name="referred"/> + <field name="state" groups="base.group_no_one"/> </group> <group colspan="2" col="2"> <separator string="Mailings" colspan="2" col="2"/> @@ -161,72 +159,55 @@ </div> </form> </field> - </record> - - <!-- CRM Lead Tree View --> - - <record model="ir.ui.view" id="crm_case_tree_view_leads"> - <field name="name">Leads</field> - <field name="model">crm.lead</field> - <field name="type">tree</field> - <field name="arch" type="xml"> - <tree string="Leads" fonts="bold:needaction_pending==True" colors="blue:state=='pending';grey:state in ('cancel', 'done')"> - <field name="needaction_pending" invisible="1"/> - <field name="date_deadline" invisible="1"/> - <field name="create_date" groups="base.group_no_one"/> - <field name="name" string="Subject"/> - <field name="contact_name"/> - <field name="country_id" invisible="context.get('invisible_country', True)" /> - <field name="email_from"/> - <field name="phone"/> - - <field name="categ_id" invisible="1"/> - <field name="type_id" invisible="1"/> - <field name="referred" invisible="1"/> - <field name="channel_id" invisible="1"/> - <field name="subjects" invisible="1"/> - - <field name="stage_id"/> - <button name="stage_previous" string="Previous Stage" - states="open,pending" type="object" icon="gtk-go-back" /> - <button name="stage_next" string="Next Stage" - states="open,pending" type="object" - icon="gtk-go-forward" /> - <field name="section_id" invisible="context.get('invisible_section', True)" /> - <field name="user_id" /> - <field name="state" /> - <button name="case_open" string="Open" - states="draft,pending" type="object" - icon="gtk-go-forward" /> - <button name="case_close" string="Close" - states="open,draft,pending" type="object" - icon="gtk-close" /> - <button name="%(crm.action_crm_lead2opportunity_partner)d" - string="Convert to Opportunity" - states="draft,open,pending" icon="gtk-index" - type="action" /> - <button name="case_escalate" string="Escalate" - states="open,draft,pending" type="object" - icon="gtk-go-up" /> - </tree> - </field> - </record> + </record> - <!-- CRM Lead Calendar View --> + <!-- CRM Lead Tree View --> + <record model="ir.ui.view" id="crm_case_tree_view_leads"> + <field name="name">Leads</field> + <field name="model">crm.lead</field> + <field name="type">tree</field> + <field name="arch" type="xml"> + <tree string="Leads" fonts="bold:needaction_pending==True" colors="blue:state=='pending';grey:state in ('cancel', 'done')"> + <field name="needaction_pending" invisible="1"/> + <field name="date_deadline" invisible="1"/> + <field name="create_date" groups="base.group_no_one"/> + <field name="name" string="Subject"/> + <field name="contact_name"/> + <field name="country_id" invisible="context.get('invisible_country', True)" /> + <field name="email_from"/> + <field name="phone"/> + <field name="stage_id"/> + <field name="categ_id" invisible="1"/> + <field name="section_id" invisible="context.get('invisible_section', True)" /> + <field name="state" groups="base.group_no_one"/> + <field name="type_id" invisible="1"/> + <field name="referred" invisible="1"/> + <field name="channel_id" invisible="1"/> + <field name="subjects" invisible="1"/> + <button name="%(crm.action_crm_lead2opportunity_partner)d" string="Convert to Opportunity" type="action" + states="draft,open,pending" icon="gtk-go-forward"/> + <button name="case_escalate" string="Escalate" type="object" + states="draft,open,pending" icon="gtk-go-up"/> + <button name="case_cancel" string="Dead" type="object" + states="open,draft,pending" icon="gtk-cancel"/> + </tree> + </field> + </record> - <record model="ir.ui.view" id="crm_case_calendar_view_leads"> - <field name="name">CRM - Leads Calendar</field> - <field name="model">crm.lead</field> - <field name="type">calendar</field> - <field name="priority" eval="2"/> - <field name="arch" type="xml"> - <calendar string="Leads Generation" - date_start="date_action" color="user_id"> - <field name="name" /> - <field name="partner_name" /> - </calendar> - </field> - </record> + <!-- CRM Lead Calendar View --> + <record model="ir.ui.view" id="crm_case_calendar_view_leads"> + <field name="name">CRM - Leads Calendar</field> + <field name="model">crm.lead</field> + <field name="type">calendar</field> + <field name="priority" eval="2"/> + <field name="arch" type="xml"> + <calendar string="Leads Generation" + date_start="date_action" color="user_id"> + <field name="name" /> + <field name="partner_name" /> + </calendar> + </field> + </record> <!-- CRM Lead Kanban View --> <record model="ir.ui.view" id="crm_case_kanban_view_leads"> @@ -235,7 +216,7 @@ <field name="type">kanban</field> <field name="arch" type="xml"> <kanban default_group_by="stage_id"> - <field name="state"/> + <field name="state" groups="base.group_no_one"/> <field name="color"/> <field name="priority"/> <field name="planned_revenue" sum="Expected Revenues"/> @@ -316,7 +297,6 @@ </record> <!-- CRM Lead Search View --> - <record id="view_crm_case_leads_filter" model="ir.ui.view"> <field name="name">CRM - Leads Search</field> <field name="model">crm.lead</field> @@ -391,6 +371,10 @@ </record> + <!-- + OPPORTUNITY + --> + <!-- Opportunities Form View --> <record model="ir.ui.view" id="crm_case_form_view_oppor"> <field name="name">Opportunities</field> @@ -400,15 +384,23 @@ <field name="arch" type="xml"> <form string="Opportunities" layout="manual"> <div class="oe_form_topbar oe_form_topbar_hifirst"> - <button name="case_mark_won" string="Mark Won" states="open,pending" type="object" /> - <button name="case_open" string="Open" states="draft,pending" type="object" /> - <button name="case_pending" string="Pending" states="draft,open" type="object" /> - <button name="case_escalate" string="Escalate" states="open,pending" type="object" /> - <button name="case_mark_lost" string="Mark Lost" states="open,pending" type="object" /> - <button name="case_reset" string="Reset to Draft" states="done,cancel" type="object" /> - <button name="case_cancel" string="Cancel" states="draft" type="object" /> + <button name="case_open" string="Open" type="object" + states="draft"/> + <button name="case_mark_won" string="Mark Won" type="object" + states="draft,open"/> + <button name="case_mark_lost" string="Mark Lost" type="object" + states="draft,open"/> + <button name="case_reset" string="Reset to Draft" type="object" + states="cancel"/> + <button name="case_escalate" string="Escalate" type="object" + states="open"/> + <button name="stage_previous" string="Previous" type="object" + states="open" icon="gtk-go-back" context="{'stage_type': 'opportunity'}"/> + <button name="stage_next" string="Next" type="object" + states="open" icon="gtk-go-forward" context="{'stage_type': 'opportunity'}"/> <div class="oe_right"> - <field name="state" nolabel="1" widget="statusbar" statusbar_visible="draft,open,done" statusbar_colors='{"pending":"blue"}'/> + <field name="stage_id" nolabel="1" widget="statusbar" + on_change="onchange_stage_id(stage_id)"/> </div> <div class="oe_clear"/> </div> @@ -461,20 +453,8 @@ <field name="categ_id" select="1" string="Category" widget="selection" domain="[('object_id.model', '=', 'crm.lead')]" /> - - <label string="Stage" for="stage_id" align="1.0"/> - <group colspan="1" col="3"> - <field name="stage_id" nolabel="1" - on_change="onchange_stage_id(stage_id)" - domain="section_id and [('section_ids', '=', section_id)] or []" width="60%%"/> - <button name="stage_previous" - states="draft,open,pending" type="object" - icon="gtk-go-back" string="" context="{'stage_type': 'opportunity'}"/> - <button name="stage_next" states="draft,open,pending" - type="object" icon="gtk-go-forward" string="" context="{'stage_type': 'opportunity'}"/> - </group> - - <field name="priority" string="Priority"/> + <field name="state"/> + <field name="priority"/> </group> <separator colspan="4" string="Details"/> @@ -534,41 +514,41 @@ </field> </record> - <!-- Opportunities Tree View --> - <record model="ir.ui.view" id="crm_case_tree_view_oppor"> - <field name="name">Opportunities Tree</field> - <field name="model">crm.lead</field> - <field name="type">tree</field> - <field name="arch" type="xml"> - <tree string="Opportunities" fonts="bold:needaction_pending==True" colors="blue:state=='pending' and not(date_deadline and (date_deadline < current_date));gray:state in ('cancel', 'done');red:date_deadline and (date_deadline < current_date)"> - <field name="needaction_pending" invisible="1"/> - <field name="date_deadline" invisible="1"/> - <field name="create_date" groups="base.group_no_one"/> - <field name="name" string="Opportunity"/> - <field name="partner_id" string="Customer"/> - <field name="country_id" invisible="context.get('invisible_country', True)" /> - <field name="date_action"/> - <field name="title_action" /> - <field name="channel_id" invisible="1"/> - <field name="type_id" invisible="1"/> - <field name="subjects" invisible="1"/> - <field name="stage_id"/> - <button name="stage_previous" string="Previous Stage" states="open,pending" type="object" icon="gtk-go-back" /> - <button name="stage_next" string="Next Stage" states="open,pending" type="object" icon="gtk-go-forward" /> - <field name="planned_revenue" sum="Expected Revenues"/> - <field name="probability" widget="progressbar" avg="Avg. of Probability"/> - <field name="section_id" invisible="context.get('invisible_section', True)" /> - <field name="user_id"/> - <field name="priority" invisible="1"/> - <field name="categ_id" invisible="1"/> - <field name="state"/> - <button name="case_open" string="Open" states="draft,pending" type="object" icon="gtk-go-forward" /> - <button name="case_pending" string="Pending" states="open,draft" type="object" icon="gtk-media-pause" /> - <button name="case_mark_lost" string="Lost" states="open,pending" type="object" icon="gtk-cancel" /> - <button name="case_mark_won" string="Won" states="open,pending" type="object" icon="gtk-apply" /> - </tree> - </field> - </record> + <!-- Opportunities Tree View --> + <record model="ir.ui.view" id="crm_case_tree_view_oppor"> + <field name="name">Opportunities Tree</field> + <field name="model">crm.lead</field> + <field name="type">tree</field> + <field name="arch" type="xml"> + <tree string="Opportunities" fonts="bold:needaction_pending==True" colors="blue:state=='pending' and not(date_deadline and (date_deadline < current_date));gray:state in ('cancel', 'done');red:date_deadline and (date_deadline < current_date)"> + <field name="needaction_pending" invisible="1"/> + <field name="date_deadline" invisible="1"/> + <field name="create_date" groups="base.group_no_one"/> + <field name="name" string="Opportunity"/> + <field name="partner_id" string="Customer"/> + <field name="country_id" invisible="context.get('invisible_country', True)" /> + <field name="date_action"/> + <field name="title_action" /> + <field name="channel_id" invisible="1"/> + <field name="type_id" invisible="1"/> + <field name="subjects" invisible="1"/> + <field name="stage_id"/> + <field name="planned_revenue" sum="Expected Revenues"/> + <field name="probability" widget="progressbar" avg="Avg. of Probability"/> + <field name="section_id" invisible="context.get('invisible_section', True)" /> + <field name="user_id"/> + <field name="priority" invisible="1"/> + <field name="categ_id" invisible="1"/> + <field name="state" groups="base.group_no_one"/> + <button name="case_open" string="Open" type="object" + states="draft,pending" icon="gtk-go-forward"/> + <button name="case_mark_won" string="Won" type="object" + states="open,pending" icon="gtk-apply"/> + <button name="case_mark_lost" string="Lost" type="object" + states="open,pending" icon="gtk-cancel"/> + </tree> + </field> + </record> <!-- Opportunities Search View --> @@ -599,8 +579,7 @@ help="Unassigned Opportunities" /> </field> <field name="section_id" - context="{'invisible_section': False}" - widget="selection"> + context="{'invisible_section': False, 'default_section_id': self}"> <filter icon="terp-personal+" domain="['|', ('section_id.user_id','=',uid), ('section_id.member_ids', 'in', [uid])]" context="{'invisible_section': False}" @@ -630,19 +609,19 @@ </field> </record> - <!-- crm.lead Opportunities Graph View --> - <record model="ir.ui.view" id="crm_case_graph_view_opportunity"> - <field name="name">CRM - Opportunity Graph</field> - <field name="model">crm.lead</field> - <field name="type">graph</field> - <field name="arch" type="xml"> - <graph string="Opportunity by Categories" type="bar" orientation="horizontal"> - <field name="categ_id"/> - <field name="planned_revenue" operator="+"/> - <field name="state" group="True"/> - </graph> - </field> - </record> + <!-- crm.lead Opportunities Graph View --> + <record model="ir.ui.view" id="crm_case_graph_view_opportunity"> + <field name="name">CRM - Opportunity Graph</field> + <field name="model">crm.lead</field> + <field name="type">graph</field> + <field name="arch" type="xml"> + <graph string="Opportunity by Categories" type="bar" orientation="horizontal"> + <field name="categ_id"/> + <field name="planned_revenue" operator="+"/> + <field name="state" group="True"/> + </graph> + </field> + </record> -</data> + </data> </openerp> diff --git a/addons/crm/crm_meeting.py b/addons/crm/crm_meeting.py index a0e4803cde7..740db3d967f 100644 --- a/addons/crm/crm_meeting.py +++ b/addons/crm/crm_meeting.py @@ -2,7 +2,7 @@ ############################################################################## # # OpenERP, Open Source Management Solution -# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>). +# Copyright (C) 2004-today OpenERP SA (<http://www.openerp.com>) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -20,43 +20,35 @@ ############################################################################## from base_calendar import base_calendar -from crm import crm_base, crm_case +from base_status.base_state import base_state +from base_status.base_stage import base_stage +import logging from osv import fields, osv import tools from tools.translate import _ -import logging - -class crm_lead(crm_case, osv.osv): - """ CRM Leads """ - _name = 'crm.lead' -crm_lead() - -class crm_phonecall(crm_case, osv.osv): - """ CRM Phonecall """ - _name = 'crm.phonecall' -crm_phonecall() +class crm_lead(base_stage, osv.osv): + """ CRM Leads """ + _name = 'crm.lead' -class crm_meeting(crm_base, osv.osv): - """ CRM Meeting Cases """ - +class crm_meeting(base_state, osv.Model): + """ Model for CRM meetings """ _name = 'crm.meeting' _description = "Meeting" _order = "id desc" _inherit = ["calendar.event", 'ir.needaction_mixin', "mail.thread"] _columns = { - # From crm.case - 'name': fields.char('Summary', size=124, required=True, states={'done': [('readonly', True)]}), + # base_state required fields 'partner_id': fields.many2one('res.partner', 'Partner', states={'done': [('readonly', True)]}), 'section_id': fields.many2one('crm.case.section', 'Sales Team', states={'done': [('readonly', True)]}, \ select=True, help='Sales team to which Case belongs to.'), 'email_from': fields.char('Email', size=128, states={'done': [('readonly', True)]}, help="These people will receive email."), - 'id': fields.integer('ID', readonly=True), 'create_date': fields.datetime('Creation Date' , readonly=True), 'write_date': fields.datetime('Write Date' , readonly=True), 'date_action_last': fields.datetime('Last Action', readonly=1), 'date_action_next': fields.datetime('Next Action', readonly=1), # Meeting fields + 'name': fields.char('Summary', size=124, required=True, states={'done': [('readonly', True)]}), 'categ_id': fields.many2one('crm.case.categ', 'Meeting Type', \ domain="[('object_id.model', '=', 'crm.meeting')]", \ ), @@ -67,11 +59,11 @@ class crm_meeting(crm_base, osv.osv): 'date_closed': fields.datetime('Closed', readonly=True), 'date_deadline': fields.datetime('Deadline', states={'done': [('readonly', True)]}), 'message_ids': fields.one2many('mail.message', 'res_id', 'Messages', domain=[('model','=',_name)]), - 'state': fields.selection([('open', 'Confirmed'), - ('draft', 'Unconfirmed'), + 'state': fields.selection([ ('draft', 'Unconfirmed'), + ('open', 'Confirmed'), ('cancel', 'Cancelled'), - ('done', 'Done')], 'Status', \ - size=16, readonly=True), + ('done', 'Done')], + string='Status', size=16, readonly=True), } _defaults = { 'state': 'draft', @@ -91,6 +83,17 @@ class crm_meeting(crm_base, osv.osv): result[obj.id] = [obj.user_id.id] return result + def case_open(self, cr, uid, ids, context=None): + """ Confirms meeting """ + res = super(crm_meeting, self).case_open(cr, uid, ids, context) + for (id, name) in self.name_get(cr, uid, ids): + id=base_calendar.base_calendar_id2real_id(id) + return res + + # ---------------------------------------- + # OpenChatter + # ---------------------------------------- + def case_get_note_msg_prefix(self, cr, uid, id, context=None): return 'Meeting' @@ -119,32 +122,12 @@ class crm_meeting(crm_base, osv.osv): meeting.message_append_note(body=parent_message) return True - def case_close_send_note(self, cr, uid, ids, context=None): - message = _("Meeting has been <b>done</b>.") - return self.message_append_note(cr, uid, ids, body=message, context=context) - def case_open_send_note(self, cr, uid, ids, context=None): - for meeting in self.browse(cr, uid, ids, context=context): - if meeting.state != 'draft': - return False - message = _("Meeting has been <b>confirmed</b>.") - meeting.message_append_note(body=message) - return True + return self.message_append_note(cr, uid, ids, body=_("Meeting has been <b>confirmed</b>."), context=context) - def case_open(self, cr, uid, ids, context=None): - """Confirms meeting - @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 Meeting Ids - @param *args: Tuple Value for additional Params - """ - res = super(crm_meeting, self).case_open(cr, uid, ids, context) - for (id, name) in self.name_get(cr, uid, ids): - id=base_calendar.base_calendar_id2real_id(id) - return res + def case_close_send_note(self, cr, uid, ids, context=None): + return self.message_append_note(cr, uid, ids, body=_("Meeting has been <b>done</b>."), context=context) -crm_meeting() class calendar_attendee(osv.osv): """ Calendar Attendee """ diff --git a/addons/crm/crm_meeting_view.xml b/addons/crm/crm_meeting_view.xml index 5b0a35a185e..1d1422ee285 100644 --- a/addons/crm/crm_meeting_view.xml +++ b/addons/crm/crm_meeting_view.xml @@ -25,7 +25,22 @@ <field name="model">crm.meeting</field> <field name="type">form</field> <field name="arch" type="xml"> - <form string="Meetings"> + <form string="Meetings" layout="manual"> + <div class="oe_form_topbar oe_form_topbar_hifirst"> + <button name="case_open" string="Confirm" type="object" + states="draft"/> + <button name="case_close" string="Done" type="object" + states="open"/> + <button name="case_reset" string="Reset to Unconfirmed" type="object" + states="cancel,done"/> + <button name="case_cancel" string="Cancel" type="object" + states="draft,open"/> + <div class="oe_right"> + <field name="state" nolabel="1" widget="statusbar" statusbar_visible="draft,open,done"/> + </div> + <div class="oe_clear"/> + </div> + <sheet layout="auto"> <group col="6" colspan="4"> <field name="name" select="1" string="Title" required="1" /> @@ -71,19 +86,6 @@ </group> <separator string="Description" colspan="4" /> <field name="description" nolabel="1" colspan="4" /> - <separator colspan="4"/> - <group col="8" colspan="4" groups="base.group_no_one"> - <field name="state" /> - <button name="case_close" string="Done" - states="open" type="object" - icon="gtk-jump-to" /> - <button name="case_reset" string="Reset to Unconfirmed" - states="open,done" type="object" - icon="gtk-convert" /> - <button name="case_open" string="Confirm" - states="draft" type="object" - icon="gtk-go-forward" /> - </group> </page> <page string="Invitation Detail"> <button string="Invite People" @@ -209,7 +211,10 @@ </page> </notebook> - <field name="message_ids_social" colspan="4" widget="ThreadView" nolabel="1"/> + </sheet> + <div class="oe_form_bottom"> + <field name="message_ids_social" colspan="4" widget="ThreadView" nolabel="1"/> + </div> </form> </field> </record> diff --git a/addons/crm/crm_phonecall.py b/addons/crm/crm_phonecall.py index 309102ec366..8c7c3fe72df 100644 --- a/addons/crm/crm_phonecall.py +++ b/addons/crm/crm_phonecall.py @@ -2,7 +2,7 @@ ############################################################################## # # OpenERP, Open Source Management Solution -# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>). +# Copyright (C) 2004-today OpenERP SA (<http://www.openerp.com>) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -19,26 +19,22 @@ # ############################################################################## -from crm import crm_base -from osv import fields, osv -from tools.translate import _ +from base_status.base_state import base_state import crm +from datetime import datetime +from osv import fields, osv import time from tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT, DATETIME_FORMATS_MAP -from datetime import datetime - -class crm_phonecall(crm_base, osv.osv): - """ Phonecall Cases """ +from tools.translate import _ +class crm_phonecall(base_state, osv.osv): + """ Model for CRM phonecalls """ _name = "crm.phonecall" _description = "Phonecall" _order = "id desc" _inherit = ['ir.needaction_mixin', 'mail.thread'] _columns = { - # From crm.case - 'id': fields.integer('ID', readonly=True), - 'name': fields.char('Call Summary', size=64, required=True), - 'active': fields.boolean('Active', required=False), + # base_state required fields 'date_action_last': fields.datetime('Last Action', readonly=1), 'date_action_next': fields.datetime('Next Action', readonly=1), 'create_date': fields.datetime('Creation Date' , readonly=True), @@ -48,20 +44,21 @@ class crm_phonecall(crm_base, osv.osv): 'partner_id': fields.many2one('res.partner', 'Partner'), 'company_id': fields.many2one('res.company', 'Company'), 'description': fields.text('Description'), - 'state': fields.selection([ - ('draft', 'Draft'), - ('open', 'Todo'), + 'state': fields.selection([ ('draft', 'Draft'), + ('open', 'Confirmed'), ('pending', 'Not Held'), ('cancel', 'Cancelled'), - ('done', 'Held'), - ], 'Status', size=16, readonly=True, - help='The state is set to \'Todo\', when a case is created.\ - \nIf the case is in progress the state is set to \'Open\'.\ - \nWhen the call is over, the state is set to \'Held\'.\ - \nIf the call needs to be done then the state is set to \'Not Held\'.'), + ('done', 'Held'),], + string='Status', size=16, readonly=True, + help='The state is set to \'Todo\', when a case is created.\ + If the case is in progress the state is set to \'Open\'.\ + When the call is over, the state is set to \'Held\'.\ + If the call needs to be done then the state is set to \'Not Held\'.'), 'email_from': fields.char('Email', size=128, help="These people will receive email."), 'date_open': fields.datetime('Opened', readonly=True), # phonecall fields + 'name': fields.char('Call Summary', size=64, required=True), + 'active': fields.boolean('Active', required=False), 'duration': fields.float('Duration', help="Duration in Minutes"), 'categ_id': fields.many2one('crm.case.categ', 'Category', \ domain="['|',('section_id','=',section_id),('section_id','=',False),\ @@ -81,11 +78,10 @@ class crm_phonecall(crm_base, osv.osv): return 'open' _defaults = { - 'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'), + 'date': fields.datetime.now, 'priority': crm.AVAILABLE_PRIORITIES[2][0], 'state': _get_default_state, 'user_id': lambda self,cr,uid,ctx: uid, - 'active': 1, } def create(self, cr, uid, vals, context=None): @@ -96,32 +92,23 @@ class crm_phonecall(crm_base, osv.osv): return obj_id def case_close(self, cr, uid, ids, context=None): - """Overrides close for crm_case for setting close date - """ + """ Overrides close for crm_case for setting duration """ res = True - for phone in self.browse(cr, uid, ids): + for phone in self.browse(cr, uid, ids, context=context): phone_id = phone.id - data = {'date_closed': time.strftime('%Y-%m-%d %H:%M:%S')} + data = {} if phone.duration <=0: - duration = datetime.now() - datetime.strptime(phone.date, '%Y-%m-%d %H:%M:%S') - data.update({'duration': duration.seconds/float(60)}) - res = super(crm_phonecall, self).case_close(cr, uid, [phone_id], context) - self.write(cr, uid, [phone_id], data) + duration = datetime.now() - datetime.strptime(phone.date, DEFAULT_SERVER_DATETIME_FORMAT) + data['duration'] = duration.seconds/float(60) + res = super(crm_phonecall, self).case_close(cr, uid, [phone_id], context=context) + self.write(cr, uid, [phone_id], data, context=context) return res def case_reset(self, cr, uid, ids, context=None): """Resets case as Todo """ res = super(crm_phonecall, self).case_reset(cr, uid, ids, context) - self.write(cr, uid, ids, {'duration': 0.0, 'state':'open'}) - return res - - - def case_open(self, cr, uid, ids, context=None): - """Overrides cancel for crm_case for setting Open Date - """ - res = super(crm_phonecall, self).case_open(cr, uid, ids, context) - self.write(cr, uid, ids, {'date_open': time.strftime('%Y-%m-%d %H:%M:%S')}) + self.write(cr, uid, ids, {'duration': 0.0, 'state':'open'}, context=context) return res def schedule_another_phonecall(self, cr, uid, ids, schedule_time, call_summary, \ @@ -300,7 +287,7 @@ class crm_phonecall(crm_base, osv.osv): return value # ---------------------------------------- - # OpenChatter methods and notifications + # OpenChatter # ---------------------------------------- def get_needaction_user_ids(self, cr, uid, ids, context=None): @@ -336,9 +323,6 @@ class crm_phonecall(crm_base, osv.osv): def _call_set_partner_send_note(self, cr, uid, ids, context=None): return self.message_append_note(cr, uid, ids, body=_("Partner has been <b>created</b>"), context=context) - - -crm_phonecall() # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/crm/crm_phonecall_view.xml b/addons/crm/crm_phonecall_view.xml index 55d8d8c9a64..788b69f8747 100644 --- a/addons/crm/crm_phonecall_view.xml +++ b/addons/crm/crm_phonecall_view.xml @@ -43,10 +43,14 @@ states="open,pending" icon="gtk-redo" name="action_make_meeting" type="object" /> <field name="state"/> - <button name="case_cancel" string="Cancel" states="open,pending" type="object" icon="gtk-cancel"/> - <button name="case_open" string="Todo" states="pending" type="object" icon="gtk-go-forward"/> - <button name="case_close" string="Held" states="open,pending" type="object" icon="gtk-jump-to"/> - <button name="case_pending" string="Not Held" states="open" type="object" icon="gtk-media-pause"/> + <button name="case_open" string="Confirm" type="object" + states="draft,pending" icon="gtk-go-forward"/> + <button name="case_close" string="Held" type="object" + states="open,pending" icon="gtk-jump-to"/> + <button name="case_cancel" string="Cancel" type="object" + states="draft,open,pending" icon="gtk-cancel"/> + <button name="case_reset" string="Reset to Todo" type="object" + states="cancel" icon="gtk-convert"/> </tree> </field> </record> @@ -60,13 +64,16 @@ <field name="arch" type="xml"> <form layout="manual"> <div class="oe_form_topbar"> - <button name="case_close" string="Held" states="open,pending" type="object" /> - <button name="case_open" string="Todo" states="pending" type="object" /> - <button name="case_pending" string="Not Held" states="open" type="object" /> - <button name="case_reset" string="Reset to Todo" states="cancel" type="object" /> - <button name="case_cancel" string="Cancel" states="open,pending" type="object" /> + <button name="case_open" string="Confirm" type="object" + states="draft,pending" icon="gtk-go-forward"/> + <button name="case_close" string="Held" type="object" + states="open,pending" icon="gtk-jump-to"/> + <button name="case_reset" string="Reset to Todo" type="object" + states="cancel" icon="gtk-convert"/> + <button name="case_cancel" string="Cancel" type="object" + states="draft,open,pending" icon="gtk-cancel"/> <div class="oe_right"> - <field name="state" widget="statusbar" nolabel="1" statusbar_visible="open,done" statusbar_colors='{"pending":"red"}' select="1"/> + <field name="state" widget="statusbar" nolabel="1" statusbar_visible="draft,open,done" select="1"/> </div> <div class="oe_clear"/> </div> diff --git a/addons/crm/crm_view.xml b/addons/crm/crm_view.xml index dd24ef38911..c8f019e685b 100644 --- a/addons/crm/crm_view.xml +++ b/addons/crm/crm_view.xml @@ -143,6 +143,8 @@ <field name="sequence"/> <field name="name"/> <field name="probability"/> + <field name="state"/> + <field name="type"/> </tree> </field> </record> @@ -162,6 +164,9 @@ <field name="on_change"/> <field name="case_default"/> <field name="sequence"/> + <field name="state"/> + <field name="fold"/> + <field name="type"/> </group> <separator string="Requirements" colspan="4"/> <field name="requirements" nolabel="1" colspan="4"/> diff --git a/addons/crm/test/process/cancel_lead.yml b/addons/crm/test/process/cancel_lead.yml index ca5f7d0174a..34dd8310022 100644 --- a/addons/crm/test/process/cancel_lead.yml +++ b/addons/crm/test/process/cancel_lead.yml @@ -6,8 +6,11 @@ - I check cancelled lead. - - !assert {model: crm.lead, id: crm.crm_case_itisatelesalescampaign0, string: Lead is in cancel state}: - - state == "cancel" + !python {model: crm.lead}: | + lead = self.browse(cr, uid, ref('crm_case_itisatelesalescampaign0')) + assert lead.stage_id.id == ref('crm.stage_lead7'), "Stage should be 'Dead' and is %s." % (lead.stage_id.name) + assert lead.state == 'cancel', "Opportunity is not in 'cancel' state." + assert lead.probability == 0.0, 'Opportunity probability is wrong and should be 0.0.' - I reset cancelled lead into unqualified lead. - @@ -19,35 +22,24 @@ !assert {model: crm.lead, id: crm.crm_case_itisatelesalescampaign0, string: Lead is in draft state}: - state == "draft" - - I put unqualified lead into pending. + I re-open the lead - !python {model: crm.lead}: | - self.case_pending(cr, uid, [ref("crm_case_itisatelesalescampaign0")]) + self.case_open(cr, uid, [ref("crm_case_itisatelesalescampaign0")]) - - I check status of pending lead. + I check stage and state of the re-opened lead - - !assert {model: crm.lead, id: crm.crm_case_itisatelesalescampaign0, string: Lead is in pending state}: - - state == "pending" + !python {model: crm.lead}: | + lead = self.browse(cr, uid, ref('crm.crm_case_itisatelesalescampaign0')) + assert lead.stage_id.id == ref('crm.stage_lead2'), "Opportunity stage should be 'Qualification'." + assert lead.state == 'open', "Opportunity should be in 'open' state." - I escalate the lead to parent team. - !python {model: crm.lead}: | self.case_escalate(cr, uid, [ref("crm_case_itisatelesalescampaign0")]) - - I check lead escalate to parent team. + I check the lead is correctly escalated to the parent team. - !assert {model: crm.lead, id: crm.crm_case_itisatelesalescampaign0, string: Escalate lead to parent team}: - section_id.name == "Sales Department" -- - I mark as lost the opportunity. -- - !python {model: crm.lead}: | - self.case_mark_lost(cr, uid, [ref("crm_case_itisatelesalescampaign0")]) -- - I check opportunity after lost. -- - !python {model: crm.lead}: | - lead = self.browse(cr, uid, ref('crm_case_itisatelesalescampaign0')) - assert lead.state == 'done', "lead is not done state" - assert lead.stage_id.id == ref('crm.stage_lead6'), 'Stage is not changed!' - assert lead.probability == 0.0, 'Probability is wrong!' diff --git a/addons/crm/test/process/lead2opportunity2win.yml b/addons/crm/test/process/lead2opportunity2win.yml index a5b6c421dbb..c1caf394c2e 100644 --- a/addons/crm/test/process/lead2opportunity2win.yml +++ b/addons/crm/test/process/lead2opportunity2win.yml @@ -51,7 +51,6 @@ - !python {model: crm.lead}: | self.action_makeMeeting(cr, uid, [ref('crm_case_qrecorp0')]) - - After communicated with customer, I put some notes with contract details. - @@ -60,18 +59,18 @@ note_id = self.create(cr, uid, {'body': "ces détails envoyés par le client sur ​​le FAX pour la qualité"}) self.action_add(cr, uid, [note_id], context=context) - - Finally, I won this opportunity, so I close this opportunity. + I win this opportunity - !python {model: crm.lead}: | self.case_mark_won(cr, uid, [ref("crm_case_qrecorp0")]) - - I check details of the opportunity after won the opportunity. + I check details of the opportunity after having won the opportunity. - !python {model: crm.lead}: | lead = self.browse(cr, uid, ref('crm_case_qrecorp0')) - assert lead.state == 'done', 'Opportunity is not in done state!' - assert lead.stage_id.name == 'Won', ' Stage of Opportunity is not win!' - assert lead.probability == 100.0, 'probability revenue should not be 100.0!' + assert lead.stage_id.id == ref('crm.stage_lead6'), "Opportunity stage should be 'Won'." + assert lead.state == 'done', "Opportunity is not in 'done' state!" + assert lead.probability == 100.0, "Revenue probability should be 100.0!" - I convert mass lead into opportunity customer. - @@ -97,6 +96,19 @@ assert opp.type == 'opportunity', 'Lead is not converted to opportunity!' assert opp.stage_id.id == ref("stage_lead1"), 'Stage of probability is incorrect!' - + I loose the second opportunity +- + !python {model: crm.lead}: | + self.case_mark_lost(cr, uid, [ref("crm_case_electonicgoodsdealer0")]) +- + I check details of the opportunity after the loose +- + !python {model: crm.lead}: | + lead = self.browse(cr, uid, ref('crm_case_electonicgoodsdealer0')) + assert lead.stage_id.id == ref('crm.stage_lead8'), "Opportunity stage should be 'Lost'." + assert lead.state == 'cancel', "Lost opportunity is not in 'cancel' state!" + assert lead.probability == 0.0, "Revenue probability should be 0.0!" +- I confirm review needs meeting. - !python {model: crm.meeting}: | diff --git a/addons/crm_claim/crm_claim.py b/addons/crm_claim/crm_claim.py index 1b6d386b0cc..c4e19a19dfd 100644 --- a/addons/crm_claim/crm_claim.py +++ b/addons/crm_claim/crm_claim.py @@ -2,7 +2,7 @@ ############################################################################## # # OpenERP, Open Source Management Solution -# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>). +# Copyright (C) 2004-today OpenERP SA (<http://www.openerp.com>) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -19,25 +19,57 @@ # ############################################################################## -from osv import fields, osv +from base_status.base_stage import base_stage +import binascii from crm import crm -import time from crm import wizard -import binascii +from osv import fields, osv +import time import tools from tools.translate import _ wizard.mail_compose_message.SUPPORTED_MODELS.append('crm.claim') + CRM_CLAIM_PENDING_STATES = ( crm.AVAILABLE_STATES[2][0], # Cancelled crm.AVAILABLE_STATES[3][0], # Done crm.AVAILABLE_STATES[4][0], # Pending ) - -class crm_claim(crm.crm_case, osv.osv): +class crm_claim_stage(osv.osv): + """ Model for claim stages. This models the main stages of a claim + management flow. Main CRM objects (leads, opportunities, project + issues, ...) will now use only stages, instead of state and stages. + Stages are for example used to display the kanban view of records. """ - Crm claim + _name = "crm.claim.stage" + _description = "Claim stages" + _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. Lower is better."), + 'section_ids':fields.many2many('crm.case.section', 'section_claim_stage_rel', 'stage_id', 'section_id', string='Sections', + help="Link between stages and sales teams. When set, this limitate the current stage to the selected sales teams."), + 'state': fields.selection(crm.AVAILABLE_STATES, 'State', required=True, help="The related state for the stage. The state of your document will automatically change regarding the selected stage. For example, if a stage is related to the state 'Close', when your document reaches this stage, it will be automatically have the 'closed' state."), + 'case_refused': fields.boolean('Refused stage', + help='Refused stages are specific stages for done.'), + '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."), + 'fold': fields.boolean('Hide in Views when Empty', + help="This stage is not visible, for example in status bar or kanban view, when there are no records in that stage to display."), + } + + _defaults = { + 'sequence': lambda *args: 1, + 'state': 'draft', + 'fold': False, + 'case_refused': False, + } + +class crm_claim(base_stage, osv.osv): + """ Crm claim """ _name = "crm.claim" _description = "Claim" @@ -73,31 +105,68 @@ class crm_claim(crm.crm_case, osv.osv): 'email_cc': fields.text('Watchers Emails', size=252, help="These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma"), 'email_from': fields.char('Email', size=128, help="These people will receive email."), 'partner_phone': fields.char('Phone', size=32), - 'stage_id': fields.many2one ('crm.case.stage', 'Stage', domain="[('section_ids','=',section_id)]"), + 'stage_id': fields.many2one ('crm.claim.stage', 'Stage', + domain="['|', ('section_ids', '=', section_id), ('case_default', '=', True)]"), 'cause': fields.text('Root Cause'), - 'state': fields.selection(crm.AVAILABLE_STATES, 'Status', size=16, readonly=True, - help='The state is set to \'Draft\', when a case is created.\ - \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\'.'), + 'state': fields.related('stage_id', 'state', type="selection", store=True, + selection=crm.AVAILABLE_STATES, string="State", readonly=True, + help='The state is set to \'Draft\', when a case is created.\ + If the case is in progress the state is set to \'Open\'.\ + When the case is over, the state is set to \'Done\'.\ + If the case needs to be reviewed then the state is \ + set to \'Pending\'.'), 'message_ids': fields.one2many('mail.message', 'res_id', 'Messages', domain=[('model','=',_name)]), } _defaults = { - 'user_id': crm.crm_case._get_default_user, - 'partner_id': crm.crm_case._get_default_partner, - 'email_from':crm.crm_case. _get_default_email, - 'state': lambda *a: 'draft', - 'section_id':crm.crm_case. _get_section, - 'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'), + 'user_id': lambda s, cr, uid, c: s._get_default_user(cr, uid, c), + 'partner_id': lambda s, cr, uid, c: s._get_default_partner(cr, uid, c), + 'email_from': lambda s, cr, uid, c: s._get_default_email(cr, uid, c), + 'section_id': lambda s, cr, uid, c: s._get_default_section_id(cr, uid, c), + 'date': fields.datetime.now, 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.case', context=c), 'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0], 'active': lambda *a: 1 } - def case_get_note_msg_prefix(self, cr, uid, id, context=None): - return 'Claim' + def stage_find(self, cr, uid, cases, section_id, domain=[], order='sequence', context=None): + """ Override of the base.stage method + Parameter of the stage search taken from the lead: + - section_id: if set, stages must belong to this section or + be a default case + """ + if isinstance(cases, (int, long)): + cases = self.browse(cr, uid, cases, context=context) + # collect all section_ids + section_ids = [] + if section_id: + section_ids.append(section_id) + for claim in cases: + if claim.section_id: + section_ids.append(claim.section_id.id) + # OR all section_ids and OR with case_default + search_domain = [] + if section_ids: + search_domain += [('|')] * len(section_ids) + for section_id in section_ids: + search_domain.append(('section_ids', '=', section_id)) + search_domain.append(('case_default', '=', True)) + # AND with the domain in parameter + search_domain += list(domain) + # perform search, return the first found + stage_ids = self.pool.get('crm.claim.stage').search(cr, uid, search_domain, order=order, context=context) + if stage_ids: + return stage_ids[0] + return False + def case_refuse(self, cr, uid, ids, context=None): + """ Mark the case as refused: state=done and case_refused=True """ + for lead in self.browse(cr, uid, ids): + stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, ['&', ('state', '=', 'done'), ('case_refused', '=', True)], context=context) + if stage_id: + self.case_set(cr, uid, [lead.id], values_to_update={}, new_stage_id=stage_id, context=context) + return self.case_refuse_send_note(cr, uid, ids, context=context) + 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 @@ -110,19 +179,6 @@ class crm_claim(crm.crm_case, osv.osv): } address = self.pool.get('res.partner').browse(cr, uid, part) return {'value': {'email_from': address.email, 'partner_phone': address.phone}} - - def case_open(self, cr, uid, ids, *args): - """Opens Claim""" - for l in self.browse(cr, uid, ids): - # When coming from draft override date and stage otherwise just set state - if l.state == 'draft': - message = _("The claim '%s' has been opened.") % l.name - self.log(cr, uid, l.id, message) - stage_id = self.stage_find(cr, uid, l.section_id.id or False, [('sequence','>',0)]) - if stage_id: - self.stage_set(cr, uid, [l.id], stage_id) - res = super(crm_claim, self).case_open(cr, uid, ids, *args) - return res def message_new(self, cr, uid, msg, custom_values=None, context=None): """Automatically called when new email message arrives""" @@ -177,6 +233,26 @@ class crm_claim(crm.crm_case, osv.osv): res = self.write(cr, uid, [case.id], values, context=context) return res + # --------------------------------------------------- + # OpenChatter methods and notifications + # --------------------------------------------------- + + def case_get_note_msg_prefix(self, cr, uid, id, context=None): + """ Override of default prefix for notifications. """ + return 'Claim' + + def case_refuse_send_note(self, cr, uid, ids, context=None): + for id in ids: + msg = _('%s has been <b>refused</b>.') % (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 stage_set_send_note(self, cr, uid, ids, stage_id, context=None): + """ Override of the (void) default notification method. """ + stage_name = self.pool.get('crm.claim.stage').name_get(cr, uid, [stage_id], context=context)[0][1] + return self.message_append_note(cr, uid, ids, body= _("Stage changed to <b>%s</b>.") % (stage_name), context=context) + + class res_partner(osv.osv): _inherit = 'res.partner' _columns = { diff --git a/addons/crm_claim/crm_claim_data.xml b/addons/crm_claim/crm_claim_data.xml index 5b3a2b0ed2a..33bd280c364 100644 --- a/addons/crm_claim/crm_claim_data.xml +++ b/addons/crm_claim/crm_claim_data.xml @@ -42,27 +42,39 @@ Case Stage --> - <record model="crm.case.stage" id="stage_claim1"> - <field name="name">Accepted as Claim</field> + <record model="crm.claim.stage" id="stage_claim1"> + <field name="name">Draft claim</field> + <field name="state">draft</field> <field name="sequence">26</field> + <field name="case_default" eval="True"/> </record> - <record model="crm.case.stage" id="stage_claim5"> + <record model="crm.claim.stage" id="stage_claim5"> <field name="name">Actions Defined</field> + <field name="state">open</field> <field name="sequence">27</field> + <field name="case_default" eval="True"/> </record> - <record model="crm.case.stage" id="stage_claim2"> + <record model="crm.claim.stage" id="stage_claim2"> <field name="name">Actions Done</field> + <field name="state">done</field> <field name="sequence">28</field> + <field name="case_default" eval="True"/> </record> - <record model="crm.case.stage" id="stage_claim3"> - <field name="name">Won't fix</field> + <record model="crm.claim.stage" id="stage_claim3"> + <field name="name">Refused</field> + <field name="state">done</field> <field name="sequence">29</field> + <field name="case_default" eval="True"/> + <field name="case_refused" eval="True"/> + <field name="fold" eval="True"/> </record> - - <record model="crm.case.section" id="crm.section_sales_department"> - <field name="name">Sales Department</field> - <field name="code">Sales</field> - <field name="stage_ids" eval="[(4, ref('stage_claim1')), (4, ref('stage_claim2')), (4, ref('stage_claim3')), (4, ref('stage_claim5'))]"/> + <record model="crm.claim.stage" id="stage_claim3"> + <field name="name">Cancelled</field> + <field name="state">cancel</field> + <field name="sequence">30</field> + <field name="case_default" eval="True"/> + <field name="fold" eval="True"/> </record> + </data> </openerp> diff --git a/addons/crm_claim/crm_claim_demo.xml b/addons/crm_claim/crm_claim_demo.xml index 592e655b489..0e368bcdffa 100644 --- a/addons/crm_claim/crm_claim_demo.xml +++ b/addons/crm_claim/crm_claim_demo.xml @@ -12,7 +12,6 @@ <field eval=""3"" name="priority"/> <field name="user_id" ref="base.user_root"/> <field eval=""Problem with the delivery of goods"" name="name"/> - <field eval=""open"" name="state"/> <field name="section_id" ref="crm.section_sales_department"/> <field name="categ_id" ref="crm_claim.categ_claim1"/> <field name="stage_id" ref="crm_claim.stage_claim1"/> @@ -25,7 +24,6 @@ <field eval=""4"" name="priority"/> <field name="user_id" ref="base.user_root"/> <field eval=""Damaged Products"" name="name"/> - <field eval=""open"" name="state"/> <field name="section_id" ref="crm.section_sales_department"/> <field name="categ_id" ref="crm_claim.categ_claim2"/> <field name="stage_id" ref="crm_claim.stage_claim5"/> @@ -38,7 +36,6 @@ <field eval=""2"" name="priority"/> <field name="user_id" ref="base.user_demo"/> <field eval=""Document related problems"" name="name"/> - <field eval=""done"" name="state"/> <field name="section_id" ref="crm.section_sales_department"/> <field name="categ_id" ref="crm_claim.categ_claim3"/> <field name="stage_id" ref="crm_claim.stage_claim2"/> @@ -52,7 +49,6 @@ <field eval=""3"" name="priority"/> <field name="user_id" ref="base.user_root"/> <field eval=""Product quality not maintained"" name="name"/> - <field eval=""draft"" name="state"/> <field name="section_id" ref="crm.section_sales_department"/> <field name="categ_id" ref="crm_claim.categ_claim1"/> <field name="stage_id" ref="crm_claim.stage_claim5"/> @@ -65,7 +61,6 @@ <field eval=""3"" name="priority"/> <field name="user_id" ref="base.user_root"/> <field eval=""Some products missing"" name="name"/> - <field eval=""pending"" name="state"/> <field name="section_id" ref="crm.section_sales_department"/> <field name="categ_id" ref="crm_claim.categ_claim3"/> <field name="stage_id" ref="crm_claim.stage_claim3"/> @@ -77,7 +72,6 @@ <field eval=""3"" name="priority"/> <field name="user_id" ref="base.user_root"/> <field eval=""Problem with the delivery of assignments"" name="name"/> - <field eval=""cancel"" name="state"/> <field name="section_id" ref="crm.section_sales_department"/> <field eval="time.strftime('%Y-%m-28 14:15:30')" name="date"/> <field name="categ_id" ref="crm_claim.categ_claim1"/> @@ -91,7 +85,6 @@ <field eval=""3"" name="priority"/> <field name="user_id" ref="base.user_root"/> <field eval=""Documents unclear"" name="name"/> - <field eval=""done"" name="state"/> <field name="section_id" ref="crm.section_sales_department"/> <field eval="time.strftime('%Y-%m-19 13:01:05')" name="date"/> <field name="categ_id" ref="crm_claim.categ_claim3"/> diff --git a/addons/crm_claim/crm_claim_menu.xml b/addons/crm_claim/crm_claim_menu.xml index 734302fa7f9..75bfd9ce211 100644 --- a/addons/crm_claim/crm_claim_menu.xml +++ b/addons/crm_claim/crm_claim_menu.xml @@ -50,5 +50,10 @@ <menuitem name="Claims" id="menu_crm_case_claims" parent="base.menu_aftersale" action="crm_case_categ_claim0" sequence="1"/> + <!-- Claim Stages --> + <menuitem id="menu_definitions" name="Configuration" parent="base.menu_main_pm" sequence="60"/> + <menuitem id="menu_project_config_project" name="Stages" parent="menu_definitions" sequence="1"/> + <menuitem id="menu_claim_stage_view" name="Claim Stages" action="crm_claim_stage_act" parent="menu_project_config_project" sequence="20"/> + </data> </openerp> diff --git a/addons/crm_claim/crm_claim_view.xml b/addons/crm_claim/crm_claim_view.xml index 57a8827d528..c6e7db167a8 100644 --- a/addons/crm_claim/crm_claim_view.xml +++ b/addons/crm_claim/crm_claim_view.xml @@ -23,12 +23,40 @@ <!-- Claim Stages --> + <record id="crm_claim_stage_tree" model="ir.ui.view"> + <field name="name">crm.claim.stage.tree</field> + <field name="model">crm.claim.stage</field> + <field name="type">tree</field> + <field name="arch" type="xml"> + <tree string="Claim Stages"> + <field name="sequence"/> + <field name="name"/> + <field name="state"/> + </tree> + </field> + </record> + + <record id="crm_claim_stage_form" model="ir.ui.view"> + <field name="name">crm.claim.stage.form</field> + <field name="model">crm.claim.stage</field> + <field name="type">form</field> + <field name="arch" type="xml"> + <form string="Claim Stage"> + <field name="name"/> + <field name="case_default"/> + <field name="sequence"/> + <field name="state"/> + <field name="case_refused"/> + <field name="fold"/> + </form> + </field> + </record> + <record id="crm_claim_stage_act" model="ir.actions.act_window"> <field name="name">Claim Stages</field> - <field name="res_model">crm.case.stage</field> + <field name="res_model">crm.claim.stage</field> <field name="view_type">form</field> - <field name="view_id" ref="crm.crm_case_stage_tree"/> - <field name="context">{'search_default_claim':1}</field> + <field name="view_id" ref="crm_claim_stage_tree"/> <field name="help">You can create claim stages to categorize the status of every claim entered in the system. The stages define all the steps required for the resolution of a claim.</field> </record> @@ -44,19 +72,19 @@ <field name="partner_id"/> <field name="user_id" /> <field name="date"/> + <field name="stage_id"/> <field name="date_action_next"/> <field name="action_next"/> <field name="categ_id" string="Type" select="1"/> - <field name="stage_id" invisible="1"/> <field name="date_deadline" invisible="1"/> <field name="date_closed" invisible="1"/> - <field name="state"/> - <button name="case_open" string="Open" - states="draft,pending" type="object" - icon="gtk-go-forward" /> - <button name="case_close" string="Close" - states="open,draft,pending" type="object" - icon="gtk-close" /> + <field name="state" groups="base.group_no_one"/> + <button name="case_open" string="Open" type="object" + states="draft,pending" icon="gtk-go-forward"/> + <button name="case_close" string="Done" type="object" + states="draft,open,pending" icon="gtk-close"/> + <button name="case_refuse" string="Refuse" type="object" + states="draft,open,pending" icon="gtk-cancel"/> </tree> </field> </record> @@ -68,13 +96,23 @@ <field name="arch" type="xml"> <form layout="manual"> <div class="oe_form_topbar"> - <button name="case_close" string="Done" states="open,pending" type="object" /> - <button name="case_open" string="Open" states="draft,pending" type="object" /> - <button name="case_pending" string="Pending" states="draft,open" type="object" /> - <button name="case_reset" string="Reset to Draft" states="done,cancel" type="object" /> - <button name="case_cancel" string="Cancel" states="draft,open,pending" type="object" /> + <button name="case_open" string="Open" type="object" + states="draft,pending"/> + <button name="case_close" string="Done" type="object" + states="open,pending"/> + <button name="case_refuse" string="Refuse" type="object" + states="draft,open,pending"/> + <button name="stage_previous" string="Previous Stage" type="object" + states="open,pending" icon="gtk-go-back" attrs="{'invisible': [('stage_id','=', False)]}"/> + <button name="stage_next" string="Next Stage" type="object" + states="open,pending" icon="gtk-go-forward" attrs="{'invisible': [('stage_id','=', False)]}"/> + <button name="case_reset" string="Reset to Draft" type="object" + states="cancel,done"/> + <button name="case_cancel" string="Cancel" type="object" + states="draft,open,pending"/> <div class="oe_right"> - <field name="state" select="1" nolabel="1" widget="statusbar" statusbar_visible="draft,open,done" statusbar_colors='{"pending":"blue"}'/> + <field name="stage_id" nolabel="1" widget="statusbar" + on_change="onchange_stage_id(stage_id)"/> </div> <div class="oe_clear"/> </div> @@ -86,11 +124,7 @@ <group colspan="4" col="6"> <field name="user_id"/> <field name="section_id" widget="selection" /> - <group colspan="2" col="4"> - <field name="stage_id" domain="[('section_ids','=',section_id)]"/> - <button name="stage_previous" string="" type="object" icon="gtk-go-back" /> - <button name="stage_next" string="" type="object" icon="gtk-go-forward" /> - </group> + <field name="state" groups="base.group_no_one"/> <newline /> <field name="priority"/> <field name="date_deadline"/> @@ -227,7 +261,7 @@ domain="[]" context="{'group_by':'categ_id'}" /> <filter string="Status" icon="terp-stock_effects-object-colorize" - domain="[]" context="{'group_by':'state'}" /> + domain="[]" context="{'group_by':'state'}" groups="base.group_no_one"/> <separator orientation="vertical"/> <filter string="Claim Date" icon="terp-go-month" domain="[]" help="Claim Date" diff --git a/addons/crm_claim/security/ir.model.access.csv b/addons/crm_claim/security/ir.model.access.csv index 5a05fbd4d87..303624288ce 100644 --- a/addons/crm_claim/security/ir.model.access.csv +++ b/addons/crm_claim/security/ir.model.access.csv @@ -1,5 +1,6 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_crm_claim_manager,crm.claim.manager,model_crm_claim,base.group_sale_manager,1,1,1,1
access_crm_claim_user,crm.claim.user,model_crm_claim,base.group_sale_salesman,1,1,1,0
+access_crm_claim_stage_user,crm.claim.stage.user,model_crm_claim_stage,base.group_sale_salesman,1,1,1,1
access_crm_claim_report_manager,crm.claim.report.manager,model_crm_claim_report,base.group_sale_manager,1,1,1,1
access_crm_claim_partner_manager,crm.claim.partner.manager,model_crm_claim,base.group_partner_manager,1,0,0,0
diff --git a/addons/crm_claim/test/process/claim.yml b/addons/crm_claim/test/process/claim.yml index e20436cd3c6..45ca7cf2962 100644 --- a/addons/crm_claim/test/process/claim.yml +++ b/addons/crm_claim/test/process/claim.yml @@ -30,7 +30,7 @@ claim_ids = self.search(cr, uid, [('email_from','=', 'Mr. John Right <info@customer.com>')]) claim = self.browse(cr, uid, claim_ids[0]) assert claim.state == "open", "Claim is not in Open state" - assert claim.stage_id.id == ref("crm.stage_lead1"), "Claim is not in New stage" + assert claim.stage_id.id == ref("crm.stage_lead2"), "Claim is not in Qualification stage" - After complete all service from our side, I close this claim. - diff --git a/addons/crm_fundraising/crm_fundraising.py b/addons/crm_fundraising/crm_fundraising.py index 90014188777..34b5f20203c 100644 --- a/addons/crm_fundraising/crm_fundraising.py +++ b/addons/crm_fundraising/crm_fundraising.py @@ -19,13 +19,14 @@ # ############################################################################## -from osv import fields, osv +from base_status.base_stage import base_stage from crm import crm from crm import wizard +from osv import fields, osv wizard.mail_compose_message.SUPPORTED_MODELS.append('crm.fundraising') -class crm_fundraising(crm.crm_case, osv.osv): +class crm_fundraising(base_stage, osv.osv): """ Fund Raising Cases """ _name = "crm.fundraising" @@ -68,14 +69,58 @@ class crm_fundraising(crm.crm_case, osv.osv): 'duration': fields.float('Duration'), 'ref': fields.reference('Reference', selection=crm._links_get, size=128), 'ref2': fields.reference('Reference 2', selection=crm._links_get, size=128), - 'state': fields.selection(crm.AVAILABLE_STATES, 'Status', size=16, readonly=True, - help='The state is set to \'Draft\', when a case is created.\ - \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\'.'), + 'state': fields.related('stage_id', 'state', type="selection", store=True, + selection=crm.AVAILABLE_STATES, string="State", readonly=True, + help='The state is set to \'Draft\', when a case is created.\ + If the case is in progress the state is set to \'Open\'.\ + When the case is over, the state is set to \'Done\'.\ + If the case needs to be reviewed then the state is \ + set to \'Pending\'.'), 'message_ids': fields.one2many('mail.message', 'res_id', 'Messages', domain=[('model','=',_name)]), } + _defaults = { + 'active': 1, + 'user_id': lambda s, cr, uid, c: s._get_default_user(cr, uid, c), + 'partner_id': lambda s, cr, uid, c: s._get_default_partner(cr, uid, c), + 'email_from': lambda s, cr, uid, c: s._get_default_email(cr, uid, c), + 'section_id': lambda s, cr, uid, c: s._get_default_section_id(cr, uid, c), + 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.case', context=c), + 'priority': crm.AVAILABLE_PRIORITIES[2][0], + 'probability': 0.0, + 'planned_cost': 0.0, + 'planned_revenue': 0.0, + } + + def stage_find(self, cr, uid, cases, section_id, domain=[], order='sequence', context=None): + """ Override of the base.stage method + Parameter of the stage search taken from the lead: + - section_id: if set, stages must belong to this section or + be a default case + """ + if isinstance(cases, (int, long)): + cases = self.browse(cr, uid, cases, context=context) + # collect all section_ids + section_ids = [] + if section_id: + section_ids.append(section_id) + for case in cases: + if case.section_id: + section_ids.append(case.section_id.id) + # OR all section_ids and OR with case_default + search_domain = [] + if section_ids: + search_domain += [('|')] * len(section_ids) + for section_id in section_ids: + search_domain.append(('section_ids', '=', section_id)) + search_domain.append(('case_default', '=', True)) + # AND with the domain in parameter + search_domain += list(domain) + # perform search, return the first found + stage_ids = self.pool.get('crm.case.stage').search(cr, uid, search_domain, order=order, context=context) + if stage_ids: + return stage_ids[0] + return False def message_new(self, cr, uid, msg, custom_values=None, context=None): """Automatically called when new email message arrives""" @@ -93,21 +138,18 @@ class crm_fundraising(crm.crm_case, osv.osv): self.write(cr, uid, [res_id], vals, context=context) return res_id + # --------------------------------------------------- + # OpenChatter methods and notifications + # --------------------------------------------------- - _defaults = { - 'active': 1, - 'user_id': crm.crm_case._get_default_user, - 'partner_id': crm.crm_case._get_default_partner, - 'email_from': crm.crm_case. _get_default_email, - 'state': 'draft', - 'section_id': crm.crm_case. _get_section, - 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.case', context=c), - 'priority': crm.AVAILABLE_PRIORITIES[2][0], - 'probability': 0.0, - 'planned_cost': 0.0, - 'planned_revenue': 0.0, - } + def case_get_note_msg_prefix(self, cr, uid, id, context=None): + """ Override of default prefix for notifications. """ + return 'Fundraising' + def stage_set_send_note(self, cr, uid, ids, stage_id, context=None): + """ Override of the (void) default notification method. """ + stage_name = self.pool.get('crm.case.stage').name_get(cr, uid, [stage_id], context=context)[0][1] + return self.message_append_note(cr, uid, ids, body= _("Stage changed to <b>%s</b>.") % (stage_name), context=context) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/crm_fundraising/crm_fundraising_demo.xml b/addons/crm_fundraising/crm_fundraising_demo.xml index 52b4094d126..70b32afabf3 100644 --- a/addons/crm_fundraising/crm_fundraising_demo.xml +++ b/addons/crm_fundraising/crm_fundraising_demo.xml @@ -9,7 +9,6 @@ <field name="partner_id" ref="base.res_partner_9"/> <field eval=""3"" name="priority"/> <field name="user_id" ref="base.user_demo"/> - <field eval=""open"" name="state"/> <field eval="250000.0" name="planned_cost"/> <field name="section_id" ref="crm.section_sales_department"/> <field eval="time.strftime('%Y-%m-01 10:35:50')" name="date"/> @@ -26,7 +25,6 @@ <field name="partner_id" ref="base.main_partner"/> <field eval=""3"" name="priority"/> <field name="user_id" ref="base.user_root"/> - <field eval=""draft"" name="state"/> <field eval="2000000.0" name="planned_cost"/> <field name="section_id" ref="crm.section_sales_department"/> <field eval="time.strftime('%Y-%m-05 12:35:50')" name="date"/> @@ -43,7 +41,6 @@ <field name="partner_id" ref="base.res_partner_3"/> <field eval=""3"" name="priority"/> <field name="user_id" ref="base.user_root"/> - <field eval=""open"" name="state"/> <field eval="500000.0" name="planned_cost"/> <field name="section_id" ref="crm.section_sales_department"/> <field eval="time.strftime('%Y-%m-07 13:50:50')" name="date"/> @@ -59,7 +56,6 @@ <field name="partner_id" ref="base.res_partner_4"/> <field eval=""3"" name="priority"/> <field name="user_id" ref="base.user_demo"/> - <field eval=""draft"" name="state"/> <field eval="1000000.0" name="planned_cost"/> <field name="section_id" ref="crm.section_sales_department"/> <field eval="time.strftime('%Y-%m-12 15:10:50')" name="date"/> @@ -75,7 +71,6 @@ <field name="partner_id" ref="base.res_partner_14"/> <field eval=""3"" name="priority"/> <field name="user_id" ref="base.user_root"/> - <field eval=""open"" name="state"/> <field eval="5000000.0" name="planned_cost"/> <field name="section_id" ref="crm.section_sales_department"/> <field eval="time.strftime('%Y-%m-17 19:00:15')" name="date"/> @@ -91,7 +86,6 @@ <field name="partner_id" ref="base.res_partner_10"/> <field eval=""3"" name="priority"/> <field name="user_id" ref="base.user_root"/> - <field eval=""done"" name="state"/> <field eval="10000000.0" name="planned_cost"/> <field name="section_id" ref="crm.section_sales_department"/> <field eval="time.strftime('%Y-%m-27 09:00:15')" name="date"/> @@ -109,7 +103,6 @@ <field name="partner_id" ref="base.res_partner_15"/> <field eval=""3"" name="priority"/> <field name="user_id" ref="base.user_demo"/> - <field eval=""draft"" name="state"/> <field eval="10000.0" name="planned_cost"/> <field name="section_id" ref="crm.section_sales_department"/> <field eval="time.strftime('%Y-%m-01 10:00:15')" name="date"/> @@ -126,7 +119,6 @@ <field name="partner_id" ref="base.res_partner_9"/> <field eval=""3"" name="priority"/> <field name="user_id" ref="base.user_root"/> - <field eval=""open"" name="state"/> <field eval="800000.0" name="planned_cost"/> <field name="section_id" ref="crm.section_sales_department"/> <field eval="time.strftime('%Y-%m-24 22:00:15')" name="date"/> diff --git a/addons/crm_helpdesk/crm_helpdesk.py b/addons/crm_helpdesk/crm_helpdesk.py index d5ee8efc17e..c77fe384fbe 100644 --- a/addons/crm_helpdesk/crm_helpdesk.py +++ b/addons/crm_helpdesk/crm_helpdesk.py @@ -19,10 +19,10 @@ # ############################################################################## +from base_status.base_state import base_state from crm import crm -from osv import fields, osv -import time from crm import wizard +from osv import fields, osv import tools from tools.translate import _ @@ -34,7 +34,7 @@ CRM_HELPDESK_STATES = ( wizard.mail_compose_message.SUPPORTED_MODELS.append('crm.helpdesk') -class crm_helpdesk(crm.crm_case, osv.osv): +class crm_helpdesk(base_state, osv.osv): """ Helpdesk Cases """ _name = "crm.helpdesk" @@ -82,12 +82,11 @@ class crm_helpdesk(crm.crm_case, osv.osv): _defaults = { 'active': lambda *a: 1, - 'user_id': crm.crm_case._get_default_user, - 'partner_id': crm.crm_case._get_default_partner, - 'email_from': crm.crm_case. _get_default_email, + 'user_id': lambda s, cr, uid, c: s._get_default_user(cr, uid, c), + 'partner_id': lambda s, cr, uid, c: s._get_default_partner(cr, uid, c), + 'email_from': lambda s, cr, uid, c: s._get_default_email(cr, uid, c), 'state': lambda *a: 'draft', - 'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'), - 'section_id': crm.crm_case. _get_section, + 'date': lambda *a: fields.datetime.now(), 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.helpdesk', context=c), 'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0], } @@ -142,5 +141,12 @@ class crm_helpdesk(crm.crm_case, osv.osv): res = self.write(cr, uid, [case.id], values, context=context) return res + # ****************************** + # OpenChatter + # ****************************** + + def case_get_note_msg_prefix(self, cr, uid, id, context=None): + return 'Helpdesk' + # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/crm_helpdesk/crm_helpdesk_view.xml b/addons/crm_helpdesk/crm_helpdesk_view.xml index b1d5a2b96f8..8030237f3da 100644 --- a/addons/crm_helpdesk/crm_helpdesk_view.xml +++ b/addons/crm_helpdesk/crm_helpdesk_view.xml @@ -30,12 +30,18 @@ <field name="arch" type="xml"> <form layout="manual"> <div class="oe_form_topbar"> - <button name="case_close" states="open,pending" string="Close" type="object" /> - <button name="case_open" states="draft,pending" string="Open" type="object" /> - <button name="case_pending" states="draft,open" string="Pending" type="object" /> - <button name="case_reset" states="done,cancel" string="Reset to Draft" type="object" /> - <button name="case_escalate" states="open,draft,pending" string="Escalate" type="object" /> - <button name="case_cancel" states="draft,open,pending" string="Cancel" type="object" /> + <button name="case_open" string="Open" type="object" + states="draft,pending"/> + <button name="case_close" string="Close" type="object" + states="open,draft,pending"/> + <button name="case_pending" string="Pending" type="object" + states="draft,open"/> + <button name="case_reset" string="Reset to Draft" type="object" + states="cancel,done"/> + <button name="case_escalate" string="Escalate" type="object" + states="open,draft,pending"/> + <button name="case_cancel" string="Cancel" type="object" + states="draft,open,pending"/> <div class="oe_right"> <field name="state" select="1" nolabel="1" widget="statusbar" statusbar_visible="draft,open,done" statusbar_colors='{"pending":"blue"}'/> </div> @@ -126,6 +132,9 @@ </page> </notebook> </sheet> + <div class="oe_form_bottom"> + <field name="message_ids_social" colspan="4" widget="ThreadView" nolabel="1"/> + </div> </form> </field> </record> @@ -147,23 +156,18 @@ <field name="section_id"/> <field name="priority"/> <field name="state"/> - <button name="case_cancel" string="Cancel" - states="draft,open,pending" type="object" - icon="gtk-cancel" /> - <button name="case_open" string="Open" - states="draft,pending" type="object" - icon="gtk-go-forward" /> - <button name="case_pending" string="Pending" - states="draft,open" type="object" - icon="gtk-media-pause" /> - <button name="case_escalate" string="Escalate" - states="open,draft,pending" type="object" - icon="gtk-go-up" /> - <button name="case_close" string="Done" - states="open,draft,pending" type="object" - icon="gtk-close" /> - <button name="case_reset" string="Reset to Draft" - states="done,cancel" type="object" icon="gtk-convert" /> + <button name="case_open" string="Open" type="object" + states="draft,pending" icon="gtk-go-forward"/> + <button name="case_close" string="Done" type="object" + states="open,draft,pending" icon="gtk-close"/> + <button name="case_pending" string="Pending" type="object" + states="draft,open" icon="gtk-media-pause"/> + <button name="case_escalate" string="Escalate" type="object" + states="open,draft,pending" icon="gtk-go-up"/> + <button name="case_cancel" string="Cancel" type="object" + states="draft,open,pending" icon="gtk-cancel"/> + <button name="case_reset" string="Reset to Draft" type="object" + states="cancel,done" icon="gtk-convert" /> </tree> </field> </record> diff --git a/addons/hr_recruitment/__openerp__.py b/addons/hr_recruitment/__openerp__.py index 4a29befbfaf..b83a482e1c1 100644 --- a/addons/hr_recruitment/__openerp__.py +++ b/addons/hr_recruitment/__openerp__.py @@ -37,7 +37,14 @@ system to store and search in your CV base. 'author': 'OpenERP SA', 'website': 'http://www.openerp.com', 'images': ['images/hr_recruitment_analysis.jpeg','images/hr_recruitment_applicants.jpeg'], - 'depends': ['decimal_precision', 'hr', 'survey', 'crm', 'fetchmail'], + 'depends': [ + 'base_status', + 'decimal_precision', + 'hr', + 'survey', + 'crm', + 'fetchmail', + ], 'update_xml': [ 'wizard/hr_recruitment_phonecall_view.xml', 'wizard/hr_recruitment_employee_hired.xml', diff --git a/addons/hr_recruitment/board_hr_recruitment_statistical_view.xml b/addons/hr_recruitment/board_hr_recruitment_statistical_view.xml index 8af4b0f8553..e9054416eea 100644 --- a/addons/hr_recruitment/board_hr_recruitment_statistical_view.xml +++ b/addons/hr_recruitment/board_hr_recruitment_statistical_view.xml @@ -12,7 +12,7 @@ <field name="job_id"/> <field name="partner_name"/> <field name="stage_id"/> - <field name="state"/> + <field name="state" groups="base.group_no_one"/> </tree> </field> </record> diff --git a/addons/hr_recruitment/hr_recruitment.py b/addons/hr_recruitment/hr_recruitment.py index 505509058e1..c77236cc456 100644 --- a/addons/hr_recruitment/hr_recruitment.py +++ b/addons/hr_recruitment/hr_recruitment.py @@ -19,6 +19,7 @@ # ############################################################################## +from base_status.base_stage import base_stage import time from datetime import datetime, timedelta @@ -57,8 +58,6 @@ class hr_recruitment_source(osv.osv): _columns = { 'name': fields.char('Source Name', size=64, required=True, translate=True), } -hr_recruitment_source() - class hr_recruitment_stage(osv.osv): """ Stage of HR Recruitment """ @@ -69,12 +68,15 @@ class hr_recruitment_stage(osv.osv): 'name': fields.char('Name', size=64, required=True, translate=True), 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of stages."), 'department_id':fields.many2one('hr.department', 'Specific to a Department', help="Stages of the recruitment process may be different per department. If this stage is common to all departments, keep tempy this field."), - 'requirements': fields.text('Requirements') + 'state': fields.selection(AVAILABLE_STATES, 'State', required=True, help="The related state for the stage. The state of your document will automatically change regarding the selected stage. Example, a stage is related to the state 'Close', when your document reach this stage, it will be automatically closed."), + 'fold': fields.boolean('Hide in views if empty', help="This stage is not visible, for example in status bar or kanban view, when there are no records in that stage to display."), + 'requirements': fields.text('Requirements'), } _defaults = { 'sequence': 1, + 'state': 'draft', + 'fold': False, } -hr_recruitment_stage() class hr_recruitment_degree(osv.osv): """ Degree of HR Recruitment """ @@ -90,14 +92,60 @@ class hr_recruitment_degree(osv.osv): _sql_constraints = [ ('name_uniq', 'unique (name)', 'The name of the Degree of Recruitment must be unique!') ] -hr_recruitment_degree() -class hr_applicant(crm.crm_case, osv.osv): +class hr_applicant(base_stage, osv.Model): _name = "hr.applicant" _description = "Applicant" _order = "id desc" _inherit = ['ir.needaction_mixin', 'mail.thread'] + def _get_default_department_id(self, cr, uid, context=None): + """ Gives default department by checking if present in the context """ + return (self._resolve_department_id_from_context(cr, uid, context=context) or False) + + def _get_default_stage_id(self, cr, uid, context=None): + """ Gives default stage_id """ + department_id = self._get_default_department_id(cr, uid, context=context) + return self.stage_find(cr, uid, [], department_id, [('state', '=', 'draft')], context=context) + + def _resolve_department_id_from_context(self, cr, uid, context=None): + """ Returns ID of department based on the value of 'default_department_id' + context key, or None if it cannot be resolved to a single + department. + """ + if context is None: + context = {} + if type(context.get('default_department_id')) in (int, long): + return context.get('default_department_id') + if isinstance(context.get('default_department_id'), basestring): + department_name = context['default_department_id'] + department_ids = self.pool.get('hr.department').name_search(cr, uid, name=department_name, context=context) + if len(department_ids) == 1: + return int(department_ids[0][0]) + return None + + def _read_group_stage_ids(self, cr, uid, ids, domain, read_group_order=None, access_rights_uid=None, context=None): + access_rights_uid = access_rights_uid or uid + stage_obj = self.pool.get('hr.recruitment.stage') + order = stage_obj._order + # lame hack to allow reverting search, should just work in the trivial case + if read_group_order == 'stage_id desc': + order = "%s desc" % order + # retrieve section_id from the context and write the domain + # - ('id', 'in', 'ids'): add columns that should be present + # - OR ('department_id', '=', False), ('fold', '=', False): add default columns that are not folded + # - OR ('department_id', 'in', department_id), ('fold', '=', False) if department_id: add department columns that are not folded + department_id = self._resolve_department_id_from_context(cr, uid, context=context) + search_domain = [] + if department_id: + search_domain += ['|', '&', ('department_id', '=', department_id), ('fold', '=', False)] + search_domain += ['|', ('id', 'in', ids), '&', ('department_id', '=', False), ('fold', '=', False)] + stage_ids = stage_obj._search(cr, uid, search_domain, order=order, access_rights_uid=access_rights_uid, context=context) + result = stage_obj.name_get(cr, access_rights_uid, stage_ids, context=context) + # restore order of the search + result.sort(lambda x,y: cmp(stage_ids.index(x[0]), stage_ids.index(y[0]))) + return result + def _compute_day(self, cr, uid, ids, fields, args, context=None): """ @param cr: the current row, from the database cursor, @@ -143,12 +191,15 @@ class hr_applicant(crm.crm_case, osv.osv): 'partner_id': fields.many2one('res.partner', 'Partner'), 'create_date': fields.datetime('Creation Date', readonly=True, select=True), 'write_date': fields.datetime('Update Date', readonly=True), - 'stage_id': fields.many2one ('hr.recruitment.stage', 'Stage'), - 'state': fields.selection(AVAILABLE_STATES, 'Status', size=16, readonly=True, - help='The state is set to \'Draft\', when a case is created.\ - \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\'.'), + 'stage_id': fields.many2one ('hr.recruitment.stage', 'Stage', + domain="['|', ('department_id', '=', department_id), ('department_id', '=', False)]"), + 'state': fields.related('stage_id', 'state', type="selection", store=True, + selection=AVAILABLE_STATES, string="State", readonly=True, + help='The state is set to \'Draft\', when a case is created.\ + If the case is in progress the state is set to \'Open\'.\ + When the case is over, the state is set to \'Done\'.\ + If the case needs to be reviewed then the state is \ + set to \'Pending\'.'), 'company_id': fields.many2one('res.company', 'Company'), 'user_id': fields.many2one('res.users', 'Responsible'), # Applicant Columns @@ -169,7 +220,6 @@ class hr_applicant(crm.crm_case, osv.osv): 'partner_mobile': fields.char('Mobile', size=32), 'type_id': fields.many2one('hr.recruitment.degree', 'Degree'), 'department_id': fields.many2one('hr.department', 'Department'), - 'state': fields.selection(AVAILABLE_STATES, 'Status', size=16, readonly=True), 'survey': fields.related('job_id', 'survey_id', type='many2one', relation='survey', string='Survey'), 'response': fields.integer("Response"), 'reference': fields.char('Refered By', size=128), @@ -185,33 +235,19 @@ class hr_applicant(crm.crm_case, osv.osv): _defaults = { 'active': lambda *a: 1, - 'user_id': lambda self, cr, uid, context: uid, - 'email_from': crm.crm_case. _get_default_email, - 'state': lambda *a: 'draft', + 'user_id': lambda s, cr, uid, c: uid, + 'email_from': lambda s, cr, uid, c: s._get_default_email(cr, uid, c), + 'stage_id': lambda s, cr, uid, c: s._get_default_stage_id(cr, uid, c), + 'department_id': lambda s, cr, uid, c: s._get_default_department_id(cr, uid, c), 'priority': lambda *a: '', 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.helpdesk', context=c), 'color': 0, } - def _read_group_stage_ids(self, cr, uid, ids, domain, read_group_order=None, access_rights_uid=None, context=None): - access_rights_uid = access_rights_uid or uid - stage_obj = self.pool.get('hr.recruitment.stage') - order = stage_obj._order - if read_group_order == 'stage_id desc': - # lame hack to allow reverting search, should just work in the trivial case - order = "%s desc" % order - stage_ids = stage_obj._search(cr, uid, ['|',('id','in',ids),('department_id','=',False)], order=order, - access_rights_uid=access_rights_uid, context=context) - result = stage_obj.name_get(cr, access_rights_uid, stage_ids, context=context) - # restore order of the search - result.sort(lambda x,y: cmp(stage_ids.index(x[0]), stage_ids.index(y[0]))) - return result - _group_by_full = { 'stage_id': _read_group_stage_ids } - def onchange_job(self,cr, uid, ids, job, context=None): result = {} @@ -229,47 +265,33 @@ class hr_applicant(crm.crm_case, osv.osv): stage_id = stage_ids and stage_ids[0] or False return {'value': {'stage_id': stage_id}} - 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""" - stage_obj = self.pool.get('hr.recruitment.stage') - for case in self.browse(cr, uid, ids, context=context): - department = (case.department_id.id or False) - st = case.stage_id.id or False - stage_ids = stage_obj.search(cr, uid, ['|',('department_id','=',department),('department_id','=',False)], context=context) - if st and stage_ids.index(st): - self.write(cr, uid, [case.id], {'stage_id': stage_ids[stage_ids.index(st)-1]}, context=context) - else: - self.write(cr, uid, [case.id], {'stage_id': False}, context=context) - return True - - 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""" - stage_obj = self.pool.get('hr.recruitment.stage') - for case in self.browse(cr, uid, ids, context=context): - department = (case.department_id.id or False) - st = case.stage_id.id or False - stage_ids = stage_obj.search(cr, uid, ['|',('department_id','=',department),('department_id','=',False)], context=context) - val = False - if st and len(stage_ids) != stage_ids.index(st)+1: - val = stage_ids[stage_ids.index(st)+1] - elif (not st) and stage_ids: - val = stage_ids[0] - else: - val = False - self.write(cr, uid, [case.id], {'stage_id': val}, context=context) - return True + def stage_find(self, cr, uid, cases, section_id, domain=[], order='sequence', context=None): + """ Override of the base.stage method + Parameter of the stage search taken from the lead: + - department_id: if set, stages must belong to this section or + be a default case + """ + if isinstance(cases, (int, long)): + cases = self.browse(cr, uid, cases, context=context) + # collect all section_ids + department_ids = [] + if section_id: + department_ids.append(section_id) + for case in cases: + if case.department_id: + department_ids.append(case.department_id.id) + # OR all section_ids and OR with case_default + search_domain = [] + if department_ids: + search_domain += ['|', ('department_id', 'in', department_ids)] + search_domain.append(('department_id', '=', False)) + # AND with the domain in parameter + search_domain += list(domain) + # perform search, return the first found + stage_ids = self.pool.get('hr.recruitment.stage').search(cr, uid, search_domain, order=order, context=context) + if stage_ids: + return stage_ids[0] + return False def action_makeMeeting(self, cr, uid, ids, context=None): """ @@ -474,12 +496,6 @@ class hr_applicant(crm.crm_case, osv.osv): """ return self.set_priority(cr, uid, ids, '3') - def write(self, cr, uid, ids, vals, context=None): - if 'stage_id' in vals and vals['stage_id']: - stage = self.pool.get('hr.recruitment.stage').browse(cr, uid, vals['stage_id'], context=context) - self.message_append_note(cr, uid, ids, body=_("Stage changed to <b>%s</b>.") % stage.name, context=context) - return super(hr_applicant,self).write(cr, uid, ids, vals, context=context) - # ------------------------------------------------------- # OpenChatter methods and notifications # ------------------------------------------------------- @@ -497,7 +513,13 @@ class hr_applicant(crm.crm_case, osv.osv): if obj.state == 'draft' and obj.user_id: result[obj.id] = [obj.user_id.id] return result - + + def stage_set_send_note(self, cr, uid, ids, stage_id, context=None): + """ Override of the (void) default notification method. """ + if not stage_id: return True + stage_name = self.pool.get('hr.recruitment.stage').name_get(cr, uid, [stage_id], context=context)[0][1] + return self.message_append_note(cr, uid, ids, body= _("Stage changed to <b>%s</b>.") % (stage_name), context=context) + def case_get_note_msg_prefix(self, cr, uid, id, context=None): return 'Applicant' @@ -529,7 +551,6 @@ class hr_applicant(crm.crm_case, osv.osv): message = _("Applicant has been <b>created</b>.") return self.message_append_note(cr, uid, ids, body=message, context=context) -hr_applicant() class hr_job(osv.osv): _inherit = "hr.job" @@ -537,6 +558,6 @@ class hr_job(osv.osv): _columns = { 'survey_id': fields.many2one('survey', 'Interview Form', help="Choose an interview form for this job position and you will be able to print/answer this interview from all applicants who apply for this job"), } -hr_job() + # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/hr_recruitment/hr_recruitment_data.xml b/addons/hr_recruitment/hr_recruitment_data.xml index d6cb5952d38..8cbebda5f1f 100644 --- a/addons/hr_recruitment/hr_recruitment_data.xml +++ b/addons/hr_recruitment/hr_recruitment_data.xml @@ -1,7 +1,9 @@ <?xml version="1.0"?> <openerp> +<!-- <data noupdate="1"> - +--> +<data> <!-- HR Recruitment Source --> @@ -37,30 +39,36 @@ <field name="name">Doctoral Degree</field> <field name="sequence">4</field> </record> - <record model="hr.recruitment.stage" id="stage_job1"> <field name="name">Initial Qualification</field> + <field name="state">draft</field> <field name="sequence">1</field> </record> <record model="hr.recruitment.stage" id="stage_job2"> <field name="name">First Interview</field> + <field name="state">open</field> <field name="sequence">2</field> </record> <record model="hr.recruitment.stage" id="stage_job3"> <field name="name">Second Interview</field> + <field name="state">open</field> <field name="sequence">3</field> </record> <record model="hr.recruitment.stage" id="stage_job4"> <field name="name">Contract Proposed</field> + <field name="state">pending</field> <field name="sequence">4</field> </record> <record model="hr.recruitment.stage" id="stage_job5"> <field name="name">Contract Signed</field> + <field name="state">done</field> <field name="sequence">5</field> </record> <record model="hr.recruitment.stage" id="stage_job6"> <field name="name">Refused</field> + <field name="state">cancel</field> <field name="sequence">6</field> + <field name="fold" eval="True"/> </record> <record id="survey_job_0" model="survey"> <field name="title">Job Survey</field> diff --git a/addons/hr_recruitment/hr_recruitment_view.xml b/addons/hr_recruitment/hr_recruitment_view.xml index 5c8b560b380..16aceee070a 100644 --- a/addons/hr_recruitment/hr_recruitment_view.xml +++ b/addons/hr_recruitment/hr_recruitment_view.xml @@ -62,9 +62,9 @@ <field name="availability" invisible="1"/> <field name="department_id" invisible="context.get('invisible_department', True)"/> <field name="user_id"/> - <field name="state"/> - <button name="case_open" string="In Progress" states="draft,pending" type="object" icon="gtk-go-forward"/> - <button name="case_pending" string="Pending" states="open" type="object" icon="gtk-media-pause"/> + <field name="state" groups="base.group_no_one"/> + <button name="case_open" string="In Progress" states="draft,pending" type="object" icon="gtk-go-forward" groups="base.group_no_one"/> + <button name="case_pending" string="Pending" states="draft,open" type="object" icon="gtk-media-pause" groups="base.group_no_one"/> </tree> </field> </record> @@ -76,14 +76,24 @@ <field name="arch" type="xml"> <form layout="manual"> <div class="oe_form_topbar"> - <button name="%(action_hr_recruitment_hired_employee)d" string="Hire" states="open,pending" type="action"/> - <button name="case_open" string="In Progress" states="draft,pending" type="object"/> - <button name="case_pending" string="Pending" states="open" type="object"/> - <button name="case_reset" string="Reset to New" states="done,cancel" type="object"/> - <button name="case_cancel" string="Refuse" states="draft,open,pending" type="object"/> + <button name="%(action_hr_recruitment_hired_employee)d" string="Hire" type="action" + states="open,pending"/> + <button name="case_open" string="Open" type="object" + states="draft,pending"/> + <button name="case_pending" string="Pending" type="object" + states="open"/> + <button name="case_reset" string="Reset to New" type="object" + states="done,cancel"/> + <button name="case_cancel" string="Refuse" type="object" + states="draft,open,pending"/> + <button name="stage_previous" string="Previous" type="object" + states="open" icon="gtk-go-back"/> + <button name="stage_next" string="Next" type="object" + states="open" icon="gtk-go-forward"/> <div class="oe_right"> - <field name="state" nolabel="1" widget="statusbar" statusbar_visible="draft,open,done" statusbar_colors='{"pending":"blue"}'/> + <field name="stage_id" nolabel="1" widget="statusbar"/> </div> + <div class="oe_clear"/> </div> <sheet string="Jobs - Recruitment Form" layout="auto"> <group colspan="4" col="4"> @@ -96,11 +106,7 @@ <field name="user_id"/> <field name="job_id" on_change="onchange_job(job_id)"/> <field name="department_id" widget="selection" on_change="onchange_department_id(department_id)"/> - <group colspan="2" col="4"> - <field name="stage_id" domain="['|',('department_id','=',department_id),('department_id','=',False)]"/> - <button name="stage_previous" string="" type="object" icon="gtk-go-back"/> - <button icon="gtk-go-forward" string="" name="stage_next" type="object"/> - </group> + <field name="state" groups="base.group_no_one"/> <field name="date_action"/> <group colspan="2" col="8"> <field name="title_action"/> @@ -139,6 +145,13 @@ <field name="source_id"/> <field name="reference"/> </group> + <group col="2" colspan="2"> + <separator colspan="2" string="Dates"/> + <field name="create_date"/> + <field name="write_date"/> + <field name="date_closed"/> + <field name="date_open"/> + </group> </page> <page string="Notes"> <field name="description" nolabel="1" colspan="4"/> @@ -241,7 +254,7 @@ <field name="arch" type="xml"> <kanban default_group_by="stage_id"> <field name="color"/> - <field name="state"/> + <field name="state" groups="base.group_no_one"/> <field name="priority"/> <field name="survey"/> <field name="user_id"/> @@ -360,6 +373,7 @@ <field name="sequence" invisible="1"/> <field name="name"/> <field name="department_id"/> + <field name="state"/> </tree> </field> </record> @@ -377,6 +391,7 @@ <field name="name" select="1"/> <field name="department_id"/> <field name="sequence"/> + <field name="state"/> </group> <separator string="Requirements" colspan="4"/> <field name="requirements" nolabel="1" colspan="4"/> diff --git a/addons/hr_recruitment/test/recruitment_process.yml b/addons/hr_recruitment/test/recruitment_process.yml index e856747ed99..38b84fccab2 100644 --- a/addons/hr_recruitment/test/recruitment_process.yml +++ b/addons/hr_recruitment/test/recruitment_process.yml @@ -1,7 +1,7 @@ - In Order to test process of Recruitment, - - Applicant interested in job position. so He send resume by email. + An applicant is interested in the job position. So he sends a resume by email. - !python {model: mail.thread}: | import addons @@ -9,28 +9,43 @@ request_message = request_file.read() self.message_process(cr, uid, 'hr.applicant', request_message) - - After getting the mail, I check details of new applicant. + After getting the mail, I check the details of the new applicant. - !python {model: hr.applicant}: | applicant_ids = self.search(cr, uid, [('email_from','=', 'Mr. Richard Anderson <Richard_Anderson@yahoo.com>')]) assert applicant_ids, "Applicant is not created after getting the mail" applicant = self.browse(cr, uid, applicant_ids[0], context=context) resume_ids = self.pool.get('ir.attachment').search(cr, uid, [('datas_fname','=','resume.doc'),('res_model','=',self._name),('res_id','=',applicant.id)]) - assert applicant.name == "Application for the post of Jr.application Programmer.", "Subject does not match" - assert applicant.state == "draft" - assert len(resume_ids), "Resume does not attached." + assert applicant.name == "Application for the post of Jr.application Programmer.", "Applicant name does not match." + assert applicant.stage_id.id == ref('hr_recruitment.stage_job1'), "Stage should be 'Initial qualification' and is '%s'." % (applicant.stage_id.name) + assert applicant.state == "draft", "Applicant state should be 'draft'." + assert len(resume_ids), "Resume is not attached." - - I refuse applicant for the Recruitment. + I refuse the applicant (hr_case_programmer) - !python {model: hr.applicant}: | - self.case_close(cr, uid, [ref("hr_case_programmer")]) + self.case_cancel(cr, uid, [ref("hr_case_programmer")]) - - I open applicant for the Recruitment. + I check the details of the refused applicant. +- + !python {model: hr.applicant}: | + applicant = self.browse(cr, uid, ref("hr_case_programmer"), context=context) + assert applicant.stage_id.id == ref('hr_recruitment.stage_job6'), "Stage should be 'Refused' and is %s." % (applicant.stage_id.name) + assert applicant.state == 'cancel', "Applicant is not in 'cancel' state." +- + I reset and re-open the previously refused applicant. - !python {model: hr.applicant}: | self.case_reset(cr, uid, [ref("hr_case_programmer")]) self.case_open(cr, uid, [ref("hr_case_programmer")]) - + I check the details of the re-opened applicant. +- + !python {model: hr.applicant}: | + applicant = self.browse(cr, uid, ref("hr_case_programmer"), context=context) + assert applicant.stage_id.id == ref('hr_recruitment.stage_job2'), "Stage should be 'First interview' and is '%s'." % (applicant.stage_id.name) + assert applicant.state == "open", "Applicant state should be 'open'." +- I assign the Job position to the applicant - !python {model: hr.applicant}: | @@ -91,7 +106,7 @@ - I check that applicant is "Hired". - - !assert {model: hr.applicant, id: hr_case_programmer}: + !assert {model: hr.applicant, id: hr_case_programmer, string: Applicant state is done}: - state == 'done' - I do not give employment to the hired the applicant. diff --git a/addons/project/__openerp__.py b/addons/project/__openerp__.py index ef26425d3c9..2c53fc2a28a 100644 --- a/addons/project/__openerp__.py +++ b/addons/project/__openerp__.py @@ -28,7 +28,7 @@ "category": "Project Management", "sequence": 8, "images": ["images/gantt.png", "images/project_dashboard.jpeg","images/project_task_tree.jpeg","images/project_task.jpeg","images/project.jpeg","images/task_analysis.jpeg"], - "depends": ["base_setup", "product", "analytic", "board", "mail", "resource","web_kanban"], + "depends": ["base_setup", "base_status", "product", "analytic", "board", "mail", "resource","web_kanban"], "description": """ Project management module tracks multi-level projects, tasks, work done on tasks, eso. ====================================================================================== diff --git a/addons/project/board_project_view.xml b/addons/project/board_project_view.xml index fa6816ebbf7..1f06ea6938c 100644 --- a/addons/project/board_project_view.xml +++ b/addons/project/board_project_view.xml @@ -17,11 +17,16 @@ <field name="planned_hours" widget="float_time"/> <field name="effective_hours" widget="float_time"/> <field name="progress" widget="progressbar"/> - <field name="state" invisible="context.get('set_visible',False)"/> - <button name="do_cancel" states="draft,open,pending" string="Cancel" type="object" icon="gtk-cancel" help="For cancelling the task"/> - <button name="do_open" states="pending,draft,done,cancel" string="Start Task" type="object" icon="gtk-execute" help="For changing to open state" invisible="context.get('set_visible',False)"/> - <button name="%(action_project_task_delegate)d" states="pending,open,draft" string="Delegate" type="action" icon="gtk-sort-descending" help="For changing to delegate state"/> - <button name="action_close" states="draft,pending,open" string="Done" type="object" icon="terp-dialog-close" help="For changing to done state"/> + <field name="stage_id" invisible="context.get('set_visible',False)"/> + <field name="state" invisible="context.get('set_visible',False)" groups="base.group_no_one"/> + <button name="do_open" string="Start Task" type="object" + states="draft,pending" icon="gtk-go-forward" invisible="context.get('set_visible',False)"/> + <button name="action_close" string="Done" type="object" + states="draft,open,pending" icon="gtk-apply" invisible="context.get('set_visible',False)"/> + <button name="do_cancel" string="Cancel" type="object" + states="open,draft,pending" icon="gtk-cancel" invisible="context.get('set_visible',False)"/> + <button name="%(action_project_task_delegate)d" string="Delegate" type="action" + states="pending,open,draft" icon="gtk-sort-descending"/> </tree> </field> </record> @@ -39,10 +44,14 @@ <field name="date_deadline"/> <field name="total_hours" widget="float_time"/> <field name="progress" widget="progressbar"/> - <field name="state" invisible="context.get('set_visible',False)"/> - <button name="do_cancel" states="draft,open,pending" string="Cancel" type="object" icon="gtk-cancel" help="For cancelling the task"/> - <button name="do_open" states="pending,draft,done,cancel" string="Start Task" type="object" icon="gtk-execute" help="For changing to open state" invisible="context.get('set_visible',False)"/> - <button name="action_close" states="draft,pending,open" string="Done" type="object" icon="gtk-apply" help="For changing to done state"/> + <field name="stage_id" invisible="context.get('set_visible',False)"/> + <field name="state" invisible="context.get('set_visible',False)" groups="base.group_no_one"/> + <button name="do_open" string="Start Task" type="object" + states="draft,pending" icon="gtk-go-forward" invisible="context.get('set_visible',False)"/> + <button name="action_close" string="Done" type="object" + states="draft,open,pending" icon="gtk-apply" invisible="context.get('set_visible',False)"/> + <button name="do_cancel" string="Cancel" type="object" + states="open,draft,pending" icon="gtk-cancel" invisible="context.get('set_visible',False)"/> </tree> </field> </record> diff --git a/addons/project/project.py b/addons/project/project.py index 0b23196a67f..fa3e0c33f4d 100644 --- a/addons/project/project.py +++ b/addons/project/project.py @@ -2,7 +2,7 @@ ############################################################################## # # OpenERP, Open Source Management Solution -# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>). +# Copyright (C) 2004-today OpenERP SA (<http://www.openerp.com>) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -19,18 +19,15 @@ # ############################################################################## -from lxml import etree -import time +from base_status.base_stage import base_stage from datetime import datetime, date - -from tools.translate import _ +from lxml import etree from osv import fields, osv from openerp.addons.resource.faces import task as Task +import time +from tools.translate import _ -# I think we can remove this in v6.1 since VMT's improvements in the framework ? -#class project_project(osv.osv): -# _name = 'project.project' -#project_project() +_TASK_STATE = [('draft', 'New'),('open', 'In Progress'),('pending', 'Pending'), ('done', 'Done'), ('cancelled', 'Cancelled')] class project_task_type(osv.osv): _name = 'project.task.type' @@ -40,14 +37,20 @@ class project_task_type(osv.osv): 'name': fields.char('Stage Name', required=True, size=64, translate=True), 'description': fields.text('Description'), 'sequence': fields.integer('Sequence'), - 'project_default': fields.boolean('Common to All Projects', help="If you check this field, this stage will be proposed by default on each new project. It will not assign this stage to existing projects."), + 'case_default': fields.boolean('Common to All Projects', + help="If you check this field, this stage will be proposed by default on each new project. It will not assign this stage to existing projects."), 'project_ids': fields.many2many('project.project', 'project_task_type_rel', 'type_id', 'project_id', 'Projects'), + 'state': fields.selection(_TASK_STATE, 'State', required=True, + help="The related state for the stage. The state of your document will automatically change regarding the selected stage. Example, a stage is related to the state 'Close', when your document reach this stage, it will be automatically closed."), + 'fold': fields.boolean('Hide in views if empty', + help="This stage is not visible, for example in status bar or kanban view, when there are no records in that stage to display."), } _defaults = { - 'sequence': 1 + 'sequence': 1, + 'state': 'draft', + 'fold': False, } _order = 'sequence' -project_task_type() class project(osv.osv): _name = "project.project" @@ -208,7 +211,7 @@ class project(osv.osv): return True def _get_type_common(self, cr, uid, context): - ids = self.pool.get('project.task.type').search(cr, uid, [('project_default','=',1)], context=context) + ids = self.pool.get('project.task.type').search(cr, uid, [('case_default','=',1)], context=context) return ids _order = "sequence" @@ -239,7 +242,7 @@ class project(osv.osv): def set_done(self, cr, uid, ids, context=None): task_obj = self.pool.get('project.task') task_ids = task_obj.search(cr, uid, [('project_id', 'in', ids), ('state', 'not in', ('cancelled', 'done'))]) - task_obj.write(cr, uid, task_ids, {'state': 'done', 'date_end':time.strftime('%Y-%m-%d %H:%M:%S'), 'remaining_hours': 0.0}) + task_obj.case_close(cr, uid, task_ids, context=context) self.write(cr, uid, ids, {'state':'close'}, context=context) self.set_close_send_note(cr, uid, ids, context=context) return True @@ -247,7 +250,7 @@ class project(osv.osv): def set_cancel(self, cr, uid, ids, context=None): task_obj = self.pool.get('project.task') task_ids = task_obj.search(cr, uid, [('project_id', 'in', ids), ('state', '!=', 'done')]) - task_obj.write(cr, uid, task_ids, {'state': 'cancelled', 'date_end':time.strftime('%Y-%m-%d %H:%M:%S'), 'remaining_hours': 0.0}) + task_obj.case_cancel(cr, uid, task_ids, context=context) self.write(cr, uid, ids, {'state':'cancelled'}, context=context) self.set_cancel_send_note(cr, uid, ids, context=context) return True @@ -501,49 +504,61 @@ def Project(): def set_close_send_note(self, cr, uid, ids, context=None): message = _("Project has been <b>closed</b>.") return self.message_append_note(cr, uid, ids, body=message, context=context) - -project() -class task(osv.osv): + +class task(base_stage, osv.osv): _name = "project.task" _description = "Task" _log_create = True _date_name = "date_start" _inherit = ['ir.needaction_mixin', 'mail.thread'] + def _get_default_project_id(self, cr, uid, context=None): + """ Gives default section by checking if present in the context """ + return (self._resolve_project_id_from_context(cr, uid, context=context) or False) + + def _get_default_stage_id(self, cr, uid, context=None): + """ Gives default stage_id """ + project_id = self._get_default_project_id(cr, uid, context=context) + return self.stage_find(cr, uid, [], project_id, [('state', '=', 'draft')], context=context) def _resolve_project_id_from_context(self, cr, uid, context=None): - """Return ID of project based on the value of 'project_id' - context key, or None if it cannot be resolved to a single project. + """ Returns ID of project based on the value of 'default_project_id' + context key, or None if it cannot be resolved to a single + project. """ if context is None: context = {} if type(context.get('default_project_id')) in (int, long): - project_id = context['default_project_id'] - return project_id + return context['default_project_id'] if isinstance(context.get('default_project_id'), basestring): project_name = context['default_project_id'] - project_ids = self.pool.get('project.project').name_search(cr, uid, name=project_name) + project_ids = self.pool.get('project.project').name_search(cr, uid, name=project_name, context=context) if len(project_ids) == 1: return project_ids[0][0] + return None - def _read_group_type_id(self, cr, uid, ids, domain, read_group_order=None, access_rights_uid=None, context=None): + def _read_group_stage_ids(self, cr, uid, ids, domain, read_group_order=None, access_rights_uid=None, context=None): stage_obj = self.pool.get('project.task.type') - project_id = self._resolve_project_id_from_context(cr, uid, context=context) order = stage_obj._order access_rights_uid = access_rights_uid or uid - if read_group_order == 'type_id desc': - # lame way to allow reverting search, should just work in the trivial case + # lame way to allow reverting search, should just work in the trivial case + if read_group_order == 'stage_id desc': order = '%s desc' % order + # retrieve section_id from the context and write the domain + # - ('id', 'in', 'ids'): add columns that should be present + # - OR ('case_default', '=', True), ('fold', '=', False): add default columns that are not folded + # - OR ('project_ids', 'in', project_id), ('fold', '=', False) if project_id: add project columns that are not folded + search_domain = [] + project_id = self._resolve_project_id_from_context(cr, uid, context=context) if project_id: - domain = ['|', ('id','in',ids), ('project_ids','in',project_id)] - else: - domain = ['|', ('id','in',ids), ('project_default','=',1)] - stage_ids = stage_obj._search(cr, uid, domain, order=order, access_rights_uid=access_rights_uid, context=context) + search_domain += ['|', '&', ('project_ids', '=', project_id), ('fold', '=', False)] + search_domain += ['|', ('id', 'in', ids), '&', ('case_default', '=', True), ('fold', '=', False)] + stage_ids = stage_obj._search(cr, uid, search_domain, order=order, access_rights_uid=access_rights_uid, context=context) result = stage_obj.name_get(cr, access_rights_uid, stage_ids, context=context) # restore order of the search result.sort(lambda x,y: cmp(stage_ids.index(x[0]), stage_ids.index(y[0]))) return result - + def _read_group_user_id(self, cr, uid, ids, domain, read_group_order=None, access_rights_uid=None, context=None): res_users = self.pool.get('res.users') project_id = self._resolve_project_id_from_context(cr, uid, context=context) @@ -562,11 +577,10 @@ class task(osv.osv): return result _group_by_full = { - 'type_id': _read_group_type_id, - 'user_id': _read_group_user_id + 'stage_id': _read_group_stage_ids, + 'user_id': _read_group_user_id, } - def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False): obj_project = self.pool.get('project.project') for domain in args: @@ -597,7 +611,6 @@ class task(osv.osv): res[task.id]['progress'] = 100.0 return res - def onchange_remaining(self, cr, uid, ids, remaining=0.0, planned = 0.0): if remaining and not planned: return {'value':{'planned_hours': remaining}} @@ -640,7 +653,7 @@ class task(osv.osv): if not default.get('remaining_hours', False): default['remaining_hours'] = float(self.read(cr, uid, id, ['planned_hours'])['planned_hours']) default['active'] = True - default['type_id'] = False + default['stage_id'] = False if not default.get('name', False): default['name'] = self.browse(cr, uid, id, context=context).name or '' if not context.get('copy',False): @@ -670,10 +683,15 @@ class task(osv.osv): 'description': fields.text('Description'), 'priority': fields.selection([('4','Very Low'), ('3','Low'), ('2','Medium'), ('1','Important'), ('0','Very important')], 'Priority', select=True), 'sequence': fields.integer('Sequence', select=True, help="Gives the sequence order when displaying a list of tasks."), - 'type_id': fields.many2one('project.task.type', 'Stage'), - 'state': fields.selection([('draft', 'New'),('cancelled', 'Cancelled'),('open', 'In Progress'),('pending', 'Pending'), ('done', 'Done')], 'Status', readonly=True, required=True, - help='If the task is created the state is \'Draft\'.\n If the task is started, the state becomes \'In Progress\'.\n If review is needed the task is in \'Pending\' state.\ - \n If the task is over, the states is set to \'Done\'.'), + 'stage_id': fields.many2one('project.task.type', 'Stage', + domain="['|', ('project_ids', '=', project_id), ('case_default', '=', True)]"), + 'state': fields.related('stage_id', 'state', type="selection", store=True, + selection=_TASK_STATE, string="State", readonly=True, + help='The state is set to \'Draft\', when a case is created.\ + If the case is in progress the state is set to \'Open\'.\ + When the case is over, the state is set to \'Done\'.\ + If the case needs to be reviewed then the state is \ + set to \'Pending\'.'), 'kanban_state': fields.selection([('normal', 'Normal'),('blocked', 'Blocked'),('done', 'Ready To Pull')], 'Kanban State', help="A task's kanban state indicates special situations affecting it:\n" " * Normal is the default situation\n" @@ -722,6 +740,8 @@ class task(osv.osv): } _defaults = { + 'stage_id': _get_default_stage_id, + 'project_id': _get_default_project_id, 'state': 'draft', 'kanban_state': 'normal', 'priority': '2', @@ -729,7 +749,7 @@ class task(osv.osv): 'sequence': 10, 'active': True, 'user_id': lambda obj, cr, uid, context: uid, - 'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'project.task', context=c) + 'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'project.task', context=c), } _order = "priority, sequence, date_start, name, id" @@ -828,6 +848,41 @@ class task(osv.osv): res['fields'][f]['string'] = res['fields'][f]['string'].replace('Hours',tm) return res + # **************************************** + # Case management + # **************************************** + + def stage_find(self, cr, uid, cases, section_id, domain=[], order='sequence', context=None): + """ Override of the base.stage method + Parameter of the stage search taken from the lead: + - section_id: if set, stages must belong to this section or + be a default stage; if not set, stages must be default + stages + """ + if isinstance(cases, (int, long)): + cases = self.browse(cr, uid, cases, context=context) + # collect all section_ids + section_ids = [] + if section_id: + section_ids.append(section_id) + for task in cases: + if task.project_id: + section_ids.append(task.project_id.id) + # OR all section_ids and OR with case_default + search_domain = [] + if section_ids: + search_domain += [('|')] * len(section_ids) + for section_id in section_ids: + search_domain.append(('project_ids', '=', section_id)) + search_domain.append(('case_default', '=', True)) + # AND with the domain in parameter + search_domain += list(domain) + # perform search, return the first found + stage_ids = self.pool.get('project.task.type').search(cr, uid, search_domain, order=order, context=context) + if stage_ids: + return stage_ids[0] + return False + def _check_child_task(self, cr, uid, ids, context=None): if context == None: context = {} @@ -840,9 +895,9 @@ class task(osv.osv): return True def action_close(self, cr, uid, ids, context=None): - # This action open wizard to send email to partner or project manager after close task. - if context == None: - context = {} + """ This action closes the task, then opens the wizard to send an + email to the partner or the project manager. + """ task_id = len(ids) and ids[0] or False self._check_child_task(cr, uid, ids, context=context) if not task_id: return False @@ -863,12 +918,14 @@ class task(osv.osv): } return res - def do_close(self, cr, uid, ids, context={}): - """ - Close Task - """ + def do_close(self, cr, uid, ids, context=None): + """ Compatibility when changing to case_close. """ + return self.case_close(cr, uid, ids, context=context) + + def case_close(self, cr, uid, ids, context=None): + """ Closes Task """ request = self.pool.get('res.request') - if not isinstance(ids,list): ids = [ids] + if not isinstance(ids, list): ids = [ids] for task in self.browse(cr, uid, ids, context=context): vals = {} project = task.project_id @@ -884,7 +941,6 @@ class task(osv.osv): 'ref_doc1': 'project.task,%d'% (task.id,), 'ref_doc2': 'project.project,%d'% (project.id,), }, context=context) - for parent_id in task.parent_ids: if parent_id.state in ('pending','draft'): reopen = True @@ -893,12 +949,12 @@ class task(osv.osv): reopen = False if reopen: self.do_reopen(cr, uid, [parent_id.id], context=context) - vals.update({'state': 'done'}) - vals.update({'remaining_hours': 0.0}) + # close task + vals['remaining_hours'] = 0.0 if not task.date_end: - vals.update({ 'date_end':time.strftime('%Y-%m-%d %H:%M:%S')}) - self.write(cr, uid, [task.id],vals, context=context) - self.do_close_send_note(cr, uid, [task.id], context) + vals['date_end'] = fields.datetime.now() + self.case_set(cr, uid, [task.id], 'done', vals, context=context) + self.case_close_send_note(cr, uid, [task.id], context=context) return True def do_reopen(self, cr, uid, ids, context=None): @@ -916,12 +972,15 @@ class task(osv.osv): 'ref_doc1': 'project.task,%d' % task.id, 'ref_doc2': 'project.project,%d' % project.id, }, context=context) - - self.write(cr, uid, [task.id], {'state': 'open'}, context=context) - self.do_open_send_note(cr, uid, [task.id], context) + self.case_set(cr, uid, [task.id], 'open', {}, context=context) + self.case_open_send_note(cr, uid, [task.id], context) return True - def do_cancel(self, cr, uid, ids, context={}): + def do_cancel(self, cr, uid, ids, context=None): + """ Compatibility when changing to case_cancel. """ + return self.case_cancel(cr, uid, ids, context=context) + + def case_cancel(self, cr, uid, ids, context=None): request = self.pool.get('res.request') tasks = self.browse(cr, uid, ids, context=context) self._check_child_task(cr, uid, ids, context=context) @@ -937,27 +996,38 @@ class task(osv.osv): 'ref_doc1': 'project.task,%d' % task.id, 'ref_doc2': 'project.project,%d' % project.id, }, context=context) - self.write(cr, uid, [task.id], {'state': 'cancelled', 'remaining_hours':0.0}, context=context) - self.do_cancel_send_note(cr, uid, [task.id], context) + # cancel task + self.case_set(cr, uid, [task.id], 'cancelled', {'remaining_hours': 0.0}, context=context) + self.case_cancel_send_note(cr, uid, [task.id], context=context) return True - def do_open(self, cr, uid, ids, context={}): + def do_open(self, cr, uid, ids, context=None): + """ Compatibility when changing to case_open. """ + return self.case_open(cr, uid, ids, context=context) + + def case_open(self, cr, uid, ids, context=None): if not isinstance(ids,list): ids = [ids] - tasks= self.browse(cr, uid, ids, context=context) - for t in tasks: - data = {'state': 'open'} - if not t.date_start: - data['date_start'] = time.strftime('%Y-%m-%d %H:%M:%S') - self.write(cr, uid, [t.id], data, context=context) - self.do_open_send_note(cr, uid, [t.id], context) + self.case_set(cr, uid, ids, 'open', {'date_start': fields.datetime.now()}, context=context) + self.case_open_send_note(cr, uid, ids, context) return True - def do_draft(self, cr, uid, ids, context={}): - self.write(cr, uid, ids, {'state': 'draft'}, context=context) - self.do_draft_send_note(cr, uid, ids, context) + def do_draft(self, cr, uid, ids, context=None): + """ Compatibility when changing to case_draft. """ + return self.case_draft(cr, uid, ids, context=context) + + def case_draft(self, cr, uid, ids, context=None): + self.case_set(cr, uid, ids, 'draft', {}, context=context) + self.case_draft_send_note(cr, uid, ids, context=context) return True - + def do_pending(self, cr, uid, ids, context=None): + """ Compatibility when changing to case_pending. """ + return self.case_pending(cr, uid, ids, context=context) + + def case_pending(self, cr, uid, ids, context=None): + self.case_set(cr, uid, ids, 'pending', {}, context=context) + return self.case_pending_send_note(cr, uid, ids, context=context) + def _delegate_task_attachments(self, cr, uid, task_id, delegated_task_id, context=None): attachment = self.pool.get('ir.attachment') attachment_ids = attachment.search(cr, uid, [('res_model', '=', self._name), ('res_id', '=', task_id)], context=context) @@ -966,7 +1036,6 @@ class task(osv.osv): new_attachment_ids.append(attachment.copy(cr, uid, attachment_id, default={'res_id': delegated_task_id}, context=context)) return new_attachment_ids - def do_delegate(self, cr, uid, ids, delegate_data={}, context=None): """ Delegate Task to another users. @@ -1000,11 +1069,6 @@ class task(osv.osv): delegated_tasks[task.id] = delegated_task_id return delegated_tasks - def do_pending(self, cr, uid, ids, context={}): - self.write(cr, uid, ids, {'state': 'pending'}, context=context) - self.do_pending_send_note(cr, uid, ids, context) - return True - def set_remaining_time(self, cr, uid, ids, remaining_time=1.0, context=None): for task in self.browse(cr, uid, ids, context=context): if (task.state=='draft') or (task.planned_hours==0.0): @@ -1033,36 +1097,6 @@ class task(osv.osv): def set_kanban_state_done(self, cr, uid, ids, context=None): self.write(cr, uid, ids, {'kanban_state': 'done'}, context=context) - def _change_type(self, cr, uid, ids, next, context=None): - """ - go to the next stage - if next is False, go to previous stage - """ - for task in self.browse(cr, uid, ids): - if task.project_id.type_ids: - typeid = task.type_id.id - types_seq={} - for type in task.project_id.type_ids : - types_seq[type.id] = type.sequence - if next: - types = sorted(types_seq.items(), lambda x, y: cmp(x[1], y[1])) - else: - types = sorted(types_seq.items(), lambda x, y: cmp(y[1], x[1])) - sorted_types = [x[0] for x in types] - if not typeid: - self.write(cr, uid, task.id, {'type_id': sorted_types[0]}) - elif typeid and typeid in sorted_types and sorted_types.index(typeid) != len(sorted_types)-1: - index = sorted_types.index(typeid) - self.write(cr, uid, task.id, {'type_id': sorted_types[index+1]}) - self.state_change_send_note(cr, uid, [task.id], context) - return True - - def next_type(self, cr, uid, ids, context=None): - return self._change_type(cr, uid, ids, True, context=context) - - def prev_type(self, cr, uid, ids, context=None): - return self._change_type(cr, uid, ids, False, context=context) - def _store_history(self, cr, uid, ids, context=None): for task in self.browse(cr, uid, ids, context=context): self.pool.get('project.task.history').create(cr, uid, { @@ -1070,7 +1104,7 @@ class task(osv.osv): 'remaining_hours': task.remaining_hours, 'planned_hours': task.planned_hours, 'kanban_state': task.kanban_state, - 'type_id': task.type_id.id, + 'type_id': task.stage_id.id, 'state': task.state, 'user_id': task.user_id.id @@ -1084,22 +1118,25 @@ class task(osv.osv): return task_id # Overridden to reset the kanban_state to normal whenever - # the stage (type_id) of the task changes. + # the stage (stage_id) of the task changes. def write(self, cr, uid, ids, vals, context=None): if isinstance(ids, (int, long)): ids = [ids] - if vals and not 'kanban_state' in vals and 'type_id' in vals: - new_stage = vals.get('type_id') + if vals and not 'kanban_state' in vals and 'stage_id' in vals: + new_stage = vals.get('stage_id') vals_reset_kstate = dict(vals, kanban_state='normal') for t in self.browse(cr, uid, ids, context=context): - write_vals = vals_reset_kstate if t.type_id != new_stage else vals + #TO FIX:Kanban view doesn't raise warning + #stages = [stage.id for stage in t.project_id.type_ids] + #if new_stage not in stages: + #raise osv.except_osv(_('Warning !'), _('Stage is not defined in the project.')) + write_vals = vals_reset_kstate if t.stage_id != new_stage else vals super(task,self).write(cr, uid, [t.id], write_vals, context=context) result = True else: result = super(task,self).write(cr, uid, ids, vals, context=context) - if ('type_id' in vals) or ('remaining_hours' in vals) or ('user_id' in vals) or ('state' in vals) or ('kanban_state' in vals): + if ('stage_id' in vals) or ('remaining_hours' in vals) or ('user_id' in vals) or ('state' in vals) or ('kanban_state' in vals): self._store_history(cr, uid, ids, context=context) - self.state_change_send_note(cr, uid, ids, context) return result def unlink(self, cr, uid, ids, context=None): @@ -1139,7 +1176,11 @@ class task(osv.osv): # --------------------------------------------------- # OpenChatter methods and notifications # --------------------------------------------------- - + + def case_get_note_msg_prefix(self, cr, uid, id, context=None): + """ Override of default prefix for notifications. """ + return 'Task' + def get_needaction_user_ids(self, cr, uid, ids, context=None): result = dict.fromkeys(ids, []) for obj in self.browse(cr, uid, ids, context=context): @@ -1156,28 +1197,16 @@ class task(osv.osv): sub_ids.append(obj.manager_id.id) return self.pool.get('res.users').read(cr, uid, sub_ids, context=context) + def stage_set_send_note(self, cr, uid, ids, stage_id, context=None): + """ Override of the (void) default notification method. """ + stage_name = self.pool.get('project.task.type').name_get(cr, uid, [stage_id], context=context)[0][1] + return self.message_append_note(cr, uid, ids, body= _("Stage changed to <b>%s</b>.") % (stage_name), context=context) + def create_send_note(self, cr, uid, ids, context=None): return self.message_append_note(cr, uid, ids, body=_("Task has been <b>created</b>."), context=context) - def do_pending_send_note(self, cr, uid, ids, context=None): - if not isinstance(ids,list): ids = [ids] - msg = _('Task is now <b>pending</b>.') - return self.message_append_note(cr, uid, ids, body=msg, context=context) - - def do_open_send_note(self, cr, uid, ids, context=None): - msg = _('Task has been <b>opened</b>.') - return self.message_append_note(cr, uid, ids, body=msg, context=context) - - def do_cancel_send_note(self, cr, uid, ids, context=None): - msg = _('Task has been <b>canceled</b>.') - return self.message_append_note(cr, uid, ids, body=msg, context=context) - - def do_close_send_note(self, cr, uid, ids, context=None): - msg = _('Task has been <b>closed</b>.') - return self.message_append_note(cr, uid, ids, body=msg, context=context) - - def do_draft_send_note(self, cr, uid, ids, context=None): - msg = _('Task has been <b>renewed</b>.') + def case_draft_send_note(self, cr, uid, ids, context=None): + msg = _('Task has been set as <b>draft</b>.') return self.message_append_note(cr, uid, ids, body=msg, context=context) def do_delegation_send_note(self, cr, uid, ids, context=None): @@ -1186,13 +1215,6 @@ class task(osv.osv): self.message_append_note(cr, uid, [task.id], body=msg, context=context) return True - def state_change_send_note(self, cr, uid, ids, context=None): - for task in self.browse(cr, uid, ids, context=context): - msg = _('Stage changed to <b>%s</b>') % (task.type_id.name) - self.message_append_note(cr, uid, [task.id], body=msg, context=context) - return True - -task() class project_work(osv.osv): _name = "project.task.work" @@ -1231,7 +1253,7 @@ class project_work(osv.osv): for work in self.browse(cr, uid, ids): cr.execute('update project_task set remaining_hours=remaining_hours + %s where id=%s', (work.hours, work.task_id.id)) return super(project_work,self).unlink(cr, uid, ids,*args, **kwargs) -project_work() + class account_analytic_account(osv.osv): @@ -1252,7 +1274,6 @@ class account_analytic_account(osv.osv): raise osv.except_osv(_('Warning !'), _('Please delete the project linked with this account first.')) return super(account_analytic_account, self).unlink(cr, uid, ids, *args, **kwargs) -account_analytic_account() # # Tasks History, used for cumulative flow charts (Lean/Agile) @@ -1313,7 +1334,7 @@ class project_task_history(osv.osv): _defaults = { 'date': fields.date.context_today, } -project_task_history() + class project_task_history_cumulative(osv.osv): _name = 'project.task.history.cumulative' @@ -1341,4 +1362,4 @@ class project_task_history_cumulative(osv.osv): ) as history ) """) -project_task_history_cumulative() + diff --git a/addons/project/project_data.xml b/addons/project/project_data.xml index b7b59911fa4..4c95373d91f 100644 --- a/addons/project/project_data.xml +++ b/addons/project/project_data.xml @@ -24,22 +24,40 @@ <record id="project_tt_specification" model="project.task.type"> <field name="sequence">1</field> <field name="name">Design</field> - <field name="project_default" eval="1"/> + <field name="state">draft</field> + <field name="case_default" eval="True"/> </record> <record id="project_tt_development" model="project.task.type"> <field name="sequence">2</field> <field name="name">Development</field> - <field name="project_default" eval="1"/> + <field name="state">open</field> + <field name="case_default" eval="True"/> </record> <record id="project_tt_testing" model="project.task.type"> <field name="sequence">3</field> <field name="name">Testing</field> - <field name="project_default" eval="1"/> + <field name="state">open</field> + <field name="case_default" eval="True"/> </record> - <record id="project_tt_merge" model="project.task.type"> + <record id="project_tt_pending" model="project.task.type"> <field name="sequence">4</field> + <field name="name">Pending</field> + <field name="state">pending</field> + <field name="case_default" eval="True"/> + <field name="fold" eval="True"/> + </record> + <record id="project_tt_merge" model="project.task.type"> + <field name="sequence">5</field> <field name="name">Deployment</field> - <field name="project_default" eval="1"/> + <field name="state">done</field> + <field name="case_default" eval="True"/> + </record> + <record id="project_tt_cancel" model="project.task.type"> + <field name="sequence">6</field> + <field name="name">Cancelled</field> + <field name="state">cancelled</field> + <field name="case_default" eval="True"/> + <field name="fold" eval="True"/> </record> </data> </openerp> diff --git a/addons/project/project_demo.xml b/addons/project/project_demo.xml index 2f8c9e9638a..f685f14c85b 100644 --- a/addons/project/project_demo.xml +++ b/addons/project/project_demo.xml @@ -77,7 +77,7 @@ <field name="priority">2</field> <field name="project_id" ref="project.project_integrate_openerp"/> <field name="name">Prepare Requirements Document</field> - <field name="type_id" ref="project_tt_specification"/> + <field name="stage_id" ref="project_tt_specification"/> <field name="color">3</field> </record> @@ -88,7 +88,7 @@ <field name="priority">2</field> <field name="project_id" ref="project.project_integrate_openerp"/> <field name="name">Make SRS</field> - <field name="type_id" ref="project_tt_specification"/> + <field name="stage_id" ref="project_tt_specification"/> </record> <record id="project_task_3" model="project.task"> @@ -99,7 +99,7 @@ <field name="project_id" ref="project.project_integrate_openerp"/> <field name="name">Design Use Cases</field> <field name="date_deadline" eval="time.strftime('%Y-%m-24')"/> - <field name="type_id" ref="project_tt_specification"/> + <field name="stage_id" ref="project_tt_specification"/> </record> <record id="project_task_4" model="project.task"> @@ -112,7 +112,7 @@ <field name="description">Use the account_budget module</field> <field name="date_deadline" eval="time.strftime('%Y-%m-19')"/> <field name="color">3</field> - <field name="type_id" ref="project_tt_specification"/> + <field name="stage_id" ref="project_tt_specification"/> </record> <record id="project_task_5" model="project.task"> @@ -125,7 +125,7 @@ <field name="kanban_state">done</field> <field name="priority">0</field> <field name="date_deadline" eval="time.strftime('%Y-%m-%d')"/> - <field name="type_id" ref="project_tt_specification"/> + <field name="stage_id" ref="project_tt_specification"/> </record> <record id="project_task_6" model="project.task"> @@ -135,7 +135,7 @@ <field name="priority">2</field> <field name="project_id" ref="project.project_integrate_openerp"/> <field name="name">Risk Management Planning</field> - <field name="type_id" ref="project_tt_specification"/> + <field name="stage_id" ref="project_tt_specification"/> </record> <record id="project_task_7" model="project.task"> @@ -145,7 +145,7 @@ <field name="priority">2</field> <field name="project_id" ref="project.project_integrate_openerp"/> <field name="name">Create Project Schedules</field> - <field name="type_id" ref="project_tt_specification"/> + <field name="stage_id" ref="project_tt_specification"/> </record> <record id="project_task_8" model="project.task"> @@ -156,7 +156,7 @@ <field name="project_id" ref="project.project_integrate_openerp"/> <field name="name">Dataflow Design</field> <field name="priority">0</field> - <field name="type_id" ref="project_tt_specification"/> + <field name="stage_id" ref="project_tt_specification"/> </record> <record id="project_task_9" model="project.task"> @@ -166,7 +166,7 @@ <field name="priority">2</field> <field name="project_id" ref="project.project_integrate_openerp"/> <field name="name">User Interface Design</field> - <field name="type_id" ref="project_tt_specification"/> + <field name="stage_id" ref="project_tt_specification"/> </record> <record id="project_task_10" model="project.task"> @@ -177,7 +177,7 @@ <field name="project_id" ref="project.project_integrate_openerp"/> <field name="name">Develop Module in Sale Management</field> <field name="kanban_state">blocked</field> - <field name="type_id" ref="project_tt_development"/> + <field name="stage_id" ref="project_tt_development"/> </record> <record id="project_task_11" model="project.task"> @@ -187,7 +187,7 @@ <field name="priority">2</field> <field name="project_id" ref="project.project_integrate_openerp"/> <field name="name">Develop module in Warehouse</field> - <field name="type_id" ref="project_tt_merge"/> + <field name="stage_id" ref="project_tt_merge"/> </record> <function model="project.task" name="do_close" eval="[ref('project_task_11')], {'install_mode': True}"/> @@ -199,7 +199,7 @@ <field name="priority">2</field> <field name="project_id" ref="project.project_integrate_openerp"/> <field name="name">Integrate Modules</field> - <field name="type_id" ref="project_tt_development"/> + <field name="stage_id" ref="project_tt_development"/> </record> <function model="project.task" name="do_close" eval="[ref('project_task_12')], {'install_mode': True}"/> @@ -211,7 +211,7 @@ <field name="priority">2</field> <field name="project_id" ref="project.project_integrate_openerp"/> <field name="name">Unit Testing</field> - <field name="type_id" ref="project_tt_development"/> + <field name="stage_id" ref="project_tt_development"/> </record> <function model="project.task" name="do_pending" eval="[ref('project_task_13')], {'install_mode': True}"/> @@ -223,8 +223,7 @@ <field name="priority">2</field> <field name="project_id" ref="project.project_integrate_openerp"/> <field name="name">Regression Test</field> - <field name="type_id" ref="project_tt_development"/> - <field name="state">pending</field> + <field name="stage_id" ref="project_tt_development"/> </record> <record id="project_task_15" model="project.task"> @@ -234,7 +233,7 @@ <field name="priority">2</field> <field name="project_id" ref="project.project_integrate_openerp"/> <field name="name">Documentation</field> - <field name="type_id" ref="project_tt_specification"/> + <field name="stage_id" ref="project_tt_specification"/> <field name="date_start">2011-02-06</field> </record> @@ -247,7 +246,7 @@ <field name="project_id" ref="project.project_integrate_openerp"/> <field name="name">Performance Tuning</field> <field name="description">Test on Runbot</field> - <field name="type_id" ref="project_tt_specification"/> + <field name="stage_id" ref="project_tt_specification"/> </record> <function model="project.task" name="do_open" eval="[ref('project_task_16')], {'install_mode': True}"/> @@ -258,7 +257,7 @@ <field name="priority">2</field> <field name="project_id" ref="project.project_integrate_openerp"/> <field name="name">Deploy and Review on Customer System</field> - <field name="type_id" ref="project_tt_specification"/> + <field name="stage_id" ref="project_tt_specification"/> </record> <function model="project.task" name="do_open" eval="[ref('project_task_17')], {'install_mode': True}"/> @@ -269,7 +268,7 @@ <field name="priority">2</field> <field name="project_id" ref="project.project_integrate_openerp"/> <field name="name">Training and Presentation</field> - <field name="type_id" ref="project_tt_specification"/> + <field name="stage_id" ref="project_tt_specification"/> </record> <!-- @@ -322,7 +321,7 @@ <record id="project_task_116" model="project.task"> <field name="planned_hours">38.0</field> <field name="remaining_hours">38.0</field> - <field name="type_id" ref="project_tt_development"/> + <field name="stage_id" ref="project_tt_development"/> <field name="user_id" eval="False"/> <field name="project_id" ref="project_project_22"/> <field name="description">BoM, After sales returns, interventions. Traceability.</field> @@ -334,7 +333,7 @@ <field name="planned_hours">16.0</field> <field name="remaining_hours">16.0</field> <field name="user_id" eval="False"/> - <field name="type_id" ref="project_tt_development"/> + <field name="stage_id" ref="project_tt_development"/> <field name="project_id" ref="project_project_23"/> <field name="name">Data importation + Doc</field> </record> @@ -344,7 +343,7 @@ <field name="planned_hours">16.0</field> <field name="remaining_hours">16.0</field> <field name="user_id" eval="False"/> - <field name="type_id" ref="project_tt_development"/> + <field name="stage_id" ref="project_tt_development"/> <field name="project_id" ref="project_project_23"/> <field name="name">Modifications asked by the customer.</field> </record> @@ -353,7 +352,7 @@ <record id="project_task_184" model="project.task"> <field name="planned_hours">16.0</field> <field name="remaining_hours">16.0</field> - <field name="type_id" ref="project_tt_testing"/> + <field name="stage_id" ref="project_tt_testing"/> <field name="user_id" eval="False"/> <field name="priority">0</field> <field name="project_id" ref="project_project_21"/> @@ -364,7 +363,7 @@ <field name="sequence">15</field> <field name="planned_hours">8.0</field> <field name="remaining_hours">8.0</field> - <field name="type_id" ref="project_tt_testing"/> + <field name="stage_id" ref="project_tt_testing"/> <field name="user_id" eval="False"/> <field name="project_id" ref="project_project_21"/> <field name="name">Internal testing + Software Install</field> @@ -374,7 +373,7 @@ <field name="sequence">17</field> <field name="planned_hours">16.0</field> <field name="remaining_hours">16.0</field> - <field name="type_id" ref="project_tt_development"/> + <field name="stage_id" ref="project_tt_development"/> <field name="user_id" eval="False"/> <field name="priority">2</field> <field name="project_id" ref="project_project_21"/> @@ -389,7 +388,7 @@ <field name="user_id" eval="False"/> <field name="project_id" ref="project_project_23"/> <field name="name">Parameters</field> - <field name="type_id" ref="project_tt_specification"/> + <field name="stage_id" ref="project_tt_specification"/> </record> <function model="project.task" name="do_open" eval="[ref('project_task_189')], {'install_mode': True}"/> @@ -398,10 +397,9 @@ <field name="planned_hours">32.0</field> <field name="remaining_hours">32.0</field> <field name="user_id" eval="False"/> - <field name="state">open</field> <field name="project_id" ref="project_project_21"/> <field name="name">Start of the doc redaction + MRP</field> - <field name="type_id" ref="project_tt_testing"/> + <field name="stage_id" ref="project_tt_testing"/> </record> <!-- Schedule tasks to assign users and dates --> diff --git a/addons/project/project_view.xml b/addons/project/project_view.xml index 70cdac1efa0..d001af367c4 100644 --- a/addons/project/project_view.xml +++ b/addons/project/project_view.xml @@ -161,7 +161,7 @@ </tree> </field> </record> - + <act_window context="{'search_default_project_id': [active_id], 'default_project_id': active_id}" id="act_project_project_2_project_task_all" @@ -305,15 +305,26 @@ <field name="arch" type="xml"> <form string="Project" layout="manual"> <div class="oe_form_topbar"> - <button name="action_close" states="pending,open" string="Done" type="object"/> - <button name="do_open" states="pending,draft" string="Start Task" type="object"/> - <button name="%(action_project_task_reevaluate)d" states="done,cancelled" string="Reactivate" type="action" context="{'button_reactivate':True}" /> - <button name="do_pending" states="open" string="Pending" type="object"/> - <button name="do_draft" states="open" string="Draft" type="object"/> - <button name="%(action_project_task_delegate)d" states="pending,open,draft" string="Delegate" type="action"/> - <button name="do_cancel" states="draft,open,pending" string="Cancel" type="object"/> + <button name="do_open" string="Start Task" type="object" + states="draft,pending"/> + <button name="%(action_project_task_reevaluate)d" string="Reactivate" type="action" + states="done,cancelled" context="{'button_reactivate':True}"/> + <button name="do_pending" string="Pending" type="object" + states="open"/> + <button name="action_close" string="Done" type="object" + states="draft,open,pending"/> + <button name="do_draft" string="Draft" type="object" + states="cancel,done"/> + <button name="%(action_project_task_delegate)d" string="Delegate" type="action" + states="pending,open,draft"/> + <button name="do_cancel" string="Cancel" type="object" + states="draft,open,pending" /> + <button name="stage_previous" string="Previous Stage" type="object" + states="open,pending" icon="gtk-go-back" attrs="{'invisible': [('stage_id','=', False)]}"/> + <button name="stage_next" string="Next Stage" type="object" + states="open,pending" icon="gtk-go-forward" attrs="{'invisible': [('stage_id','=', False)]}"/> <div class="oe_right"> - <field name="state" nolabel="1" widget="statusbar" statusbar_visible="draft,open,done" statusbar_colors='{"pending":"blue"}' select="1"/> + <field name="stage_id" nolabel="1" widget="statusbar"/> </div> <div class="oe_clear"/> </div> @@ -362,7 +373,8 @@ <tree string="Delegated tasks"> <field name="name"/> <field name="user_id"/> - <field name="state"/> + <field name="stage_id"/> + <field name="state" groups="base.group_no_one"/> <field name="effective_hours" widget="float_time"/> <field name="progress" widget="progressbar"/> <field name="remaining_hours" widget="float_time"/> @@ -376,16 +388,13 @@ <separator string="Planning" colspan="2"/> <field name="priority"/> <field name="sequence"/> + <field name="state" groups="base.group_no_one"/> </group> <separator string="Miscellaneous" colspan="4"/> <field name="partner_id" /> <field name="company_id" select="1" groups="base.group_multi_company" widget="selection"/> - <group col="4" colspan="2"> - <field name="type_id" widget="selection" readonly="1"/> - <button name="prev_type" string="Previous" type="object" icon="gtk-go-back" help="Change to Previous Stage"/> - <button name="next_type" string="Next" type="object" icon="gtk-go-forward" help="Change to Next Stage"/> - </group> + <field name="state" groups="base.group_no_one"/> <separator colspan="4" string="Notes"/> <field colspan="4" name="notes" nolabel="1"/> </page> @@ -397,22 +406,22 @@ </form> </field> </record> - + <!-- Project Task Kanban View --> <record model="ir.ui.view" id="view_task_kanban"> <field name="name">project.task.kanban</field> <field name="model">project.task</field> <field name="type">kanban</field> <field name="arch" type="xml"> - <kanban default_group_by="type_id" > + <kanban default_group_by="stage_id" > <field name="color"/> <field name="priority"/> - <field name="type_id"/> + <field name="stage_id"/> <field name="user_id"/> <field name="user_email"/> <field name="description"/> <field name="sequence"/> - <field name="state"/> + <field name="state" groups="base.group_no_one"/> <field name="kanban_state"/> <field name="remaining_hours" sum="Remaining Time" groups="project.group_time_work_estimation_tasks"/> <field name="date_deadline"/> @@ -508,20 +517,21 @@ <field name="effective_hours" widget="float_time" sum="Spent Hours" invisible="1"/> <field name="remaining_hours" widget="float_time" sum="Remaining Hours" on_change="onchange_remaining(remaining_hours,planned_hours)" invisible="context.get('set_visible',False)" groups="project.group_time_work_estimation_tasks"/> <field name="date_deadline" invisible="context.get('deadline_visible',True)"/> - <field name="type_id" invisible="context.get('set_visible',False)"/> - <button name="next_type" invisible="context.get('set_visible',False)" - states="draft,open,pending" - string="Change Stage" - type="object" - icon="gtk-go-forward" - help="Change Type"/> + <button name="do_open" string="Start Task" type="object" + states="draft,pending" icon="gtk-go-forward"/> + <button name="action_close" string="Done" type="object" + states="draft,open,pending" icon="gtk-apply"/> + <button name="%(action_project_task_delegate)d" string="Delegate" type="action" + states="pending,open,draft" icon="terp-personal"/> + <field name="stage_id" invisible="context.get('set_visible',False)"/> + <button name="stage_previous" string="Previous Stage" type="object" + states="open,pending" icon="gtk-go-back" attrs="{'invisible': [('stage_id','=', False)]}"/> + <button name="stage_next" string="Next Stage" type="object" + states="open,pending" icon="gtk-go-forward" attrs="{'invisible': [('stage_id','=', False)]}"/> + <field name="state" invisible="context.get('set_visible',False)" groups="base.group_no_one"/> <field name="date_start" invisible="1" groups="base.group_no_one"/> <field name="date_end" invisible="1" groups="base.group_no_one"/> <field name="progress" widget="progressbar" invisible="context.get('set_visible',False)"/> - <field name="state" invisible="context.get('set_visible',False)"/> - <button name="do_open" states="pending,draft,done,cancelled" string="Start Task" type="object" icon="gtk-media-play" help="For changing to open state" invisible="context.get('set_visible',False)"/> - <button name="%(action_project_task_delegate)d" states="pending,open,draft" string="Delegate" type="action" icon="terp-personal" help="For changing to delegate state"/> - <button name="action_close" states="draft,pending,open" string="Done" type="object" icon="terp-dialog-close" help="For changing to done state"/> </tree> </field> </record> @@ -589,7 +599,7 @@ <separator orientation="vertical"/> <filter string="Project" name="group_project_id" icon="terp-folder-violet" domain="[]" context="{'group_by':'project_id'}"/> <separator orientation="vertical"/> - <filter string="Stage" name="group_stage_id" icon="terp-stage" domain="[]" context="{'group_by':'type_id'}"/> + <filter string="Stage" name="group_stage_id" icon="terp-stage" domain="[]" context="{'group_by':'stage_id'}"/> <filter string="Status" name="group_state" icon="terp-stock_effects-object-colorize" domain="[]" context="{'group_by':'state'}"/> <separator orientation="vertical"/> <filter string="Deadline" icon="terp-gnome-cpu-frequency-applet+" domain="[]" context="{'group_by':'date_deadline'}"/> @@ -660,7 +670,7 @@ <field name="arch" type="xml"> <search string="Tasks Stages"> <group> - <filter icon="terp-check" string="Common" name="common" domain="[('project_default', '=', 1)]" help="Stages common to all projects"/> + <filter icon="terp-check" string="Common" name="common" domain="[('case_default', '=', 1)]" help="Stages common to all projects"/> <separator orientation="vertical"/> <field name="name"/> </group> @@ -674,10 +684,14 @@ <field name="type">form</field> <field name="arch" type="xml"> <form string="Task Stage"> - <group colspan="4" col="6"> - <field name="name"/> - <field name="project_default"/> - <field name="sequence"/> + <group colspan="2" col="2"> + <field name="name"/> + <field name="state"/> + </group> + <group colspan="2" col="4"> + <field name="case_default"/> + <field name="sequence"/> + <field name="fold"/> </group> <separator string="Description" colspan="4"/> <field colspan="4" name="description" nolabel="1"/> @@ -693,6 +707,7 @@ <tree string="Task Stage"> <field name="sequence"/> <field name="name"/> + <field name="state"/> </tree> </field> </record> @@ -707,10 +722,10 @@ <menuitem id="menu_tasks_config" name="GTD" parent="project.menu_definitions" sequence="1"/> - <menuitem id="menu_project_config_project" name="Stages" parent="menu_definitions" sequence="1"/> + <menuitem id="menu_project_config_project" name="Stages" parent="project.menu_definitions" sequence="1"/> - <menuitem action="open_task_type_form" id="menu_task_types_view" parent="menu_project_config_project" sequence="2" groups="base.group_no_one"/> - <menuitem action="open_view_project_all" id="menu_projects" name="Projects" parent="menu_project_management" sequence="1"/> + <menuitem action="open_task_type_form" name="Task Stages" id="menu_task_types_view" parent="menu_project_config_project" sequence="2"/> + <menuitem action="open_view_project_all" id="menu_projects" name="Projects" parent="menu_project_management" sequence="1" groups="base.group_no_one"/> <act_window context="{'search_default_user_id': [active_id], 'default_user_id': active_id}" id="act_res_users_2_project_project" name="User's projects" res_model="project.project" src_model="res.users" view_mode="tree,form" view_type="form"/> diff --git a/addons/project/report/project_cumulative.xml b/addons/project/report/project_cumulative.xml index 89667992f0a..3c04d36feb6 100644 --- a/addons/project/report/project_cumulative.xml +++ b/addons/project/report/project_cumulative.xml @@ -15,7 +15,8 @@ <field name="user_id"/> <field name="remaining_hours"/> <field name="kanban_state"/> - <field name="state"/> + <field name="stage_id"/> + <field name="state" groups="base.group_no_one"/> </tree> </field> </record> diff --git a/addons/project/report/project_report.py b/addons/project/report/project_report.py index 1012ca9d518..58f7739e96d 100644 --- a/addons/project/report/project_report.py +++ b/addons/project/report/project_report.py @@ -82,7 +82,7 @@ class report_project_task_user(osv.osv): t.name as name, t.company_id, t.partner_id, - t.type_id, + t.stage_id, remaining_hours as remaining_hours, total_hours as total_hours, t.delay_hours as hours_delay, @@ -114,7 +114,7 @@ class report_project_task_user(osv.osv): name, t.company_id, t.partner_id, - t.type_id + t.stage_id """) diff --git a/addons/project/report/project_report_view.xml b/addons/project/report/project_report_view.xml index 3bdec47fcb9..240176f7941 100644 --- a/addons/project/report/project_report_view.xml +++ b/addons/project/report/project_report_view.xml @@ -214,7 +214,7 @@ <tree string="Task By Days" > <field name="day"/> <field name="total_task"/> - <field name="state"/> + <field name="state" groups="base.group_no_one"/> </tree> </field> </record> diff --git a/addons/project/test/task_process.yml b/addons/project/test/task_process.yml index c4d17f783f6..ad4bbe53bb3 100644 --- a/addons/project/test/task_process.yml +++ b/addons/project/test/task_process.yml @@ -40,7 +40,7 @@ I change the stage of task to next stage. - !python {model: project.task}: | - self.next_type(cr, uid, [ref("project_task_1")]) + self.stage_next(cr, uid, [ref("project_task_1")]) - !record {model: project.task.reevaluate, id: reevaluate_id}: remaining_hours : 120 @@ -68,9 +68,9 @@ I change the stage of task to previous stage. - !python {model: project.task}: | - self.prev_type(cr, uid, [ref("project_task_1")]) + self.stage_previous(cr, uid, [ref("project_task_1")]) - I cancel Task. - !python {model: project.task}: | - self.do_cancel(cr, uid, [ref("project_task_2")])
\ No newline at end of file + self.do_cancel(cr, uid, [ref("project_task_2")]) diff --git a/addons/project_issue/__openerp__.py b/addons/project_issue/__openerp__.py index fc88372fe90..9037523d41e 100644 --- a/addons/project_issue/__openerp__.py +++ b/addons/project_issue/__openerp__.py @@ -38,6 +38,7 @@ and decide on their status as they evolve. 'website': 'http://www.openerp.com', 'images': ['images/issue_analysis.jpeg','images/project_issue.jpeg'], 'depends': [ + 'base_status', 'crm', 'project', ], diff --git a/addons/project_issue/board_project_issue_view.xml b/addons/project_issue/board_project_issue_view.xml index a070bdbe521..9945f006e43 100644 --- a/addons/project_issue/board_project_issue_view.xml +++ b/addons/project_issue/board_project_issue_view.xml @@ -35,7 +35,7 @@ <field name="type">graph</field> <field name="arch" type="xml"> <graph orientation="vertical" string="Project Issue" type="bar"> - <field name="type_id"/> + <field name="stage_id"/> <field name="nbr" operator="+"/> <field group="True" name="user_id"/> </graph> @@ -96,18 +96,22 @@ <field name="partner_id"/> <field name="project_id" /> <field name="priority" string="Priority"/> - <field name="type_id" widget="selection" readonly="1"/> - <button name="prev_type" string="Previous" type="object" icon="gtk-go-back" help="Change to Previous Stage"/> - <button name="next_type" string="Next" type="object" icon="gtk-go-forward" help="Change to Next Stage"/> <field name="version_id" widget="selection"/> <field name="progress" widget="progressbar" attrs="{'invisible':[('task_id','=',False)]}"/> - <field name="state"/> - <button name="case_cancel" string="Cancel" states="draft,open,pending" type="object" icon="gtk-cancel"/> - <button name="case_close" string="Done" states="open,draft,pending" type="object" icon="gtk-jump-to"/> - <button name="case_open" string="Open" states="draft,pending" type="object" icon="gtk-go-forward" help="To Do"/> - <button name="case_pending" string="Pending" states="draft,open" type="object" icon="gtk-media-pause"/> - <button name="case_escalate" string="Escalate" states="open,draft,pending" type="object" icon="gtk-go-up"/> - <button name="case_reset" string="Reset to Draft" states="done,cancel" type="object" icon="gtk-convert"/> + <field name="stage_id" widget="selection" readonly="1"/> + <button name="stage_previous" string="Previous" type="object" + icon="gtk-go-back" help="Change to Previous Stage" + attrs="{'invisible': [('stage_id','=', False)]}"/> + <button name="stage_next" string="Next" type="object" + icon="gtk-go-forward" help="Change to Next Stage" + attrs="{'invisible': [('stage_id','=', False)]}"/> + <field name="state" groups="base.group_no_one"/> + <button name="case_open" string="Open" type="object" + states="draft,pending" icon="gtk-go-forward"/> + <button name="case_close" string="Done" type="object" + states="draft,open,pending" icon="gtk-close"/> + <button name="case_cancel" string="Cancel" type="object" + states="draft,open,pending" icon="gtk-cancel"/> <field name="categ_id" invisible="1"/> <field name="task_id" invisible="1"/> </tree> diff --git a/addons/project_issue/project_issue.py b/addons/project_issue/project_issue.py index 176ffa61acd..177c21570cd 100644 --- a/addons/project_issue/project_issue.py +++ b/addons/project_issue/project_issue.py @@ -19,6 +19,7 @@ # ############################################################################## +from base_status.base_stage import base_stage from crm import crm from datetime import datetime from osv import fields,osv @@ -42,12 +43,62 @@ class project_issue_version(osv.osv): } project_issue_version() -class project_issue(crm.crm_case, osv.osv): +_ISSUE_STATE= [('draft', 'New'), ('open', 'In Progress'), ('cancel', 'Cancelled'), ('done', 'Done'),('pending', 'Pending')] + +class project_issue(base_stage, osv.osv): _name = "project.issue" _description = "Project Issue" _order = "priority, create_date desc" _inherit = ['ir.needaction_mixin', 'mail.thread'] + def _get_default_project_id(self, cr, uid, context=None): + """ Gives default project by checking if present in the context """ + return self._resolve_project_id_from_context(cr, uid, context=context) + + def _get_default_stage_id(self, cr, uid, context=None): + """ Gives default stage_id """ + project_id = self._get_default_project_id(cr, uid, context=context) + return self.stage_find(cr, uid, [], project_id, [('state', '=', 'draft')], context=context) + + def _resolve_project_id_from_context(self, cr, uid, context=None): + """ Returns ID of project based on the value of 'default_project_id' + context key, or None if it cannot be resolved to a single + project. + """ + if context is None: + context = {} + if type(context.get('default_project_id')) in (int, long): + return context.get('default_project_id') + if isinstance(context.get('default_project_id'), basestring): + project_name = context['default_project_id'] + project_ids = self.pool.get('project.project').name_search(cr, uid, name=project_name, context=context) + if len(project_ids) == 1: + return int(project_ids[0][0]) + return None + + def _read_group_stage_ids(self, cr, uid, ids, domain, read_group_order=None, access_rights_uid=None, context=None): + access_rights_uid = access_rights_uid or uid + stage_obj = self.pool.get('project.task.type') + order = stage_obj._order + # lame hack to allow reverting search, should just work in the trivial case + if read_group_order == 'stage_id desc': + order = "%s desc" % order + # retrieve section_id from the context and write the domain + # - ('id', 'in', 'ids'): add columns that should be present + # - OR ('case_default', '=', True), ('fold', '=', False): add default columns that are not folded + # - OR ('project_ids', 'in', project_id), ('fold', '=', False) if project_id: add project columns that are not folded + search_domain = [] + project_id = self._resolve_project_id_from_context(cr, uid, context=context) + if project_id: + search_domain += ['|', '&', ('project_ids', '=', project_id), ('fold', '=', False)] + search_domain += ['|', ('id', 'in', ids), '&', ('case_default', '=', True), ('fold', '=', False)] + # perform search + stage_ids = stage_obj._search(cr, uid, search_domain, order=order, access_rights_uid=access_rights_uid, context=context) + result = stage_obj.name_get(cr, access_rights_uid, stage_ids, context=context) + # restore order of the search + result.sort(lambda x,y: cmp(stage_ids.index(x[0]), stage_ids.index(y[0]))) + return result + def _compute_day(self, cr, uid, ids, fields, args, context=None): """ @param cr: the current row, from the database cursor, @@ -173,11 +224,13 @@ class project_issue(crm.crm_case, osv.osv): 'partner_id': fields.many2one('res.partner', 'Partner', select=1), 'company_id': fields.many2one('res.company', 'Company'), 'description': fields.text('Description'), - 'state': fields.selection([('draft', 'New'), ('cancel', 'Cancelled'), ('open', 'In Progress'),('pending', 'Pending'),('done', 'Done') ], 'Status', size=16, readonly=True, - help='The state is set to \'Draft\', when a case is created.\ - \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\'.'), + 'state': fields.related('stage_id', 'state', type="selection", store=True, + selection=_ISSUE_STATE, string="State", readonly=True, + help='The state is set to \'Draft\', when a case is created.\ + If the case is in progress the state is set to \'Open\'.\ + When the case is over, the state is set to \'Done\'.\ + If the case needs to be reviewed then the state is \ + set to \'Pending\'.'), 'email_from': fields.char('Email', size=128, help="These people will receive email.", select=1), 'email_cc': fields.char('Watchers Emails', size=256, help="These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma"), 'date_open': fields.datetime('Opened', readonly=True,select=True), @@ -188,7 +241,8 @@ class project_issue(crm.crm_case, osv.osv): 'categ_id': fields.many2one('crm.case.categ', 'Category', domain="[('object_id.model', '=', 'crm.project.bug')]"), 'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority', select=True), 'version_id': fields.many2one('project.issue.version', 'Version'), - 'type_id': fields.many2one ('project.task.type', 'Stages', domain="[('project_ids', '=', project_id)]"), + 'stage_id': fields.many2one ('project.task.type', 'Stages', + domain="['|', ('project_ids', '=', project_id), ('case_default', '=', True)]"), 'project_id':fields.many2one('project.project', 'Project'), 'duration': fields.float('Duration'), 'task_id': fields.many2one('project.task', 'Task', domain="[('project_id','=',project_id)]"), @@ -221,15 +275,20 @@ class project_issue(crm.crm_case, osv.osv): _defaults = { 'active': 1, - 'partner_id': crm.crm_case._get_default_partner, - 'email_from': crm.crm_case._get_default_email, + 'partner_id': lambda s, cr, uid, c: s._get_default_partner(cr, uid, c), + 'email_from': lambda s, cr, uid, c: s._get_default_email(cr, uid, c), 'state': 'draft', - 'section_id': crm.crm_case._get_section, + 'stage_id': lambda s, cr, uid, c: s._get_default_stage_id(cr, uid, c), + 'section_id': lambda s, cr, uid, c: s._get_default_section_id(cr, uid, c), 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.helpdesk', context=c), 'priority': crm.AVAILABLE_PRIORITIES[2][0], 'categ_id' : lambda *a: False, } + _group_by_full = { + 'stage_id': _read_group_stage_ids + } + def set_priority(self, cr, uid, ids, priority): """Set lead priority """ @@ -312,36 +371,20 @@ class project_issue(crm.crm_case, osv.osv): def convert_to_bug(self, cr, uid, ids, context=None): return self._convert(cr, uid, ids, 'bug_categ', context=context) - def next_type(self, cr, uid, ids, context=None): - for task in self.browse(cr, uid, ids): - typeid = task.type_id.id - types = map(lambda x:x.id, task.project_id.type_ids or []) - if types: - if not typeid: - self.write(cr, uid, [task.id], {'type_id': types[0]}) - elif typeid and typeid in types and types.index(typeid) != len(types)-1 : - index = types.index(typeid) - self.write(cr, uid, [task.id], {'type_id': types[index+1]}) - return True - - def prev_type(self, cr, uid, ids, context=None): - for task in self.browse(cr, uid, ids): - typeid = task.type_id.id - types = map(lambda x:x.id, task.project_id and task.project_id.type_ids or []) - if types: - if typeid and typeid in types: - index = types.index(typeid) - self.write(cr, uid, [task.id], {'type_id': index and types[index-1] or False}) - return True - + def copy(self, cr, uid, id, default=None, context=None): + issue = self.read(cr, uid, id, ['name'], context=context) + if not default: + default = {} + default = default.copy() + default['name'] = issue['name'] + _(' (copy)') + return super(project_issue, self).copy(cr, uid, id, default=default, + context=context) + def write(self, cr, uid, ids, vals, context=None): #Update last action date every time the user change the stage, the state or send a new email - logged_fields = ['type_id', 'state', 'message_ids'] + logged_fields = ['stage_id', 'state', 'message_ids'] if any([field in vals for field in logged_fields]): vals['date_action_last'] = time.strftime('%Y-%m-%d %H:%M:%S') - if vals.get('type_id', False): - stage = self.pool.get('project.task.type').browse(cr, uid, vals['type_id'], context=context) - self.message_append_note(cr, uid, ids, body=_("Stage changed to <b>%s</b>.") % stage.name, context=context) return super(project_issue, self).write(cr, uid, ids, vals, context) def onchange_task_id(self, cr, uid, ids, task_id, context=None): @@ -363,15 +406,51 @@ class project_issue(crm.crm_case, osv.osv): self.create_send_note(cr, uid, [obj_id], context=context) return obj_id - def case_open(self, cr, uid, ids, context=None): - res = super(project_issue, self).case_open(cr, uid, ids, context) - self.write(cr, uid, ids, {'date_open': time.strftime('%Y-%m-%d %H:%M:%S'), 'user_id' : uid}) - return res + # ------------------------------------------------------- + # Stage management + # ------------------------------------------------------- + + def stage_find(self, cr, uid, cases, section_id, domain=[], order='sequence', context=None): + """ Override of the base.stage method + Parameter of the stage search taken from the issue: + - type: stage type must be the same or 'both' + - section_id: if set, stages must belong to this section or + be a default case + """ + if isinstance(cases, (int, long)): + cases = self.browse(cr, uid, cases, context=context) + # collect all section_ids + section_ids = [] + if section_id: + section_ids.append(section_id) + for task in cases: + if task.project_id: + section_ids.append(task.project_id.id) + # OR all section_ids and OR with case_default + search_domain = [] + if section_ids: + search_domain += [('|')] * len(section_ids) + for section_id in section_ids: + search_domain.append(('project_ids', '=', section_id)) + search_domain.append(('case_default', '=', True)) + # AND with the domain in parameter + search_domain += list(domain) + # perform search, return the first found + stage_ids = self.pool.get('project.task.type').search(cr, uid, search_domain, order=order, context=context) + if stage_ids: + return stage_ids[0] + return False + + def case_cancel(self, cr, uid, ids, context=None): + """ Cancels case """ + self.case_set(cr, uid, ids, 'cancelled', {'active': True}, context=context) + self.case_cancel_send_note(cr, uid, ids, context=context) + return True def case_escalate(self, cr, uid, ids, context=None): cases = self.browse(cr, uid, ids) for case in cases: - data = {'state' : 'draft'} + data = {} if case.project_id.project_escalation_id: data['project_id'] = case.project_id.project_escalation_id.id if case.project_id.project_escalation_id.user_id: @@ -380,10 +459,14 @@ class project_issue(crm.crm_case, osv.osv): self.pool.get('project.task').write(cr, uid, [case.task_id.id], {'project_id': data['project_id'], 'user_id': False}) else: raise osv.except_osv(_('Warning !'), _('You cannot escalate this issue.\nThe relevant Project has not configured the Escalation Project!')) - self.write(cr, uid, [case.id], data) - self.case_escalate_send_note(cr, uid, [case.id], context) + self.case_set(cr, uid, ids, 'draft', data, context=context) + self.case_escalate_send_note(cr, uid, [case.id], context=context) return True + # ------------------------------------------------------- + # Mail gateway + # ------------------------------------------------------- + def message_new(self, cr, uid, msg, custom_values=None, context=None): """Automatically called when new email message arrives""" if context is None: @@ -434,7 +517,7 @@ class project_issue(crm.crm_case, osv.osv): # Reassign the 'open' state to the case if this one is in pending or done for record in self.browse(cr, uid, ids, context=context): if record.state in ('pending', 'done'): - record.write({'state' : 'open'}) + self.case_set(cr, uid, ids, 'open', {}, context=context) vls = { } for line in msg['body_text'].split('\n'): @@ -448,15 +531,6 @@ class project_issue(crm.crm_case, osv.osv): res = self.write(cr, uid, ids, vals) self.message_append_dict(cr, uid, ids, msg, context=context) return res - - def copy(self, cr, uid, id, default=None, context=None): - issue = self.read(cr, uid, id, ['name'], context=context) - if not default: - default = {} - default = default.copy() - default['name'] = issue['name'] + _(' (copy)') - return super(project_issue, self).copy(cr, uid, id, default=default, - context=context) # ------------------------------------------------------- # OpenChatter methods and notifications @@ -475,9 +549,15 @@ class project_issue(crm.crm_case, osv.osv): if obj.user_id: sub_ids.append(obj.user_id.id) return self.pool.get('res.users').read(cr, uid, sub_ids, context=context) - + + def stage_set_send_note(self, cr, uid, ids, stage_id, context=None): + """ Override of the (void) default notification method. """ + stage_name = self.pool.get('project.task.type').name_get(cr, uid, [stage_id], context=context)[0][1] + return self.message_append_note(cr, uid, ids, body= _("Stage changed to <b>%s</b>.") % (stage_name), context=context) + def case_get_note_msg_prefix(self, cr, uid, id, context=None): - return 'Project issue ' + """ Override of default prefix for notifications. """ + return 'Project issue' def convert_to_task_send_note(self, cr, uid, ids, context=None): message = _("Project issue has been <b>converted</b> in to task.") diff --git a/addons/project_issue/project_issue_demo.xml b/addons/project_issue/project_issue_demo.xml index 358e9e55aa5..a213cb02c2e 100644 --- a/addons/project_issue/project_issue_demo.xml +++ b/addons/project_issue/project_issue_demo.xml @@ -7,7 +7,6 @@ <field eval=""5"" name="priority"/> <field name="user_id" ref="base.user_root"/> <field name="partner_id" ref="base.res_partner_agrolait"/> - <field eval=""open"" name="state"/> <field name="section_id" ref="crm.section_sales_department"/> <field eval="1" name="active"/> <field name="categ_id" ref="bug_categ"/> @@ -15,7 +14,7 @@ <field eval="15.0" name="duration"/> <field eval=""Bug in Accounts module"" name="name"/> <field eval=""agr@agrolait.com"" name="email_from"/> - <field name="type_id" ref="project.project_tt_specification"/> + <field name="stage_id" ref="project.project_tt_specification"/> </record> <record id="crm_case_programnotgivingproperoutput0" model="project.issue"> @@ -23,28 +22,26 @@ <field eval=""3"" name="priority"/> <field name="user_id" ref="base.user_root"/> <field name="partner_id" ref="base.res_partner_asus"/> - <field eval=""done"" name="state"/> <field name="section_id" ref="crm.section_sales_department"/> <field eval="1" name="active"/> <field eval="3.5" name="duration"/> <field name="categ_id" ref="bug_categ"/> <field eval=""Program not giving proper output"" name="name"/> <field name="project_id" ref="project.project_project_22"/> - <field name="type_id" ref="project.project_tt_specification"/> + <field name="stage_id" ref="project.project_tt_specification"/> </record> <record id="crm_case_outputincorrect0" model="project.issue"> <field eval="time.strftime('%Y-%m-18 14:30:00')" name="date"/> <field eval=""4"" name="priority"/> <field name="user_id" ref="base.user_demo"/> - <field eval=""cancel"" name="state"/> <field name="section_id" ref="crm.section_sales_department"/> <field eval="1" name="active"/> <field eval="2.3" name="duration"/> <field name="categ_id" ref="bug_categ"/> <field name="project_id" ref="project.project_project_23"/> <field eval=""Output incorrect"" name="name"/> - <field name="type_id" ref="project.project_tt_development"/> + <field name="stage_id" ref="project.project_tt_development"/> </record> <record id="crm_case_problemloadingpage0" model="project.issue"> @@ -52,14 +49,13 @@ <field eval=""3"" name="priority"/> <field name="user_id" ref="base.user_root"/> <field name="partner_id" ref="base.res_partner_14"/> - <field eval=""cancel"" name="state"/> <field name="section_id" ref="crm.section_sales_department"/> <field eval="1" name="active"/> <field eval="4.0" name="duration"/> <field name="categ_id" ref="bug_categ"/> <field name="project_id" ref="project.project_project_22"/> <field eval=""Problem loading page"" name="name"/> - <field name="type_id" ref="project.project_tt_testing"/> + <field name="stage_id" ref="project.project_tt_testing"/> </record> <record id="crm_case_pagenotfound0" model="project.issue"> @@ -67,14 +63,13 @@ <field eval=""3"" name="priority"/> <field name="user_id" ref="base.user_root"/> <field name="partner_id" ref="base.res_partner_desertic_hispafuentes"/> - <field eval=""draft"" name="state"/> <field name="section_id" ref="crm.section_sales_department"/> <field eval="1" name="active"/> <field eval="1.0" name="duration"/> <field name="categ_id" ref="bug_categ"/> <field name="project_id" ref="project.project_project_22"/> <field eval=""Page not Found"" name="name"/> - <field name="type_id" ref="project.project_tt_development"/> + <field name="stage_id" ref="project.project_tt_development"/> </record> <record id="crm_case_programmingerror0" model="project.issue"> @@ -82,14 +77,13 @@ <field eval=""3"" name="priority"/> <field name="user_id" ref="base.user_root"/> <field name="partner_id" ref="base.res_partner_5"/> - <field eval=""pending"" name="state"/> <field name="section_id" ref="crm.section_sales_department"/> <field eval="1" name="active"/> <field eval="4.0" name="duration"/> <field name="categ_id" ref="bug_categ"/> <field name="project_id" ref="project.project_project_22"/> <field eval=""Programming Error"" name="name"/> - <field name="type_id" ref="project.project_tt_testing"/> + <field name="stage_id" ref="project.project_tt_testing"/> </record> <record id="crm_case_logicalerrorinprogram0" model="project.issue"> @@ -97,14 +91,13 @@ <field eval=""2"" name="priority"/> <field name="user_id" ref="base.user_root"/> <field name="partner_id" ref="base.res_partner_6"/> - <field eval=""pending"" name="state"/> <field name="section_id" ref="crm.section_sales_department"/> <field eval="1" name="active"/> <field eval="2.0" name="duration"/> <field name="categ_id" ref="bug_categ"/> <field name="project_id" ref="project.project_project_9"/> <field eval=""Logical Error in Program"" name="name"/> - <field name="type_id" ref="project.project_tt_testing"/> + <field name="stage_id" ref="project.project_tt_testing"/> </record> <record id="crm_case_constrainterror0" model="project.issue"> @@ -112,14 +105,13 @@ <field eval=""2"" name="priority"/> <field name="user_id" ref="base.user_root"/> <field name="partner_id" ref="base.res_partner_6"/> - <field eval=""pending"" name="state"/> <field name="section_id" ref="crm.section_sales_department"/> <field eval="1" name="active"/> <field eval="7.3" name="duration"/> <field name="categ_id" ref="bug_categ"/> <field name="project_id" ref="project.project_project_9"/> <field eval=""Constraint Error"" name="name"/> - <field name="type_id" ref="project.project_tt_testing"/> + <field name="stage_id" ref="project.project_tt_testing"/> </record> <record id="crm_case_errorinprogram0" model="project.issue"> @@ -127,13 +119,12 @@ <field eval=""2"" name="priority"/> <field name="user_id" ref="base.user_demo"/> <field name="partner_id" ref="base.res_partner_5"/> - <field eval=""open"" name="state"/> <field eval="1" name="active"/> <field eval="1.3" name="duration"/> <field name="categ_id" ref="feature_request_categ"/> <field name="project_id" ref="project.project_project_22"/> <field eval=""Error in Program"" name="name"/> - <field name="type_id" ref="project.project_tt_testing"/> + <field name="stage_id" ref="project.project_tt_testing"/> </record> <record id="crm_case_patcheserrorinprogram0" model="project.issue"> @@ -141,14 +132,13 @@ <field eval=""2"" name="priority"/> <field name="user_id" ref="base.user_root"/> <field name="partner_id" ref="base.res_partner_2"/> - <field eval=""open"" name="state"/> <field name="section_id" ref="crm.section_sales_department"/> <field eval="1" name="active"/> <field eval="13.0" name="duration"/> <field name="categ_id" ref="feature_request_categ"/> <field name="project_id" ref="project.project_project_9"/> <field eval=""Patches Error in Program"" name="name"/> - <field name="type_id" ref="project.project_tt_testing"/> + <field name="stage_id" ref="project.project_tt_testing"/> </record> <record id="crm_case_newfeaturestobeadded0" model="project.issue"> @@ -156,14 +146,13 @@ <field eval=""4"" name="priority"/> <field name="user_id" ref="base.user_root"/> <field name="partner_id" ref="base.res_partner_maxtor"/> - <field eval=""open"" name="state"/> <field name="section_id" ref="crm.section_sales_department"/> <field eval="1" name="active"/> <field eval="3.2" name="duration"/> <field name="categ_id" ref="feature_request_categ"/> <field name="project_id" ref="project.project_project_21"/> <field eval=""New Features To Be Added"" name="name"/> - <field name="type_id" ref="project.project_tt_merge"/> + <field name="stage_id" ref="project.project_tt_merge"/> </record> <record id="crm_case_addmenustothemodule0" model="project.issue"> @@ -171,7 +160,6 @@ <field eval=""1"" name="priority"/> <field name="user_id" ref="base.user_demo"/> <field name="partner_id" ref="base.res_partner_9"/> - <field eval=""done"" name="state"/> <field name="section_id" ref="crm.section_sales_department"/> <field eval="1" name="active"/> <field eval="3.0" name="duration"/> @@ -179,7 +167,7 @@ <field name="project_id" ref="project.project_project_21"/> <field eval=""Add menus to the module"" name="name"/> <field eval=""info@opensides.be"" name="email_from"/> - <field name="type_id" ref="project.project_tt_development"/> + <field name="stage_id" ref="project.project_tt_development"/> </record> <record id="crm_case_includeattendancesheetinproject0" model="project.issue"> @@ -187,7 +175,6 @@ <field eval=""3"" name="priority"/> <field name="user_id" ref="base.user_root"/> <field name="partner_id" ref="base.res_partner_10"/> - <field eval=""cancel"" name="state"/> <field name="section_id" ref="crm.section_sales_department"/> <field eval="1" name="active"/> <field eval="2.0" name="duration"/> @@ -195,7 +182,7 @@ <field name="project_id" ref="project.project_project_9"/> <field eval=""Include Attendance sheet in Project"" name="name"/> <field eval=""contact@tecsas.fr"" name="email_from"/> - <field name="type_id" ref="project.project_tt_development"/> + <field name="stage_id" ref="project.project_tt_development"/> </record> <record id="crm_case_createnewobject0" model="project.issue"> @@ -203,14 +190,13 @@ <field eval=""3"" name="priority"/> <field name="user_id" ref="base.user_root"/> <field name="partner_id" ref="base.res_partner_6"/> - <field eval=""draft"" name="state"/> <field name="section_id" ref="crm.section_sales_department"/> <field eval="1" name="active"/> <field eval="2.45" name="duration"/> <field name="categ_id" ref="feature_request_categ"/> <field name="project_id" ref="project.project_project_22"/> <field eval=""Create new object"" name="name"/> - <field name="type_id" ref="project.project_tt_specification"/> + <field name="stage_id" ref="project.project_tt_specification"/> </record> <record id="crm_case_improvereportsinhrms0" model="project.issue"> @@ -218,14 +204,13 @@ <field eval=""4"" name="priority"/> <field name="user_id" ref="base.user_root"/> <field name="partner_id" ref="base.res_partner_11"/> - <field eval=""pending"" name="state"/> <field name="section_id" ref="crm.section_sales_department"/> <field eval="1" name="active"/> <field eval="15.0" name="duration"/> <field name="categ_id" ref="feature_request_categ"/> <field name="project_id" ref="project.project_project_22"/> <field eval=""Improve Reports in HRMS"" name="name"/> - <field name="type_id" ref="project.project_tt_development"/> + <field name="stage_id" ref="project.project_tt_development"/> </record> <record id="crm_case_improvereportsinpms0" model="project.issue"> @@ -233,14 +218,13 @@ <field eval=""2"" name="priority"/> <field name="user_id" ref="base.user_demo"/> <field name="partner_id" ref="base.res_partner_11"/> - <field eval=""pending"" name="state"/> <field name="section_id" ref="crm.section_sales_department"/> <field eval="1" name="active"/> <field eval="06.15" name="duration"/> <field name="categ_id" ref="feature_request_categ"/> <field name="project_id" ref="project.project_project_22"/> <field eval=""Improve Reports in PMS"" name="name"/> - <field name="type_id" ref="project.project_tt_specification"/> + <field name="stage_id" ref="project.project_tt_specification"/> </record> </data> diff --git a/addons/project_issue/project_issue_view.xml b/addons/project_issue/project_issue_view.xml index 716a02f2f60..c3b4bf184be 100644 --- a/addons/project_issue/project_issue_view.xml +++ b/addons/project_issue/project_issue_view.xml @@ -52,14 +52,26 @@ <field name="arch" type="xml"> <form layout="manual"> <div class="oe_form_topbar"> - <button name="case_open" string="Open" states="draft,pending" type="object"/> - <button name="case_close" string="Done" states="open,draft,pending" type="object"/> - <button name="case_pending" string="Pending" states="draft,open" type="object"/> - <button name="case_reset" string="Reset to New" states="done,cancel" type="object"/> - <button name="case_escalate" string="Escalate" states="open,draft,pending" type="object"/> - <button name="case_cancel" string="Cancel" states="draft,open,pending" type="object"/> + <button name="case_open" string="Open" type="object" + states="draft,pending"/> + <button name="case_close" string="Done" type="object" + states="draft,open,pending"/> + <button name="case_pending" string="Pending" type="object" + states="draft,open"/> + <button name="case_escalate" string="Escalate" type="object" + states="draft,open,pending"/> + <button name="case_reset" string="Reset to New" type="object" + states="cancelled,done"/> + <button name="stage_previous" string="Previous Stage" type="object" + states="open,pending" icon="gtk-go-back" + attrs="{'invisible': [('stage_id','=', False)]}"/> + <button name="stage_next" string="Next Stage" type="object" + states="open,pending" icon="gtk-go-forward" + attrs="{'invisible': [('stage_id','=', False)]}"/> + <button name="case_cancel" string="Cancel" type="object" + states="draft,open,pending"/> <div class="oe_right"> - <field name="state" nolabel="1" widget="statusbar" statusbar_visible="draft,open,done" statusbar_colors='{"pending":"blue"}'/> + <field name="stage_id" nolabel="1" widget="statusbar"/> </div> <div class="oe_clear"/> </div> @@ -75,11 +87,7 @@ </group> <field name="user_id"/> <field name="version_id" colspan="2" widget="selection"/> - <group colspan="2" col="4"> - <field name="type_id" string="Stages" /> - <button name="prev_type" string="Previous" type="object" icon="gtk-go-back" help="Change to Previous Stage"/> - <button name="next_type" string="Next" type="object" icon="gtk-go-forward" help="Change to Next Stage"/> - </group> + <field name="state" groups="base.group_no_one"/> </group> <notebook colspan="4"> <page string="General"> @@ -138,19 +146,23 @@ <field name="partner_id"/> <field name="project_id" /> <field name="priority" string="Priority"/> - <field name="type_id" widget="selection" readonly="1" string="Stages" /> - <button name="prev_type" string="Previous" type="object" icon="gtk-go-back" help="Change to Previous Stage"/> - <button name="next_type" string="Next" type="object" icon="gtk-go-forward" help="Change to Next Stage"/> <field name="version_id" widget="selection"/> <field name="user_id"/> <field name="progress" widget="progressbar" attrs="{'invisible':[('task_id','=',False)]}"/> - <field name="state"/> - <button name="case_cancel" string="Cancel" states="draft,open,pending" type="object" icon="gtk-cancel"/> - <button name="case_close" string="Done" states="open,draft,pending" type="object" icon="gtk-jump-to"/> - <button name="case_open" string="Open" states="draft,pending" type="object" icon="gtk-go-forward" help="To Do"/> - <button name="case_pending" string="Pending" states="draft,open" type="object" icon="gtk-media-pause"/> - <button name="case_escalate" string="Escalate" states="open,draft,pending" type="object" icon="gtk-go-up"/> - <button name="case_reset" string="Reset to New" states="done,cancel" type="object" icon="gtk-convert"/> + <field name="stage_id" widget="selection" readonly="1"/> + <button name="stage_previous" string="Previous" type="object" + icon="gtk-go-back" help="Change to Previous Stage" + attrs="{'invisible': [('stage_id','=', False)]}"/> + <button name="stage_next" string="Next" type="object" + icon="gtk-go-forward" help="Change to Next Stage" + attrs="{'invisible': [('stage_id','=', False)]}"/> + <field name="state" groups="base.group_no_one"/> + <button name="case_open" string="Open" type="object" + icon="gtk-go-forward" states="draft,pending"/> + <button name="case_close" string="Done" type="object" + icon="gtk-close" states="draft,open,pending"/> + <button name="case_cancel" string="Cancel" type="object" + icon="gtk-cancel" states="draft,open,pending"/> <field name="categ_id" invisible="1"/> <field name="task_id" invisible="1"/> </tree> @@ -192,7 +204,7 @@ <filter string="Priority" icon="terp-rating-rated" domain="[]" context="{'group_by':'priority'}" /> <filter string="Stage" icon="terp-stage" domain="[]" - context="{'group_by':'type_id'}" /> + context="{'group_by':'stage_id'}" /> <filter string="Status" icon="terp-stock_effects-object-colorize" domain="[]" context="{'group_by':'state'}" /> <separator orientation="vertical" /> @@ -224,9 +236,9 @@ <field name="model">project.issue</field> <field name="type">kanban</field> <field name="arch" type="xml"> - <kanban default_group_by="type_id"> + <kanban default_group_by="stage_id"> <field name="color"/> - <field name="state"/> + <field name="state" groups="base.group_no_one"/> <field name="priority"/> <field name="user_email"/> <field name="user_id"/> @@ -270,8 +282,7 @@ <a string="Convert To Task" name="convert_issue_task" icon="gtk-index" type="object"/> </div> <div class="oe_kanban_right"> - <a name="case_open" string="Open" states="draft,pending" type="object" icon="kanban-apply" /> - <a name="case_pending" string="Pending" states="draft,open" type="object" icon="kanban-pause"/> + <a name="case_open" string="Open" states="draft,pending" type="object" icon="kanban-apply"/> <a name="case_close" string="Close" states="open,draft,pending" type="object" icon="kanban-stop"/> </div> <div class="oe_kanban_clear"/> @@ -304,18 +315,22 @@ <field name="name" string="Feature description"/> <field name="partner_id"/> <field name="priority" string="Priority"/> - <field name="type_id" widget="selection" readonly="1"/> - <button name="prev_type" string="Previous" type="object" icon="gtk-go-back" help="Change to Previous Stage"/> - <button name="next_type" string="Next" type="object" icon="gtk-go-forward" help="Change to Next Stage"/> <field name="version_id"/> <field name="user_id"/> - <field name="state"/> - <button name="case_cancel" string="Cancel" states="draft,open,pending" type="object" icon="gtk-cancel"/> - <button name="case_close" string="Done" states="open,draft,pending" type="object" icon="gtk-jump-to"/> - <button name="case_open" string="Open" states="draft,pending" type="object" icon="gtk-go-forward"/> - <button name="case_pending" string="Pending" states="draft,open" type="object" icon="gtk-media-pause"/> - <button name="case_escalate" string="Escalate" states="open,draft,pending" type="object" icon="gtk-go-up"/> - <button name="case_reset" string="Reset to New" states="done,cancel" type="object" icon="gtk-convert"/> + <field name="stage_id" widget="selection" readonly="1"/> + <button name="stage_previous" string="Previous" type="object" + icon="gtk-go-back" help="Change to Previous Stage" + attrs="{'invisible': [('stage_id','=', False)]}"/> + <button name="stage_next" string="Next" type="object" + icon="gtk-go-forward" help="Change to Next Stage" + attrs="{'invisible': [('stage_id','=', False)]}"/> + <field name="state" groups="base.group_no_one"/> + <button name="case_open" string="Open" type="object" + states="draft,pending" icon="gtk-go-forward"/> + <button name="case_close" string="Done" type="object" + states="draft,open,pending" icon="gtk-close"/> + <button name="case_cancel" string="Cancel" type="object" + states="draft,open,pending" icon="gtk-cancel"/> </tree> </field> </record> @@ -326,13 +341,13 @@ <field name="type">search</field> <field name="arch" type="xml"> <search string="Feature Tracker Search"> - <filter icon="terp-go-today" string="Today" + <filter icon="terp-go-today" string="Today" domain="[('date','=',time.strftime('%%Y-%%m-%%d'))]" help="Today's features"/> <separator orientation="vertical"/> <group> <field name="name" string="Feature description"/> <field name="user_id"/> - <field name="state"> + <field name="state" groups="base.group_no_one"> <filter icon="terp-check" domain="[('state','in',('open','draft'))]" help="Current Features" name="current_feature"/> <filter icon="terp-camera_test" domain="[('state','=','open')]" help="Open Features"/> </field> diff --git a/addons/project_issue/report/project_issue_report.py b/addons/project_issue/report/project_issue_report.py index 7cc520308e8..be3309d7175 100644 --- a/addons/project_issue/report/project_issue_report.py +++ b/addons/project_issue/report/project_issue_report.py @@ -51,7 +51,7 @@ class project_issue_report(osv.osv): 'creation_date': fields.date('Creation Date', readonly=True), 'date_closed': fields.date('Date of Closing', readonly=True), 'categ_id': fields.many2one('crm.case.categ', 'Category', domain="[('section_id','=',section_id),('object_id.model', '=', 'project.issue')]"), - 'type_id': fields.many2one('project.task.type', 'Stage'), + 'stage_id': fields.many2one('project.task.type', 'Stage'), 'nbr': fields.integer('# of Issues', readonly=True), 'working_hours_open': fields.float('Avg. Working Hours to Open', readonly=True), 'working_hours_close': fields.float('Avg. Working Hours to Close', readonly=True), @@ -87,7 +87,7 @@ class project_issue_report(osv.osv): c.working_hours_close, c.section_id, c.categ_id, - c.type_id, + c.stage_id, to_char(c.date_closed, 'YYYY-mm-dd') as date_closed, c.company_id as company_id, c.priority as priority, diff --git a/addons/project_issue/report/project_issue_report_view.xml b/addons/project_issue/report/project_issue_report_view.xml index ee0a4fbd1ed..43a49abf2b8 100644 --- a/addons/project_issue/report/project_issue_report_view.xml +++ b/addons/project_issue/report/project_issue_report_view.xml @@ -11,7 +11,7 @@ <field name="name" invisible="1"/> <field name="month" invisible="1"/> <field name="project_id" invisible="1"/> - <field name="type_id" invisible="1"/> + <field name="stage_id" invisible="1"/> <field name="version_id" string="Version" invisible="1"/> <field name="priority" invisible="1"/> <field name="company_id" invisible="1" groups="base.group_multi_company"/> @@ -103,7 +103,7 @@ <filter string="Version" icon="terp-stock_symbol-selection" domain="[]" context="{'group_by':'version_id'}"/> <separator orientation="vertical" /> <filter string="Priority" icon="terp-rating-rated" domain="[]" context="{'group_by':'priority'}" /> - <filter string="Stage" icon="terp-stage" domain="[]" context="{'group_by':'type_id'}"/> + <filter string="Stage" icon="terp-stage" domain="[]" context="{'group_by':'stage_id'}"/> <filter string="Status" icon="terp-stock_effects-object-colorize" domain="[]" context="{'group_by':'state'}" /> <separator orientation="vertical"/> diff --git a/addons/project_issue/test/cancel_issue.yml b/addons/project_issue/test/cancel_issue.yml index ff05d923ed0..76ffa212638 100644 --- a/addons/project_issue/test/cancel_issue.yml +++ b/addons/project_issue/test/cancel_issue.yml @@ -7,7 +7,7 @@ I check the issue is in cancel state. - !assert {model: project.issue, id: crm_case_buginaccountsmodule0, severity: error, string: Issue is in cancel state}: - - state == 'cancel' + - state == 'cancelled' - I re-open the Issue. - @@ -37,7 +37,7 @@ I check the issue is in cancel state. - !assert {model: project.issue, id: crm_case_buginaccountsmodule0, severity: error, string: Issue is in cancel state}: - - state == 'cancel' + - state == 'cancelled' - I close Issue. - @@ -57,4 +57,4 @@ I check the issue is in cancel state. - !assert {model: project.issue, id: crm_case_buginaccountsmodule0, severity: error, string: Issue is in cancel state}: - - state == 'cancel'
\ No newline at end of file + - state == 'cancelled' diff --git a/addons/project_long_term/project_long_term_view.xml b/addons/project_long_term/project_long_term_view.xml index 1e9f50cdd87..eb2c86a0f1b 100644 --- a/addons/project_long_term/project_long_term_view.xml +++ b/addons/project_long_term/project_long_term_view.xml @@ -330,7 +330,7 @@ <field name="type">search</field> <field name="inherit_id" ref="project.view_task_search_form"/> <field name="arch" type="xml"> - <field name="project_id" position="after"> + <field name="user_id" position="before"> <field name="phase_id" select="1"/> </field> </field> diff --git a/addons/project_planning/project_planning_view.xml b/addons/project_planning/project_planning_view.xml index 11e9b057c06..ef230543e9c 100644 --- a/addons/project_planning/project_planning_view.xml +++ b/addons/project_planning/project_planning_view.xml @@ -129,7 +129,7 @@ <field name="sequence"/> </group> <separator string="Miscellaneous" colspan="4"/> - <field name="type_id"/> + <field name="stage_id"/> <field name="active"/> <field name="partner_id"/> <separator colspan="4" string="Notes"/> diff --git a/addons/project_scrum/project_scrum_demo.xml b/addons/project_scrum/project_scrum_demo.xml index 50c67146716..6e3de7665bd 100644 --- a/addons/project_scrum/project_scrum_demo.xml +++ b/addons/project_scrum/project_scrum_demo.xml @@ -265,7 +265,6 @@ <field model="res.users" name="user_id" search="[('login','=','demo')]"/> <field name="project_id" ref="project.project_project_9"/> <field name="name">Review all English Terms</field> - <field name="state">done</field> <field eval="time.strftime('%Y-%m-%d')" name="date_end"/> <field name="product_backlog_id" ref="scrum_product_backlog_3"/> </record> @@ -274,7 +273,6 @@ <field model="res.users" name="user_id" search="[('login','=','demo')]"/> <field name="project_id" ref="project.project_project_9"/> <field name="name">Review all french terms</field> - <field name="state">done</field> <field eval="time.strftime('%Y-%m-%d')" name="date_end"/> <field name="product_backlog_id" ref="scrum_product_backlog_3"/> </record> @@ -283,7 +281,6 @@ <field model="res.users" name="user_id" search="[('login','=','demo')]"/> <field name="project_id" ref="project.project_project_9"/> <field name="name">Analytic Accounting features</field> - <field name="state">done</field> <field eval="time.strftime('%Y-%m-%d')" name="date_end"/> <field name="product_backlog_id" ref="scrum_product_backlog_1"/> </record> @@ -292,7 +289,6 @@ <field model="res.users" name="user_id" search="[('login','=','demo')]"/> <field name="project_id" ref="project.project_project_9"/> <field name="name">Test and improve automatic migration system</field> - <field name="state">open</field> <field name="product_backlog_id" ref="scrum_product_backlog_0"/> </record> <record id="scrum_task_7" model="project.task"> @@ -300,7 +296,6 @@ <field model="res.users" name="user_id" search="[('login','=','demo')]"/> <field name="project_id" ref="project.project_project_9"/> <field name="name">General accounting features</field> - <field name="state">open</field> <field name="product_backlog_id" ref="scrum_product_backlog_1"/> </record> <record id="scrum_task_8" model="project.task"> @@ -316,7 +311,6 @@ <field name="project_id" ref="project.project_project_9"/> <field name="description">Cash book, general ledger, accounts list, aged trial balance</field> <field name="name">Accounting Report General</field> - <field name="state">open</field> <field eval="[(6,0,[ref('scrum_task_8')])]" name="child_ids"/> <field name="product_backlog_id" ref="scrum_product_backlog_1"/> </record> @@ -332,7 +326,6 @@ <field model="res.users" name="user_id" search="[('login','=','demo')]"/> <field name="project_id" ref="project.project_project_9"/> <field name="name">Accounting Report Analytic</field> - <field name="state">open</field> <field name="product_backlog_id" ref="scrum_product_backlog_1"/> </record> <record id="scrum_task_12" model="project.task"> @@ -340,7 +333,6 @@ <field model="res.users" name="user_id" search="[('login','=','demo')]"/> <field name="project_id" ref="project.project_project_9"/> <field name="name">Bugfix - memory leak</field> - <field name="state">done</field> <field eval="time.strftime('%Y-%m-%d')" name="date_end"/> <field name="product_backlog_id" ref="scrum_product_backlog_4"/> </record> @@ -349,7 +341,6 @@ <field model="res.users" name="user_id" search="[('login','=','demo')]"/> <field name="project_id" ref="project.project_project_9"/> <field name="name">Bugfix - Translations</field> - <field name="state">open</field> <field eval="[(6,0,[ref('scrum_task_12')])]" name="child_ids"/> <field name="product_backlog_id" ref="scrum_product_backlog_4"/> </record> @@ -358,7 +349,6 @@ <field model="res.users" name="user_id" search="[('login','=','demo')]"/> <field name="project_id" ref="project.project_project_9"/> <field name="name">Finish the automated plugin system</field> - <field name="state">pending</field> <field name="product_backlog_id" ref="scrum_product_backlog_2"/> </record> <record id="scrum_task_15" model="project.task"> @@ -373,7 +363,6 @@ <field model="res.users" name="user_id" search="[('login','=','demo')]"/> <field name="project_id" ref="project.project_project_9"/> <field name="name">Test 3.2.0 before releasing</field> - <field name="state">open</field> <field name="product_backlog_id" ref="scrum_product_backlog_5"/> </record> <record id="scrum_task_17" model="project.task"> @@ -382,7 +371,6 @@ <field name="project_id" ref="project.project_project_9"/> <field name="description">default values, onchange, required, add on top or bottom and shortcuts (Ctrl S, Ctrl X, ...)</field> <field name="name">Editable trees</field> - <field name="state">open</field> <field name="product_backlog_id" ref="scrum_product_backlog_7"/> </record> diff --git a/addons/project_scrum/project_scrum_view.xml b/addons/project_scrum/project_scrum_view.xml index ef8a2681620..168a56f46aa 100644 --- a/addons/project_scrum/project_scrum_view.xml +++ b/addons/project_scrum/project_scrum_view.xml @@ -106,7 +106,7 @@ <field name="type_id" invisible="context.get('set_visible',False)"/> <field name="date_start" invisible="1"/> <field name="date_end" invisible="1"/> - <button name="next_type" invisible="context.get('set_visible',False)" + <button name="stage_next" invisible="context.get('set_visible',False)" states="draft,open,pending" string="Change Stage" type="object" @@ -578,7 +578,7 @@ <field name="type">search</field> <field name="inherit_id" ref="project.view_task_search_form"/> <field name="arch" type="xml"> - <field name="project_id" position="after"> + <field name="user_id" position="before"> <field name="sprint_id" context="{'sprint_invisible':False}"> <filter icon="terp-check" context="{'sprint_invisible':False}" domain="[('sprint_id.state','in',('draft','open'))]" help="Current Sprints"/> <filter icon="gtk-find" context="{'sprint_invisible':False}" domain="[]" help="View Sprints"/> |
