merge trunk
bzr revid: nicolas.vanhoren@openerp.com-20130819123730-tjkhbg2vd3k1caaz
This commit is contained in:
@@ -20,6 +20,5 @@
|
||||
##############################################################################
|
||||
|
||||
import base_state
|
||||
import base_stage
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -24,11 +24,10 @@
|
||||
'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.
|
||||
===================================================================================================
|
||||
This module handles state. 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',
|
||||
|
||||
@@ -1,264 +0,0 @@
|
||||
# -*- 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 openerp.osv import fields, osv
|
||||
from openerp.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 context.get('portal'):
|
||||
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
|
||||
return user.partner_id.id
|
||||
return False
|
||||
|
||||
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 context.get('portal'):
|
||||
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
|
||||
return user.email
|
||||
return False
|
||||
|
||||
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 context.get('portal'):
|
||||
return False
|
||||
return uid
|
||||
|
||||
def onchange_partner_address_id(self, cr, uid, ids, add, email=False, context=None):
|
||||
""" 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'] = {'partner_name': address and address.name or False,
|
||||
'email_from': address and address.email or False,
|
||||
'phone': address and address.phone or False,
|
||||
'street': address and address.street or False,
|
||||
'street2': address and address.street2 or False,
|
||||
'city': address and address.city or False,
|
||||
'state_id': address.state_id and address.state_id.id or False,
|
||||
'zip': address and address.zip or False,
|
||||
'country_id': address.country_id and address.country_id.id or False,
|
||||
}
|
||||
fields = self.fields_get(cr, uid, context=context or {})
|
||||
for key in data['value'].keys():
|
||||
if key not in fields:
|
||||
del data['value'][key]
|
||||
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
|
||||
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 """
|
||||
for case in self.browse(cr, uid, ids, context=context):
|
||||
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 are already at the top level of your sales-team category.\nTherefore you cannot escalate furthermore."))
|
||||
self.write(cr, uid, [case.id], data, 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 not case.user_id:
|
||||
data['user_id'] = uid
|
||||
self.case_set(cr, uid, [case.id], 'open', data, context=context)
|
||||
return True
|
||||
|
||||
def case_close(self, cr, uid, ids, context=None):
|
||||
""" Closes case """
|
||||
return self.case_set(cr, uid, ids, 'done', {'active': True, 'date_closed': fields.datetime.now()}, context=context)
|
||||
|
||||
def case_cancel(self, cr, uid, ids, context=None):
|
||||
""" Cancels case """
|
||||
return self.case_set(cr, uid, ids, 'cancel', {'active': True}, context=context)
|
||||
|
||||
def case_pending(self, cr, uid, ids, context=None):
|
||||
""" Set case as pending """
|
||||
return self.case_set(cr, uid, ids, 'pending', {'active': True}, context=context)
|
||||
|
||||
def case_reset(self, cr, uid, ids, context=None):
|
||||
""" Resets case as draft """
|
||||
return self.case_set(cr, uid, ids, 'draft', {'active': True}, context=context)
|
||||
|
||||
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.
|
||||
|
||||
: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)
|
||||
# 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)
|
||||
return True
|
||||
@@ -116,6 +116,7 @@ Dashboard for CRM will include:
|
||||
'test/crm_lead_onchange.yml',
|
||||
'test/crm_lead_copy.yml',
|
||||
'test/crm_lead_unlink.yml',
|
||||
'test/crm_lead_find_stage.yml',
|
||||
],
|
||||
'css': [
|
||||
'static/src/css/crm.css'
|
||||
|
||||
@@ -19,7 +19,11 @@
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">graph,tree,form</field>
|
||||
<field name="view_id" ref="view_crm_opportunity_stage_graph"/>
|
||||
<field name="domain">[('state', 'not in', ('done', 'cancel')), ('type', '=', 'opportunity')]</field>
|
||||
<!-- avoid done / cancelled -->
|
||||
<field name="domain">['|',
|
||||
'!', '&', ('probability', '=', 100), ('stage_id.on_change', '=', 1),
|
||||
'!', '&', ('probability', '=', 0), ('stage_id.sequence', '!=', 1),
|
||||
('type', '=', 'opportunity')]</field>
|
||||
<field name="context">{'search_default_Stage':1}</field>
|
||||
</record>
|
||||
|
||||
@@ -43,8 +47,9 @@
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">graph,tree,form</field>
|
||||
<field name="view_id" ref="view_crm_opportunity_user_stage_graph"/>
|
||||
<field name="domain">[('state','!=','cancel'),('opening_date','>',context_today().strftime("%Y-%m-%d"))]</field>
|
||||
<field name="context">{'search_default_Stage':1}</field>
|
||||
<!-- avoid cancelled -->
|
||||
<field name="domain">['!', '&', ('probability', '=', 0), ('stage_id.sequence', '!=', 1)]</field>
|
||||
<field name="context">{'search_default_user': 1, 'search_default_Stage': 1}</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="board_crm_statistical_form">
|
||||
|
||||
+3
-15
@@ -26,15 +26,6 @@ from openerp import tools
|
||||
from openerp.osv import fields
|
||||
from openerp.osv import osv
|
||||
|
||||
MAX_LEVEL = 15
|
||||
AVAILABLE_STATES = [
|
||||
('draft', 'New'),
|
||||
('cancel', 'Cancelled'),
|
||||
('open', 'In Progress'),
|
||||
('pending', 'Pending'),
|
||||
('done', 'Closed')
|
||||
]
|
||||
|
||||
AVAILABLE_PRIORITIES = [
|
||||
('1', 'Highest'),
|
||||
('2', 'High'),
|
||||
@@ -72,16 +63,13 @@ class crm_case_stage(osv.osv):
|
||||
'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', string='Sections',
|
||||
'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, 'Related Status', required=True,
|
||||
help="The status of your document will automatically change regarding the selected stage. " \
|
||||
"For example, if a stage is related to the status 'Close', when your document reaches this stage, it is automatically closed."),
|
||||
'case_default': fields.boolean('Default to New Sales Team',
|
||||
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('Fold by Default',
|
||||
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'),
|
||||
'type': fields.selection([('lead', 'Lead'),
|
||||
('opportunity', 'Opportunity'),
|
||||
('both', 'Both')],
|
||||
string='Type', size=16, required=True,
|
||||
@@ -91,7 +79,7 @@ class crm_case_stage(osv.osv):
|
||||
_defaults = {
|
||||
'sequence': lambda *args: 1,
|
||||
'probability': lambda *args: 0.0,
|
||||
'state': 'open',
|
||||
'on_change': True,
|
||||
'fold': False,
|
||||
'type': 'both',
|
||||
'case_default': True,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<record id="filter_draft_lead" model="ir.filters">
|
||||
<field name="name">Draft Leads</field>
|
||||
<field name="model_id">crm.lead</field>
|
||||
<field name="domain">[('state','=','draft')]</field>
|
||||
<field name="domain">[('stage_id.sequence', '=', 1)]</field>
|
||||
<field name="user_id" eval="False"/>
|
||||
</record>
|
||||
<record id="action_email_reminder_lead" model="ir.actions.server">
|
||||
|
||||
+53
-81
@@ -22,14 +22,13 @@
|
||||
import crm
|
||||
from datetime import datetime
|
||||
from operator import itemgetter
|
||||
from openerp.osv import fields, osv, orm
|
||||
import time
|
||||
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp import tools
|
||||
from openerp.tools.translate import _
|
||||
from openerp.tools import html2plaintext
|
||||
|
||||
from openerp.addons.base.res.res_partner import format_address
|
||||
from openerp.osv import fields, osv, orm
|
||||
from openerp.tools import html2plaintext
|
||||
from openerp.tools.translate import _
|
||||
|
||||
CRM_LEAD_FIELDS_TO_MERGE = ['name',
|
||||
'partner_id',
|
||||
@@ -61,11 +60,7 @@ CRM_LEAD_FIELDS_TO_MERGE = ['name',
|
||||
'email_from',
|
||||
'email_cc',
|
||||
'partner_name']
|
||||
CRM_LEAD_PENDING_STATES = (
|
||||
crm.AVAILABLE_STATES[2][0], # Cancelled
|
||||
crm.AVAILABLE_STATES[3][0], # Done
|
||||
crm.AVAILABLE_STATES[4][0], # Pending
|
||||
)
|
||||
|
||||
|
||||
class crm_lead(format_address, osv.osv):
|
||||
""" CRM Lead Case """
|
||||
@@ -75,13 +70,11 @@ class crm_lead(format_address, osv.osv):
|
||||
_inherit = ['mail.thread', 'ir.needaction_mixin']
|
||||
|
||||
_track = {
|
||||
'state': {
|
||||
'crm.mt_lead_create': lambda self, cr, uid, obj, ctx=None: obj.state in ['new', 'draft'],
|
||||
'crm.mt_lead_won': lambda self, cr, uid, obj, ctx=None: obj.state == 'done',
|
||||
'crm.mt_lead_lost': lambda self, cr, uid, obj, ctx=None: obj.state == 'cancel',
|
||||
},
|
||||
'stage_id': {
|
||||
'crm.mt_lead_stage': lambda self, cr, uid, obj, ctx=None: obj.state not in ['new', 'draft', 'cancel', 'done'],
|
||||
'crm.mt_lead_create': lambda self, cr, uid, obj, ctx=None: obj.probability == 0 and obj.stage_id and obj.stage_id.sequence == 1,
|
||||
'crm.mt_lead_stage': lambda self, cr, uid, obj, ctx=None: (obj.stage_id and obj.stage_id.sequence != 1) and obj.probability < 100,
|
||||
'crm.mt_lead_won': lambda self, cr, uid, obj, ctx=None: obj.probability == 100 and obj.stage_id and obj.stage_id.on_change,
|
||||
'crm.mt_lead_lost': lambda self, cr, uid, obj, ctx=None: obj.probability == 0 and obj.stage_id and obj.stage_id.sequence != 1,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -99,7 +92,7 @@ class crm_lead(format_address, osv.osv):
|
||||
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')], context=context)
|
||||
return self.stage_find(cr, uid, [], section_id, [('sequence', '=', '1')], context=context)
|
||||
|
||||
def _resolve_section_id_from_context(self, cr, uid, context=None):
|
||||
""" Returns ID of section based on the value of 'section_id'
|
||||
@@ -150,7 +143,7 @@ class crm_lead(format_address, osv.osv):
|
||||
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])))
|
||||
result.sort(lambda x, y: cmp(stage_ids.index(x[0]), stage_ids.index(y[0])))
|
||||
|
||||
fold = {}
|
||||
for stage in stage_obj.browse(cr, access_rights_uid, stage_ids, context=context):
|
||||
@@ -158,7 +151,7 @@ class crm_lead(format_address, osv.osv):
|
||||
return result, fold
|
||||
|
||||
def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
|
||||
res = super(crm_lead,self).fields_view_get(cr, user, view_id, view_type, context, toolbar=toolbar, submenu=submenu)
|
||||
res = super(crm_lead, self).fields_view_get(cr, user, view_id, view_type, context, toolbar=toolbar, submenu=submenu)
|
||||
if view_type == 'form':
|
||||
res['arch'] = self.fields_view_get_address(cr, user, res['arch'], context=context)
|
||||
return res
|
||||
@@ -220,17 +213,6 @@ class crm_lead(format_address, osv.osv):
|
||||
res[lead.id][field] = abs(int(duration))
|
||||
return res
|
||||
|
||||
def _history_search(self, cr, uid, obj, name, args, context=None):
|
||||
res = []
|
||||
msg_obj = self.pool.get('mail.message')
|
||||
message_ids = msg_obj.search(cr, uid, [('email_from','!=',False), ('subject', args[0][1], args[0][2])], context=context)
|
||||
lead_ids = self.search(cr, uid, [('message_ids', 'in', message_ids)], context=context)
|
||||
|
||||
if lead_ids:
|
||||
return [('id', 'in', lead_ids)]
|
||||
else:
|
||||
return [('id', '=', '0')]
|
||||
|
||||
_columns = {
|
||||
'partner_id': fields.many2one('res.partner', 'Partner', ondelete='set null', track_visibility='onchange',
|
||||
select=True, help="Linked partner (optional). Usually created when converting the lead."),
|
||||
@@ -243,10 +225,10 @@ class crm_lead(format_address, osv.osv):
|
||||
'email_from': fields.char('Email', size=128, help="Email address of the contact", select=1),
|
||||
'section_id': fields.many2one('crm.case.section', 'Sales Team',
|
||||
select=True, track_visibility='onchange', help='When sending mails, the default email address is taken from the sales team.'),
|
||||
'create_date': fields.datetime('Creation Date' , readonly=True),
|
||||
'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"),
|
||||
'create_date': fields.datetime('Creation Date', readonly=True),
|
||||
'email_cc': fields.text('Global CC', 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),
|
||||
'write_date': fields.datetime('Update Date', readonly=True),
|
||||
'categ_ids': fields.many2many('crm.case.categ', 'crm_lead_category_rel', 'lead_id', 'category_id', 'Categories', \
|
||||
domain="['|',('section_id','=',section_id),('section_id','=',False), ('object_id.model', '=', 'crm.lead')]"),
|
||||
'type_id': fields.many2one('crm.case.resource.type', 'Campaign', \
|
||||
@@ -264,17 +246,15 @@ class crm_lead(format_address, osv.osv):
|
||||
domain="['&', ('section_ids', '=', section_id), '|', ('type', '=', type), ('type', '=', 'both')]"),
|
||||
'user_id': fields.many2one('res.users', 'Salesperson', select=True, track_visibility='onchange'),
|
||||
'referred': fields.char('Referred By', size=64),
|
||||
'date_open': fields.datetime('Opened', readonly=True),
|
||||
'date_open': fields.datetime('Assigned', readonly=True),
|
||||
'day_open': fields.function(_compute_day, string='Days to Open', \
|
||||
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.related('stage_id', 'state', type="selection", store=True,
|
||||
selection=crm.AVAILABLE_STATES, string="Status", readonly=True,
|
||||
help='The Status is set to \'Draft\', when a case is created. If the case is in progress the Status is set to \'Open\'. When the case is over, the Status is set to \'Done\'. If the case needs to be reviewed then the Status is set to \'Pending\'.'),
|
||||
'date_last_stage_update': fields.datetime('Last Stage Update', select=True),
|
||||
|
||||
# Only used for type opportunity
|
||||
'probability': fields.float('Success Rate (%)',group_operator="avg"),
|
||||
'probability': fields.float('Success Rate (%)', group_operator="avg"),
|
||||
'planned_revenue': fields.float('Expected Revenue', track_visibility='always'),
|
||||
'ref': fields.reference('Reference', selection=crm._links_get, size=128),
|
||||
'ref2': fields.reference('Reference 2', selection=crm._links_get, size=128),
|
||||
@@ -316,6 +296,7 @@ class crm_lead(format_address, osv.osv):
|
||||
'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,
|
||||
'date_last_stage_update': fields.datetime.now(),
|
||||
}
|
||||
|
||||
_sql_constraints = [
|
||||
@@ -325,7 +306,7 @@ class crm_lead(format_address, osv.osv):
|
||||
def onchange_stage_id(self, cr, uid, ids, stage_id, context=None):
|
||||
if not stage_id:
|
||||
return {'value': {}}
|
||||
stage = self.pool.get('crm.case.stage').browse(cr, uid, stage_id, context)
|
||||
stage = self.pool.get('crm.case.stage').browse(cr, uid, stage_id, context=context)
|
||||
if not stage.on_change:
|
||||
return {'value': {}}
|
||||
return {'value': {'probability': stage.probability}}
|
||||
@@ -358,22 +339,6 @@ class crm_lead(format_address, osv.osv):
|
||||
section_id = section_ids[0]
|
||||
return {'value': {'section_id': section_id}}
|
||||
|
||||
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=None, order='sequence', context=None):
|
||||
""" Override of the base.stage method
|
||||
Parameter of the stage search taken from the lead:
|
||||
@@ -385,16 +350,16 @@ class crm_lead(format_address, osv.osv):
|
||||
if isinstance(cases, (int, long)):
|
||||
cases = self.browse(cr, uid, cases, context=context)
|
||||
# collect all section_ids
|
||||
section_ids = []
|
||||
section_ids = set()
|
||||
types = ['both']
|
||||
if not cases:
|
||||
type = context.get('default_type')
|
||||
types += [type]
|
||||
ctx_type = context.get('default_type')
|
||||
types += [ctx_type]
|
||||
if section_id:
|
||||
section_ids.append(section_id)
|
||||
section_ids.add(section_id)
|
||||
for lead in cases:
|
||||
if lead.section_id:
|
||||
section_ids.append(lead.section_id.id)
|
||||
section_ids.add(lead.section_id.id)
|
||||
if lead.type not in types:
|
||||
types.append(lead.type)
|
||||
# OR all section_ids and OR with case_default
|
||||
@@ -403,8 +368,7 @@ class crm_lead(format_address, osv.osv):
|
||||
search_domain += [('|')] * len(section_ids)
|
||||
for section_id in section_ids:
|
||||
search_domain.append(('section_ids', '=', section_id))
|
||||
else:
|
||||
search_domain.append(('case_default', '=', True))
|
||||
search_domain.append(('case_default', '=', True))
|
||||
# AND with cases types
|
||||
search_domain.append(('type', 'in', types))
|
||||
# AND with the domain in parameter
|
||||
@@ -415,27 +379,29 @@ class crm_lead(format_address, osv.osv):
|
||||
return stage_ids[0]
|
||||
return False
|
||||
|
||||
def stage_set(self, cr, uid, ids, stage_id, context=None):
|
||||
""" Set the new stage. Now just writes the stage.
|
||||
TDE TODO: remove me when removing state
|
||||
"""
|
||||
return self.write(cr, uid, ids, {'stage_id': stage_id}, context=context)
|
||||
|
||||
def case_mark_lost(self, cr, uid, ids, context=None):
|
||||
""" Mark the case as lost: state=cancel and probability=0 """
|
||||
for lead in self.browse(cr, uid, ids):
|
||||
stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, [('probability', '=', 0.0),('on_change','=',True)], context=context)
|
||||
stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, [('probability', '=', 0.0), ('on_change', '=', True), ('sequence', '>', 1)], context=context)
|
||||
if stage_id:
|
||||
self.stage_set(cr, uid, [lead.id], stage_id, context=context)
|
||||
return True
|
||||
return self.write(cr, uid, [lead.id], {'stage_id': stage_id}, context=context)
|
||||
else:
|
||||
raise osv.except_osv(_('Warning!'),
|
||||
_('To relieve your sales pipe and group all Lost opportunities, configure one of your sales stage as follow:\n'
|
||||
'probability = 0 %, select "Change Probability Automatically".\n'
|
||||
'Create a specific stage or edit an existing one by editing columns of your opportunity pipe.'))
|
||||
|
||||
def case_mark_won(self, cr, uid, ids, context=None):
|
||||
""" Mark the case as won: state=done and probability=100 """
|
||||
for lead in self.browse(cr, uid, ids):
|
||||
stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, [('probability', '=', 100.0),('on_change','=',True)], context=context)
|
||||
stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, [('probability', '=', 100.0), ('on_change', '=', True), ('sequence', '>', 1)], context=context)
|
||||
if stage_id:
|
||||
self.stage_set(cr, uid, [lead.id], stage_id, context=context)
|
||||
return True
|
||||
return self.write(cr, uid, [lead.id], {'stage_id': stage_id}, context=context)
|
||||
else:
|
||||
raise osv.except_osv(_('Warning!'),
|
||||
_('To relieve your sales pipe and group all Won opportunities, configure one of your sales stage as follow:\n'
|
||||
'probability = 100 % and select "Change Probability Automatically".\n'
|
||||
'Create a specific stage or edit an existing one by editing columns of your opportunity pipe.'))
|
||||
|
||||
def case_escalate(self, cr, uid, ids, context=None):
|
||||
""" Escalates case to parent level """
|
||||
@@ -451,20 +417,20 @@ class crm_lead(format_address, osv.osv):
|
||||
self.write(cr, uid, [case.id], data, context=context)
|
||||
return True
|
||||
|
||||
def set_priority(self, cr, uid, ids, priority):
|
||||
def set_priority(self, cr, uid, ids, priority, context=None):
|
||||
""" Set lead priority
|
||||
"""
|
||||
return self.write(cr, uid, ids, {'priority' : priority})
|
||||
return self.write(cr, uid, ids, {'priority': priority}, context=context)
|
||||
|
||||
def set_high_priority(self, cr, uid, ids, context=None):
|
||||
""" Set lead priority to high
|
||||
"""
|
||||
return self.set_priority(cr, uid, ids, '1')
|
||||
return self.set_priority(cr, uid, ids, '1', context=context)
|
||||
|
||||
def set_normal_priority(self, cr, uid, ids, context=None):
|
||||
""" Set lead priority to normal
|
||||
"""
|
||||
return self.set_priority(cr, uid, ids, '3')
|
||||
return self.set_priority(cr, uid, ids, '3', context=context)
|
||||
|
||||
def _merge_get_result_type(self, cr, uid, opps, context=None):
|
||||
"""
|
||||
@@ -640,7 +606,8 @@ class crm_lead(format_address, osv.osv):
|
||||
sequenced_opps = []
|
||||
for opportunity in opportunities:
|
||||
sequence = -1
|
||||
if opportunity.stage_id and opportunity.stage_id.state != 'cancel':
|
||||
# TDE: was "if opportunity.stage_id and opportunity.stage_id.state != 'cancel':"
|
||||
if opportunity.probability == 0 and opportunity.stage_id and opportunity.stage_id.sequence != 1 and opportunity.stage_id.fold:
|
||||
sequence = opportunity.stage_id.sequence
|
||||
sequenced_opps.append(((int(sequence != -1 and opportunity.type == 'opportunity'), sequence, -opportunity.id), opportunity))
|
||||
|
||||
@@ -701,7 +668,7 @@ class crm_lead(format_address, osv.osv):
|
||||
'phone': customer and customer.phone or lead.phone,
|
||||
}
|
||||
if not lead.stage_id or lead.stage_id.type=='lead':
|
||||
val['stage_id'] = self.stage_find(cr, uid, [lead], section_id, [('state', '=', 'draft'),('type', 'in', ('opportunity','both'))], context=context)
|
||||
val['stage_id'] = self.stage_find(cr, uid, [lead], section_id, [('sequence', '=', '1'), ('type', 'in', ('opportunity','both'))], context=context)
|
||||
return val
|
||||
|
||||
def convert_opportunity(self, cr, uid, ids, partner_id, user_ids=False, section_id=False, context=None):
|
||||
@@ -710,7 +677,8 @@ class crm_lead(format_address, osv.osv):
|
||||
partner = self.pool.get('res.partner')
|
||||
customer = partner.browse(cr, uid, partner_id, context=context)
|
||||
for lead in self.browse(cr, uid, ids, context=context):
|
||||
if lead.state in ('done', 'cancel'):
|
||||
# TDE: was if lead.state in ('done', 'cancel'):
|
||||
if (lead.probability == '100') or (lead.probability == '0' and lead.stage_id.sequence != '1'):
|
||||
continue
|
||||
vals = self._convert_opportunity_data(cr, uid, lead, customer, section_id, context=context)
|
||||
self.write(cr, uid, [lead.id], vals, context=context)
|
||||
@@ -940,6 +908,10 @@ class crm_lead(format_address, osv.osv):
|
||||
return super(crm_lead, self).create(cr, uid, vals, context=create_context)
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
# stage change: update date_last_stage_update
|
||||
if 'stage_id' in vals:
|
||||
vals['date_last_stage_update'] = fields.datetime.now()
|
||||
# stage change with new stage: update probability
|
||||
if vals.get('stage_id') and not vals.get('probability'):
|
||||
onchange_stage_values = self.onchange_stage_id(cr, uid, ids, vals.get('stage_id'), context=context)['value']
|
||||
vals.update(onchange_stage_values)
|
||||
|
||||
@@ -5,71 +5,57 @@
|
||||
<!-- Crm stages -->
|
||||
<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="0" name="probability"/>
|
||||
<field eval="10" name="sequence"/>
|
||||
<field name="case_default">1</field>
|
||||
<field name="probability">0</field>
|
||||
<field name="on_change">1</field>
|
||||
<field name="sequence">1</field>
|
||||
<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="20" name="sequence"/>
|
||||
<field name="type">lead</field>
|
||||
</record>
|
||||
<record model="crm.case.stage" id="stage_lead7">
|
||||
<field name="name">Dead</field>
|
||||
<field eval="1" name="case_default"/>
|
||||
<field eval="False" name="fold"/>
|
||||
<field name="state">cancel</field>
|
||||
<field eval="0" name="probability"/>
|
||||
<field eval="30" name="sequence"/>
|
||||
<field name="case_default">1</field>
|
||||
<field name="probability">0</field>
|
||||
<field name="on_change">1</field>
|
||||
<field name="sequence">30</field>
|
||||
<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="100" name="sequence"/>
|
||||
<field name="case_default">1</field>
|
||||
<field name="probability">20</field>
|
||||
<field name="on_change">1</field>
|
||||
<field name="sequence">40</field>
|
||||
<field name="type">opportunity</field>
|
||||
</record>
|
||||
<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="110" name="sequence"/>
|
||||
<field name="case_default">1</field>
|
||||
<field name="probability">40</field>
|
||||
<field name="sequence">50</field>
|
||||
<field name="type">opportunity</field>
|
||||
</record>
|
||||
<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="120" name="sequence"/>
|
||||
<field name="case_default">1</field>
|
||||
<field name="probability">60</field>
|
||||
<field name="sequence">60</field>
|
||||
<field name="type">opportunity</field>
|
||||
</record>
|
||||
<record model="crm.case.stage" id="stage_lead6">
|
||||
<field name="name">Won</field>
|
||||
<field eval="True" name="fold"/>
|
||||
<field eval="1" name="case_default"/>
|
||||
<field name="state">done</field>
|
||||
<field eval="100" name="probability"/>
|
||||
<field eval="130" name="sequence"/>
|
||||
<field eval="1" name="on_change"/>
|
||||
<field name="case_default">1</field>
|
||||
<field name="probability">100</field>
|
||||
<field name="on_change">1</field>
|
||||
<field name="sequence">70</field>
|
||||
<field name="type">opportunity</field>
|
||||
</record>
|
||||
<record model="crm.case.stage" id="stage_lead8">
|
||||
<record model="crm.case.stage" id="stage_lead7">
|
||||
<field name="name">Lost</field>
|
||||
<field eval="1" name="case_default"/>
|
||||
<field eval="True" name="fold"/>
|
||||
<field eval="1" name="on_change"/>
|
||||
<field name="state">cancel</field>
|
||||
<field eval="0" name="probability"/>
|
||||
<field eval="140" name="sequence"/>
|
||||
<field name="case_default">1</field>
|
||||
<field name="fold">1</field>
|
||||
<field name="probability">0</field>
|
||||
<field name="on_change">1</field>
|
||||
<field name="sequence">80</field>
|
||||
<field name="type">opportunity</field>
|
||||
</record>
|
||||
|
||||
@@ -77,7 +63,7 @@
|
||||
<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'))]"/>
|
||||
(4, ref('stage_lead7'))]"/>
|
||||
</record>
|
||||
|
||||
<!-- Crm campain -->
|
||||
@@ -161,7 +147,7 @@
|
||||
<field name="name">Lead Created</field>
|
||||
<field name="res_model">crm.lead</field>
|
||||
<field name="default" eval="False"/>
|
||||
<field name="description">Opportunity created</field>
|
||||
<field name="description">Lead created</field>
|
||||
</record>
|
||||
<record id="mt_lead_stage" model="mail.message.subtype">
|
||||
<field name="name">Stage Changed</field>
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
<record id="crm_case_1" model="crm.lead">
|
||||
<field name="type">lead</field>
|
||||
<field name="name">Plan to Attend a Training</field>
|
||||
<field name="contact_name">Jason Dunagan</field>
|
||||
<field name="contact_name">Jacques Dunagan</field>
|
||||
<field name="partner_name">Le Club SARL</field>
|
||||
<field name="email_from">jason@leclub.fr</field>
|
||||
<field name="email_from">jdunagan@leclub.fr</field>
|
||||
<field name="partner_id" ref=""/>
|
||||
<field name="function">Training Manager</field>
|
||||
<field name="street">73, rue Léon Dierx</field>
|
||||
@@ -20,14 +20,25 @@
|
||||
<field name="categ_ids" eval="[(6, 0, [categ_oppor6])]"/>
|
||||
<field name="channel_id" ref="crm_case_channel_email"/>
|
||||
<field name="priority">1</field>
|
||||
<field name="section_id" ref="crm_case_section_2"/>
|
||||
<field name="section_id" ref="section_sales_department"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="stage_id" ref="stage_lead1"/>
|
||||
<field name="description">Hello,
|
||||
I am Jason from Le Club SARL.
|
||||
I am intertested to attend a training organized in your company.
|
||||
Can you send me the details ?</field>
|
||||
<field eval="1" name="active"/>
|
||||
I am Jacques from Le Club SARL.
|
||||
I am interested to attend a training organized in your company.
|
||||
Could you please send me the details ?</field>
|
||||
</record>
|
||||
<record id="crm_case_1" model="crm.lead">
|
||||
<field name="create_date" eval="(DateTime.today() - relativedelta(months=2)).strftime('%Y-%m-%d %H:%M')"/>
|
||||
</record>
|
||||
<record id="msg_case1_1" model="mail.message">
|
||||
<field name="subject">Inquiry</field>
|
||||
<field name="model">crm.lead</field>
|
||||
<field name="res_id" ref="crm_case_1"/>
|
||||
<field name="body"><![CDATA[<p>Hello,<br />
|
||||
I am Jacques from Le Club SARL. I am interested to attend a training organized in your company.<br />
|
||||
Can you send me the details ?</p>]]></field>
|
||||
<field name="type">email</field>
|
||||
</record>
|
||||
|
||||
<record id="crm_case_2" model="crm.lead">
|
||||
@@ -44,10 +55,20 @@ Can you send me the details ?</field>
|
||||
<field name="categ_ids" eval="[(6, 0, [categ_oppor2])]"/>
|
||||
<field name="channel_id" ref="crm_case_channel_website"/>
|
||||
<field name="priority">4</field>
|
||||
<field name="section_id" ref="crm_case_section_2"/>
|
||||
<field name="section_id" ref="section_sales_department"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="stage_id" ref="stage_lead1"/>
|
||||
<field eval="1" name="active"/>
|
||||
</record>
|
||||
<record id="crm_case_2" model="crm.lead">
|
||||
<field name="create_date" eval="(DateTime.today() - relativedelta(months=1)).strftime('%Y-%m-%d %H:%M')"/>
|
||||
</record>
|
||||
<record id="msg_case2_1" model="mail.message">
|
||||
<field name="subject">Need Details</field>
|
||||
<field name="model">crm.lead</field>
|
||||
<field name="author_id" ref="base.partner_demo"/>
|
||||
<field name="res_id" ref="crm_case_2"/>
|
||||
<field name="body">Want to know features and benefits to use the new software.</field>
|
||||
<field name="type">comment</field>
|
||||
</record>
|
||||
|
||||
<record id="crm_case_3" model="crm.lead">
|
||||
@@ -65,8 +86,10 @@ Can you send me the details ?</field>
|
||||
<field name="priority">2</field>
|
||||
<field name="section_id" ref="crm_case_section_1"/>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field name="stage_id" ref="stage_lead2"/>
|
||||
<field eval="1" name="active"/>
|
||||
<field name="stage_id" ref="stage_lead1"/>
|
||||
</record>
|
||||
<record id="crm_case_2" model="crm.lead">
|
||||
<field name="create_date" eval="(DateTime.today() - relativedelta(months=1)).strftime('%Y-%m-%d %H:%M')"/>
|
||||
</record>
|
||||
|
||||
<record id="crm_case_4" model="crm.lead">
|
||||
@@ -82,10 +105,12 @@ Can you send me the details ?</field>
|
||||
<field name="categ_ids" eval="[(6, 0, [categ_oppor5])]"/>
|
||||
<field name="channel_id" ref=""/>
|
||||
<field name="priority">3</field>
|
||||
<field name="section_id" ref="crm_case_section_1"/>
|
||||
<field name="user_id" ref=""/>
|
||||
<field name="stage_id" ref="stage_lead7"/>
|
||||
<field eval="1" name="active"/>
|
||||
<field name="section_id" ref="crm_case_section_2"/>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field name="stage_id" ref="stage_lead6"/>
|
||||
</record>
|
||||
<record id="crm_case_2" model="crm.lead">
|
||||
<field name="create_date" eval="(DateTime.today() - relativedelta(months=1)).strftime('%Y-%m-%d %H:%M')"/>
|
||||
</record>
|
||||
|
||||
<record id="crm_case_5" model="crm.lead">
|
||||
@@ -105,8 +130,8 @@ Can you send me the details ?</field>
|
||||
<field name="categ_ids" eval="[(6, 0, [categ_oppor1])]"/>
|
||||
<field name="channel_id" ref="crm_case_channel_website"/>
|
||||
<field name="priority">3</field>
|
||||
<field name="section_id" ref="section_sales_department"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="section_id" ref="crm_case_section_1"/>
|
||||
<field name="user_id" ref=""/>
|
||||
<field name="stage_id" ref="stage_lead1"/>
|
||||
<field name="description">Hi,
|
||||
Can you send me a quotation for 20 computers with speakers?
|
||||
@@ -116,7 +141,6 @@ Purchase Manager
|
||||
Stonage IT,
|
||||
Franklinville
|
||||
Contact: +1 813 494 5005</field>
|
||||
<field eval="1" name="active"/>
|
||||
</record>
|
||||
|
||||
<record id="crm_case_6" model="crm.lead">
|
||||
@@ -134,9 +158,8 @@ Contact: +1 813 494 5005</field>
|
||||
<field name="channel_id" ref=""/>
|
||||
<field name="priority">3</field>
|
||||
<field name="section_id" ref="crm_case_section_2"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="user_id" ref=""/>
|
||||
<field name="stage_id" ref="stage_lead1"/>
|
||||
<field eval="1" name="active"/>
|
||||
</record>
|
||||
|
||||
<record id="crm_case_7" model="crm.lead">
|
||||
@@ -153,9 +176,8 @@ Contact: +1 813 494 5005</field>
|
||||
<field name="channel_id" ref=""/>
|
||||
<field name="priority">5</field>
|
||||
<field name="section_id" ref="crm_case_section_2"/>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field name="stage_id" ref="stage_lead7"/>
|
||||
<field eval="1" name="active"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="stage_id" ref="stage_lead1"/>
|
||||
</record>
|
||||
|
||||
<record id="crm_case_8" model="crm.lead">
|
||||
@@ -173,9 +195,8 @@ Contact: +1 813 494 5005</field>
|
||||
<field name="channel_id" ref=""/>
|
||||
<field name="priority">4</field>
|
||||
<field name="section_id" ref="section_sales_department"/>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field name="stage_id" ref="stage_lead7"/>
|
||||
<field eval="1" name="active"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="stage_id" ref="stage_lead1"/>
|
||||
</record>
|
||||
|
||||
<record id="crm_case_9" model="crm.lead">
|
||||
@@ -193,12 +214,8 @@ Contact: +1 813 494 5005</field>
|
||||
<field name="channel_id" ref="crm_case_channel_phone"/>
|
||||
<field name="priority">2</field>
|
||||
<field name="section_id" ref="section_sales_department"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field name="stage_id" ref="stage_lead1"/>
|
||||
<field eval="1" name="active"/>
|
||||
</record>
|
||||
<record id="crm_case_9" model="crm.lead">
|
||||
<field name="create_date" eval="(DateTime.today() - relativedelta(months=1)).strftime('%Y-%m-%d %H:%M')"/>
|
||||
</record>
|
||||
|
||||
<record id="crm_case_10" model="crm.lead">
|
||||
@@ -215,14 +232,13 @@ Contact: +1 813 494 5005</field>
|
||||
<field name="channel_id" ref="crm_case_channel_email"/>
|
||||
<field name="priority">2</field>
|
||||
<field name="section_id" ref="crm_case_section_2"/>
|
||||
<field name="user_id" ref=""/>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field name="stage_id" ref="stage_lead1"/>
|
||||
<field name="description">Hi,
|
||||
I would like to know more about specification and cost of laptops of your company.
|
||||
|
||||
Thanks,
|
||||
Andrew</field>
|
||||
<field eval="1" name="active"/>
|
||||
</record>
|
||||
|
||||
<record id="crm_case_11" model="crm.lead">
|
||||
@@ -239,9 +255,8 @@ Andrew</field>
|
||||
<field name="channel_id" ref="crm_case_channel_direct"/>
|
||||
<field name="priority">3</field>
|
||||
<field name="section_id" ref="crm_case_section_1"/>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field name="user_id" ref=""/>
|
||||
<field name="stage_id" ref="stage_lead1"/>
|
||||
<field eval="1" name="active"/>
|
||||
</record>
|
||||
|
||||
<record id="crm_case_12" model="crm.lead">
|
||||
@@ -258,15 +273,23 @@ Andrew</field>
|
||||
<field name="channel_id" ref="crm_case_channel_website"/>
|
||||
<field name="priority">2</field>
|
||||
<field name="section_id" ref="section_sales_department"/>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field name="stage_id" ref="stage_lead2"/>
|
||||
<field eval="1" name="active"/>
|
||||
<field name="user_id" ref=""/>
|
||||
<field name="stage_id" ref="stage_lead1"/>
|
||||
</record>
|
||||
|
||||
<!-- Call Function to Cancel the leads (set as Dead) -->
|
||||
<function model="crm.lead" name="write"
|
||||
eval="[ ref('crm_case_7'), ref('crm_case_8'),
|
||||
ref('crm_case_9'), ref('crm_case_10'),
|
||||
ref('crm_case_11'), ref('crm_case_12')],
|
||||
{'stage_id': ref('stage_lead2')},
|
||||
{'install_mode': True}"
|
||||
/>
|
||||
|
||||
<!-- Call Function to set the leads as Unread -->
|
||||
<function model="crm.lead" name="message_mark_as_unread"
|
||||
eval="[ ref('crm_case_1'), ref('crm_case_2'),
|
||||
ref('crm_case_5'), ref('crm_case_11')], {}"
|
||||
ref('crm_case_5'), ref('crm_case_6')], {}"
|
||||
/>
|
||||
|
||||
<!-- Demo Opportunities -->
|
||||
@@ -289,8 +312,7 @@ Andrew</field>
|
||||
<field name="title_action">Meeting for pricing information.</field>
|
||||
<field name="section_id" ref="section_sales_department"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="stage_id" ref="crm.stage_lead3"/>
|
||||
<field eval="1" name="active"/>
|
||||
<field name="stage_id" ref="crm.stage_lead1"/>
|
||||
</record>
|
||||
|
||||
<record id="crm_case_14" model="crm.lead">
|
||||
@@ -316,7 +338,6 @@ Andrew</field>
|
||||
<field name="section_id" ref="section_sales_department"/>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field name="stage_id" ref="crm.stage_lead3"/>
|
||||
<field eval="1" name="active"/>
|
||||
</record>
|
||||
|
||||
<record id="crm_case_15" model="crm.lead">
|
||||
@@ -337,10 +358,12 @@ Andrew</field>
|
||||
<field name="title_action">Call to ask system requirement</field>
|
||||
<field name="section_id" ref="section_sales_department"/>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field name="stage_id" ref="crm.stage_lead6"/>
|
||||
<field eval="1" name="active"/>
|
||||
<field name="stage_id" ref="crm.stage_lead3"/>
|
||||
<field name="date_closed" eval="(DateTime.today() - relativedelta(months=2)).strftime('%Y-%m-%d %H:%M')"/>
|
||||
</record>
|
||||
<record id="crm_case_15" model="crm.lead">
|
||||
<field name="message_follower_ids" eval="[(3, ref('base.partner_root')), (4, ref('base.partner_demo'))]"/>
|
||||
</record>
|
||||
|
||||
<record id="crm_case_16" model="crm.lead">
|
||||
<field name="type">opportunity</field>
|
||||
@@ -362,8 +385,7 @@ Andrew</field>
|
||||
<field name="title_action">Convert to quote</field>
|
||||
<field name="section_id" ref="crm_case_section_1"/>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field name="stage_id" ref="crm.stage_lead6"/>
|
||||
<field eval="1" name="active"/>
|
||||
<field name="stage_id" ref="crm.stage_lead3"/>
|
||||
<field name="date_closed" eval="(DateTime.today() - relativedelta(hours=1)).strftime('%Y-%m-%d %H:%M')"/>
|
||||
</record>
|
||||
|
||||
@@ -388,10 +410,12 @@ Andrew</field>
|
||||
<field name="title_action">Send price list regarding our interventions</field>
|
||||
<field name="section_id" ref="crm_case_section_1"/>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field name="stage_id" ref="crm.stage_lead6"/>
|
||||
<field eval="1" name="active"/>
|
||||
<field name="stage_id" ref="crm.stage_lead4"/>
|
||||
<field name="date_closed" eval="(DateTime.today() - relativedelta(hours=1)).strftime('%Y-%m-%d %H:%M')"/>
|
||||
</record>
|
||||
<record id="crm_case_17" model="crm.lead">
|
||||
<field name="message_follower_ids" eval="[(3, ref('base.partner_root')), (4, ref('base.partner_demo'))]"/>
|
||||
</record>
|
||||
|
||||
<record id="crm_case_18" model="crm.lead">
|
||||
<field name="type">opportunity</field>
|
||||
@@ -413,9 +437,12 @@ Andrew</field>
|
||||
<field name="title_action">Call to define real needs about training</field>
|
||||
<field name="section_id" ref="crm_case_section_1"/>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field name="stage_id" ref="crm.stage_lead3"/>
|
||||
<field name="stage_id" ref="crm.stage_lead4"/>
|
||||
<field eval="1" name="active"/>
|
||||
</record>
|
||||
<record id="crm_case_18" model="crm.lead">
|
||||
<field name="message_follower_ids" eval="[(4, ref('base.partner_demo'))]"/>
|
||||
</record>
|
||||
|
||||
<record id="crm_case_19" model="crm.lead">
|
||||
<field name="type">opportunity</field>
|
||||
@@ -437,8 +464,7 @@ Andrew</field>
|
||||
<field name="title_action">Ask for the good receprion of the proposition</field>
|
||||
<field name="section_id" ref="crm_case_section_1"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="stage_id" ref="crm.stage_lead6"/>
|
||||
<field eval="1" name="active"/>
|
||||
<field name="stage_id" ref="crm.stage_lead4"/>
|
||||
<field name="date_closed" eval="(DateTime.today() - relativedelta(months=3)).strftime('%Y-%m-%d %H:%M')"/>
|
||||
</record>
|
||||
|
||||
@@ -455,8 +481,7 @@ Andrew</field>
|
||||
<field name="priority">2</field>
|
||||
<field name="section_id" ref="crm_case_section_1"/>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field name="stage_id" ref="crm.stage_lead1"/>
|
||||
<field eval="1" name="active"/>
|
||||
<field name="stage_id" ref="crm.stage_lead4"/>
|
||||
</record>
|
||||
|
||||
<record id="crm_case_21" model="crm.lead">
|
||||
@@ -470,8 +495,7 @@ Andrew</field>
|
||||
<field name="priority">3</field>
|
||||
<field name="section_id" ref="crm_case_section_2"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="stage_id" ref="crm.stage_lead6"/>
|
||||
<field eval="1" name="active"/>
|
||||
<field name="stage_id" ref="crm.stage_lead4"/>
|
||||
<field name="date_closed" eval="(DateTime.today() - relativedelta(months=1)).strftime('%Y-%m-%d %H:%M')"/>
|
||||
</record>
|
||||
|
||||
@@ -489,8 +513,7 @@ Andrew</field>
|
||||
<field name="priority">3</field>
|
||||
<field name="section_id" ref="crm_case_section_2"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="stage_id" ref="crm.stage_lead8"/>
|
||||
<field eval="1" name="active"/>
|
||||
<field name="stage_id" ref="crm.stage_lead5"/>
|
||||
</record>
|
||||
|
||||
<record id="crm_case_23" model="crm.lead">
|
||||
@@ -505,8 +528,7 @@ Andrew</field>
|
||||
<field name="priority">5</field>
|
||||
<field name="section_id" ref="section_sales_department"/>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field name="stage_id" ref="crm.stage_lead6"/>
|
||||
<field eval="1" name="active"/>
|
||||
<field name="stage_id" ref="crm.stage_lead5"/>
|
||||
<field name="date_closed" eval="(DateTime.today() - relativedelta(hours=1)).strftime('%Y-%m-%d %H:%M')"/>
|
||||
</record>
|
||||
|
||||
@@ -525,8 +547,7 @@ Andrew</field>
|
||||
<field eval="time.strftime('%Y-%m-6')" name="date_deadline"/>
|
||||
<field name="section_id" ref="section_sales_department"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="stage_id" ref="crm.stage_lead6"/>
|
||||
<field eval="1" name="active"/>
|
||||
<field name="stage_id" ref="crm.stage_lead5"/>
|
||||
<field name="date_closed" eval="(DateTime.today() - relativedelta(month=1)).strftime('%Y-%m-%d %H:%M')"/>
|
||||
</record>
|
||||
|
||||
@@ -549,8 +570,7 @@ Andrew</field>
|
||||
<field name="title_action">Conf call with technical service</field>
|
||||
<field name="section_id" ref="crm_case_section_1"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="stage_id" ref="crm.stage_lead4"/>
|
||||
<field eval="1" name="active"/>
|
||||
<field name="stage_id" ref="crm.stage_lead5"/>
|
||||
</record>
|
||||
|
||||
<record id="crm_case_26" model="crm.lead">
|
||||
@@ -574,21 +594,10 @@ Andrew</field>
|
||||
<field name="title_action">Send Catalogue by Email</field>
|
||||
<field name="section_id" ref="crm_case_section_2"/>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field name="stage_id" ref="crm.stage_lead6"/>
|
||||
<field eval="1" name="active"/>
|
||||
<field name="date_closed" eval="(DateTime.today() - relativedelta(hours=1)).strftime('%Y-%m-%d %H:%M')"/>
|
||||
<field name="stage_id" ref="crm.stage_lead5"/>
|
||||
<!-- <field name="date_closed" eval="(DateTime.today() - relativedelta(hours=1)).strftime('%Y-%m-%d %H:%M')"/> -->
|
||||
</record>
|
||||
|
||||
<!-- Unsubscribe Admin from case15, subscribe Demo -->
|
||||
<record id="crm_case_15" model="crm.lead">
|
||||
<field name="message_follower_ids" eval="[(3, ref('base.partner_root')), (4, ref('base.partner_demo'))]"/>
|
||||
</record>
|
||||
<record id="crm_case_17" model="crm.lead">
|
||||
<field name="message_follower_ids" eval="[(3, ref('base.partner_root')), (4, ref('base.partner_demo'))]"/>
|
||||
</record>
|
||||
<record id="crm_case_18" model="crm.lead">
|
||||
<field name="message_follower_ids" eval="[(4, ref('base.partner_demo'))]"/>
|
||||
</record>
|
||||
<!-- Some messages linked to the previous opportunities -->
|
||||
<record id="msg_case15_attach1" model="ir.attachment">
|
||||
<field name="datas">bWlncmF0aW9uIHRlc3Q=</field>
|
||||
@@ -682,26 +691,9 @@ Andrew</field>
|
||||
<field name="parent_id" ref="msg_case18_1"/>
|
||||
<field name="author_id" ref="base.partner_demo"/>
|
||||
</record>
|
||||
<record id="msg_case1_1" model="mail.message">
|
||||
<field name="subject">Inquiry</field>
|
||||
<field name="model">crm.lead</field>
|
||||
<field name="res_id" ref="crm_case_1"/>
|
||||
<field name="body"><![CDATA[<p>Hello,<br />
|
||||
I am Jason from Le Club SARL. I am interested to attend a training organized in your company.<br />
|
||||
Can you send me the details ?</p>]]></field>
|
||||
<field name="type">email</field>
|
||||
</record>
|
||||
<record id="msg_case2_1" model="mail.message">
|
||||
<field name="subject">Need Details</field>
|
||||
<field name="model">crm.lead</field>
|
||||
<field name="res_id" ref="crm_case_2"/>
|
||||
<field name="body">Want to know features and benefits to use the new software.</field>
|
||||
<field name="type">comment</field>
|
||||
</record>
|
||||
<function model="mail.message" name="set_message_starred"
|
||||
eval="[ ref('msg_case18_1'), ref('msg_case18_2')], True, {}"
|
||||
/>
|
||||
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
@@ -12,9 +12,10 @@
|
||||
<field name="model">crm.case.stage</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Stage Search">
|
||||
<field name="name" string="Stage Name"/>
|
||||
<field name="state"/>
|
||||
<field name="name"/>
|
||||
<field name="type"/>
|
||||
<field name="sequence"/>
|
||||
<field name="probability"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
@@ -94,7 +95,8 @@
|
||||
<form string="Leads Form" version="7.0">
|
||||
<header>
|
||||
<button name="%(crm.action_crm_lead2opportunity_partner)d" string="Convert to Opportunity" type="action"
|
||||
states="draft,open,pending" help="Convert to Opportunity" class="oe_highlight"/>
|
||||
attrs="{'invisible': [('probability', '=', 100)]}"
|
||||
help="Convert to Opportunity" class="oe_highlight"/>
|
||||
<field name="stage_id" widget="statusbar" clickable="True"
|
||||
domain="['&', '|', ('case_default', '=', True), ('section_ids', '=', section_id), '|', ('type', '=', type), ('type', '=', 'both')]"
|
||||
on_change="onchange_stage_id(stage_id)"/>
|
||||
@@ -140,12 +142,6 @@
|
||||
<field name="phone"/>
|
||||
<field name="mobile"/>
|
||||
<field name="fax"/>
|
||||
<!--
|
||||
This should be integrated in Open Chatter
|
||||
<button string="Mail"
|
||||
name="%(mail.action_email_compose_message_wizard)d"
|
||||
icon="terp-mail-message-new" type="action" colspan="1"/>
|
||||
-->
|
||||
</group>
|
||||
<group>
|
||||
<field name="user_id" on_change="on_change_user(user_id, context)"
|
||||
@@ -155,7 +151,7 @@
|
||||
<field name="section_id"/>
|
||||
<button name="case_escalate" string="Escalate"
|
||||
type="object" class="oe_link"
|
||||
attrs="{'invisible': ['|', ('section_id','=',False), ('state', 'not in', ['draft','open','pending'])]}"/>
|
||||
attrs="{'invisible': ['|', ('section_id','=',False), ('probability', '=', 100)]}"/>
|
||||
</div>
|
||||
<field name="type" invisible="1"/>
|
||||
</group>
|
||||
@@ -173,20 +169,24 @@
|
||||
<field name="description"/>
|
||||
</page>
|
||||
<page string="Extra Info">
|
||||
<group>
|
||||
<group string="Categorization" groups="base.group_multi_company,base.group_no_one" name="categorization">
|
||||
<field name="company_id"
|
||||
groups="base.group_multi_company"
|
||||
widget="selection" colspan="2"/>
|
||||
<field name="state" groups="base.group_no_one"/>
|
||||
</group>
|
||||
<group string="Mailings">
|
||||
<field name="opt_out"/>
|
||||
</group>
|
||||
<group string="Misc">
|
||||
<group string="Categorization" groups="base.group_multi_company,base.group_no_one" name="categorization">
|
||||
<field name="company_id"
|
||||
groups="base.group_multi_company"
|
||||
widget="selection" colspan="2"/>
|
||||
</group>
|
||||
<group string="Mailings">
|
||||
<field name="opt_out"/>
|
||||
</group>
|
||||
<group string="Misc">
|
||||
<group>
|
||||
<field name="probability" groups="base.group_no_one"/>
|
||||
<field name="active"/>
|
||||
<field name="referred"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="date_open" groups="base.group_no_one"/>
|
||||
<field name="date_closed" groups="base.group_no_one"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
@@ -216,7 +216,7 @@
|
||||
<field name="name">Leads</field>
|
||||
<field name="model">crm.lead</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Leads" fonts="bold:message_unread==True" colors="grey:state in ('cancel', 'done')">
|
||||
<tree string="Leads" fonts="bold:message_unread==True" colors="grey:probability == 100">
|
||||
<field name="date_deadline" invisible="1"/>
|
||||
<field name="create_date"/>
|
||||
<field name="name"/>
|
||||
@@ -228,7 +228,7 @@
|
||||
<field name="user_id" invisible="1"/>
|
||||
<field name="partner_id" invisible="1"/>
|
||||
<field name="section_id" invisible="context.get('invisible_section', True)" groups="base.group_multi_salesteams"/>
|
||||
<field name="state" invisible="1"/>
|
||||
<field name="probability" invisible="1"/>
|
||||
<field name="type_id" invisible="1"/>
|
||||
<field name="referred" invisible="1"/>
|
||||
<field name="channel_id" invisible="1"/>
|
||||
@@ -258,7 +258,6 @@
|
||||
<field name="model">crm.lead</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban default_group_by="stage_id">
|
||||
<field name="state" groups="base.group_no_one"/>
|
||||
<field name="stage_id"/>
|
||||
<field name="color"/>
|
||||
<field name="priority"/>
|
||||
@@ -331,16 +330,17 @@
|
||||
<field name="create_date"/>
|
||||
<field name="country_id" context="{'invisible_country': False}"/>
|
||||
<separator/>
|
||||
<filter string="Open" name="open" domain="[('state','!=','cancel')]" help="Open Leads"/>
|
||||
<filter string="Dead" name="dead" domain="[('state','=','cancel')]"/>
|
||||
<filter string="Unassigned" name="unassigned" domain="[('user_id','=', False)]" help="No salesperson"/>
|
||||
<filter string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]" help="Unread messages"/>
|
||||
<filter string="Unassigned" name="unassigned"
|
||||
domain="[('user_id','=', False)]"
|
||||
help="No salesperson"/>
|
||||
<filter string="Assigned to Me"
|
||||
domain="[('user_id','=',uid)]" context="{'invisible_section': False}"
|
||||
help="Leads that are assigned to me"/>
|
||||
<filter string="Assigned to My Team(s)"
|
||||
<filter string="Assigned to My Team(s)" groups="base.group_multi_salesteams"
|
||||
domain="[('section_id.member_ids', 'in', [uid])]" context="{'invisible_section': False}"
|
||||
help="Leads that are assigned to any sales teams I am member of" groups="base.group_multi_salesteams"/>
|
||||
help="Leads that are assigned to any sales teams I am member of"/>
|
||||
<filter string="Dead" name="dead"
|
||||
domain="[('probability', '=', '0'), ('stage_id.sequence', '!=', 1)]"/>
|
||||
<separator />
|
||||
<filter string="Available for mass mailing"
|
||||
name='not_opt_out' domain="[('opt_out', '=', False)]"
|
||||
@@ -378,20 +378,19 @@
|
||||
<field name="arch" type="xml">
|
||||
<form string="Opportunities" version="7.0">
|
||||
<header>
|
||||
<button name="case_mark_won" string="Mark Won" type="object"
|
||||
states="draft,open,pending" class="oe_highlight"/>
|
||||
<button name="case_mark_lost" string="Mark Lost" type="object"
|
||||
states="draft,open" class="oe_highlight"/>
|
||||
<field name="stage_id" widget="statusbar" clickable="True"/>
|
||||
<button name="case_mark_won" string="Mark Won" type="object" class="oe_highlight"
|
||||
attrs="{'invisible': [('probability', '=', 100)]}"/>
|
||||
<button name="case_mark_lost" string="Mark Lost" type="object" class="oe_highlight"
|
||||
attrs="{'invisible': ['|', ('probability', '=', 0), ('probability', '=', 100)]}"/>
|
||||
<field name="stage_id" widget="statusbar" clickable="True"
|
||||
domain="['&', ('section_ids', '=', section_id), '|', ('type', '=', type), ('type', '=', 'both')]"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_right oe_button_box">
|
||||
<button string="Schedule/Log Call"
|
||||
name="%(opportunity2phonecall_act)d"
|
||||
type="action"/>
|
||||
<button string="Meeting"
|
||||
<button string="Schedule/Log Call" type="action"
|
||||
name="%(opportunity2phonecall_act)d"/>
|
||||
<button string="Meeting" type="object"
|
||||
name="action_makeMeeting"
|
||||
type="object"
|
||||
context="{'search_default_attendee_id': active_id, 'default_attendee_id' : active_id}"/>
|
||||
</div>
|
||||
<div class="oe_title">
|
||||
@@ -430,7 +429,8 @@
|
||||
<label for="section_id" groups="base.group_multi_salesteams"/>
|
||||
<div groups="base.group_multi_salesteams">
|
||||
<field name="section_id" widget="selection"/>
|
||||
<button name="case_escalate" string="Escalate" type="object" class="oe_link" attrs="{'invisible': ['|', ('section_id','=',False), ('state', 'not in', ['draft','open','pending'])]}"/>
|
||||
<button name="case_escalate" string="Escalate" type="object" class="oe_link"
|
||||
attrs="{'invisible': ['|', ('section_id','=',False), ('probability', '=', 100)]}"/>
|
||||
</div>
|
||||
</group>
|
||||
<group>
|
||||
@@ -481,7 +481,6 @@
|
||||
<field name="day_open" groups="base.group_no_one"/>
|
||||
<field name="day_close" groups="base.group_no_one"/>
|
||||
<field name="referred"/>
|
||||
<field name="state" invisible="1"/>
|
||||
<field name="type" invisible="1"/>
|
||||
</group>
|
||||
<group string="References">
|
||||
@@ -511,7 +510,7 @@
|
||||
<field name="name">Opportunities Tree</field>
|
||||
<field name="model">crm.lead</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Opportunities" fonts="bold:message_unread==True" colors="gray:state in ('cancel', 'done');red:date_deadline and (date_deadline < current_date)">
|
||||
<tree string="Opportunities" fonts="bold:message_unread==True" colors="gray:probability == 100;red:date_deadline and (date_deadline < current_date)">
|
||||
<field name="date_deadline" invisible="1"/>
|
||||
<field name="create_date"/>
|
||||
<field name="name" string="Opportunity"/>
|
||||
@@ -529,7 +528,8 @@
|
||||
<field name="referred" invisible="1"/>
|
||||
<field name="priority" invisible="1"/>
|
||||
<field name="message_unread" invisible="1"/>
|
||||
<field name="state" invisible="1"/>
|
||||
<field name="probability" invisible="1"/>
|
||||
<field name="write_date" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
@@ -546,15 +546,19 @@
|
||||
<field name="section_id" context="{'invisible_section': False}" groups="base.group_multi_salesteams"/>
|
||||
<field name="user_id"/>
|
||||
<field name="partner_id" filter_domain="[('partner_id','child_of',self)]"/>
|
||||
<field name="stage_id" domain="[]"/>
|
||||
<field name="probability"/>
|
||||
<separator/>
|
||||
<filter string="New" name="new" domain="[('state','=','draft')]" help="New Opportunities"/>
|
||||
<filter string="In Progress" name="open" domain="[('state','=','open')]" help="Open Opportunities"/>
|
||||
<filter string="Won" name="won" domain="[('state','=','done')]"/>
|
||||
<filter string="Lost" name="lost" domain="[('state','=','cancel')]"/>
|
||||
<filter string="Unassigned" name="unassigned" domain="[('user_id','=', False)]" help="No salesperson"/>
|
||||
<filter string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]" help="Unread messages"/>
|
||||
<filter string="New" name="new"
|
||||
domain="[('probability', '=', 0), ('stage_id.sequence', '=', 1)]"/>
|
||||
<filter string="Won" name="won"
|
||||
domain="[('probability', '=', 100), ('stage_id.on_change', '=', 1)]"/>
|
||||
<filter string="Lost" name="lost"
|
||||
domain="[('probability', '=', 0), ('stage_id.sequence', '!=', 1)]"/>
|
||||
<filter string="Unassigned" name="unassigned"
|
||||
domain="[('user_id','=', False)]" help="No salesperson"/>
|
||||
<filter string="My Opportunities" name="assigned_to_me"
|
||||
domain="[('user_id','=',uid)]" context="{'invisible_section': False}"
|
||||
domain="[('user_id', '=', uid)]" context="{'invisible_section': False}"
|
||||
help="Opportunities that are assigned to me"/>
|
||||
<filter string="Assigned to My Team(s)"
|
||||
domain="[('section_id.member_ids', 'in', [uid])]" context="{'invisible_section': False}"
|
||||
@@ -572,6 +576,7 @@
|
||||
<filter string="Campaign" domain="[]" context="{'group_by':'type_id'}"/>
|
||||
<filter string="Channel" domain="[]" context="{'group_by':'channel_id'}"/>
|
||||
<filter string="Creation" domain="[]" context="{'group_by':'create_date'}"/>
|
||||
<filter string="Last Update Month" domain="[]" context="{'group_by':'write_date'}"/>
|
||||
</group>
|
||||
<group string="Display">
|
||||
<filter string="Show Sales Team" context="{'invisible_section': False}" domain="[]" help="Show Sales Team" groups="base.group_multi_salesteams"/>
|
||||
|
||||
@@ -80,7 +80,6 @@
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="name"/>
|
||||
<field name="probability"/>
|
||||
<field name="state"/>
|
||||
<field name="type"/>
|
||||
</tree>
|
||||
</field>
|
||||
@@ -96,7 +95,6 @@
|
||||
<form string="Stage" version="7.0">
|
||||
<group col="4">
|
||||
<field name="name"/>
|
||||
<field name="state"/>
|
||||
<field name="probability"/>
|
||||
<field name="type"/>
|
||||
<field name="on_change"/>
|
||||
|
||||
@@ -6,6 +6,12 @@ Changelog
|
||||
`trunk (saas-2)`
|
||||
----------------
|
||||
|
||||
- Stage/state update
|
||||
|
||||
- ``crm.lead``: removed ``state`` field. Added ``date_last_stage_update`` field
|
||||
holding last stage_id modification. Updated reports.
|
||||
- ``crm.case.stage``: removed ``state`` field.
|
||||
|
||||
- ``crm``, ``crm_claim``: removed inheritance from ``base_stage`` class. Missing
|
||||
methods have been added into ``crm`` and ``crm_claim``. Also removed inheritance
|
||||
in ``crm_helpdesk`` because it uses states, not stages.
|
||||
@@ -4,6 +4,11 @@ CRM module documentation
|
||||
CRM documentation topics
|
||||
'''''''''''''''''''''''''
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
stage_status.rst
|
||||
|
||||
Changelog
|
||||
'''''''''
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
.. _stage_status:
|
||||
|
||||
Stage and Status
|
||||
================
|
||||
|
||||
.. versionchanged:: 8.0 saas-2 state/stage cleaning
|
||||
|
||||
Stage
|
||||
+++++
|
||||
|
||||
This revision removed the concept of state on crm.lead objects. The ``state``
|
||||
field has been totally removed and replaced by stages, using ``stage_id``. The
|
||||
following models are impacted:
|
||||
|
||||
- ``crm.lead`` now use only stages. However conventions still exist about
|
||||
'New', 'Won' and 'Lost' stages. Those conventions are:
|
||||
|
||||
- ``new``: ``stage_id and stage_id.sequence = 1``
|
||||
- ``won``: ``stage_id and stage_id.probability = 100 and stage_id.on_change = True``
|
||||
- ``lost``: ``stage_id and stage_id.probability = 0 and stage_id.on_change = True
|
||||
and stage_id.sequence != 1``
|
||||
|
||||
- ``crm.case.stage`` do not have any ``state`` field anymore.
|
||||
- ``crm.lead.report`` do not have any ``state`` field anymore.
|
||||
|
||||
By default a newly created lead is in a new stage. It means that it will
|
||||
fetch the stage having ``sequence = 1``. Stage mangement is done using the
|
||||
kanban view or the clikable statusbar. It is not done using buttons anymore.
|
||||
|
||||
Stage analysis
|
||||
++++++++++++++
|
||||
|
||||
Stage analysis can be performed using the newly introduced ``date_last_stage_update``
|
||||
datetime field. This field is updated everytime ``stage_id`` is updated.
|
||||
|
||||
``crm.lead.report`` model also uses the ``date_last_stage_update`` field.
|
||||
This allows to group and analyse the time spend in the various stages.
|
||||
|
||||
Open / Assignation date
|
||||
+++++++++++++++++++++++
|
||||
|
||||
The ``date_open`` field meaning has been updated. It is now set when the ``user_id``
|
||||
(responsible) is set. It is therefore the assignation date.
|
||||
|
||||
Subtypes
|
||||
++++++++
|
||||
|
||||
The following subtypes are triggered on ``crm.lead``:
|
||||
|
||||
- ``mt_lead_create``: new leads. Condition: ``obj.probability == 0 and obj.stage_id
|
||||
and obj.stage_id.sequence == 1``
|
||||
- ``mt_lead_stage``: stage changed. Condition: ``(obj.stage_id and obj.stage_id.sequence != 1)
|
||||
and obj.probability < 100``
|
||||
- ``mt_lead_won``: lead/oportunity is won. condition: `` obj.probability == 100
|
||||
and obj.stage_id and obj.stage_id.on_change``
|
||||
- ``mt_lead_lost``: lead/opportunity is lost. Condition: ``obj.probability == 0
|
||||
and obj.stage_id and obj.stage_id.sequence != 1'``
|
||||
|
||||
|
||||
Those subtypes are also available on the ``crm.case.section`` model and are used
|
||||
for the auto subscription.
|
||||
+200
-129
File diff suppressed because it is too large
Load Diff
@@ -19,17 +19,9 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.osv import fields,osv
|
||||
from openerp.osv import fields, osv
|
||||
from openerp import tools
|
||||
from .. import crm
|
||||
|
||||
AVAILABLE_STATES = [
|
||||
('draft','Draft'),
|
||||
('open','Open'),
|
||||
('cancel', 'Cancelled'),
|
||||
('done', 'Closed'),
|
||||
('pending','Pending')
|
||||
]
|
||||
from openerp.addons.crm import crm
|
||||
|
||||
MONTHS = [
|
||||
('01', 'January'),
|
||||
@@ -66,11 +58,12 @@ class crm_lead_report(osv.osv):
|
||||
|
||||
# other date fields
|
||||
'create_date': fields.datetime('Create Date', readonly=True),
|
||||
'opening_date': fields.date('Opening Date', readonly=True),
|
||||
'opening_date': fields.date('Assignation Date', readonly=True),
|
||||
'date_closed': fields.date('Close Date', readonly=True),
|
||||
'date_last_stage_update': fields.datetime('Last Stage Update', readonly=True),
|
||||
|
||||
# durations
|
||||
'delay_open': fields.float('Delay to Open',digits=(16,2),readonly=True, group_operator="avg",help="Number of Days to open the case"),
|
||||
'delay_open': fields.float('Delay to Assign',digits=(16,2),readonly=True, group_operator="avg",help="Number of Days to open the case"),
|
||||
'delay_close': fields.float('Delay to Close',digits=(16,2),readonly=True, group_operator="avg",help="Number of Days to close the case"),
|
||||
'delay_expected': fields.float('Overpassed Deadline',digits=(16,2),readonly=True, group_operator="avg"),
|
||||
|
||||
@@ -79,7 +72,6 @@ class crm_lead_report(osv.osv):
|
||||
'section_id':fields.many2one('crm.case.section', 'Sales Team', readonly=True),
|
||||
'channel_id':fields.many2one('crm.case.channel', 'Channel', readonly=True),
|
||||
'type_id':fields.many2one('crm.case.resource.type', 'Campaign', readonly=True),
|
||||
'state': fields.selection(AVAILABLE_STATES, 'Status', size=16, readonly=True),
|
||||
'company_id': fields.many2one('res.company', 'Company', readonly=True),
|
||||
'probability': fields.float('Probability',digits=(16,2),readonly=True, group_operator="avg"),
|
||||
'planned_revenue': fields.float('Planned Revenue',digits=(16,2),readonly=True),
|
||||
@@ -94,10 +86,7 @@ class crm_lead_report(osv.osv):
|
||||
('opportunity','Opportunity'),
|
||||
],'Type', help="Type is used to separate Leads and Opportunities"),
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def init(self, cr):
|
||||
|
||||
"""
|
||||
@@ -109,7 +98,6 @@ class crm_lead_report(osv.osv):
|
||||
CREATE OR REPLACE VIEW crm_lead_report AS (
|
||||
SELECT
|
||||
id,
|
||||
|
||||
to_char(c.date_deadline, 'YYYY') as deadline_year,
|
||||
to_char(c.date_deadline, 'MM') as deadline_month,
|
||||
to_char(c.date_deadline, 'YYYY-MM-DD') as deadline_day,
|
||||
@@ -121,7 +109,8 @@ class crm_lead_report(osv.osv):
|
||||
to_char(c.date_open, 'YYYY-MM-DD') as opening_date,
|
||||
to_char(c.date_closed, 'YYYY-mm-dd') as date_closed,
|
||||
|
||||
c.state,
|
||||
date_trunc('day',c.date_last_stage_update) as date_last_stage_update,
|
||||
|
||||
c.user_id,
|
||||
c.probability,
|
||||
c.stage_id,
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<field name="creation_month" invisible="1"/>
|
||||
<field name="creation_day" invisible="1"/>
|
||||
<field name="deadline_month" invisible="1"/>
|
||||
<field name="state" invisible="1"/>
|
||||
<field name="date_last_stage_update" invisible="1"/>
|
||||
<field name="stage_id" invisible="1"/>
|
||||
<field name="type_id" invisible="1"/>
|
||||
<field name="channel_id" invisible="1"/>
|
||||
@@ -69,10 +69,12 @@
|
||||
<filter icon="terp-personal" name="lead" string="Lead" domain="[('type','=', 'lead')]" help="Show only lead"/>
|
||||
<filter icon="terp-personal+" string="Opportunity" name="opportunity" domain="[('type','=','opportunity')]" help="Show only opportunity"/>
|
||||
<separator/>
|
||||
<filter icon="terp-check" string="New" domain="[('state','=','draft')]" help="Leads/Opportunities which are in New state"/>
|
||||
<filter icon="terp-camera_test" string="Open" domain="[('state','=','open')]" help="Leads/Opportunities which are in open state"/>
|
||||
<filter icon="gtk-media-pause" string="Pending" domain="[('state','=','pending')]" help="Leads/Opportunities which are in pending state"/>
|
||||
<filter icon="terp-dialog-close" string="Closed" domain="[('state','=','done')]" help="Leads/Opportunities which are in done state"/>
|
||||
<filter string="New" name="new"
|
||||
domain="[('probability', '=', 0), ('stage_id.sequence', '=', 1)]"/>
|
||||
<filter string="Won" name="won"
|
||||
domain="[('probability', '=', 100), ('stage_id.on_change', '=', 1)]"/>
|
||||
<filter string="Lost" name="lost"
|
||||
domain="[('probability', '=', 0), ('stage_id.sequence', '!=', 1)]"/>
|
||||
<separator/>
|
||||
<filter string="My Sales Team(s)" icon="terp-personal+" context="{'invisible_section': False}" domain="[('section_id.user_id','=',uid)]"
|
||||
help="Leads/Opportunities that are assigned to one of the sale teams I manage" groups="base.group_multi_salesteams"/>
|
||||
@@ -120,6 +122,7 @@
|
||||
<separator orientation="vertical" />
|
||||
<filter string="Exp. Closing" icon="terp-go-month"
|
||||
domain="[]" context="{'group_by':'deadline_month'}"/>
|
||||
<filter string="Last Stage Update" context="{'group_by':'date_last_stage_update'}" />
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
@@ -131,7 +134,7 @@
|
||||
<field name="name">crm.lead.report.tree</field>
|
||||
<field name="model">crm.lead.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree colors="blue:state == 'draft';black:state in ('open','pending','done');gray:state == 'cancel' " create="false" string="Opportunities Analysis">
|
||||
<tree create="false" string="Opportunities Analysis">
|
||||
<field name="creation_year" invisible="1"/>
|
||||
<field name="creation_month" invisible="1"/>
|
||||
<field name="creation_day" invisible="1"/>
|
||||
@@ -141,7 +144,6 @@
|
||||
<field name="user_id" invisible="1"/>
|
||||
<field name="partner_id" invisible="1"/>
|
||||
<field name="country_id" invisible="1"/>
|
||||
<field name="state" invisible="1"/>
|
||||
<field name="stage_id" invisible="1"/>
|
||||
<field name="priority" invisible="1"/>
|
||||
<field name="type_id" invisible="1"/>
|
||||
@@ -150,8 +152,9 @@
|
||||
<field name="company_id" invisible="1" groups="base.group_multi_company"/>
|
||||
<field name="nbr" string="#Opportunities" sum="#Opportunities"/>
|
||||
<field name="planned_revenue" sum="Planned Revenues"/>
|
||||
<field name="delay_open" sum='Delay to open'/>
|
||||
<field name="delay_open" sum='Delay to Assign'/>
|
||||
<field name="delay_close" sum='Delay to close'/>
|
||||
<field name="date_last_stage_update"/>
|
||||
<field name="delay_expected"/>
|
||||
<field name="probability" widget="progressbar"/>
|
||||
<field name="probable_revenue"/>
|
||||
|
||||
@@ -19,16 +19,16 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.osv import fields,osv
|
||||
from openerp import tools
|
||||
from .. import crm
|
||||
from openerp.addons.crm import crm
|
||||
from openerp.osv import fields, osv
|
||||
|
||||
AVAILABLE_STATES = [
|
||||
('draft','Draft'),
|
||||
('open','Todo'),
|
||||
('draft', 'Draft'),
|
||||
('open', 'Todo'),
|
||||
('cancel', 'Cancelled'),
|
||||
('done', 'Held'),
|
||||
('pending','Pending')
|
||||
('pending', 'Pending')
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<field name="nbr" string="#Phone calls" sum="#Phone calls"/>
|
||||
<field name="duration" avg="Duration"/>
|
||||
<field name="delay_close" avg="Avg Closing Delay"/>
|
||||
<field name="delay_open" sum='Delay to open'/>
|
||||
<field name="delay_open" sum='Delay to Assign'/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -41,7 +41,7 @@ class res_partner(osv.osv):
|
||||
_columns = {
|
||||
'section_id': fields.many2one('crm.case.section', 'Sales Team'),
|
||||
'opportunity_ids': fields.one2many('crm.lead', 'partner_id',\
|
||||
'Leads and Opportunities', domain=[('state','in', ('draft','open','pending'))]),
|
||||
'Leads and Opportunities', domain=[('probability' 'not in', ['0', '100'])]),
|
||||
'meeting_ids': fields.many2many('crm.meeting', 'crm_meeting_partner_rel','partner_id', 'meeting_id',
|
||||
'Meetings'),
|
||||
'phonecall_ids': fields.one2many('crm.phonecall', 'partner_id',\
|
||||
@@ -87,7 +87,6 @@ class res_partner(osv.osv):
|
||||
'probability' : probability,
|
||||
'partner_id' : partner_id,
|
||||
'categ_ids' : categ_ids and categ_ids[0:1] or [],
|
||||
'state' :'draft',
|
||||
'type': 'opportunity'
|
||||
}, context=context)
|
||||
opportunity_ids[partner_id] = opportunity_id
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
-
|
||||
I set a new sale team (with Marketing at parent) and I cancel unqualified lead .
|
||||
I set a new sale team (with Marketing at parent) .
|
||||
-
|
||||
!python {model: crm.lead}: |
|
||||
section_id = self.pool.get('crm.case.section').create(cr, uid, {'name': "Phone Marketing", 'parent_id': ref("crm.crm_case_section_2")})
|
||||
self.write(cr, uid, [ref("crm_case_1")], {'section_id': section_id})
|
||||
-
|
||||
I check unqualified lead .
|
||||
-
|
||||
!assert {model: crm.lead, id: crm.crm_case_1, string: Lead is in new stage}:
|
||||
- stage_id.sequence == 1
|
||||
-
|
||||
I escalate the lead to parent team.
|
||||
-
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
-
|
||||
I create a new lead.
|
||||
-
|
||||
!record {model: crm.lead, id: test_crm_lead_new}:
|
||||
type: 'lead'
|
||||
name: 'Test lead new'
|
||||
partner_id: base.res_partner_1
|
||||
description: This is the description of the test new lead.
|
||||
section_id: crm.section_sales_department
|
||||
-
|
||||
I check default stage of lead.
|
||||
-
|
||||
!python {model: crm.lead}: |
|
||||
stage = self.pool.get('crm.case.stage').search_read(cr,uid,[('sequence','=',1)],['id'],context)[0]
|
||||
lead = self.browse(cr, uid, ref('test_crm_lead_new'))
|
||||
stage_id = self.stage_find(cr, uid , [lead], lead.section_id.id or False,[], context)
|
||||
assert stage_id == stage['id'], "Default stage of lead is incorrect!"
|
||||
-
|
||||
I change type from lead to opportunity.
|
||||
-
|
||||
!python {model: crm.lead}: |
|
||||
self.convert_opportunity(cr, uid ,[ref("test_crm_lead_new")], ref("base.res_partner_2"))
|
||||
-
|
||||
Now I check default stage after change type.
|
||||
-
|
||||
!python {model: crm.lead}: |
|
||||
stage = self.pool.get('crm.case.stage').search_read(cr,uid,[('sequence','=',1)],['id'],context)[0]
|
||||
opp = self.browse(cr, uid, ref('test_crm_lead_new'))
|
||||
stage_id = self.stage_find(cr, uid , [opp], opp.section_id.id or False,[], context)
|
||||
assert stage_id == stage['id'], "Default stage of opportunity is incorrect!"
|
||||
-
|
||||
Now I change the stage of opportunity to won.
|
||||
-
|
||||
!python {model: crm.lead}: |
|
||||
self.case_mark_won(cr, uid, [ref("test_crm_lead_new")])
|
||||
-
|
||||
I check statge of opp should won, after change stage.
|
||||
-
|
||||
!python {model: crm.lead}: |
|
||||
opp = self.browse(cr, uid, ref('test_crm_lead_new'))
|
||||
stage_id = self.stage_find(cr, uid , [opp], opp.section_id.id or False,[('probability','=',100.0)], context)
|
||||
assert stage_id == opp.stage_id.id, "Stage of opportunity is incorrect!"
|
||||
|
||||
@@ -2,29 +2,29 @@
|
||||
-
|
||||
During a mixed merge (involving leads and opps), data should be handled a certain way following their type (m2o, m2m, text, ...) Start by creating two leads and an opp.
|
||||
-
|
||||
!record {model: crm.lead, id: test_crm_lead_01}:
|
||||
type: 'lead'
|
||||
name: 'Test lead 1'
|
||||
partner_id: base.res_partner_1
|
||||
stage_id: stage_lead1
|
||||
description: This is the description of the test lead 1.
|
||||
-
|
||||
!record {model: crm.lead, id: test_crm_lead_02}:
|
||||
type: 'lead'
|
||||
name: 'Test lead 2'
|
||||
partner_id: base.res_partner_3
|
||||
stage_id: stage_lead1
|
||||
description: This is the description of the test lead 2.
|
||||
-
|
||||
!record {model: crm.lead, id: test_crm_opp_01}:
|
||||
!record {model: crm.lead, id: test_crm_opp_1}:
|
||||
type: 'opportunity'
|
||||
name: 'Test opportunity 1'
|
||||
partner_id: base.res_partner_5
|
||||
stage_id: stage_lead1
|
||||
description: This is the description of the test opp 1.
|
||||
-
|
||||
!record {model: crm.lead, id: test_crm_lead_first}:
|
||||
type: 'lead'
|
||||
name: 'Test lead first'
|
||||
partner_id: base.res_partner_1
|
||||
stage_id: stage_lead1
|
||||
description: This is the description of the test lead first.
|
||||
-
|
||||
!record {model: crm.lead, id: test_crm_lead_second}:
|
||||
type: 'lead'
|
||||
name: 'Test lead second'
|
||||
partner_id: base.res_partner_1
|
||||
stage_id: stage_lead1
|
||||
description: This is the description of the test lead second.
|
||||
-
|
||||
!python {model: crm.lead}: |
|
||||
lead_ids = [ref('test_crm_lead_01'), ref('test_crm_lead_02'), ref('test_crm_opp_01')]
|
||||
lead_ids = [ref('test_crm_opp_1'), ref('test_crm_lead_first'), ref('test_crm_lead_second')]
|
||||
context.update({'active_model': 'crm.lead', 'active_ids': lead_ids, 'active_id': lead_ids[0]})
|
||||
-
|
||||
I create a merge wizard and merge the leads and opp together in the first item of the list.
|
||||
@@ -38,18 +38,19 @@
|
||||
-
|
||||
!python {model: crm.lead}: |
|
||||
merge_id = self.search(cr, uid, [('name', '=', 'Test opportunity 1'), ('partner_id','=', ref("base.res_partner_5"))])
|
||||
|
||||
assert merge_id, 'Fail to create merge opportunity wizard'
|
||||
merge_result = self.browse(cr, uid, merge_id)[0]
|
||||
assert merge_result.description == 'This is the description of the test opp 1.\n\nThis is the description of the test lead 1.\n\nThis is the description of the test lead 2.', 'Description mismatch: when merging leads/opps with different text values, these values should get concatenated and separated with line returns'
|
||||
assert merge_result.description == 'This is the description of the test opp 1.\n\nThis is the description of the test lead first.\n\nThis is the description of the test lead second.', 'Description mismatch: when merging leads/opps with different text values, these values should get concatenated and separated with line returns'
|
||||
assert merge_result.type == 'opportunity', 'Type mismatch: when at least one opp in involved in the merge, the result should be a new opp (instead of %s)' % merge_result.type
|
||||
-
|
||||
The other (tailing) leads/opps shouldn't exist anymore.
|
||||
-
|
||||
!python {model: crm.lead}: |
|
||||
tailing_lead = self.search(cr, uid, [('id', '=', ref('test_crm_lead_01'))])
|
||||
assert not tailing_lead, 'This tailing lead (id %s) should not exist anymore' % ref('test_crm_lead_02')
|
||||
tailing_lead = self.search(cr, uid, [('id', '=', ref('test_crm_lead_first'))])
|
||||
assert not tailing_lead, 'This tailing lead (id %s) should not exist anymore' % ref('test_crm_lead_second')
|
||||
|
||||
tailing_opp = self.search(cr, uid, [('id', '=', ref('test_crm_lead_02'))])
|
||||
tailing_opp = self.search(cr, uid, [('id', '=', ref('test_crm_lead_second'))])
|
||||
assert not tailing_opp, 'This tailing opp (id %s) should not exist anymore' % ref('test_crm_opp_01')
|
||||
-
|
||||
I want to test leads merge. Start by creating two leads (with the same partner).
|
||||
@@ -122,4 +123,4 @@
|
||||
assert merge_result.partner_id.id == ref("base.res_partner_5"), 'Partner mismatch'
|
||||
assert merge_result.type == 'opportunity', 'Type mismatch: when opps get merged together, the result should be a new opp (instead of %s)' % merge_result.type
|
||||
tailing_opp = self.search(cr, uid, [('id', '=', ref('test_crm_opp_03'))])
|
||||
assert not tailing_opp, 'This tailing opp (id %s) should not exist anymore' % ref('test_crm_opp_03')
|
||||
assert not tailing_opp, 'This tailing opp (id %s) should not exist anymore' % ref('test_crm_opp_03')
|
||||
|
||||
@@ -6,14 +6,13 @@
|
||||
partner_id: base.res_partner_2
|
||||
type: opportunity
|
||||
stage_id: crm.stage_lead1
|
||||
state: draft
|
||||
-
|
||||
I create a lead record to call a mailing opt-out onchange method.
|
||||
-
|
||||
!record {model: crm.lead, id: crm_case_18}:
|
||||
name: 'Need 20 Days of Consultancy'
|
||||
type: opportunity
|
||||
state: draft
|
||||
stage_id: crm.stage_lead1
|
||||
opt_out: True
|
||||
-
|
||||
I create a phonecall record to call a partner onchange method.
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
-
|
||||
In order to test the conversion of a lead into a opportunity,
|
||||
-
|
||||
I set lead to open stage.
|
||||
-
|
||||
!python {model: crm.lead}: |
|
||||
self.write(cr, uid, [ref("crm_case_3")],{'stage_id':ref("stage_lead1")})
|
||||
-
|
||||
I check if the lead stage is "Open".
|
||||
-
|
||||
!assert {model: crm.lead, id: crm.crm_case_3, string: Lead stage is Open}:
|
||||
- stage_id.sequence == 1
|
||||
-
|
||||
I convert lead into opportunity for exiting customer.
|
||||
-
|
||||
!python {model: crm.lead}: |
|
||||
self.convert_opportunity(cr, uid ,[ref("crm_case_1")], ref("base.res_partner_2"))
|
||||
self.convert_opportunity(cr, uid ,[ref("crm_case_3")], ref("base.res_partner_2"))
|
||||
-
|
||||
I check details of converted opportunity.
|
||||
-
|
||||
!python {model: crm.lead}: |
|
||||
lead = self.browse(cr, uid, ref('crm_case_1'))
|
||||
lead = self.browse(cr, uid, ref('crm_case_3'))
|
||||
assert lead.type == 'opportunity', 'Lead is not converted to opportunity!'
|
||||
assert lead.partner_id.id == ref("base.res_partner_2"), 'Partner mismatch!'
|
||||
assert lead.stage_id.id == ref("stage_lead1"), 'Stage of opportunity is incorrect!'
|
||||
@@ -18,7 +28,7 @@
|
||||
-
|
||||
!python {model: crm.opportunity2phonecall}: |
|
||||
import time
|
||||
context.update({'active_model': 'crm.lead', 'active_ids': [ref('crm_case_1')]})
|
||||
context.update({'active_model': 'crm.lead', 'active_ids': [ref('crm_case_3')]})
|
||||
call_id = self.create(cr, uid, {'date': time.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'name': "Bonjour M. Jean, Comment allez-vous? J'ai bien reçu votre demande, pourrions-nous en parler quelques minutes?"}, context=context)
|
||||
self.action_schedule(cr, uid, [call_id], context=context)
|
||||
@@ -26,46 +36,46 @@
|
||||
I check that phonecall is scheduled for that opportunity.
|
||||
-
|
||||
!python {model: crm.phonecall}: |
|
||||
ids = self.search(cr, uid, [('opportunity_id', '=', ref('crm_case_1'))])
|
||||
ids = self.search(cr, uid, [('opportunity_id', '=', ref('crm_case_3'))])
|
||||
assert len(ids), 'Phonecall is not scheduled'
|
||||
-
|
||||
Now I schedule meeting with customer.
|
||||
-
|
||||
!python {model: crm.lead}: |
|
||||
self.action_makeMeeting(cr, uid, [ref('crm_case_1')])
|
||||
self.action_makeMeeting(cr, uid, [ref('crm_case_3')])
|
||||
-
|
||||
After communicated with customer, I put some notes with contract details.
|
||||
-
|
||||
!python {model: crm.lead}: |
|
||||
self.message_post(cr, uid, [ref('crm_case_1')], subject='Test note', body='Détails envoyés par le client sur le FAX pour la qualité')
|
||||
self.message_post(cr, uid, [ref('crm_case_3')], subject='Test note', body='Détails envoyés par le client sur le FAX pour la qualité')
|
||||
-
|
||||
I win this opportunity
|
||||
-
|
||||
!python {model: crm.lead}: |
|
||||
self.case_mark_won(cr, uid, [ref("crm_case_1")])
|
||||
self.case_mark_won(cr, uid, [ref("crm_case_3")])
|
||||
-
|
||||
I check details of the opportunity after having won the opportunity.
|
||||
-
|
||||
!python {model: crm.lead}: |
|
||||
lead = self.browse(cr, uid, ref('crm_case_1'))
|
||||
lead = self.browse(cr, uid, ref('crm_case_3'))
|
||||
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.stage_id.probability == 100.0, "Opportunity is not 'done'"
|
||||
assert lead.probability == 100.0, "Revenue probability should be 100.0!"
|
||||
-
|
||||
I convert mass lead into opportunity customer.
|
||||
-
|
||||
!python {model: crm.lead2opportunity.partner.mass}: |
|
||||
context.update({'active_model': 'crm.lead', 'active_ids': [ref("crm_case_11"), ref("crm_case_2")], 'active_id': ref("crm_case_11")})
|
||||
context.update({'active_model': 'crm.lead', 'active_ids': [ref("crm_case_13"), ref("crm_case_2")], 'active_id': ref("crm_case_13")})
|
||||
id = self.create(cr, uid, {'user_ids': [(6, 0, [ref('base.user_root')])], 'section_id': ref('crm.section_sales_department')}, context=context)
|
||||
self.mass_convert(cr, uid, [id], context=context)
|
||||
-
|
||||
Now I check first lead converted on opportunity.
|
||||
-
|
||||
!python {model: crm.lead}: |
|
||||
opp = self.browse(cr, uid, ref('crm_case_11'))
|
||||
assert opp.name == "Need estimated cost for new project", "Opportunity name not correct"
|
||||
opp = self.browse(cr, uid, ref('crm_case_13'))
|
||||
assert opp.name == "Plan to buy 60 keyboards and mouses", "Opportunity name not correct"
|
||||
assert opp.type == 'opportunity', 'Lead is not converted to opportunity!'
|
||||
expected_partner = "Thomas Passot"
|
||||
expected_partner = "Will McEncroe"
|
||||
assert opp.partner_id.name == expected_partner, 'Partner mismatch! %s vs %s' % (opp.partner_id.name, expected_partner)
|
||||
assert opp.stage_id.id == ref("stage_lead1"), 'Stage of probability is incorrect!'
|
||||
-
|
||||
@@ -86,14 +96,15 @@
|
||||
-
|
||||
!python {model: crm.lead}: |
|
||||
lead = self.browse(cr, uid, ref('crm_case_2'))
|
||||
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.stage_id.id == ref('crm.stage_lead7'), "Opportunity stage should be 'Lost'."
|
||||
assert lead.stage_id.sequence != 1 and lead.stage_id.probability == 0.0, "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}: |
|
||||
context.update({'active_model': 'crm.meeting'})
|
||||
self.case_open(cr, uid, [ref('base_calendar.crm_meeting_4')])
|
||||
-
|
||||
I invite a user for meeting.
|
||||
-
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools.translate import _
|
||||
from openerp import tools
|
||||
import re
|
||||
|
||||
class crm_lead2opportunity_partner(osv.osv_memory):
|
||||
@@ -60,11 +59,11 @@ class crm_lead2opportunity_partner(osv.osv_memory):
|
||||
|
||||
if partner_id:
|
||||
# Search for opportunities that have the same partner and that arent done or cancelled
|
||||
ids = lead_obj.search(cr, uid, [('partner_id', '=', partner_id), ('state', '!=', 'done')])
|
||||
ids = lead_obj.search(cr, uid, [('partner_id', '=', partner_id), ('probability', '<', '100')])
|
||||
for id in ids:
|
||||
tomerge.add(id)
|
||||
if email:
|
||||
ids = lead_obj.search(cr, uid, [('email_from', 'ilike', email[0]), ('state', '!=', 'done')])
|
||||
ids = lead_obj.search(cr, uid, [('email_from', 'ilike', email[0]), ('probability', '<', '100')])
|
||||
for id in ids:
|
||||
tomerge.add(id)
|
||||
|
||||
@@ -105,8 +104,8 @@ class crm_lead2opportunity_partner(osv.osv_memory):
|
||||
context = {}
|
||||
lead_obj = self.pool.get('crm.lead')
|
||||
for lead in lead_obj.browse(cr, uid, context.get('active_ids', []), context=context):
|
||||
if lead.state in ['done', 'cancel']:
|
||||
raise osv.except_osv(_("Warning!"), _("Closed/Cancelled leads cannot be converted into opportunities."))
|
||||
if lead.probability == 100:
|
||||
raise osv.except_osv(_("Warning!"), _("Closed/Dead leads cannot be converted into opportunities."))
|
||||
return False
|
||||
|
||||
def _convert_opportunity(self, cr, uid, ids, vals, context=None):
|
||||
|
||||
@@ -62,8 +62,7 @@ class crm_merge_opportunity(osv.osv_memory):
|
||||
def default_get(self, cr, uid, fields, context=None):
|
||||
"""
|
||||
Use active_ids from the context to fetch the leads/opps to merge.
|
||||
In order to get merged, these leads/opps can't be in 'Done' or
|
||||
'Cancel' state.
|
||||
In order to get merged, these leads/opps can't be in 'Dead' or 'Closed'
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
@@ -74,7 +73,7 @@ class crm_merge_opportunity(osv.osv_memory):
|
||||
opp_ids = []
|
||||
opps = self.pool.get('crm.lead').browse(cr, uid, record_ids, context=context)
|
||||
for opp in opps:
|
||||
if opp.state not in ('done', 'cancel'):
|
||||
if opp.probability < 100:
|
||||
opp_ids.append(opp.id)
|
||||
if 'opportunity_ids' in fields:
|
||||
res.update({'opportunity_ids': opp_ids})
|
||||
|
||||
@@ -42,7 +42,6 @@ class crm_claim_stage(osv.osv):
|
||||
'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, 'Status', required=True, help="The related status for the stage. The status of your document will automatically change regarding the selected stage. For example, if a stage is related to the status 'Close', when your document reaches this stage, it will be automatically have the 'closed' status."),
|
||||
'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',
|
||||
@@ -51,7 +50,6 @@ class crm_claim_stage(osv.osv):
|
||||
|
||||
_defaults = {
|
||||
'sequence': lambda *args: 1,
|
||||
'state': 'draft',
|
||||
'fold': False,
|
||||
}
|
||||
|
||||
@@ -70,7 +68,7 @@ class crm_claim(osv.osv):
|
||||
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')], context=context)
|
||||
return self.stage_find(cr, uid, [], section_id, [('sequence', '=', '1')], context=context)
|
||||
|
||||
_columns = {
|
||||
'id': fields.integer('ID', readonly=True),
|
||||
@@ -105,13 +103,6 @@ class crm_claim(osv.osv):
|
||||
'stage_id': fields.many2one ('crm.claim.stage', 'Stage', track_visibility='onchange',
|
||||
domain="['|', ('section_ids', '=', section_id), ('case_default', '=', True)]"),
|
||||
'cause': fields.text('Root Cause'),
|
||||
'state': fields.related('stage_id', 'state', type="selection", store=True,
|
||||
selection=crm.AVAILABLE_STATES, string="Status", readonly=True,
|
||||
help='The status is set to \'Draft\', when a case is created.\
|
||||
If the case is in progress the status is set to \'Open\'.\
|
||||
When the case is over, the status is set to \'Done\'.\
|
||||
If the case needs to be reviewed then the status is \
|
||||
set to \'Pending\'.'),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
|
||||
@@ -44,28 +44,23 @@
|
||||
|
||||
<record model="crm.claim.stage" id="stage_claim1">
|
||||
<field name="name">New</field>
|
||||
<field name="state">draft</field>
|
||||
<field name="sequence">26</field>
|
||||
<field name="sequence">1</field>
|
||||
<field name="case_default" eval="True"/>
|
||||
</record>
|
||||
<record model="crm.claim.stage" id="stage_claim5">
|
||||
<field name="name">In Progress</field>
|
||||
<field name="state">open</field>
|
||||
<field name="sequence">27</field>
|
||||
<field name="case_default" eval="True"/>
|
||||
</record>
|
||||
<record model="crm.claim.stage" id="stage_claim2">
|
||||
<field name="name">Settled</field>
|
||||
<field name="state">done</field>
|
||||
<field name="sequence">28</field>
|
||||
<field name="case_default" eval="True"/>
|
||||
</record>
|
||||
<record model="crm.claim.stage" id="stage_claim3">
|
||||
<field name="name">Rejected</field>
|
||||
<field name="state">cancel</field>
|
||||
<field name="sequence">29</field>
|
||||
<field name="case_default" eval="True"/>
|
||||
<field name="case_refused" eval="True"/>
|
||||
<field name="fold" eval="True"/>
|
||||
</record>
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@
|
||||
<tree string="Claim Stages">
|
||||
<field name="sequence"/>
|
||||
<field name="name"/>
|
||||
<field name="state"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
@@ -51,7 +50,6 @@
|
||||
<field name="name"/>
|
||||
<field name="case_default"/>
|
||||
<field name="sequence"/>
|
||||
<field name="state"/>
|
||||
<field name="fold"/>
|
||||
</form>
|
||||
</field>
|
||||
@@ -79,7 +77,7 @@
|
||||
<field name="name">CRM - Claims Tree</field>
|
||||
<field name="model">crm.claim</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Claims" colors="blue:state=='pending' and not(date_deadline and (date_deadline < current_date));gray:state in ('close', 'cancel');red:date_deadline and (date_deadline < current_date)">
|
||||
<tree string="Claims">
|
||||
<field name="name"/>
|
||||
<field name="partner_id"/>
|
||||
<field name="user_id"/>
|
||||
@@ -90,7 +88,6 @@
|
||||
<field name="categ_id" string="Type"/>
|
||||
<field name="date_deadline" invisible="1"/>
|
||||
<field name="date_closed" invisible="1"/>
|
||||
<field name="state" groups="base.group_no_one"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
@@ -113,7 +110,6 @@
|
||||
<field name="priority"/>
|
||||
<field name="section_id" groups="base.group_multi_salesteams"/>
|
||||
<field name="date_deadline"/>
|
||||
<field name="state" groups="base.group_no_one"/>
|
||||
</group>
|
||||
<group colspan="4" col="4">
|
||||
<notebook>
|
||||
@@ -191,9 +187,6 @@
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Claims">
|
||||
<field name="name" string="Claims"/>
|
||||
<filter icon="terp-check" string="New" name="current" domain="[('state','=','draft')]" help="New Claims"/>
|
||||
<filter icon="terp-camera_test" string="In Progress" domain="[('state','=','open')]" help="In Progress Claims"/>
|
||||
<filter icon="terp-gtk-media-pause" string="Pending" domain="[('state','=','pending')]"/>
|
||||
<separator/>
|
||||
<filter string="Unassigned Claims" icon="terp-personal-" domain="[('user_id','=', False)]" help="Unassigned Claims" />
|
||||
<field name="partner_id" filter_domain="[('partner_id','child_of',self)]"/>
|
||||
@@ -203,7 +196,6 @@
|
||||
<filter string="Responsible" icon="terp-personal" domain="[]" help="Responsible User" context="{'group_by':'user_id'}"/>
|
||||
<filter string="Stage" icon="terp-stage" domain="[]" context="{'group_by':'stage_id'}"/>
|
||||
<filter string="Type" icon="terp-stock_symbol-selection" domain="[]" context="{'group_by':'categ_id'}"/>
|
||||
<filter string="Status" icon="terp-stock_effects-object-colorize" domain="[]" context="{'group_by':'state'}" groups="base.group_no_one"/>
|
||||
<filter string="Claim Date" icon="terp-go-month" domain="[]" help="Claim Date" context="{'group_by':'date'}"/>
|
||||
<filter string="Deadline" icon="terp-go-month" domain="[]" context="{'group_by':'date_deadline'}"/>
|
||||
<filter string="Closure" icon="terp-go-month" domain="[]" help="Date Closed" context="{'group_by':'date_closed'}" groups="base.group_no_one"/>
|
||||
|
||||
@@ -22,14 +22,6 @@
|
||||
from openerp.osv import fields,osv
|
||||
from openerp import tools
|
||||
|
||||
AVAILABLE_STATES = [
|
||||
('draft','Draft'),
|
||||
('open','Open'),
|
||||
('cancel', 'Cancelled'),
|
||||
('done', 'Closed'),
|
||||
('pending','Pending')
|
||||
]
|
||||
|
||||
AVAILABLE_PRIORITIES = [
|
||||
('5', 'Lowest'),
|
||||
('4', 'Low'),
|
||||
@@ -51,7 +43,6 @@ class crm_claim_report(osv.osv):
|
||||
'user_id':fields.many2one('res.users', 'User', readonly=True),
|
||||
'section_id':fields.many2one('crm.case.section', 'Section', readonly=True),
|
||||
'nbr': fields.integer('# of Cases', readonly=True),
|
||||
'state': fields.selection(AVAILABLE_STATES, 'Status', size=16, readonly=True),
|
||||
'month':fields.selection([('01', 'January'), ('02', 'February'), \
|
||||
('03', 'March'), ('04', 'April'),\
|
||||
('05', 'May'), ('06', 'June'), \
|
||||
@@ -92,7 +83,6 @@ class crm_claim_report(osv.osv):
|
||||
to_char(c.date, 'YYYY-MM-DD') as day,
|
||||
to_char(c.date_closed, 'YYYY-MM-DD') as date_closed,
|
||||
to_char(c.date_deadline, 'YYYY-MM-DD') as date_deadline,
|
||||
c.state,
|
||||
c.user_id,
|
||||
c.stage_id,
|
||||
c.section_id,
|
||||
@@ -109,7 +99,7 @@ class crm_claim_report(osv.osv):
|
||||
from
|
||||
crm_claim c
|
||||
group by to_char(c.date, 'YYYY'), to_char(c.date, 'MM'),to_char(c.date, 'YYYY-MM-DD'),\
|
||||
c.state, c.user_id,c.section_id, c.stage_id,\
|
||||
c.user_id,c.section_id, c.stage_id,\
|
||||
c.categ_id,c.partner_id,c.company_id,c.create_date,
|
||||
c.priority,c.type_action,c.date_deadline,c.date_closed,c.id
|
||||
)""")
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
<field name="email" sum="# Mails"/>
|
||||
<field name="delay_close" avg="Avg Closing Delay"/>
|
||||
<field name="delay_expected"/>
|
||||
<field name="state" invisible="1"/>
|
||||
<field name="stage_id" invisible="1"/>
|
||||
<field name="categ_id" invisible="1"/>
|
||||
<field name="priority" invisible="1"/>
|
||||
@@ -37,7 +36,6 @@
|
||||
<field name="model">crm.claim.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph orientation="horizontal" string="Claims" type="bar">
|
||||
<field name="state"/>
|
||||
<field name="nbr" operator="+"/>
|
||||
<field group="True" name="user_id"/>
|
||||
</graph>
|
||||
@@ -51,10 +49,6 @@
|
||||
<field name="model">crm.claim.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search">
|
||||
<filter icon="terp-document-new" string="New" domain="[('state','=','draft')]"/>
|
||||
<filter icon="terp-camera_test" string="Open" domain="[('state','=','open')]"/>
|
||||
<filter icon="terp-gtk-media-pause" string="Pending" domain="[('state','=','pending')]"/>
|
||||
<separator/>
|
||||
<filter string="My Sales Team(s)" icon="terp-personal+" context="{'invisible_section': False}" domain="[('section_id.user_id','=',uid)]" help="My Sales Team(s)" groups="base.group_multi_salesteams"/>
|
||||
<separator/>
|
||||
<filter string="My Company" icon="terp-go-home" context="{'invisible_section': False}" domain="[('section_id.user_id.company_id','=',uid)]" help="My company"/>
|
||||
@@ -73,8 +67,6 @@
|
||||
<field name="create_date" />
|
||||
<field name="date_closed" />
|
||||
<field name="date_deadline" />
|
||||
<filter icon="terp-dialog-close" string="Done" domain="[('state','=','done')]"/>
|
||||
<filter icon="gtk-cancel" string="Cancel" domain="[('state','=','cancel')]"/>
|
||||
</group>
|
||||
<group expand="1" string="Group By...">
|
||||
<filter string="Salesperson" name="Salesperson" icon="terp-personal" domain="[]" context="{'group_by':'user_id'}" />
|
||||
@@ -84,7 +76,6 @@
|
||||
<filter string="Priority" icon="terp-rating-rated" domain="[]" context="{'group_by':'priority'}" />
|
||||
<filter string="Category" icon="terp-stock_symbol-selection" domain="[]" context="{'group_by':'categ_id'}" />
|
||||
<filter string="Type" icon="terp-stock_symbol-selection" domain="[]" context="{'group_by':'type_action'}" help="Action Type"/>
|
||||
<filter string="Status" icon="terp-stock_effects-object-colorize" domain="[]" context="{'group_by':'state'}" />
|
||||
<filter string="Company" icon="terp-go-home" domain="[]" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
|
||||
<filter string="Day" icon="terp-go-today" domain="[]" context="{'group_by':'day'}" help="Date of claim"/>
|
||||
<filter string="Month" icon="terp-go-month" domain="[]" context="{'group_by':'month'}" help="Month of claim"/>
|
||||
|
||||
@@ -26,6 +26,14 @@ from openerp import tools
|
||||
from openerp.tools.translate import _
|
||||
from openerp.tools import html2plaintext
|
||||
|
||||
AVAILABLE_STATES = [
|
||||
('draft', 'New'),
|
||||
('cancel', 'Cancelled'),
|
||||
('open', 'In Progress'),
|
||||
('pending', 'Pending'),
|
||||
('done', 'Closed')
|
||||
]
|
||||
|
||||
|
||||
class crm_helpdesk(base_state, osv.osv):
|
||||
""" Helpdesk Cases """
|
||||
@@ -65,7 +73,7 @@ class crm_helpdesk(base_state, osv.osv):
|
||||
domain="['|',('section_id','=',False),('section_id','=',section_id),\
|
||||
('object_id.model', '=', 'crm.helpdesk')]"),
|
||||
'duration': fields.float('Duration', states={'done': [('readonly', True)]}),
|
||||
'state': fields.selection(crm.AVAILABLE_STATES, 'Status', size=16, readonly=True,
|
||||
'state': fields.selection(AVAILABLE_STATES, 'Status', size=16, readonly=True,
|
||||
help='The status is set to \'Draft\', when a case is created.\
|
||||
\nIf the case is in progress the status is set to \'Open\'.\
|
||||
\nWhen the case is over, the status is set to \'Done\'.\
|
||||
|
||||
@@ -49,7 +49,8 @@
|
||||
<field name="arch" type="xml">
|
||||
<field name="partner_id" position="after">
|
||||
<field name="partner_assigned_id"/>
|
||||
</field>
|
||||
<field name="date_assign" invisible="1"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -60,6 +61,9 @@
|
||||
<field name="arch" type="xml">
|
||||
<filter string="Team" position="after">
|
||||
<filter string="Assigned Partner" icon="terp-personal" domain="[]" context="{'group_by':'partner_assigned_id'}"/>
|
||||
<filter string="Assigned Month" icon="terp-go-month"
|
||||
domain="[]" context="{'group_by':'date_assign'}"/>
|
||||
|
||||
</filter>
|
||||
<field name="partner_id" position="after">
|
||||
<field name="partner_assigned_id"/>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<field name="model">crm.lead</field>
|
||||
<field name="priority" eval="32"/>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Leads" colors="blue:state=='pending';grey:state in ('cancel', 'done');red:stage_id[1]=='Disinterested';black:stage_id[1]=='Interested'">
|
||||
<tree string="Leads" colors="red:stage_id[1]=='Disinterested';black:stage_id[1]=='Interested'">
|
||||
<field name="date_deadline" invisible="1"/>
|
||||
<field name="create_date"/>
|
||||
<field name="name" string="Subject"/>
|
||||
@@ -18,7 +18,6 @@
|
||||
<field name="type_id" invisible="1"/>
|
||||
<field name="referred" invisible="1"/>
|
||||
<field name="channel_id" invisible="1"/>
|
||||
<field name="state" invisible="1"/>
|
||||
<field name="section_id" invisible="context.get('invisible_section', True)" />
|
||||
|
||||
<button string="I'm interested" name="case_interested" icon="gtk-index" type="object"/>
|
||||
@@ -69,7 +68,7 @@
|
||||
<field name="model">crm.lead</field>
|
||||
<field name="priority" eval="32"/>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Leads" colors="blue:state=='pending';grey:state in ('cancel', 'done')">
|
||||
<tree string="Leads">
|
||||
<field name="date_deadline" invisible="1"/>
|
||||
<field name="create_date" groups="base.group_no_one"/>
|
||||
<field name="name" string="Opportunity"/>
|
||||
@@ -83,7 +82,6 @@
|
||||
<field name="probability" widget="progressbar" avg="Avg. of Probability"/>
|
||||
<field name="section_id" invisible="context.get('invisible_section', True)" />
|
||||
<field name="priority" invisible="1"/>
|
||||
<field name="state" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -23,13 +23,6 @@ from openerp.osv import fields,osv
|
||||
from openerp import tools
|
||||
from openerp.addons.crm import crm
|
||||
|
||||
AVAILABLE_STATES = [
|
||||
('draft','Draft'),
|
||||
('open','Open'),
|
||||
('cancel', 'Cancelled'),
|
||||
('done', 'Closed'),
|
||||
('pending','Pending')
|
||||
]
|
||||
|
||||
class crm_lead_report_assign(osv.osv):
|
||||
""" CRM Lead Report """
|
||||
@@ -43,7 +36,6 @@ class crm_lead_report_assign(osv.osv):
|
||||
'user_id':fields.many2one('res.users', 'User', readonly=True),
|
||||
'country_id':fields.many2one('res.country', 'Country', readonly=True),
|
||||
'section_id':fields.many2one('crm.case.section', 'Sales Team', readonly=True),
|
||||
'state': fields.selection(AVAILABLE_STATES, 'Status', size=16, readonly=True),
|
||||
'month':fields.selection([('01', 'January'), ('02', 'February'), \
|
||||
('03', 'March'), ('04', 'April'),\
|
||||
('05', 'May'), ('06', 'June'), \
|
||||
@@ -54,7 +46,7 @@ class crm_lead_report_assign(osv.osv):
|
||||
'date_assign': fields.date('Partner Date', readonly=True),
|
||||
'create_date': fields.datetime('Create Date', readonly=True),
|
||||
'day': fields.char('Day', size=128, readonly=True),
|
||||
'delay_open': fields.float('Delay to Open',digits=(16,2),readonly=True, group_operator="avg",help="Number of Days to open the case"),
|
||||
'delay_open': fields.float('Delay to Assign',digits=(16,2),readonly=True, group_operator="avg",help="Number of Days to open the case"),
|
||||
'delay_close': fields.float('Delay to Close',digits=(16,2),readonly=True, group_operator="avg",help="Number of Days to close the case"),
|
||||
'delay_expected': fields.float('Overpassed Deadline',digits=(16,2),readonly=True, group_operator="avg"),
|
||||
'probability': fields.float('Avg Probability',digits=(16,2),readonly=True, group_operator="avg"),
|
||||
@@ -91,7 +83,6 @@ class crm_lead_report_assign(osv.osv):
|
||||
to_char(c.create_date, 'YYYY-MM-DD') as creation_date,
|
||||
to_char(c.date_open, 'YYYY-MM-DD') as opening_date,
|
||||
to_char(c.date_closed, 'YYYY-mm-dd') as date_closed,
|
||||
c.state,
|
||||
c.date_assign,
|
||||
c.user_id,
|
||||
c.probability,
|
||||
@@ -110,7 +101,7 @@ class crm_lead_report_assign(osv.osv):
|
||||
c.planned_revenue*(c.probability/100) as probable_revenue,
|
||||
1 as nbr,
|
||||
date_trunc('day',c.create_date) as create_date,
|
||||
extract('epoch' from (c.date_closed-c.create_date))/(3600*24) as delay_close,
|
||||
extract('epoch' from (c.write_date-c.create_date))/(3600*24) as delay_close,
|
||||
extract('epoch' from (c.date_deadline - c.date_closed))/(3600*24) as delay_expected,
|
||||
extract('epoch' from (c.date_open-c.create_date))/(3600*24) as delay_open
|
||||
FROM
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
<field name="model">crm.lead.report.assign</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Leads Analysis">
|
||||
<filter icon="terp-check" string="Current" domain="[('state','in',('draft','open'))]"/>
|
||||
<filter icon="terp-dialog-close" string="Closed" domain="[('state','=','done')]"/>
|
||||
<field name="section_id" context="{'invisible_section': False}" groups="base.group_multi_salesteams"/>
|
||||
<field name="grade_id"/>
|
||||
<field name="user_id"/>
|
||||
@@ -36,7 +34,6 @@
|
||||
domain="[]" context="{'group_by':'grade_id'}" />
|
||||
<filter string="Stage" icon="terp-stage" domain="[]" context="{'group_by':'stage_id'}"/>
|
||||
<filter string="Priority" icon="terp-rating-rated" domain="[]" context="{'group_by':'priority'}" />
|
||||
<filter string="Status" icon="terp-stock_effects-object-colorize" domain="[]" context="{'group_by':'state'}" />
|
||||
<filter string="Company" icon="terp-go-home" domain="[]" context="{'group_by':'company_id'}" />
|
||||
<filter string="Assign Date" icon="terp-go-today" domain="[]" name="group_partner_date" context="{'group_by':'date_assign'}"/>
|
||||
<filter string="Day" icon="terp-go-today" domain="[]" context="{'group_by':'day'}"/>
|
||||
@@ -54,7 +51,6 @@
|
||||
<field name="model">crm.lead.report.assign</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph orientation="horizontal" string="Lead Assign" type="bar">
|
||||
<field name="state"/>
|
||||
<field name="nbr" operator="+"/>
|
||||
<field group="True" name="user_id"/>
|
||||
</graph>
|
||||
@@ -76,14 +72,13 @@
|
||||
<field name="partner_id" invisible="1"/>
|
||||
<field name="country_id" invisible="1"/>
|
||||
<field name="day" invisible="1"/>
|
||||
<field name="state" invisible="1"/>
|
||||
<field name="stage_id" invisible="1"/>
|
||||
<field name="priority" invisible="1"/>
|
||||
<field name="type" invisible="1"/>
|
||||
<field name="company_id" invisible="1" groups="base.group_multi_company"/>
|
||||
<field name="nbr" string="#Opportunities" sum="#Opportunities"/>
|
||||
<field name="planned_revenue" sum="Planned Revenues"/>
|
||||
<field name="delay_open" sum='Delay to open'/>
|
||||
<field name="delay_open" sum='Delay to Assign'/>
|
||||
<field name="delay_close" sum='Delay to close'/>
|
||||
<field name="delay_expected"/>
|
||||
<field name="probability" widget="progressbar"/>
|
||||
|
||||
@@ -33,134 +33,130 @@
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="res_partner_activation_config_mi" parent="base.menu_config_address_book" action="res_partner_activation_act" groups="base.group_no_one" />
|
||||
<menuitem id="res_partner_activation_config_mi" parent="base.menu_config_address_book" action="res_partner_activation_act" groups="base.group_no_one"/>
|
||||
|
||||
<!--Partner Grade -->
|
||||
<!--Partner Grade -->
|
||||
|
||||
<record id="view_partner_grade_tree" model="ir.ui.view">
|
||||
<field name="name">res.partner.grade.tree</field>
|
||||
<field name="model">res.partner.grade</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Partner Grade">
|
||||
<field name="sequence" invisible="1" />
|
||||
<field name="name" />
|
||||
</tree>
|
||||
|
||||
<record id="view_partner_grade_tree" model="ir.ui.view">
|
||||
<field name="name">res.partner.grade.tree</field>
|
||||
<field name="model">res.partner.grade</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Partner Grade">
|
||||
<field name="sequence" invisible="1"/>
|
||||
<field name="name"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="view_partner_grade_form" model="ir.ui.view">
|
||||
<field name="name">res.partner.grade.form</field>
|
||||
<field name="model">res.partner.grade</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Partner Grade" version="7.0">
|
||||
<group col="4">
|
||||
<field name="name"/>
|
||||
<field name="sequence"/>
|
||||
<field name="active"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id="res_partner_grade_action" model="ir.actions.act_window">
|
||||
<field name="name">Partner Grade</field>
|
||||
<field name="res_model">res.partner.grade</field>
|
||||
<field name="view_type">form</field>
|
||||
</record>
|
||||
<menuitem action="res_partner_grade_action" id="menu_res_partner_grade_action"
|
||||
groups="base.group_no_one"
|
||||
parent="base.menu_crm_config_lead" />
|
||||
|
||||
<!-- Partner form -->
|
||||
<record id="view_res_partner_filter_assign_tree" model="ir.ui.view">
|
||||
<field name="name">res.partner.geo.inherit.tree</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="inherit_id" ref="base.view_partner_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="user_id" position="after">
|
||||
<field name="date_review_next"/>
|
||||
<field name="grade_id"/>
|
||||
<field name="activation"/>
|
||||
</field>
|
||||
</record>
|
||||
<record id="view_partner_grade_form" model="ir.ui.view">
|
||||
<field name="name">res.partner.grade.form</field>
|
||||
<field name="model">res.partner.grade</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Partner Grade" version="7.0">
|
||||
<group col="4">
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_res_partner_filter_assign" model="ir.ui.view">
|
||||
<field name="name">res.partner.geo.inherit.search</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="inherit_id" ref="base.view_res_partner_filter"/>
|
||||
<field name="arch" type="xml">
|
||||
<filter string="Salesperson" position="after">
|
||||
<filter string="Activation" context="{'group_by' : 'activation'}" domain="[]" icon="terp-personal" />
|
||||
</filter>
|
||||
<field name="category_id" position="after">
|
||||
<field name="grade_id"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_crm_partner_geo_form" model="ir.ui.view">
|
||||
<field name="name">res.partner.geo.inherit</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="inherit_id" ref="base.view_partner_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//notebook[last()]" position="inside">
|
||||
<page string="Geo Localization">
|
||||
<group>
|
||||
<group>
|
||||
<field name="name" />
|
||||
<field name="partner_weight" />
|
||||
<div>
|
||||
<p class="oe_grey">
|
||||
Define a weight to this grade. The weight will be used as default in the partner form to compute the chance for this partner to get leads. For instance, for business purpose, you can define a target revenue for each grade. To give the same chance to each partner to get leads, keep 1 in this field.
|
||||
</p>
|
||||
</div>
|
||||
<separator string="Partner Activation" colspan="2"/>
|
||||
<field name="grade_id" widget="selection"/>
|
||||
<field name="activation" widget="selection"/>
|
||||
<field name="partner_weight"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="sequence" />
|
||||
<field name="active" />
|
||||
<separator string="Partner Review" colspan="2"/>
|
||||
<field name="date_review"/>
|
||||
<field name="date_review_next"/>
|
||||
<field name="date_partnership"/>
|
||||
</group>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id="res_partner_grade_action" model="ir.actions.act_window">
|
||||
<field name="name">Partner Grade</field>
|
||||
<field name="res_model">res.partner.grade</field>
|
||||
<field name="view_type">form</field>
|
||||
</record>
|
||||
<menuitem action="res_partner_grade_action" id="menu_res_partner_grade_action" groups="base.group_no_one" parent="base.menu_crm_config_lead" />
|
||||
<group colspan="2" col="2">
|
||||
<separator string="Geo Localization" colspan="2"/>
|
||||
<button
|
||||
string="Geo Localize"
|
||||
name="geo_localize"
|
||||
colspan="2"
|
||||
icon="gtk-apply"
|
||||
type="object"/>
|
||||
<field name="partner_latitude"/>
|
||||
<field name="partner_longitude"/>
|
||||
<field name="date_localization"/>
|
||||
</group>
|
||||
<newline/>
|
||||
|
||||
<!-- Partner form -->
|
||||
<record id="view_res_partner_filter_assign_tree" model="ir.ui.view">
|
||||
<field name="name">res.partner.geo.inherit.tree</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="inherit_id" ref="base.view_partner_tree" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="user_id" position="after">
|
||||
<field name="date_review_next" />
|
||||
<field name="grade_id" />
|
||||
<field name="activation" />
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_res_partner_filter_assign" model="ir.ui.view">
|
||||
<field name="name">res.partner.geo.inherit.search</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="inherit_id" ref="base.view_res_partner_filter" />
|
||||
<field name="arch" type="xml">
|
||||
<filter string="Salesperson" position="after">
|
||||
<filter string="Activation" context="{'group_by' : 'activation'}" domain="[]" icon="terp-personal" />
|
||||
</filter>
|
||||
<field name="category_id" position="after">
|
||||
<field name="grade_id" />
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_crm_partner_geo_form" model="ir.ui.view">
|
||||
<field name="name">res.partner.geo.inherit</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="inherit_id" ref="base.view_partner_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//notebook[last()]" position="inside">
|
||||
<page string="Forwarded Leads">
|
||||
<group>
|
||||
<group string="Partner Activation">
|
||||
<label for="partner_latitude" string="Geolocalisation" />
|
||||
<div class="oe_title oe_inline">
|
||||
<h3 class="oe_inline">
|
||||
<span class="oe_grey">( </span>
|
||||
<field name="partner_latitude" nolabel="1" readonly="1" class="oe_inline" />
|
||||
<span class="oe_grey oe_inline" attrs="{'invisible':[('partner_latitude','<=',0)]}">N </span>
|
||||
<span class="oe_grey oe_inline" attrs="{'invisible':[('partner_latitude','>=',0)]}">S </span>
|
||||
<field name="partner_longitude" class="oe_inline" readonly="1" nolabel="1" />
|
||||
<span class="oe_grey oe_inline" attrs="{'invisible':[('partner_longitude','<=',0)]}">E </span>
|
||||
<span class="oe_grey oe_inline" attrs="{'invisible':[('partner_longitude','>=',0)]}">W </span>
|
||||
<span class="oe_grey">) </span>
|
||||
</h3>
|
||||
<button string="Geolocalize" name="geo_localize" class="oe_inline" type="object" />
|
||||
</div>
|
||||
<field name="grade_id" widget="selection" on_change="onchange_grade_id(grade_id)" />
|
||||
<field name="partner_weight" class="oe_inline" />
|
||||
<div colspan="2">
|
||||
<p class="oe_grey">
|
||||
Higher is the value, higher is the probability for this partner to get more leads.
|
||||
</p>
|
||||
</div>
|
||||
</group>
|
||||
<group>
|
||||
<separator string="Partner Review" colspan="2" />
|
||||
<field name="date_review" />
|
||||
<field name="date_review_next" />
|
||||
<field name="date_partnership" />
|
||||
</group>
|
||||
<group>
|
||||
</group>
|
||||
</group>
|
||||
<newline />
|
||||
<separator string="Forwarded Leads" colspan="2" />
|
||||
<field name="opportunity_assigned_ids" colspan="4" nolabel="1">
|
||||
<tree string="Assigned Opportunities" colors="blue:state=='pending';gray:state=='cancel'">
|
||||
<field name="name" />
|
||||
<field name="contact_name" />
|
||||
<field name="email_from" />
|
||||
<field name="phone" />
|
||||
<field name="stage_id" />
|
||||
<field name="state" invisible="1" />
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
<field name="opportunity_assigned_ids" colspan="4" nolabel="1">
|
||||
<tree string="Assigned Opportunities">
|
||||
<field name="create_date"/>
|
||||
<field name="name"/>
|
||||
<field name="type"/>
|
||||
<field name="stage_id"/>
|
||||
<field name="section_id"
|
||||
invisible="context.get('invisible_section', True)"
|
||||
groups="base.group_multi_salesteams"/>
|
||||
<field name="user_id" />
|
||||
<button string="Convert to Opportunity"
|
||||
name="convert_opportunity"
|
||||
type="object"
|
||||
attrs="{'invisible':[('type','=','opportunity'),('probability', '=', 100)]}" />
|
||||
<button name="case_escalate" string="Escalate"
|
||||
type="object"
|
||||
icon="gtk-go-up"
|
||||
attrs="{'invisible':[('probability', '=', 100)]}" />
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
.. _changelog:
|
||||
|
||||
Changelog
|
||||
=========
|
||||
|
||||
`trunk (saas-2)`
|
||||
----------------
|
||||
|
||||
- updated ``hr.holidays`` workflow. It now starts in ``confirm`` state. In
|
||||
``confirm```and ``refuse`` a Reset to Draft has been added in view / workflow,
|
||||
allowing to edit the request. Added a ``can_reset`` computed field to enable
|
||||
this transition. A user can edit its own requests, or all requests if he is
|
||||
an Hr Manager.
|
||||
@@ -0,0 +1,13 @@
|
||||
Hr Holidays module documentation
|
||||
================================
|
||||
|
||||
Hr Holidays documentation topics
|
||||
''''''''''''''''''''''''''''''''
|
||||
|
||||
Changelog
|
||||
'''''''''
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
changelog.rst
|
||||
@@ -22,11 +22,10 @@
|
||||
##############################################################################
|
||||
|
||||
import datetime
|
||||
import time
|
||||
from itertools import groupby
|
||||
from operator import attrgetter, itemgetter
|
||||
|
||||
import math
|
||||
import time
|
||||
from operator import attrgetter
|
||||
|
||||
from openerp import tools
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools.translate import _
|
||||
@@ -36,40 +35,37 @@ class hr_holidays_status(osv.osv):
|
||||
_name = "hr.holidays.status"
|
||||
_description = "Leave Type"
|
||||
|
||||
def get_days(self, cr, uid, ids, employee_id, return_false, context=None):
|
||||
cr.execute("""SELECT id, type, number_of_days, holiday_status_id FROM hr_holidays WHERE employee_id = %s AND state='validate' AND holiday_status_id in %s""",
|
||||
[employee_id, tuple(ids)])
|
||||
result = sorted(cr.dictfetchall(), key=lambda x: x['holiday_status_id'])
|
||||
grouped_lines = dict((k, [v for v in itr]) for k, itr in groupby(result, itemgetter('holiday_status_id')))
|
||||
res = {}
|
||||
for record in self.browse(cr, uid, ids, context=context):
|
||||
res[record.id] = {}
|
||||
max_leaves = leaves_taken = 0
|
||||
if not return_false:
|
||||
if record.id in grouped_lines:
|
||||
leaves_taken = -sum([item['number_of_days'] for item in grouped_lines[record.id] if item['type'] == 'remove'])
|
||||
max_leaves = sum([item['number_of_days'] for item in grouped_lines[record.id] if item['type'] == 'add'])
|
||||
res[record.id]['max_leaves'] = max_leaves
|
||||
res[record.id]['leaves_taken'] = leaves_taken
|
||||
res[record.id]['remaining_leaves'] = max_leaves - leaves_taken
|
||||
return res
|
||||
def get_days(self, cr, uid, ids, employee_id, context=None):
|
||||
result = dict((id, dict(max_leaves=0, leaves_taken=0, remaining_leaves=0,
|
||||
virtual_remaining_leaves=0)) for id in ids)
|
||||
holiday_ids = self.pool['hr.holidays'].search(cr, uid, [('employee_id', '=', employee_id),
|
||||
('state', 'in', ['confirm', 'validate1', 'validate']),
|
||||
('holiday_status_id', 'in', ids)
|
||||
], context=context)
|
||||
for holiday in self.pool['hr.holidays'].browse(cr, uid, holiday_ids, context=context):
|
||||
status_dict = result[holiday.holiday_status_id.id]
|
||||
if holiday.type == 'add':
|
||||
status_dict['virtual_remaining_leaves'] += holiday.number_of_days
|
||||
if holiday.state == 'validate':
|
||||
status_dict['max_leaves'] += holiday.number_of_days
|
||||
status_dict['remaining_leaves'] += holiday.number_of_days
|
||||
elif holiday.type == 'remove': # number of days is negative
|
||||
status_dict['virtual_remaining_leaves'] += holiday.number_of_days
|
||||
if holiday.state == 'validate':
|
||||
status_dict['leaves_taken'] -= holiday.number_of_days
|
||||
status_dict['remaining_leaves'] += holiday.number_of_days
|
||||
return result
|
||||
|
||||
def _user_left_days(self, cr, uid, ids, name, args, context=None):
|
||||
return_false = False
|
||||
employee_id = False
|
||||
res = {}
|
||||
if context and context.has_key('employee_id'):
|
||||
if not context['employee_id']:
|
||||
return_false = True
|
||||
if context and 'employee_id' in context:
|
||||
employee_id = context['employee_id']
|
||||
else:
|
||||
employee_ids = self.pool.get('hr.employee').search(cr, uid, [('user_id','=',uid)], context=context)
|
||||
employee_ids = self.pool.get('hr.employee').search(cr, uid, [('user_id', '=', uid)], context=context)
|
||||
if employee_ids:
|
||||
employee_id = employee_ids[0]
|
||||
else:
|
||||
return_false = True
|
||||
if employee_id:
|
||||
res = self.get_days(cr, uid, ids, employee_id, return_false, context=context)
|
||||
res = self.get_days(cr, uid, ids, employee_id, context=context)
|
||||
else:
|
||||
res = dict.fromkeys(ids, {'leaves_taken': 0, 'remaining_leaves': 0, 'max_leaves': 0})
|
||||
return res
|
||||
@@ -84,6 +80,7 @@ class hr_holidays_status(osv.osv):
|
||||
'max_leaves': fields.function(_user_left_days, string='Maximum Allowed', help='This value is given by the sum of all holidays requests with a positive value.', multi='user_left_days'),
|
||||
'leaves_taken': fields.function(_user_left_days, string='Leaves Already Taken', help='This value is given by the sum of all holidays requests with a negative value.', multi='user_left_days'),
|
||||
'remaining_leaves': fields.function(_user_left_days, string='Remaining Leaves', help='Maximum Leaves Allowed - Leaves Already Taken', multi='user_left_days'),
|
||||
'virtual_remaining_leaves': fields.function(_user_left_days, string='Virtual Remaining Leaves', help='Maximum Leaves Allowed - Leaves Already Taken - Leaves Waiting Approval', multi='user_left_days'),
|
||||
'double_validation': fields.boolean('Apply Double Validation', help="When selected, the Allocation/Leave Requests for this type require a second validation to be approved."),
|
||||
}
|
||||
_defaults = {
|
||||
@@ -92,8 +89,6 @@ class hr_holidays_status(osv.osv):
|
||||
}
|
||||
|
||||
def name_get(self, cr, uid, ids, context=None):
|
||||
if not ids:
|
||||
return []
|
||||
res = []
|
||||
for record in self.browse(cr, uid, ids, context=context):
|
||||
name = record.name
|
||||
@@ -134,6 +129,19 @@ class hr_holidays(osv.osv):
|
||||
result[hol.id] = hol.number_of_days_temp
|
||||
return result
|
||||
|
||||
def _get_can_reset(self, cr, uid, ids, name, arg, context=None):
|
||||
"""User can reset a leave request if it is its own leave request or if
|
||||
he is an Hr Manager. """
|
||||
user = self.pool['res.users'].browse(cr, uid, uid, context=context)
|
||||
group_hr_manager_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'group_hr_manager')[1]
|
||||
if group_hr_manager_id in [g.id for g in user.groups_id]:
|
||||
return dict.fromkeys(ids, True)
|
||||
result = dict.fromkeys(ids, False)
|
||||
for holiday in self.browse(cr, uid, ids, context=context):
|
||||
if holiday.employee_id and holiday.employee_id.user_id and holiday.employee_id.user_id.id == uid:
|
||||
result[holiday.id] = True
|
||||
return result
|
||||
|
||||
def _check_date(self, cr, uid, ids):
|
||||
for holiday in self.browse(cr, uid, ids):
|
||||
holiday_ids = self.search(cr, uid, [('date_from', '<=', holiday.date_to), ('date_to', '>=', holiday.date_from), ('employee_id', '=', holiday.employee_id.id), ('id', '<>', holiday.id)])
|
||||
@@ -167,10 +175,13 @@ class hr_holidays(osv.osv):
|
||||
'holiday_type': fields.selection([('employee','By Employee'),('category','By Employee Tag')], 'Allocation Mode', readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}, help='By Employee: Allocation/Request for individual Employee, By Employee Tag: Allocation/Request for group of employees in category', required=True),
|
||||
'manager_id2': fields.many2one('hr.employee', 'Second Approval', readonly=True, help='This area is automaticly filled by the user who validate the leave with second level (If Leave type need second validation)'),
|
||||
'double_validation': fields.related('holiday_status_id', 'double_validation', type='boolean', relation='hr.holidays.status', string='Apply Double Validation'),
|
||||
'can_reset': fields.function(
|
||||
_get_can_reset,
|
||||
type='boolean'),
|
||||
}
|
||||
_defaults = {
|
||||
'employee_id': _employee_get,
|
||||
'state': 'draft',
|
||||
'state': 'confirm',
|
||||
'type': 'remove',
|
||||
'user_id': lambda obj, cr, uid, context: uid,
|
||||
'holiday_type': 'employee'
|
||||
@@ -302,27 +313,20 @@ class hr_holidays(osv.osv):
|
||||
if context is None:
|
||||
context = {}
|
||||
context = dict(context, mail_create_nolog=True)
|
||||
return super(hr_holidays, self).create(cr, uid, values, context=context)
|
||||
hol_id = super(hr_holidays, self).create(cr, uid, values, context=context)
|
||||
self.check_holidays(cr, uid, [hol_id], context=context)
|
||||
return hol_id
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
check_fnct = self.pool.get('hr.holidays.status').check_access_rights
|
||||
for holiday in self.browse(cr, uid, ids, context=context):
|
||||
if holiday.state in ('validate','validate1') and not check_fnct(cr, uid, 'write', raise_exception=False):
|
||||
raise osv.except_osv(_('Warning!'),_('You cannot modify a leave request that has been approved. Contact a human resource manager.'))
|
||||
return super(hr_holidays, self).write(cr, uid, ids, vals, context=context)
|
||||
|
||||
def set_to_draft(self, cr, uid, ids, context=None):
|
||||
def holidays_reset(self, cr, uid, ids, context=None):
|
||||
self.write(cr, uid, ids, {
|
||||
'state': 'draft',
|
||||
'manager_id': False,
|
||||
'manager_id2': False,
|
||||
})
|
||||
self.delete_workflow(cr, uid, ids)
|
||||
self.create_workflow(cr, uid, ids)
|
||||
to_unlink = []
|
||||
for record in self.browse(cr, uid, ids, context=context):
|
||||
for record2 in record.linked_request_ids:
|
||||
self.set_to_draft(cr, uid, [record2.id], context=context)
|
||||
self.holidays_reset(cr, uid, [record2.id], context=context)
|
||||
to_unlink.append(record2.id)
|
||||
if to_unlink:
|
||||
self.unlink(cr, uid, to_unlink, context=context)
|
||||
@@ -421,13 +425,16 @@ class hr_holidays(osv.osv):
|
||||
return True
|
||||
|
||||
def check_holidays(self, cr, uid, ids, context=None):
|
||||
holi_status_obj = self.pool.get('hr.holidays.status')
|
||||
for record in self.browse(cr, uid, ids):
|
||||
if record.holiday_type == 'employee' and record.type == 'remove':
|
||||
if record.employee_id and not record.holiday_status_id.limit:
|
||||
leaves_rest = holi_status_obj.get_days( cr, uid, [record.holiday_status_id.id], record.employee_id.id, False)[record.holiday_status_id.id]['remaining_leaves']
|
||||
if leaves_rest < record.number_of_days_temp:
|
||||
raise osv.except_osv(_('Warning!'), _('There are not enough %s allocated for employee %s; please create an allocation request for this leave type.') % (record.holiday_status_id.name, record.employee_id.name))
|
||||
for record in self.browse(cr, uid, ids, context=context):
|
||||
if record.holiday_type != 'employee' or record.type != 'remove' or not record.employee_id or record.holiday_status_id.limit:
|
||||
continue
|
||||
leave_days = self.pool.get('hr.holidays.status').get_days(cr, uid, [record.holiday_status_id.id], record.employee_id.id, context=context)[record.holiday_status_id.id]
|
||||
if leave_days['remaining_leaves'] < record.number_of_days_temp:
|
||||
raise osv.except_osv(_('Warning!'),
|
||||
_('There are not enough remaining days available in %s for employee %s.') % (record.holiday_status_id.name, record.employee_id.name))
|
||||
if leave_days['virtual_remaining_leaves'] < record.number_of_days_temp:
|
||||
raise osv.except_osv(_('Warning!'),
|
||||
_('Other pending requests already book too much days in %s for employee %s.') % (record.holiday_status_id.name, record.employee_id.name))
|
||||
return True
|
||||
|
||||
# -----------------------------
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
<record id="mt_holidays_confirmed" model="mail.message.subtype">
|
||||
<field name="name">To Approve</field>
|
||||
<field name="res_model">hr.holidays</field>
|
||||
<field name="description">Request created and waiting confirmation</field>
|
||||
<field name="description">Request confirmed and waiting approval</field>
|
||||
</record>
|
||||
<record id="mt_holidays_approved" model="mail.message.subtype">
|
||||
<field name="name">Approved</field>
|
||||
|
||||
@@ -53,11 +53,14 @@
|
||||
<field name="priority">1</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Leave Request" version="7.0">
|
||||
<field name="can_reset" invisible="1"/>
|
||||
<header>
|
||||
<button string="Confirm" name="confirm" states="draft" type="workflow" class="oe_highlight"/>
|
||||
<button string="Approve" name="validate" states="confirm" type="workflow" groups="base.group_hr_user" class="oe_highlight"/>
|
||||
<button string="Validate" name="second_validate" states="validate1" type="workflow" groups="base.group_hr_user" class="oe_highlight"/>
|
||||
<button string="Refuse" name="refuse" states="confirm,validate1,validate" type="workflow" groups="base.group_hr_user"/>
|
||||
<button string="Reset to New" name="set_to_draft" states="refuse" type="object" groups="base.group_hr_user"/>
|
||||
<button string="Reset to Draft" name="reset" type="workflow"
|
||||
attrs="{'invisible': ['|', ('can_reset', '=', False), ('state', 'not in', ['confirm', 'refuse'])]}"/>
|
||||
<field name="state" widget="statusbar" statusbar_visible="draft,confirm,validate" statusbar_colors='{"confirm":"blue","validate1":"blue","refuse":"red"}'/>
|
||||
</header>
|
||||
<sheet string="Leave Request">
|
||||
@@ -98,11 +101,14 @@
|
||||
<field name="model">hr.holidays</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Allocation Request" version="7.0">
|
||||
<field name="can_reset" invisible="1"/>
|
||||
<header>
|
||||
<button string="Confirm" name="confirm" states="draft" type="workflow" class="oe_highlight"/>
|
||||
<button string="Approve" name="validate" states="confirm" type="workflow" groups="base.group_hr_user" class="oe_highlight"/>
|
||||
<button string="Validate" name="second_validate" states="validate1" type="workflow" groups="base.group_hr_user" class="oe_highlight"/>
|
||||
<button string="Refuse" name="refuse" states="confirm,validate,validate1" type="workflow" groups="base.group_hr_user"/>
|
||||
<button string="Reset to New" name="set_to_draft" states="cancel,refuse" type="object" groups="base.group_hr_user"/>
|
||||
<button string="Reset to Draft" name="reset" type="workflow"
|
||||
attrs="{'invisible': ['|', ('can_reset', '=', False), ('state', 'not in', ['confirm', 'refuse'])]}"/>
|
||||
<field name="state" widget="statusbar" statusbar_visible="draft,confirm,validate" statusbar_colors='{"confirm":"blue","validate1":"blue","refuse":"red"}'/>
|
||||
</header>
|
||||
<sheet>
|
||||
@@ -163,7 +169,7 @@
|
||||
<button string="Submit to Manager" name="confirm" states="draft" type="workflow" icon="gtk-yes"/>
|
||||
<button string="Approve" name="validate" states="confirm" type="workflow" icon="gtk-apply"/>
|
||||
<button string="Refuse" name="refuse" states="confirm,validate,draft" type="workflow" icon="gtk-no"/>
|
||||
<button string="Reset to New" name="set_to_draft" states="cancel" type="object" icon="gtk-convert"/>
|
||||
<button string="Reset to Draft" name="reset" states="confirm" type="workflow" groups="base.group_hr_manager"/>
|
||||
<field name="state"/>
|
||||
</header>
|
||||
<group col="4">
|
||||
@@ -351,8 +357,8 @@
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="view_id" eval="view_holiday_simple"/>
|
||||
<field name="context">{'search_default_group_type': 1, 'search_default_validated': 1}</field>
|
||||
<field name="domain">[('holiday_type','=','employee')]</field>
|
||||
<field name="context">{'search_default_group_type': 1}</field>
|
||||
<field name="domain">[('holiday_type','=','employee'), ('state', '!=', 'refuse')]</field>
|
||||
<field name="search_view_id" ref="view_hr_holidays_filter"/>
|
||||
</record>
|
||||
|
||||
|
||||
@@ -2,15 +2,16 @@
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<!-- Workflow definition
|
||||
1. draft->submitted (no signal)
|
||||
<!-- Workflow definition
|
||||
1. draft->submitted (confirm signal) if can_reset
|
||||
2. submitted->draft (reset signal) if can_reset
|
||||
2. submitted->accepted (validate signal) if not double_validation
|
||||
2. submitted -> first_accepted (validate signal) if double_validation
|
||||
2. submitted->first_accepted (validate signal) if double_validation
|
||||
2. submitted->refused (refuse signal)
|
||||
3. accepted->refused (refuse signal)
|
||||
4. first_accepted -> accepted (second_validate signal)
|
||||
4. first_accepted -> accepted (second_validate signal)
|
||||
4. first_accepted -> refused (refuse signal)
|
||||
|
||||
5. refuse -> draft (reset signal) if can_reset
|
||||
-->
|
||||
|
||||
<record model="workflow" id="wkf_holidays">
|
||||
@@ -21,13 +22,15 @@
|
||||
|
||||
<record model="workflow.activity" id="act_draft"> <!-- draft -->
|
||||
<field name="wkf_id" ref="wkf_holidays" />
|
||||
<field name="flow_start">True</field>
|
||||
<field name="name">draft</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">holidays_reset()</field>
|
||||
</record>
|
||||
|
||||
<record model="workflow.activity" id="act_confirm"> <!-- submitted -->
|
||||
<field name="wkf_id" ref="wkf_holidays" />
|
||||
<field name="name">confirm</field>
|
||||
<field name="flow_start">True</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">holidays_confirm()</field>
|
||||
<field name="split_mode">OR</field>
|
||||
@@ -48,11 +51,9 @@
|
||||
<field name="split_mode">OR</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record model="workflow.activity" id="act_refuse"> <!-- refused -->
|
||||
<field name="wkf_id" ref="wkf_holidays" />
|
||||
<field name="name">refuse</field>
|
||||
<field name="flow_stop">True</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">holidays_refuse()</field>
|
||||
</record>
|
||||
@@ -61,9 +62,20 @@
|
||||
workflow transition
|
||||
-->
|
||||
|
||||
<record model="workflow.transition" id="holiday_draft2confirm"> <!-- 1. draft->submitted (no signal) -->
|
||||
<record model="workflow.transition" id="holiday_draft2confirm"> <!-- 1. draft->submitted (confirm signal) -->
|
||||
<field name="act_from" ref="act_draft" />
|
||||
<field name="act_to" ref="act_confirm" />
|
||||
<field name="signal">confirm</field>
|
||||
<field name="condition">can_reset</field>
|
||||
<field name="group_id" ref="base.group_user"/>
|
||||
</record>
|
||||
|
||||
<record model="workflow.transition" id="holiday_confirm2draft"> <!-- 2. submitted->draft (reset signal) -->
|
||||
<field name="act_from" ref="act_confirm" />
|
||||
<field name="act_to" ref="act_draft" />
|
||||
<field name="signal">reset</field>
|
||||
<field name="condition">can_reset</field>
|
||||
<field name="group_id" ref="base.group_user"/>
|
||||
</record>
|
||||
|
||||
<record model="workflow.transition" id="holiday_confirm2validate"> <!-- 2. submitted->accepted (validate signal) if not double_validation-->
|
||||
@@ -82,7 +94,6 @@
|
||||
<field name="group_id" ref="base.group_hr_user"/>
|
||||
</record>
|
||||
|
||||
|
||||
<record model="workflow.transition" id="holiday_confirm2refuse"> <!-- 2. submitted->refused (refuse signal) -->
|
||||
<field name="act_from" ref="act_confirm" />
|
||||
<field name="act_to" ref="act_refuse" />
|
||||
@@ -107,7 +118,6 @@
|
||||
<field name="group_id" ref="base.group_hr_user"/>
|
||||
</record>
|
||||
|
||||
|
||||
<record model="workflow.transition" id="holiday_validate1_validate"> <!-- 4. first_accepted -> accepted (second_validate signal) -->
|
||||
<field name="act_from" ref="act_validate1" />
|
||||
<field name="act_to" ref="act_validate" />
|
||||
@@ -124,5 +134,13 @@
|
||||
<field name="group_id" ref="base.group_hr_user"/>
|
||||
</record>
|
||||
|
||||
<record model="workflow.transition" id="holiday_refuse2draft"> <!-- 5. refused->draft (reset signal) -->
|
||||
<field name="act_from" ref="act_refuse" />
|
||||
<field name="act_to" ref="act_draft" />
|
||||
<field name="signal">reset</field>
|
||||
<field name="condition">can_reset</field>
|
||||
<field name="group_id" ref="base.group_hr_manager"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_hr_holydays_status_user,hr.holidays.status user,model_hr_holidays_status,base.group_hr_user,1,1,1,1
|
||||
access_hr_holidays_user,hr.holidays.user,model_hr_holidays,base.group_hr_user,1,1,1,1
|
||||
access_hr_holidays_employee,hr.holidays.employee,model_hr_holidays,base.group_user,1,1,1,1
|
||||
access_hr_holydays_status_employee,hr.holidays.status employee,model_hr_holidays_status,base.group_user,1,0,0,0
|
||||
access_hr_holydays_status_manager,hr.holidays.status manager,model_hr_holidays_status,base.group_hr_manager,1,1,1,1
|
||||
access_hr_holidays_remain_user,hr.holidays.ramain.user,model_hr_holidays_remaining_leaves_user,base.group_hr_user,1,1,1,1
|
||||
access_resource_calendar_leaves_user,resource_calendar_leaves_user,resource.model_resource_calendar_leaves,base.group_hr_user,1,1,1,1
|
||||
access_crm_meeting_hr_user,crm.meeting.hr.user,base_calendar.model_crm_meeting,base.group_hr_user,1,1,1,1
|
||||
access_crm_meeting_type_manager,crm.meeting.type.manager,base_calendar.model_crm_meeting_type,base.group_hr_manager,1,1,1,1
|
||||
|
||||
|
@@ -18,7 +18,7 @@
|
||||
I again set to draft and then confirm.
|
||||
-
|
||||
!python {model: hr.holidays}: |
|
||||
self.set_to_draft(cr, uid, [ref('hr_holidays_employee1_cl')])
|
||||
self.holidays_reset(cr, uid, [ref('hr_holidays_employee1_cl')])
|
||||
self.signal_confirm(cr, uid, [ref('hr_holidays_employee1_cl')])
|
||||
-
|
||||
I validate the holiday request by clicking on "To Approve" button.
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (c) 2013-TODAY OpenERP S.A. <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 openerp.addons.hr_holidays.tests import test_holidays_flow
|
||||
|
||||
checks = [
|
||||
test_holidays_flow,
|
||||
]
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
@@ -0,0 +1,98 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (c) 2013-TODAY OpenERP S.A. <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 openerp.tests import common
|
||||
|
||||
|
||||
class TestHrHolidaysBase(common.TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestHrHolidaysBase, self).setUp()
|
||||
cr, uid = self.cr, self.uid
|
||||
|
||||
# Usefull models
|
||||
self.hr_employee = self.registry('hr.employee')
|
||||
self.hr_holidays = self.registry('hr.holidays')
|
||||
self.hr_holidays_status = self.registry('hr.holidays.status')
|
||||
self.res_users = self.registry('res.users')
|
||||
self.res_partner = self.registry('res.partner')
|
||||
|
||||
# Find Employee group
|
||||
group_employee_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'base', 'group_user')
|
||||
self.group_employee_id = group_employee_ref and group_employee_ref[1] or False
|
||||
|
||||
# Find Hr User group
|
||||
group_hr_user_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'base', 'group_hr_user')
|
||||
self.group_hr_user_ref_id = group_hr_user_ref and group_hr_user_ref[1] or False
|
||||
|
||||
# Find Hr Manager group
|
||||
group_hr_manager_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'base', 'group_hr_manager')
|
||||
self.group_hr_manager_ref_id = group_hr_manager_ref and group_hr_manager_ref[1] or False
|
||||
|
||||
# Test partners to use through the various tests
|
||||
self.hr_partner_id = self.res_partner.create(cr, uid, {
|
||||
'name': 'Gertrude AgrolaitPartner',
|
||||
'email': 'gertrude.partner@agrolait.com',
|
||||
})
|
||||
self.email_partner_id = self.res_partner.create(cr, uid, {
|
||||
'name': 'Patrick Ratatouille',
|
||||
'email': 'patrick.ratatouille@agrolait.com',
|
||||
})
|
||||
|
||||
# Test users to use through the various tests
|
||||
self.user_hruser_id = self.res_users.create(cr, uid, {
|
||||
'name': 'Armande HrUser',
|
||||
'login': 'Armande',
|
||||
'alias_name': 'armande',
|
||||
'email': 'armande.hruser@example.com',
|
||||
'groups_id': [(6, 0, [self.group_employee_id, self.group_hr_user_ref_id])]
|
||||
}, {'no_reset_password': True})
|
||||
self.user_hrmanager_id = self.res_users.create(cr, uid, {
|
||||
'name': 'Bastien HrManager',
|
||||
'login': 'bastien',
|
||||
'alias_name': 'bastien',
|
||||
'email': 'bastien.hrmanager@example.com',
|
||||
'groups_id': [(6, 0, [self.group_employee_id, self.group_hr_manager_ref_id])]
|
||||
}, {'no_reset_password': True})
|
||||
self.user_none_id = self.res_users.create(cr, uid, {
|
||||
'name': 'Charlie Avotbonkeur',
|
||||
'login': 'charlie',
|
||||
'alias_name': 'charlie',
|
||||
'email': 'charlie.noone@example.com',
|
||||
'groups_id': [(6, 0, [])]
|
||||
}, {'no_reset_password': True})
|
||||
self.user_employee_id = self.res_users.create(cr, uid, {
|
||||
'name': 'David Employee',
|
||||
'login': 'david',
|
||||
'alias_name': 'david',
|
||||
'email': 'david.employee@example.com',
|
||||
'groups_id': [(6, 0, [self.group_employee_id])]
|
||||
}, {'no_reset_password': True})
|
||||
|
||||
# Hr Data
|
||||
self.employee_emp_id = self.hr_employee.create(cr, uid, {
|
||||
'name': 'David Employee',
|
||||
'user_id': self.user_employee_id,
|
||||
})
|
||||
self.employee_hruser_id = self.hr_employee.create(cr, uid, {
|
||||
'name': 'Armande HrUser',
|
||||
'user_id': self.user_hruser_id,
|
||||
})
|
||||
@@ -0,0 +1,207 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (c) 2013-TODAY OpenERP S.A. <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 datetime import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from openerp.addons.hr_holidays.tests.common import TestHrHolidaysBase
|
||||
from openerp.osv.orm import except_orm
|
||||
from openerp.tools import mute_logger
|
||||
|
||||
|
||||
class TestHolidaysFlow(TestHrHolidaysBase):
|
||||
|
||||
@mute_logger('openerp.addons.base.ir.ir_model', 'openerp.osv.orm')
|
||||
def test_00_leave_request_flow(self):
|
||||
""" Testing leave request flow """
|
||||
cr, uid = self.cr, self.uid
|
||||
|
||||
def _check_holidays_status(holiday_status, ml, lt, rl, vrl):
|
||||
self.assertEqual(holiday_status.max_leaves, ml,
|
||||
'hr_holidays: wrong type days computation')
|
||||
self.assertEqual(holiday_status.leaves_taken, lt,
|
||||
'hr_holidays: wrong type days computation')
|
||||
self.assertEqual(holiday_status.remaining_leaves, rl,
|
||||
'hr_holidays: wrong type days computation')
|
||||
self.assertEqual(holiday_status.virtual_remaining_leaves, vrl,
|
||||
'hr_holidays: wrong type days computation')
|
||||
|
||||
# HrUser creates some holiday statuses -> crash because only HrManagers should do this
|
||||
with self.assertRaises(except_orm):
|
||||
self.holidays_status_dummy = self.hr_holidays_status.create(cr, self.user_hruser_id, {
|
||||
'name': 'UserCheats',
|
||||
'limit': True,
|
||||
})
|
||||
|
||||
# HrManager creates some holiday statuses
|
||||
self.holidays_status_0 = self.hr_holidays_status.create(cr, self.user_hrmanager_id, {
|
||||
'name': 'WithMeetingType',
|
||||
'limit': True,
|
||||
'categ_id': self.registry('crm.meeting.type').create(cr, self.user_hrmanager_id, {'name': 'NotLimitedMeetingType'}),
|
||||
})
|
||||
self.holidays_status_1 = self.hr_holidays_status.create(cr, self.user_hrmanager_id, {
|
||||
'name': 'NotLimited',
|
||||
'limit': True,
|
||||
})
|
||||
self.holidays_status_2 = self.hr_holidays_status.create(cr, self.user_hrmanager_id, {
|
||||
'name': 'Limited',
|
||||
'limit': False,
|
||||
'double_validation': True,
|
||||
})
|
||||
|
||||
# --------------------------------------------------
|
||||
# Case1: unlimited type of leave request
|
||||
# --------------------------------------------------
|
||||
|
||||
# Employee creates a leave request for another employee -> should crash
|
||||
with self.assertRaises(except_orm):
|
||||
self.hr_holidays.create(cr, self.user_employee_id, {
|
||||
'name': 'Hol10',
|
||||
'employee_id': self.employee_hruser_id,
|
||||
'holiday_status_id': self.holidays_status_1,
|
||||
'date_from': (datetime.today() - relativedelta(days=1)),
|
||||
'date_to': datetime.today(),
|
||||
'number_of_days_temp': 1,
|
||||
})
|
||||
|
||||
# Employee creates a leave request in a no-limit category
|
||||
hol1_id = self.hr_holidays.create(cr, self.user_employee_id, {
|
||||
'name': 'Hol11',
|
||||
'employee_id': self.employee_emp_id,
|
||||
'holiday_status_id': self.holidays_status_1,
|
||||
'date_from': (datetime.today() - relativedelta(days=1)),
|
||||
'date_to': datetime.today(),
|
||||
'number_of_days_temp': 1,
|
||||
})
|
||||
hol1 = self.hr_holidays.browse(cr, self.user_hruser_id, hol1_id)
|
||||
self.assertEqual(hol1.state, 'confirm', 'hr_holidays: newly created leave request should be in confirm state')
|
||||
|
||||
# Employee validates its leave request -> should not work
|
||||
self.hr_holidays.signal_validate(cr, self.user_employee_id, [hol1_id])
|
||||
hol1.refresh()
|
||||
self.assertEqual(hol1.state, 'confirm', 'hr_holidays: employee should not be able to validate its own leave request')
|
||||
|
||||
# HrUser validates the employee leave request
|
||||
self.hr_holidays.signal_validate(cr, self.user_hrmanager_id, [hol1_id])
|
||||
hol1.refresh()
|
||||
self.assertEqual(hol1.state, 'validate', 'hr_holidays: validates leave request should be in validate state')
|
||||
|
||||
# --------------------------------------------------
|
||||
# Case2: limited type of leave request
|
||||
# --------------------------------------------------
|
||||
|
||||
# Employee creates a new leave request at the same time -> crash, avoid interlapping
|
||||
with self.assertRaises(except_orm):
|
||||
self.hr_holidays.create(cr, self.user_employee_id, {
|
||||
'name': 'Hol21',
|
||||
'employee_id': self.employee_emp_id,
|
||||
'holiday_status_id': self.holidays_status_1,
|
||||
'date_from': (datetime.today() - relativedelta(days=1)).strftime('%Y-%m-%d %H:%M'),
|
||||
'date_to': datetime.today(),
|
||||
'number_of_days_temp': 1,
|
||||
})
|
||||
|
||||
# Employee creates a leave request in a limited category -> crash, not enough days left
|
||||
with self.assertRaises(except_orm):
|
||||
self.hr_holidays.create(cr, self.user_employee_id, {
|
||||
'name': 'Hol22',
|
||||
'employee_id': self.employee_emp_id,
|
||||
'holiday_status_id': self.holidays_status_2,
|
||||
'date_from': (datetime.today() + relativedelta(days=0)).strftime('%Y-%m-%d %H:%M'),
|
||||
'date_to': (datetime.today() + relativedelta(days=1)),
|
||||
'number_of_days_temp': 1,
|
||||
})
|
||||
|
||||
# Clean transaction
|
||||
self.hr_holidays.unlink(cr, uid, self.hr_holidays.search(cr, uid, [('name', 'in', ['Hol21', 'Hol22'])]))
|
||||
|
||||
# HrUser allocates some leaves to the employee
|
||||
aloc1_id = self.hr_holidays.create(cr, self.user_hruser_id, {
|
||||
'name': 'Days for limited category',
|
||||
'employee_id': self.employee_emp_id,
|
||||
'holiday_status_id': self.holidays_status_2,
|
||||
'type': 'add',
|
||||
'number_of_days_temp': 2,
|
||||
})
|
||||
# HrUser validates the allocation request
|
||||
self.hr_holidays.signal_validate(cr, self.user_hruser_id, [aloc1_id])
|
||||
self.hr_holidays.signal_second_validate(cr, self.user_hruser_id, [aloc1_id])
|
||||
# Checks Employee has effectively some days left
|
||||
hol_status_2 = self.hr_holidays_status.browse(cr, self.user_employee_id, self.holidays_status_2)
|
||||
_check_holidays_status(hol_status_2, 2.0, 0.0, 2.0, 2.0)
|
||||
|
||||
# Employee creates a leave request in the limited category, now that he has some days left
|
||||
hol2_id = self.hr_holidays.create(cr, self.user_employee_id, {
|
||||
'name': 'Hol22',
|
||||
'employee_id': self.employee_emp_id,
|
||||
'holiday_status_id': self.holidays_status_2,
|
||||
'date_from': (datetime.today() + relativedelta(days=2)).strftime('%Y-%m-%d %H:%M'),
|
||||
'date_to': (datetime.today() + relativedelta(days=3)),
|
||||
'number_of_days_temp': 1,
|
||||
})
|
||||
hol2 = self.hr_holidays.browse(cr, self.user_hruser_id, hol2_id)
|
||||
# Check left days: - 1 virtual remaining day
|
||||
hol_status_2.refresh()
|
||||
_check_holidays_status(hol_status_2, 2.0, 0.0, 2.0, 1.0)
|
||||
|
||||
# HrUser validates the first step
|
||||
self.hr_holidays.signal_validate(cr, self.user_hruser_id, [hol2_id])
|
||||
hol2.refresh()
|
||||
self.assertEqual(hol2.state, 'validate1',
|
||||
'hr_holidays: first validation should lead to validate1 state')
|
||||
|
||||
# HrUser validates the second step
|
||||
self.hr_holidays.signal_second_validate(cr, self.user_hruser_id, [hol2_id])
|
||||
hol2.refresh()
|
||||
self.assertEqual(hol2.state, 'validate',
|
||||
'hr_holidays: second validation should lead to validate state')
|
||||
# Check left days: - 1 day taken
|
||||
hol_status_2.refresh()
|
||||
_check_holidays_status(hol_status_2, 2.0, 1.0, 1.0, 1.0)
|
||||
|
||||
# HrManager finds an error: he refuses the leave request
|
||||
self.hr_holidays.signal_refuse(cr, self.user_hrmanager_id, [hol2_id])
|
||||
hol2.refresh()
|
||||
self.assertEqual(hol2.state, 'refuse',
|
||||
'hr_holidays: refuse should lead to refuse state')
|
||||
# Check left days: 2 days left again
|
||||
hol_status_2.refresh()
|
||||
_check_holidays_status(hol_status_2, 2.0, 0.0, 2.0, 2.0)
|
||||
|
||||
# Annoyed, HrUser tries to fix its error and tries to reset the leave request -> does not work, only HrManager
|
||||
self.hr_holidays.signal_reset(cr, self.user_hruser_id, [hol2_id])
|
||||
self.assertEqual(hol2.state, 'refuse',
|
||||
'hr_holidays: hr_user should not be able to reset a refused leave request')
|
||||
|
||||
# HrManager resets the request
|
||||
self.hr_holidays.signal_reset(cr, self.user_hrmanager_id, [hol2_id])
|
||||
hol2.refresh()
|
||||
self.assertEqual(hol2.state, 'draft',
|
||||
'hr_holidays: resetting should lead to draft state')
|
||||
|
||||
# HrManager changes the date and put too much days -> crash when confirming
|
||||
self.hr_holidays.write(cr, self.user_hrmanager_id, [hol2_id], {
|
||||
'date_from': (datetime.today() + relativedelta(days=4)).strftime('%Y-%m-%d %H:%M'),
|
||||
'date_to': (datetime.today() + relativedelta(days=7)),
|
||||
'number_of_days_temp': 4,
|
||||
})
|
||||
with self.assertRaises(except_orm):
|
||||
self.hr_holidays.signal_confirm(cr, self.user_hrmanager_id, [hol2_id])
|
||||
@@ -7,19 +7,19 @@ msgstr ""
|
||||
"Project-Id-Version: OpenERP Server 6.0dev\n"
|
||||
"Report-Msgid-Bugs-To: support@openerp.com\n"
|
||||
"POT-Creation-Date: 2012-12-21 17:04+0000\n"
|
||||
"PO-Revision-Date: 2013-08-06 09:36+0000\n"
|
||||
"PO-Revision-Date: 2013-08-13 04:04+0000\n"
|
||||
"Last-Translator: Wei \"oldrev\" Li <oldrev@gmail.com>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2013-08-07 04:46+0000\n"
|
||||
"X-Generator: Launchpad (build 16721)\n"
|
||||
"X-Launchpad-Export-Date: 2013-08-14 04:54+0000\n"
|
||||
"X-Generator: Launchpad (build 16723)\n"
|
||||
|
||||
#. module: project
|
||||
#: view:project.project:0
|
||||
msgid "Email Interface"
|
||||
msgstr "Email 接口"
|
||||
msgstr "电子邮件接口"
|
||||
|
||||
#. module: project
|
||||
#: help:account.analytic.account,use_tasks:0
|
||||
@@ -69,7 +69,7 @@ msgstr "基于该模板新建项目"
|
||||
#: view:report.project.task.user:0
|
||||
#: field:report.project.task.user,day:0
|
||||
msgid "Day"
|
||||
msgstr "天"
|
||||
msgstr "日"
|
||||
|
||||
#. module: project
|
||||
#: model:project.task.type,name:project.project_tt_merge
|
||||
|
||||
@@ -560,7 +560,6 @@ class task(osv.osv):
|
||||
}
|
||||
|
||||
def _get_default_partner(self, cr, uid, context=None):
|
||||
""" Override of base_stage to add project specific behavior """
|
||||
project_id = self._get_default_project_id(cr, uid, context)
|
||||
if project_id:
|
||||
project = self.pool.get('project.project').browse(cr, uid, project_id, context=context)
|
||||
|
||||
@@ -60,7 +60,6 @@ class project_issue(osv.Model):
|
||||
}
|
||||
|
||||
def _get_default_partner(self, cr, uid, context=None):
|
||||
""" Override of base_stage to add project specific behavior """
|
||||
project_id = self._get_default_project_id(cr, uid, context)
|
||||
if project_id:
|
||||
project = self.pool.get('project.project').browse(cr, uid, project_id, context=context)
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
<field name="arch" type="xml">
|
||||
<data>
|
||||
<xpath expr="/form/header/button[@name='case_mark_lost']" position="after">
|
||||
<button states="done" string="Create Quotation" name="%(action_crm_make_sale)d" type="action"/>
|
||||
<button states="draft,open,pending" string="Convert to Quotation" name="%(action_crm_make_sale)d" type="action" class="oe_highlight"/>
|
||||
<button attrs="{'invisible': [('probability', '<', 100)]}" string="Create Quotation" name="%(action_crm_make_sale)d" type="action"/>
|
||||
<button attrs="{'invisible': [('probability', '=', 100)]}" string="Convert to Quotation" name="%(action_crm_make_sale)d" type="action" class="oe_highlight"/>
|
||||
</xpath>
|
||||
</data>
|
||||
</field>
|
||||
|
||||
@@ -112,7 +112,7 @@ class crm_make_sale(osv.osv_memory):
|
||||
message = _("Opportunity has been <b>converted</b> to the quotation <em>%s</em>.") % (sale_order.name)
|
||||
case.message_post(body=message)
|
||||
if make.close:
|
||||
case_obj.case_close(cr, uid, data)
|
||||
case_obj.case_mark_won(cr, uid, data, context=context)
|
||||
if not new_ids:
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
if len(new_ids)<=1:
|
||||
|
||||
Reference in New Issue
Block a user