[MERGE] with trunk.

bzr revid: nco@tinyerp.com-20130605065435-pwoa0dbany8pecu7
This commit is contained in:
Nimesh Contractor (OpenERP) 2013-06-05 12:24:35 +05:30
commit d307d7bd9b
47 changed files with 4638 additions and 267 deletions

View File

@ -23,6 +23,7 @@ import random
from urllib import urlencode
from urlparse import urljoin
from openerp.addons.base.ir.ir_mail_server import MailDeliveryException
from openerp.osv import osv, fields
from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT
from openerp.tools.safe_eval import safe_eval
@ -103,6 +104,9 @@ class res_partner(osv.Model):
def action_signup_prepare(self, cr, uid, ids, context=None):
return self.signup_prepare(cr, uid, ids, context=context)
def signup_cancel(self, cr, uid, ids, context=None):
return self.write(cr, uid, ids, {'signup_token': False, 'signup_type': False, 'signup_expiration': False}, context=context)
def signup_prepare(self, cr, uid, ids, signup_type="signup", expiration=False, context=None):
""" generate a new token for the partners with the given validity, if necessary
:param expiration: the expiration datetime of the token (string, optional)
@ -202,7 +206,7 @@ class res_users(osv.Model):
})
if partner.company_id:
values['company_id'] = partner.company_id.id
values['company_ids'] = [(6,0,[partner.company_id.id])]
values['company_ids'] = [(6, 0, [partner.company_id.id])]
self._signup_create_user(cr, uid, values, context=context)
else:
# no token, sign up an external user
@ -259,25 +263,26 @@ class res_users(osv.Model):
pass
if not bool(template):
template = self.pool.get('ir.model.data').get_object(cr, uid, 'auth_signup', 'reset_password_email')
mail_obj = self.pool.get('mail.mail')
assert template._name == 'email.template'
for user in self.browse(cr, uid, ids, context):
if not user.email:
raise osv.except_osv(_("Cannot send email: user has no email address."), user.name)
mail_id = self.pool.get('email.template').send_mail(cr, uid, template.id, user.id, True, context=context)
mail_state = mail_obj.read(cr, uid, mail_id, ['state'], context=context)
if mail_state and mail_state['state'] == 'exception':
raise self.pool.get('res.config.settings').get_config_warning(cr, _("Cannot send email: no outgoing email server configured.\nYou can configure it under %(menu:base_setup.menu_general_configuration)s."), context)
else:
return True
try:
self.pool.get('email.template').send_mail(cr, uid, template.id, user.id, force_send=True, raise_exception=True, context=context)
except Exception:
raise
def create(self, cr, uid, values, context=None):
if context is None:
context = {}
# overridden to automatically invite user to sign up
user_id = super(res_users, self).create(cr, uid, values, context=context)
user = self.browse(cr, uid, user_id, context=context)
if context and context.get('reset_password') and user.email:
ctx = dict(context, create_user=True)
self.action_reset_password(cr, uid, [user.id], context=ctx)
if user.email and not context.get('no_reset_password'):
context.update({'create_user': True})
try:
self.action_reset_password(cr, uid, [user.id], context=context)
except MailDeliveryException:
self.pool.get('res.partner').signup_cancel(cr, uid, [user.partner_id.id], context=context)
return user_id

View File

@ -31,9 +31,11 @@
<!-- add Reset Password button -->
<xpath expr="//div[@class='oe_right oe_button_box']//button" position="replace">
<button string="Send Reset Password Instructions"
class="oe_link"
type="object" name="action_reset_password"
attrs="{'invisible': [('state', '!=', 'active')]}"/>
<button string="Send an Invitation Email"
class="oe_link"
type="object" name="action_reset_password" context="{'create_user': 1}"
attrs="{'invisible': [('state', '!=', 'new')]}"/>
</xpath>

View File

@ -50,6 +50,7 @@ class base_action_rule(osv.osv):
_name = 'base.action.rule'
_description = 'Action Rules'
_order = 'sequence'
_columns = {
'name': fields.char('Rule Name', size=64, required=True),
@ -61,6 +62,9 @@ class base_action_rule(osv.osv):
help="When unchecked, the rule is hidden and will not be executed."),
'sequence': fields.integer('Sequence',
help="Gives the sequence order when displaying a list of rules."),
'kind': fields.selection(
[('on_create', 'On Creation'), ('on_write', 'On Update'), ('on_time', 'Based on Timed Condition')],
string='When to Run'),
'trg_date_id': fields.many2one('ir.model.fields', string='Trigger Date',
domain="[('model_id', '=', model_id), ('ttype', 'in', ('date', 'datetime'))]"),
'trg_date_range': fields.integer('Delay after trigger date',
@ -78,10 +82,10 @@ class base_action_rule(osv.osv):
ondelete='restrict',
domain="[('model_id', '=', model_id.model)]",
help="If present, this condition must be satisfied before the update of the record."),
'filter_id': fields.many2one('ir.filters', string='After Update Filter',
'filter_id': fields.many2one('ir.filters', string='Filter',
ondelete='restrict',
domain="[('model_id', '=', model_id.model)]",
help="If present, this condition must be satisfied after the update of the record."),
help="If present, this condition must be satisfied before executing the action rule."),
'last_run': fields.datetime('Last Run', readonly=1),
}
@ -90,7 +94,15 @@ class base_action_rule(osv.osv):
'trg_date_range_type': 'day',
}
_order = 'sequence'
def onchange_kind(self, cr, uid, ids, kind, context=None):
clear_fields = []
if kind == 'on_create':
clear_fields = ['filter_pre_id', 'trg_date_id', 'trg_date_range', 'trg_date_range_type']
elif kind == 'on_write':
clear_fields = ['trg_date_id', 'trg_date_range', 'trg_date_range_type']
elif kind == 'on_time':
clear_fields = ['filter_pre_id']
return {'value': dict.fromkeys(clear_fields, False)}
def _filter(self, cr, uid, action, action_filter, record_ids, context=None):
""" filter the list record_ids that satisfy the action filter """
@ -105,14 +117,7 @@ class base_action_rule(osv.osv):
def _process(self, cr, uid, action, record_ids, context=None):
""" process the given action on the records """
# execute server actions
model = self.pool[action.model_id.model]
if action.server_action_ids:
server_action_ids = map(int, action.server_action_ids)
for record in model.browse(cr, uid, record_ids, context):
action_server_obj = self.pool.get('ir.actions.server')
ctx = dict(context, active_model=model._name, active_ids=[record.id], active_id=record.id)
action_server_obj.run(cr, uid, server_action_ids, context=ctx)
# modify records
values = {}
@ -127,13 +132,21 @@ class base_action_rule(osv.osv):
follower_ids = map(int, action.act_followers)
model.message_subscribe(cr, uid, record_ids, follower_ids, context=context)
# execute server actions
if action.server_action_ids:
server_action_ids = map(int, action.server_action_ids)
for record in model.browse(cr, uid, record_ids, context):
action_server_obj = self.pool.get('ir.actions.server')
ctx = dict(context, active_model=model._name, active_ids=[record.id], active_id=record.id)
action_server_obj.run(cr, uid, server_action_ids, context=ctx)
return True
def _wrap_create(self, old_create, model):
""" Return a wrapper around `old_create` calling both `old_create` and
`_process`, in that order.
"""
def wrapper(cr, uid, vals, context=None):
def create(cr, uid, vals, context=None):
# avoid loops or cascading actions
if context and context.get('action'):
return old_create(cr, uid, vals, context=context)
@ -141,8 +154,8 @@ class base_action_rule(osv.osv):
context = dict(context or {}, action=True)
new_id = old_create(cr, uid, vals, context=context)
# as it is a new record, we do not consider the actions that have a prefilter
action_dom = [('model', '=', model), ('trg_date_id', '=', False), ('filter_pre_id', '=', False)]
# retrieve the action rules to run on creation
action_dom = [('model', '=', model), ('kind', '=', 'on_create')]
action_ids = self.search(cr, uid, action_dom, context=context)
# check postconditions, and execute actions on the records that satisfy them
@ -151,13 +164,13 @@ class base_action_rule(osv.osv):
self._process(cr, uid, action, [new_id], context=context)
return new_id
return wrapper
return create
def _wrap_write(self, old_write, model):
""" Return a wrapper around `old_write` calling both `old_write` and
`_process`, in that order.
"""
def wrapper(cr, uid, ids, vals, context=None):
def write(cr, uid, ids, vals, context=None):
# avoid loops or cascading actions
if context and context.get('action'):
return old_write(cr, uid, ids, vals, context=context)
@ -165,8 +178,8 @@ class base_action_rule(osv.osv):
context = dict(context or {}, action=True)
ids = [ids] if isinstance(ids, (int, long, str)) else ids
# retrieve the action rules to possibly execute
action_dom = [('model', '=', model), ('trg_date_id', '=', False)]
# retrieve the action rules to run on update
action_dom = [('model', '=', model), ('kind', '=', 'on_write')]
action_ids = self.search(cr, uid, action_dom, context=context)
actions = self.browse(cr, uid, action_ids, context=context)
@ -185,7 +198,7 @@ class base_action_rule(osv.osv):
self._process(cr, uid, action, post_ids, context=context)
return True
return wrapper
return write
def _register_hook(self, cr, ids=None):
""" Wrap the methods `create` and `write` of the models specified by
@ -224,8 +237,8 @@ class base_action_rule(osv.osv):
def _check(self, cr, uid, automatic=False, use_new_cursor=False, context=None):
""" This Function is called by scheduler. """
context = context or {}
# retrieve all the action rules that have a trg_date_id and no precondition
action_dom = [('trg_date_id', '!=', False), ('filter_pre_id', '=', False)]
# retrieve all the action rules to run based on a timed condition
action_dom = [('kind', '=', 'on_time')]
action_ids = self.search(cr, uid, action_dom, context=context)
for action in self.browse(cr, uid, action_ids, context=context):
now = datetime.now()

View File

@ -26,24 +26,32 @@
<notebook>
<page string="Conditions">
<group>
<group name="filter" string="Filter Condition">
<field name="filter_pre_id" domain="[('model_id','=',model)]" context="{'default_model_id': model}"/>
<field name="filter_id" domain="[('model_id','=',model)]" context="{'default_model_id': model}"/>
</group>
<group name="timing" string="Timer">
<field name="trg_date_id"/>
<label for="trg_date_range" string="Delay After Trigger Date" attrs="{'invisible': [('trg_date_id','=',False)]}"/>
<div attrs="{'invisible': [('trg_date_id','=',False)]}">
<field name="trg_date_range" class="oe_inline"/>
<field name="trg_date_range_type" class="oe_inline" attrs="{'required': [('trg_date_id','!=',False)]}"/>
</div>
</group>
<field name="kind" required="1"
on_change="onchange_kind(kind)"/>
<field name="filter_pre_id"
domain="[('model_id','=',model), ('user_id', '=', False)]"
context="{'default_model_id': model}"
attrs="{'invisible': [('kind', '!=', 'on_write')]}"/>
<field name="filter_id"
domain="[('model_id','=',model), ('user_id', '=', False)]"
context="{'default_model_id': model}"/>
<field name="trg_date_id"
attrs="{'invisible': [('kind', '!=', 'on_time')], 'required': [('kind', '=', 'on_time')]}"/>
<label for="trg_date_range" string="Delay After Trigger Date"
attrs="{'invisible': [('trg_date_id','=',False)]}"/>
<div attrs="{'invisible': [('trg_date_id','=',False)]}">
<field name="trg_date_range" class="oe_inline"/>
<field name="trg_date_range_type" class="oe_inline" attrs="{'required': [('trg_date_id','!=',False)]}"/>
</div>
</group>
<p class="oe_grey">
<b>Select a filter or a timer as condition.</b><br/> An action rule is checked when you create or modify the "Related Document Model". The precondition filter is checked right before the modification while the postcondition filter is checked after the modification. A precondition filter will therefore not work during a creation.<br/>
<b>To create a new filter:</b><br/>
- Go to your "Related Document Model" page and set the filter parameters in the "Search" view (Example of filter based on Leads/Opportunities: Creation Date "is equal to" 01/01/2012)<br/>
- In this same "Search" view, select the menu "Save Current Filter", enter the name (Ex: Create the 01/01/2012) and add the option "Share with all users"<br/>
<p>
Select when the action must be run, and add filters and/or timing conditions.
<br/>
In order to create a new filter:
<ul>
<li>Go to your "Related Document Model" page and set the filter parameters in the "Search" view (Example of filter based on Leads/Opportunities: Creation Date "is equal to" 01/01/2012)</li>
<li>In this same "Search" view, select the menu "Save Current Filter", enter the name (Ex: Create the 01/01/2012) and add the option "Share with all users"</li>
</ul>
The filter must therefore be available in this page.
</p>
</page>
@ -76,6 +84,7 @@
<tree string="Action Rule">
<field name="sequence"/>
<field name="name"/>
<field name="kind"/>
<field name="filter_id"/>
</tree>
</field>

View File

@ -19,8 +19,8 @@ class base_action_rule_test(common.TransactionCase):
'name': "Lead is in done state",
'is_default': False,
'model_id': 'base.action.rule.lead.test',
'domain' : "[('state','=','done')]",
}, context=context)
'domain': "[('state','=','done')]",
}, context=context)
def create_filter_draft(self, cr, uid, context=None):
filter_pool = self.registry('ir.filters')
@ -40,16 +40,16 @@ class base_action_rule_test(common.TransactionCase):
'user_id': self.admin,
}, context=context)
def create_rule(self, cr, uid, filter_id=False, filter_pre_id=False, context=None):
def create_rule(self, cr, uid, kind, filter_id=False, filter_pre_id=False, context=None):
"""
The "Rule 1" says that when a lead goes to the 'draft' state, the responsible for that lead changes to user "demo"
"""
return self.base_action_rule.create(cr,uid,{
'name' : "Rule 1",
'name': "Rule 1",
'model_id': self.registry('ir.model').search(cr, uid, [('model','=','base.action.rule.lead.test')], context=context)[0],
'active' : 1,
'filter_pre_id' : filter_pre_id,
'filter_id' : filter_id,
'kind': kind,
'filter_pre_id': filter_pre_id,
'filter_id': filter_id,
'act_user_id': self.demo,
}, context=context)
@ -64,7 +64,7 @@ class base_action_rule_test(common.TransactionCase):
"""
cr, uid = self.cr, self.uid
filter_draft = self.create_filter_draft(cr, uid)
self.create_rule(cr, uid, filter_pre_id=filter_draft)
self.create_rule(cr, uid, 'on_write', filter_pre_id=filter_draft)
new_lead_id = self.create_lead_test_1(cr, uid)
new_lead = self.model.browse(cr, uid, new_lead_id)
self.assertEquals(new_lead.state, 'draft')
@ -73,11 +73,11 @@ class base_action_rule_test(common.TransactionCase):
def test_01_check_to_state_draft_post(self):
"""
Check that a new record (with state = draft) changes its responsible when there is a postcondition filter which check that the state is draft.
Check that a new record changes its responsible when there is a postcondition filter which check that the state is draft.
"""
cr, uid = self.cr, self.uid
filter_draft = self.create_filter_draft(cr, uid)
self.create_rule(cr, uid, filter_id=filter_draft)
self.create_rule(cr, uid, 'on_create')
new_lead_id = self.create_lead_test_1(cr, uid)
new_lead = self.model.browse(cr, uid, new_lead_id)
self.assertEquals(new_lead.state, 'draft')
@ -95,7 +95,7 @@ class base_action_rule_test(common.TransactionCase):
cr, uid = self.cr, self.uid
filter_draft = self.create_filter_draft(cr, uid)
filter_done = self.create_filter_done(cr, uid)
self.create_rule(cr, uid, filter_pre_id=filter_draft, filter_id=filter_done)
self.create_rule(cr, uid, 'on_write', filter_pre_id=filter_draft, filter_id=filter_done)
new_lead_id = self.create_lead_test_1(cr, uid)
new_lead = self.model.browse(cr, uid, new_lead_id)
self.assertEquals(new_lead.state, 'draft')
@ -133,7 +133,7 @@ class base_action_rule_test(common.TransactionCase):
cr, uid = self.cr, self.uid
filter_draft = self.create_filter_draft(cr, uid)
filter_done = self.create_filter_done(cr, uid)
self.create_rule(cr, uid, filter_pre_id=filter_draft, filter_id=filter_done)
self.create_rule(cr, uid, 'on_write', filter_pre_id=filter_draft, filter_id=filter_done)
new_lead_id = self.create_lead_test_1(cr, uid)
new_lead = self.model.browse(cr, uid, new_lead_id)
self.assertEquals(new_lead.state, 'draft')

View File

@ -81,8 +81,6 @@ Dashboard for CRM will include:
'crm_lead_view.xml',
'crm_lead_menu.xml',
'crm_case_section_view.xml',
'crm_meeting_menu.xml',
'crm_phonecall_view.xml',
@ -98,6 +96,8 @@ Dashboard for CRM will include:
'res_config_view.xml',
'base_partner_merge_view.xml',
'crm_case_section_view.xml',
],
'demo': [
'crm_demo.xml',
@ -121,7 +121,8 @@ Dashboard for CRM will include:
'static/src/css/crm.css'
],
'js': [
'static/src/js/crm.js'
'static/lib/sparkline/jquery.sparkline.js',
'static/src/js/crm_case_section.js',
],
'installable': True,
'application': True,

View File

@ -19,13 +19,12 @@
#
##############################################################################
import base64
import time
from lxml import etree
from datetime import date, datetime
from dateutil import relativedelta
from openerp import tools
from openerp.osv import fields
from openerp.osv import osv
from openerp import tools
from openerp.tools.translate import _
MAX_LEVEL = 15
AVAILABLE_STATES = [
@ -106,10 +105,57 @@ class crm_case_section(osv.osv):
_inherit = "mail.thread"
_description = "Sales Teams"
_order = "complete_name"
# number of periods for lead/opportunities/... tracking in salesteam kanban dashboard/kanban view
_period_number = 5
def get_full_name(self, cr, uid, ids, field_name, arg, context=None):
return dict(self.name_get(cr, uid, ids, context=context))
def __get_bar_values(self, cr, uid, obj, domain, read_fields, value_field, groupby_field, context=None):
""" Generic method to generate data for bar chart values using SparklineBarWidget.
This method performs obj.read_group(cr, uid, domain, read_fields, groupby_field).
:param obj: the target model (i.e. crm_lead)
:param domain: the domain applied to the read_group
:param list read_fields: the list of fields to read in the read_group
:param str value_field: the field used to compute the value of the bar slice
:param str groupby_field: the fields used to group
:return list section_result: a list of dicts: [
{ 'value': (int) bar_column_value,
'tootip': (str) bar_column_tooltip,
}
]
"""
month_begin = date.today().replace(day=1)
section_result = [{
'value': 0,
'tooltip': (month_begin + relativedelta.relativedelta(months=-i)).strftime('%B'),
} for i in range(self._period_number - 1, -1, -1)]
group_obj = obj.read_group(cr, uid, domain, read_fields, groupby_field, context=context)
for group in group_obj:
group_begin_date = datetime.strptime(group['__domain'][0][2], tools.DEFAULT_SERVER_DATE_FORMAT)
month_delta = relativedelta.relativedelta(month_begin, group_begin_date)
section_result[self._period_number - (month_delta.months + 1)] = {'value': group.get(value_field, 0), 'tooltip': group_begin_date.strftime('%B')}
return section_result
def _get_opportunities_data(self, cr, uid, ids, field_name, arg, context=None):
""" Get opportunities-related data for salesteam kanban view
monthly_open_leads: number of open lead during the last months
monthly_planned_revenue: planned revenu of opportunities during the last months
"""
obj = self.pool.get('crm.lead')
res = dict.fromkeys(ids, False)
month_begin = date.today().replace(day=1)
groupby_begin = (month_begin + relativedelta.relativedelta(months=-4)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
for id in ids:
res[id] = dict()
lead_domain = [('type', '=', 'lead'), ('section_id', '=', id), ('create_date', '>=', groupby_begin)]
res[id]['monthly_open_leads'] = self.__get_bar_values(cr, uid, obj, lead_domain, ['create_date'], 'create_date_count', 'create_date', context=context)
opp_domain = [('type', '=', 'opportunity'), ('section_id', '=', id), ('create_date', '>=', groupby_begin)]
res[id]['monthly_planned_revenue'] = self.__get_bar_values(cr, uid, obj, opp_domain, ['planned_revenue', 'create_date'], 'planned_revenue', 'create_date', context=context)
return res
_columns = {
'name': fields.char('Sales Team', size=64, required=True, translate=True),
'complete_name': fields.function(get_full_name, type='char', size=256, readonly=True, store=True),
@ -129,15 +175,16 @@ class crm_case_section(osv.osv):
'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="cascade", required=True,
help="The email address associated with this team. New emails received will automatically "
"create new leads assigned to the team."),
'open_lead_ids': fields.one2many('crm.lead', 'section_id',
string='Open Leads', readonly=True,
domain=['&', ('type', '!=', 'opportunity'), ('state', 'not in', ['done', 'cancel'])]),
'open_opportunity_ids': fields.one2many('crm.lead', 'section_id',
string='Open Opportunities', readonly=True,
domain=['&', '|', ('type', '=', 'opportunity'), ('type', '=', 'both'), ('state', 'not in', ['done', 'cancel'])]),
'color': fields.integer('Color Index'),
'use_leads': fields.boolean('Leads',
help="This enables the management of leads in the sales team. Otherwise the sales team manages only opportunities."),
help="The first contact you get with a potential customer is a lead you qualify before converting it into a real business opportunity. Check this box to manage leads in this sales team."),
'monthly_open_leads': fields.function(_get_opportunities_data,
type="string", readonly=True, multi='_get_opportunities_data',
string='Open Leads per Month'),
'monthly_planned_revenue': fields.function(_get_opportunities_data,
type="string", readonly=True, multi='_get_opportunities_data',
string='Planned Revenue per Month')
}
def _get_stage_common(self, cr, uid, context):

View File

@ -26,6 +26,7 @@ Description: [[object.description]]
<field name="name">Set Auto Reminder on leads which are not open since 5 days.</field>
<field name="model_id" ref="model_crm_lead"/>
<field name="sequence">1</field>
<field name="kind">on_time</field>
<field name="filter_id" ref="filter_draft_lead"/>
<field name="trg_date_id" ref="field_crm_lead_create_date"/>
<field name="trg_date_range">5</field>
@ -54,12 +55,10 @@ object.write({'section_id': sales_team.id})
<field name="name">Set Auto Followers on leads which are urgent and come from USA.</field>
<field name="model_id" ref="model_crm_lead"/>
<field name="sequence">2</field>
<field name="kind">on_create</field>
<field name="filter_id" ref="filter_usa_lead"/>
<field name="act_followers" eval="[(6,0,[ref('base.res_partner_4'), ref('base.res_partner_5'), ref('base.res_partner_6')])]"/>
<field name="act_user_id" ref="base.user_root"/>
<field name="trg_date_id" ref="field_crm_lead_create_date"/>
<field name="trg_date_range">0</field>
<field name="trg_date_range_type">minutes</field>
<field name="act_followers" eval="[(6,0,[ref('base.res_partner_4'), ref('base.res_partner_5'), ref('base.res_partner_6')])]"/>
<field name="server_action_ids" eval="[(6,0,[ref('action_set_team_sales_department')])]"/>
</record>
</data>

View File

@ -78,12 +78,12 @@
<field name="note"/>
<field name="alias_id"/>
<field name="color"/>
<field name="open_lead_ids"/>
<field name="open_opportunity_ids"/>
<field name="monthly_open_leads"/>
<field name="monthly_planned_revenue"/>
<templates>
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_project oe_kanban_global_click oe_kanban_crm_salesteams">
<div class="oe_dropdown_toggle oe_dropdown_kanban" groups="base.group_user">
<div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_global_click oe_kanban_crm_salesteams">
<div class="oe_dropdown_toggle oe_dropdown_kanban" groups="base.group_sale_manager">
<span class="oe_e">í</span>
<ul class="oe_dropdown_menu">
<li t-if="widget.view.is_action_enabled('edit')"><a type="edit">Sales Teams Settings</a></li>
@ -92,25 +92,19 @@
</ul>
</div>
<div class="oe_kanban_content">
<h4><field name="name"/></h4>
<div class="oe_kanban_alias" t-if="record.use_leads.raw_value and record.alias_id.value">
<span class="oe_e">%%</span><small><field name="alias_id"/></small>
<h4 class="oe_center"><field name="name"/></h4>
<div class="oe_kanban_alias oe_center" t-if="record.use_leads.raw_value and record.alias_id.value">
<small><span class="oe_e" style="float: none;">%%</span><t t-raw="record.alias_id.raw_value[1]"/></small>
</div>
<div class="oe_items_list">
<a t-if="record.use_leads.raw_value" name="%(crm_case_form_view_salesteams_lead)d" type="action">
<t t-raw="record.open_lead_ids.raw_value.length"/>
<t t-if="record.open_lead_ids.raw_value.length &gt;= 2">Leads</t>
<t t-if="record.open_lead_ids.raw_value.length &lt; 2">Lead</t></a>
<a name="%(crm_case_form_view_salesteams_opportunity)d" type="action">
<t t-raw="record.open_opportunity_ids.raw_value.length"/>
<t t-if="record.open_opportunity_ids.raw_value.length &gt;= 2">Opportunities</t>
<t t-if="record.open_opportunity_ids.raw_value.length &lt; 2">Opportunity</t></a>
</div>
<div class="oe_avatars">
<img t-if="record.user_id.raw_value" t-att-src="kanban_image('res.users', 'image_small', record.user_id.raw_value)" t-att-data-member_id="record.user_id.raw_value"/>
<t t-foreach="record.member_ids.raw_value.slice(0,11)" t-as="member">
<img t-att-src="kanban_image('res.users', 'image_small', member)" t-att-data-member_id="member"/>
</t>
<div class="oe_salesteams_leads" t-if="record.use_leads.raw_value">
<a name="%(crm_case_form_view_salesteams_lead)d" type="action">Leads</a>
<a name="%(action_report_crm_lead)d" type="action" class="oe_sparkline_bar_link"><field name="monthly_open_leads" widget="sparkline_bar">Open Leads per Month<br/>Click to see a detailed analysis of leads.</field></a>
</div>
<div class="oe_salesteams_opportunities">
<a name="%(crm_case_form_view_salesteams_opportunity)d" type="action">Opportunities</a>
<a name="%(action_report_crm_opportunity)d" type="action"><field name="monthly_planned_revenue" widget="sparkline_bar">Planned Revenue per Month<br/>Click to see a detailed analysis of opportunities.</field></a>
</div>
</div>
</div>
</div>
@ -120,6 +114,27 @@
</field>
</record>
<!-- Case Sections Search view -->
<record id="crm_case_section_salesteams_search" model="ir.ui.view">
<field name="name">Case Sections - Search</field>
<field name="model">crm.case.section</field>
<field name="arch" type="xml">
<search string="Salesteams Search">
<field name="name"/>
<field name="parent_id"/>
<field name="user_id"/>
<field name="note"/>
<field name="code"/>
<filter name="personal" string="My Salesteams" domain="['|', ('member_ids', '=', uid), ('user_id', '=', uid)]"/>
<group expand="0" string="Group By...">
<filter string="Team Leader" domain="[]" context="{'group_by':'user_id'}"/>
<filter string="Parent Sales Teams" domain="[]" context="{'group_by':'parent_id'}"/>
</group>
</search>
</field>
</record>
<!-- Case Sections Action -->
<record id="crm_case_section_salesteams_act" model="ir.actions.act_window">
@ -127,6 +142,7 @@
<field name="res_model">crm.case.section</field>
<field name="view_type">form</field>
<field name="view_mode">kanban,tree,form</field>
<field name="context">{'search_default_personal': True}</field>
<field name="view_id" ref="crm_case_section_salesteams_view_kanban"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
@ -148,9 +164,9 @@
<form string="Sales Team" version="7.0">
<sheet>
<div class="oe_title">
<label for="name" class="oe_edit_only" string="Project Name"/>
<label for="name" class="oe_edit_only" string="Sales team"/>
<h1>
<field name="name" string="Project Name"/>
<field name="name" string="Salesteam"/>
</h1>
<div name="group_alias"
attrs="{'invisible': [('alias_domain', '=', False)]}">
@ -168,25 +184,18 @@
</div>
</div>
<group>
<group>
<field name="parent_id"/>
<field name="resource_calendar_id"/>
<field name="active"/>
</group>
<group>
<field name="user_id"/>
<field name="code"/>
</group>
<group colspan="4" attrs="{'invisible': [('use_leads', '=', False)]}">
<group>
<field name="parent_id"/>
<field name="change_responsible"/>
<field name="active"/>
</group>
</group>
<notebook colspan="4">
<page string="Sales Team">
<group>
<field name="change_responsible"/>
</group>
<separator string="Team Members"/>
<page string="Team Members">
<field name="member_ids" widget="many2many_kanban">
<kanban quick_create="false" create="true">
<field name="name"/>

View File

@ -27,9 +27,10 @@
</record>
<record model="crm.case.section" id="section_sales_department">
<field name="name">Sales</field>
<field name="code">Sales</field>
<field name="name">Direct Sales</field>
<field name="code">DM</field>
<field name="use_leads">True</field>
<field name="alias_name">info</field>
<field name="member_ids" eval="[(4, ref('base.user_root'))]"/>
</record>

View File

@ -7,27 +7,13 @@
</record>
<record model="crm.case.section" id="crm_case_section_1">
<field name="name">Sales Marketing Department</field>
<field name="code">SMD</field>
<field name="parent_id" ref="crm.section_sales_department"/>
<field name="name">Indirect Sales</field>
<field name="code">IM</field>
</record>
<record model="crm.case.section" id="crm_case_section_2">
<field name="name">Support Department</field>
<field name="name">Marketing</field>
<field name="code">SPD</field>
<field name="parent_id" ref="crm.section_sales_department"/>
</record>
<record model="crm.case.section" id="crm_case_section_3">
<field name="name">Direct Marketing</field>
<field name="code">DM</field>
<field name="parent_id" ref="crm.section_sales_department"/>
</record>
<record model="crm.case.section" id="crm_case_section_4">
<field name="name">Online Support</field>
<field name="code">OS</field>
<field name="parent_id" ref="crm.crm_case_section_2"/>
</record>
<record model="crm.segmentation" id="crm_segmentation0">

View File

@ -367,8 +367,8 @@ class crm_lead(base_stage, format_address, osv.osv):
def on_change_user(self, cr, uid, ids, user_id, context=None):
""" When changing the user, also set a section_id or restrict section id
to the ones user_id is member of. """
section_id = False
if user_id:
section_id = self._get_default_section_id(cr, uid, context=context) or False
if user_id and not section_id:
section_ids = self.pool.get('crm.case.section').search(cr, uid, ['|', ('user_id', '=', user_id), ('member_ids', '=', user_id)], context=context)
if section_ids:
section_id = section_ids[0]

View File

@ -20,7 +20,7 @@
<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_4"/>
<field name="section_id" ref="crm_case_section_2"/>
<field name="user_id" ref="base.user_root"/>
<field name="stage_id" ref="stage_lead1"/>
<field name="description">Hello,
@ -44,7 +44,7 @@ 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_3"/>
<field name="section_id" ref="crm_case_section_2"/>
<field name="user_id" ref="base.user_root"/>
<field name="stage_id" ref="stage_lead1"/>
<field eval="1" name="active"/>
@ -63,7 +63,7 @@ Can you send me the details ?</field>
<field name="categ_ids" eval="[(6, 0, [categ_oppor4])]"/>
<field name="channel_id" ref=""/>
<field name="priority">2</field>
<field name="section_id" ref="crm_case_section_2"/>
<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"/>
@ -82,7 +82,7 @@ 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_2"/>
<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"/>
@ -133,7 +133,7 @@ Contact: +1 813 494 5005</field>
<field name="categ_ids" eval="[(6, 0, [categ_oppor3,categ_oppor4])]"/>
<field name="channel_id" ref=""/>
<field name="priority">3</field>
<field name="section_id" ref="crm_case_section_4"/>
<field name="section_id" ref="crm_case_section_2"/>
<field name="user_id" ref="base.user_root"/>
<field name="stage_id" ref="stage_lead1"/>
<field eval="1" name="active"/>
@ -152,7 +152,7 @@ Contact: +1 813 494 5005</field>
<field name="categ_ids" eval="[(6, 0, [categ_oppor4])]"/>
<field name="channel_id" ref=""/>
<field name="priority">5</field>
<field name="section_id" ref="crm_case_section_4"/>
<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"/>
@ -197,6 +197,9 @@ Contact: +1 813 494 5005</field>
<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">
<field name="type">lead</field>
@ -211,7 +214,7 @@ Contact: +1 813 494 5005</field>
<field name="categ_ids" eval="[(6, 0, [categ_oppor1])]"/>
<field name="channel_id" ref="crm_case_channel_email"/>
<field name="priority">2</field>
<field name="section_id" ref="crm_case_section_3"/>
<field name="section_id" ref="crm_case_section_2"/>
<field name="user_id" ref=""/>
<field name="stage_id" ref="stage_lead1"/>
<field name="description">Hi,
@ -235,7 +238,7 @@ Andrew</field>
<field name="categ_ids" eval="[(6, 0, [categ_oppor7])]"/>
<field name="channel_id" ref="crm_case_channel_direct"/>
<field name="priority">3</field>
<field name="section_id" ref="crm_case_section_2"/>
<field name="section_id" ref="crm_case_section_1"/>
<field name="user_id" ref="base.user_demo"/>
<field name="stage_id" ref="stage_lead1"/>
<field eval="1" name="active"/>
@ -291,7 +294,7 @@ Andrew</field>
<field eval="time.strftime('%Y-%m-25')" name="date_deadline"/>
<field eval="time.strftime('%Y-%m-12')" name="date_action"/>
<field name="title_action">Meeting for pricing information.</field>
<field name="section_id" ref="crm_case_section_3"/>
<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"/>
@ -317,7 +320,7 @@ Andrew</field>
<field eval="time.strftime('%Y-%m-23')" name="date_deadline"/>
<field eval="time.strftime('%Y-%m-10')" name="date_action"/>
<field name="title_action">Send Catalogue by Email</field>
<field name="section_id" ref="crm_case_section_4"/>
<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"/>
@ -326,7 +329,7 @@ Andrew</field>
<record id="crm_case_15" model="crm.lead">
<field name="type">opportunity</field>
<field name="name">Plan to buy RedHat servers</field>
<field eval="35000" name="planned_revenue"/>
<field eval="25000" name="planned_revenue"/>
<field eval="30.0" name="probability"/>
<field name="street">69 rue de Chimay</field>
<field name="country_id" ref="base.be"/>
@ -339,10 +342,11 @@ Andrew</field>
<field eval="time.strftime('%Y-%m-12')" name="date_deadline"/>
<field eval="time.strftime('%Y-%m-10')" name="date_action"/>
<field name="title_action">Call to ask system requirement</field>
<field name="section_id" ref="crm_case_section_3"/>
<field name="section_id" ref="section_sales_department"/>
<field name="user_id" ref="base.user_demo"/>
<field name="stage_id" ref="crm.stage_lead4"/>
<field name="stage_id" ref="crm.stage_lead6"/>
<field eval="1" name="active"/>
<field name="date_closed" eval="(DateTime.today() - relativedelta(months=2)).strftime('%Y-%m-%d %H:%M')"/>
</record>
<record id="crm_case_16" model="crm.lead">
@ -367,6 +371,7 @@ Andrew</field>
<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')"/>
</record>
<record id="crm_case_17" model="crm.lead">
@ -388,10 +393,11 @@ Andrew</field>
<field eval="time.strftime('%Y-%m-8')" name="date_deadline"/>
<field eval="time.strftime('%Y-%m-3')" name="date_action"/>
<field name="title_action">Send price list regarding our interventions</field>
<field name="section_id" ref="crm_case_section_2"/>
<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_lead6"/>
<field eval="1" name="active"/>
<field name="date_closed" eval="(DateTime.today() - relativedelta(hours=1)).strftime('%Y-%m-%d %H:%M')"/>
</record>
<record id="crm_case_18" model="crm.lead">
@ -412,7 +418,7 @@ Andrew</field>
<field eval="time.strftime('%Y-%m-13')" name="date_deadline"/>
<field eval="time.strftime('%Y-%m-4')" name="date_action"/>
<field name="title_action">Call to define real needs about training</field>
<field name="section_id" ref="crm_case_section_2"/>
<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 eval="1" name="active"/>
@ -438,8 +444,9 @@ 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_lead4"/>
<field name="stage_id" ref="crm.stage_lead6"/>
<field eval="1" name="active"/>
<field name="date_closed" eval="(DateTime.today() - relativedelta(months=3)).strftime('%Y-%m-%d %H:%M')"/>
</record>
<record id="crm_case_20" model="crm.lead">
@ -468,10 +475,11 @@ Andrew</field>
<field name="categ_ids" eval="[(6, 0, [categ_oppor7])]"/>
<field name="channel_id" ref="crm_case_channel_phone"/>
<field name="priority">3</field>
<field name="section_id" ref="crm_case_section_4"/>
<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 name="stage_id" ref="crm.stage_lead6"/>
<field eval="1" name="active"/>
<field name="date_closed" eval="(DateTime.today() - relativedelta(months=1)).strftime('%Y-%m-%d %H:%M')"/>
</record>
<record id="crm_case_22" model="crm.lead">
@ -486,7 +494,7 @@ Andrew</field>
<field name="categ_ids" eval="[(6, 0, [categ_oppor3])]"/>
<field name="channel_id" ref="crm_case_channel_email"/>
<field name="priority">3</field>
<field name="section_id" ref="crm_case_section_4"/>
<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"/>
@ -504,14 +512,15 @@ 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_lead5"/>
<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')"/>
</record>
<record id="crm_case_24" model="crm.lead">
<field name="type">opportunity</field>
<field name="name">Need 20 Days of Consultancy</field>
<field eval="5025" name="planned_revenue"/>
<field eval="6025" name="planned_revenue"/>
<field eval="90.0" name="probability"/>
<field name="email_from">info@mycompany.net</field>
<field name="country_id" ref="base.pe"/>
@ -525,6 +534,7 @@ Andrew</field>
<field name="user_id" ref="base.user_root"/>
<field name="stage_id" ref="crm.stage_lead6"/>
<field eval="1" name="active"/>
<field name="date_closed" eval="(DateTime.today() - relativedelta(month=1)).strftime('%Y-%m-%d %H:%M')"/>
</record>
<record id="crm_case_25" model="crm.lead">
@ -544,7 +554,7 @@ Andrew</field>
<field name="categ_ids" eval="[(6, 0, [categ_oppor4])]"/>
<field name="priority">2</field>
<field name="title_action">Conf call with technical service</field>
<field name="section_id" ref="crm_case_section_2"/>
<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"/>
@ -569,10 +579,11 @@ Andrew</field>
<field eval="time.strftime('%Y-%m-23')" name="date_deadline"/>
<field eval="time.strftime('%Y-%m-10')" name="date_action"/>
<field name="title_action">Send Catalogue by Email</field>
<field name="section_id" ref="crm_case_section_4"/>
<field name="section_id" ref="crm_case_section_2"/>
<field name="user_id" ref="base.user_demo"/>
<field name="stage_id" ref="crm.stage_lead3"/>
<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')"/>
</record>
<!-- Unsubscribe Admin from case15, subscribe Demo -->

View File

@ -157,7 +157,8 @@
<label for="section_id" groups="base.group_multi_salesteams"/>
<div groups="base.group_multi_salesteams">
<field name="section_id"/>
<button name="case_escalate" string="Escalate" type="object"
<button name="case_escalate" string="Escalate"
type="object" class="oe_link"
attrs="{'invisible': ['|', ('section_id','=',False), ('state', 'not in', ['draft','open','pending'])]}"/>
</div>
<field name="type" invisible="1"/>
@ -343,7 +344,7 @@
help="Leads that are assigned to me"/>
<filter string="Assigned to My Team(s)"
domain="[('section_id.member_ids', 'in', [uid])]" context="{'invisible_section': False}"
help="Leads that are assigned to any sales teams I am member of"/>
help="Leads that are assigned to any sales teams I am member of" groups="base.group_multi_salesteams"/>
<separator />
<filter string="Available for mass mailing"
name='not_opt_out' domain="[('opt_out', '=', False)]"
@ -351,7 +352,7 @@
<separator />
<group expand="0" string="Group By...">
<filter string="Salesperson" domain="[]" context="{'group_by':'user_id'}"/>
<filter string="Team" domain="[]" context="{'group_by':'section_id'}"/>
<filter string="Team" domain="[]" context="{'group_by':'section_id'}" groups="base.group_multi_salesteams"/>
<filter string="Stage" domain="[]" context="{'group_by':'stage_id'}"/>
<filter string="Customer" help="Partner" domain="[]" context="{'group_by':'partner_id'}"/>
<filter string="Country" domain="[]" context="{'group_by':'country_id'}"/>
@ -362,7 +363,7 @@
</group>
<group string="Display">
<filter string="Show Countries" context="{'invisible_country': False}" help="Show Countries"/>
<filter string="Show Sales Team" context="{'invisible_section': False}" domain="[]" help="Show Sales Team"/>
<filter string="Show Sales Team" context="{'invisible_section': False}" domain="[]" help="Show Sales Team" groups="base.group_multi_salesteams"/>
</group>
</search>
</field>
@ -556,7 +557,7 @@
<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="Mine" name="assigned_to_me"
<filter string="My Opportunities" name="assigned_to_me"
domain="[('user_id','=',uid)]" context="{'invisible_section': False}"
help="Opportunities that are assigned to me"/>
<filter string="Assigned to My Team(s)"
@ -577,7 +578,7 @@
<filter string="Creation" domain="[]" context="{'group_by':'create_date'}"/>
</group>
<group string="Display">
<filter string="Show Sales Team" context="{'invisible_section': False}" domain="[]" help="Show Sales Team"/>
<filter string="Show Sales Team" context="{'invisible_section': False}" domain="[]" help="Show Sales Team" groups="base.group_multi_salesteams"/>
<filter string="Show Countries" context="{'invisible_country': False}" help="Show Countries"/>
</group>
</search>

View File

@ -37,7 +37,7 @@
<field name="name">Ask for convenient time of meeting</field>
<field name="state">open</field>
<field name="partner_phone">+1 786 525 0724</field>
<field name="section_id" ref="crm.crm_case_section_3"/>
<field name="section_id" ref="crm.crm_case_section_2"/>
<field eval="1" name="active"/>
<field name="categ_id" ref="crm.categ_phone2"/>
<field eval="5.0" name="duration"/>
@ -50,7 +50,7 @@
<field name="state">done</field>
<field name="partner_phone">(077) 582-4035</field>
<field name="partner_mobile">(077) 341-3591</field>
<field name="section_id" ref="crm.crm_case_section_4"/>
<field name="section_id" ref="crm.crm_case_section_2"/>
<field eval="1" name="active"/>
<field name="categ_id" ref="crm.categ_phone1"/>
<field eval="5.45" name="duration"/>
@ -74,7 +74,7 @@
<field name="name">Proposal for discount offer</field>
<field name="state">open</field>
<field name="partner_phone">+34 230 953 485</field>
<field name="section_id" ref="crm.crm_case_section_3"/>
<field name="section_id" ref="crm.crm_case_section_2"/>
<field eval="time.strftime('%Y-%m-28 14:15:30')" name="date"/>
<field name="categ_id" ref="crm.categ_phone2"/>
<field eval="8.56" name="duration"/>

View File

@ -101,7 +101,7 @@
<field name="type"/>
<field name="on_change"/>
<field name="sequence"/>
<field name="case_default"/>
<field name="case_default" groups="base.group_multi_salesteams"/>
<field name="fold"/>
</group>
<separator string="Requirements"/>

View File

@ -75,7 +75,7 @@
<filter icon="terp-dialog-close" string="Closed" domain="[('state','=','done')]" help="Leads/Opportunities which are in done state"/>
<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"/>
help="Leads/Opportunities that are assigned to one of the sale teams I manage" groups="base.group_multi_salesteams"/>
<separator/>
<filter icon="terp-personal" string="My Case(s)" help="Leads/Opportunities that are assigned to me" domain="[('user_id','=',uid)]"/>
<field name="section_id" context="{'invisible_section': False}"

View File

@ -57,7 +57,7 @@
help="Phone calls which are in pending state"/>
<separator/>
<filter string="My Sales Team(s)" icon="terp-personal+" context="{'invisible_section': False}" domain="[('section_id.user_id','=',uid)]"
help="Phone calls that are assigned to one of the sale teams I manage"/>
help="Phone calls that are assigned to one of the sale teams I manage" groups="base.group_multi_salesteams"/>
<separator/>
<filter icon="terp-personal" string="My Phone Calls" help="Phone Calls that are assigned to me" domain="[('user_id','=',uid)]" />
<field name="section_id" string="Sales Team" context="{'invisible_section': False}"

View File

@ -22,9 +22,9 @@
</div>
</div>
</group>
<separator string="Sales Teams Configuration"/>
<separator string="Sales Teams"/>
<group>
<label for="id" string="Use Sales Teams"/>
<label for="id" string="Manage Sales Teams"/>
<div>
<field name="group_multi_salesteams" class="oe_inline"/>
<label for="group_multi_salesteams"/>

View File

@ -12,7 +12,7 @@ access_crm_phonecall_manager,crm.phonecall.manager,model_crm_phonecall,base.grou
access_crm_case_categ,crm.case.categ,model_crm_case_categ,base.group_user,1,0,0,0
access_crm_lead,crm.lead,model_crm_lead,base.group_sale_salesman,1,1,1,0
access_crm_phonecall,crm.phonecall,model_crm_phonecall,base.group_sale_salesman,1,1,1,0
access_crm_case_section_user,crm.case.section.user,model_crm_case_section,base.group_sale_salesman,1,1,1,0
access_crm_case_section_user,crm.case.section.user,model_crm_case_section,base.group_sale_salesman,1,0,0,0
access_crm_case_section_manager,crm.case.section.manager,model_crm_case_section,base.group_sale_manager,1,1,1,1
access_crm_case_stage,crm.case.stage,model_crm_case_stage,,1,0,0,0
access_crm_case_stage_manager,crm.case.stage,model_crm_case_stage,base.group_sale_manager,1,1,1,1

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
12 access_crm_case_categ crm.case.categ model_crm_case_categ base.group_user 1 0 0 0
13 access_crm_lead crm.lead model_crm_lead base.group_sale_salesman 1 1 1 0
14 access_crm_phonecall crm.phonecall model_crm_phonecall base.group_sale_salesman 1 1 1 0
15 access_crm_case_section_user crm.case.section.user model_crm_case_section base.group_sale_salesman 1 1 0 1 0 0
16 access_crm_case_section_manager crm.case.section.manager model_crm_case_section base.group_sale_manager 1 1 1 1
17 access_crm_case_stage crm.case.stage model_crm_case_stage 1 0 0 0
18 access_crm_case_stage_manager crm.case.stage model_crm_case_stage base.group_sale_manager 1 1 1 1

File diff suppressed because it is too large Load Diff

View File

@ -20,15 +20,27 @@
}
.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_items_list {
margin: 10px 0;
position: relative;
margin: 10px;
}
.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_items_list a {
width: 110px;
.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_items_list div {
width: 160px;
height: 22px;
margin: 0 !important;
position: relative;
display: inline-block;
}
.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_items_list a:hover {
text-decoration: underline !important;
}
.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_items_list div a:nth-child(2n) {
position: absolute;
left: 90px;
top: 0;
}
.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_items_list div:nth-child(2n) a:nth-child(2n) {
left: 110px;
}
.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_center {
text-align: center;
margin: 3px 0;
@ -40,3 +52,13 @@
.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_center .oe_subsum {
font-size: 10px;
}
.openerp .oe_kanban_view .oe_justgage {
color: black;
display: inline-block;
}
.openerp .oe_kanban_view .oe_sparkline_bar {
height: 20px;
width: 36px;
}

View File

@ -1,11 +0,0 @@
openerp.crm = function(openerp) {
openerp.web_kanban.KanbanRecord.include({
on_card_clicked: function() {
if (this.view.dataset.model === 'crm.case.section') {
this.$('.oe_kanban_crm_salesteams_list a').first().click();
} else {
this._super.apply(this, arguments);
}
},
});
};

View File

@ -0,0 +1,34 @@
openerp.crm = function(openerp) {
openerp.web_kanban.KanbanRecord.include({
on_card_clicked: function() {
if (this.view.dataset.model === 'crm.case.section') {
this.$('.oe_kanban_crm_salesteams_list a').first().click();
} else {
this._super.apply(this, arguments);
}
},
});
openerp.crm.SparklineBarWidget = openerp.web_kanban.AbstractField.extend({
className: "oe_sparkline_bar",
start: function() {
var self = this;
var title = this.$node.html();
setTimeout(function () {
var value = _.pluck(self.field.value, 'value');
var tooltips = _.pluck(self.field.value, 'tooltip');
self.$el.sparkline(value, {
type: 'bar',
barWidth: 5,
tooltipFormat: '{{offset:offset}} {{value}}',
tooltipValueLookups: {
'offset': tooltips
},
});
self.$el.tipsy({'delayIn': 0, 'html': true, 'title': function(){return title}, 'gravity': 'n'});
}, 0);
},
});
openerp.web_kanban.fields_registry.add("sparkline_bar", "openerp.crm.SparklineBarWidget");
};

View File

@ -1,7 +1,9 @@
-
I cancel unqualified lead.
I set a new sale team (with Marketing at parent) and I cancel unqualified lead .
-
!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})
self.case_cancel(cr, uid, [ref("crm_case_1")])
-
I check cancelled lead.
@ -42,4 +44,4 @@
I check the lead is correctly escalated to the parent team.
-
!assert {model: crm.lead, id: crm.crm_case_1, string: Escalate lead to parent team}:
- section_id.name == "Support Department"
- section_id.name == "Marketing"

View File

@ -55,7 +55,7 @@
<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)" />
<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"/>
<separator/>
@ -78,7 +78,7 @@
</group>
<group expand="1" string="Group By...">
<filter string="Salesperson" name="Salesperson" icon="terp-personal" domain="[]" context="{'group_by':'user_id'}" />
<filter string="Sales Team" icon="terp-personal+" domain="[]" context="{'group_by':'section_id'}" />
<filter string="Sales Team" icon="terp-personal+" domain="[]" context="{'group_by':'section_id'}" groups="base.group_multi_salesteams"/>
<filter string="Partner" name="partner" icon="terp-partner" domain="[]" context="{'group_by':'partner_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'}" />

View File

@ -151,14 +151,14 @@
<filter icon="terp-gtk-media-pause" string="Pending" domain="[('state','=','pending')]" help="All pending Helpdesk Request" />
<separator/>
<filter string="Assigned to Me or My Sales Team(s)" icon="terp-personal+" domain="['|', ('section_id.user_id','=',uid), ('section_id.member_ids', 'in', [uid])]"
help="Helpdesk requests that are assigned to me or to one of the sale teams I manage" />
help="Helpdesk requests that are assigned to me or to one of the sale teams I manage" groups="base.group_multi_salesteams"/>
<field name="partner_id" filter_domain="[('partner_id','child_of',self)]"/>
<field name="user_id"/>
<field name="section_id" string="Sales Team" groups="base.group_multi_salesteams"/>
<group expand="0" string="Group By...">
<filter string="Partner" icon="terp-partner" domain="[]" help="Partner" context="{'group_by':'partner_id'}" />
<filter string="Responsible" icon="terp-personal" domain="[]" help="Responsible User" context="{'group_by':'user_id'}" />
<filter string="Sales Team" icon="terp-personal+" domain="[]" help="Sales Team" context="{'group_by':'section_id'}" />
<filter string="Sales Team" icon="terp-personal+" domain="[]" help="Sales Team" context="{'group_by':'section_id'}" groups="base.group_multi_salesteams"/>
<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="Date" icon="terp-go-month" domain="[]" help="Request Date" context="{'group_by':'date'}" />

View File

@ -56,7 +56,7 @@
<separator/>
<filter icon="terp-personal" string="My Case(s)" help="My Case(s)" domain="[('user_id','=',uid)]" />
<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)" />
<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"/>
<field name="user_id" string="Salesperson"/>
@ -71,7 +71,7 @@
</group>
<group expand="1" string="Group By...">
<filter string="Salesperson" icon="terp-personal" domain="[]" context="{'group_by':'user_id'}"/>
<filter string="Sales Team" icon="terp-personal+" domain="[]" context="{'group_by':'section_id'}" />
<filter string="Sales Team" icon="terp-personal+" domain="[]" context="{'group_by':'section_id'}" groups="base.group_multi_salesteams"/>
<filter string="Partner" icon="terp-partner" domain="[]" context="{'group_by':'partner_id'}" />
<filter string="Channel" icon="terp-call-start" domain="[]" context="{'group_by':'channel_id'}" />
<filter string="Status" icon="terp-stock_effects-object-colorize" domain="[]" context="{'group_by':'state'}" />

View File

@ -3,7 +3,7 @@ Date: Tue, 25 Oct 2011 13:41:17 +0530
From: Mr. John Right <info@customer.com>
User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.14) Gecko/20110223 Lightning/1.0b2 Thunderbird/3.1.8
MIME-Version: 1.0
To: info@my.com
To: _helpdesk@my.com
Subject: Where is download link of user manual of your product ?
Content-Type: text/plain; charset=ISO-8859-1; format=flowed
Content-Transfer-Encoding: 8bit

View File

@ -358,7 +358,7 @@ class email_template(osv.osv):
values['attachment_ids'] = attachment_ids
return values
def send_mail(self, cr, uid, template_id, res_id, force_send=False, context=None):
def send_mail(self, cr, uid, template_id, res_id, force_send=False, raise_exception=False, context=None):
"""Generates a new mail message for the given template and record,
and schedules it for delivery through the ``mail`` module's scheduler.
@ -400,7 +400,7 @@ class email_template(osv.osv):
mail_mail.write(cr, uid, msg_id, {'attachment_ids': [(6, 0, attachment_ids)]}, context=context)
if force_send:
mail_mail.send(cr, uid, [msg_id], context=context)
mail_mail.send(cr, uid, [msg_id], raise_exception=raise_exception, context=context)
return msg_id
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -27,6 +27,7 @@ from urlparse import urljoin
from openerp import tools
from openerp import SUPERUSER_ID
from openerp.addons.base.ir.ir_mail_server import MailDeliveryException
from openerp.osv import fields, osv
from openerp.tools.translate import _
@ -292,7 +293,7 @@ class mail_mail(osv.Model):
'email_to': email_to,
}
def send(self, cr, uid, ids, auto_commit=False, context=None):
def send(self, cr, uid, ids, auto_commit=False, raise_exception=False, context=None):
""" Sends the selected emails immediately, ignoring their current
state (mails that have already been sent should not be passed
unless they should actually be re-sent).
@ -303,6 +304,8 @@ class mail_mail(osv.Model):
:param bool auto_commit: whether to force a commit of the mail status
after sending each mail (meant only for scheduler processing);
should never be True during normal transactions (default: False)
:param bool raise_exception: whether to raise an exception if the
email sending process has failed
:return: True
"""
ir_mail_server = self.pool.get('ir.mail_server')
@ -348,9 +351,16 @@ class mail_mail(osv.Model):
# see revid:odo@openerp.com-20120622152536-42b2s28lvdv3odyr in 6.1
if mail_sent:
self._postprocess_sent_message(cr, uid, mail, context=context)
except Exception:
except Exception as e:
_logger.exception('failed sending mail.mail %s', mail.id)
mail.write({'state': 'exception'})
if raise_exception:
if isinstance(e, AssertionError):
# get the args of the original error, wrap into a value and throw a MailDeliveryException
# that is an except_orm, with name and value as arguments
value = '. '.join(e.args)
raise MailDeliveryException(_("Mail Delivery Failed"), value)
raise
if auto_commit == True:
cr.commit()

View File

@ -26,7 +26,7 @@
<field name="signature" position="before">
<field name="notification_email_send"/>
</field>
<field name="email" position="after">
<field name="signature" position="before">
<field name="alias_domain" invisible="1"/>
<field name="alias_id" readonly="1" required="0" attrs="{'invisible': [('alias_domain', '=', False)]}"/>
</field>

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-06-04 05:20+0000\n"
"X-Launchpad-Export-Date: 2013-06-05 05:32+0000\n"
"X-Generator: Launchpad (build 16660)\n"
#. module: mrp

View File

@ -172,7 +172,7 @@ class wizard_user(osv.osv_memory):
@return: browse record of model res.users
"""
res_users = self.pool.get('res.users')
create_context = dict(context or {}, noshortcut=True) # to prevent shortcut creation
create_context = dict(context or {}, noshortcut=True, no_reset_password=True) # to prevent shortcut creation
values = {
'login': extract_email(wizard_user.email),
'partner_id': wizard_user.partner_id.id,

View File

@ -34,7 +34,7 @@ with the effect of creating, editing and deleting either ways.
'author': 'OpenERP SA',
'website': 'http://www.openerp.com',
'images': ['images/invoice_task_work.jpeg', 'images/my_timesheet.jpeg', 'images/working_hour.jpeg'],
'depends': ['project', 'hr_timesheet_sheet', 'hr_timesheet_invoice', 'account_analytic_analysis'],
'depends': ['resource', 'project', 'hr_timesheet_sheet', 'hr_timesheet_invoice', 'account_analytic_analysis'],
'data': [
'security/ir.model.access.csv',
'security/project_timesheet_security.xml',

View File

@ -288,6 +288,8 @@
</record>
<!-- Confirm some Sale Orders-->
<workflow action="order_confirm" model="sale.order" ref="sale_order_4"/>
<workflow action="order_confirm" model="sale.order" ref="sale_order_7"/>
<record id="message_sale_1" model="mail.message">

View File

@ -307,7 +307,7 @@
<filter icon="terp-dolar_ok!" string="To Invoice" domain="[('state','=','manual')]" help="Sales Order ready to be invoiced"/>
<filter icon="terp-dolar_ok!" string="Done" domain="[('state','=','done')]" help="Sales Order done"/>
<separator/>
<filter string="My Sales Orders" domain="[('user_id','=',uid)]" help="My Sales Orders" icon="terp-personal" name="my_sale_orders_filter"/>
<filter string="My" domain="[('user_id','=',uid)]" help="My Sales Orders" icon="terp-personal" name="my_sale_orders_filter"/>
<field name="partner_id" filter_domain="[('partner_id', 'child_of', self)]"/>
<field name="user_id"/>
<field name="project_id"/>

View File

@ -47,7 +47,11 @@ modules.
'security/ir.model.access.csv',
'report/sale_crm_account_invoice_report_view.xml',
],
'demo': [],
'js': [
'static/lib/justgage.js',
'static/src/js/sale_crm.js',
],
'demo': ['sale_crm_demo.xml'],
'test': ['test/sale_crm.yml'],
'installable': True,
'auto_install': True,

View File

@ -19,7 +19,10 @@
#
##############################################################################
from datetime import datetime
from datetime import date
from dateutil import relativedelta
from openerp import tools
from openerp.osv import osv, fields
@ -41,31 +44,51 @@ class sale_order(osv.osv):
class crm_case_section(osv.osv):
_inherit = 'crm.case.section'
def _get_sum_month_invoice(self, cr, uid, ids, field_name, arg, context=None):
res = dict.fromkeys(ids, 0)
def _get_sale_orders_data(self, cr, uid, ids, field_name, arg, context=None):
obj = self.pool.get('sale.order')
res = dict.fromkeys(ids, False)
month_begin = date.today().replace(day=1)
groupby_begin = (month_begin + relativedelta.relativedelta(months=-4)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
for id in ids:
res[id] = dict()
created_domain = [('section_id', '=', id), ('state', 'in', ['draft', 'sent']), ('date_order', '>=', groupby_begin)]
res[id]['monthly_quoted'] = self.__get_bar_values(cr, uid, obj, created_domain, ['amount_total', 'date_order'], 'amount_total', 'date_order', context=context)
validated_domain = [('section_id', '=', id), ('state', 'not in', ['draft', 'sent']), ('date_confirm', '>=', groupby_begin)]
res[id]['monthly_confirmed'] = self.__get_bar_values(cr, uid, obj, validated_domain, ['amount_total', 'date_confirm'], 'amount_total', 'date_confirm', context=context)
return res
def _get_invoices_data(self, cr, uid, ids, field_name, arg, context=None):
obj = self.pool.get('account.invoice.report')
when = datetime.today()
for section_id in ids:
invoice_ids = obj.search(cr, uid, [("section_id", "=", section_id), ('state', 'not in', ['draft', 'cancel']), ('year', '=', when.year), ('month', '=', when.month > 9 and when.month or "0%s" % when.month)], context=context)
for invoice in obj.browse(cr, uid, invoice_ids, context=context):
res[section_id] += invoice.price_total
res = dict.fromkeys(ids, False)
month_begin = date.today().replace(day=1)
groupby_begin = (month_begin + relativedelta.relativedelta(months=-4)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
for id in ids:
created_domain = [('section_id', '=', id), ('state', 'not in', ['draft', 'cancel']), ('date', '>=', groupby_begin)]
res[id] = self.__get_bar_values(cr, uid, obj, created_domain, ['price_total', 'date'], 'price_total', 'date', context=context)
return res
_columns = {
'quotation_ids': fields.one2many('sale.order', 'section_id',
string='Quotations', readonly=True,
domain=[('state', 'in', ['draft', 'sent', 'cancel'])]),
'sale_order_ids': fields.one2many('sale.order', 'section_id',
string='Sale Orders', readonly=True,
domain=[('state', 'not in', ['draft', 'sent', 'cancel'])]),
'invoice_ids': fields.one2many('account.invoice', 'section_id',
string='Invoices', readonly=True,
domain=[('state', 'not in', ['draft', 'cancel'])]),
'sum_month_invoice': fields.function(_get_sum_month_invoice,
string='Total invoiced this month',
type='integer', readonly=True),
'invoiced_forecast': fields.integer(string='Invoice Forecast',
help="Forecast of the invoice revenue for the current month. This is the amount the sales \n"
"team should invoice this month. It is used to compute the progression ratio \n"
" of the current and forecast revenue on the kanban view."),
'invoiced_target': fields.integer(string='Invoice Target',
help="Target of invoice revenue for the current month. This is the amount the sales \n"
"team estimates to be able to invoice this month."),
'monthly_quoted': fields.function(_get_sale_orders_data,
type='string', readonly=True, multi='_get_sale_orders_data',
string='Rate of created quotation per duration'),
'monthly_confirmed': fields.function(_get_sale_orders_data,
type='string', readonly=True, multi='_get_sale_orders_data',
string='Rate of validate sales orders per duration'),
'monthly_invoiced': fields.function(_get_invoices_data,
type='string', readonly=True,
string='Rate of sent invoices per duration'),
}
def action_forecast(self, cr, uid, id, value, context=None):
return self.write(cr, uid, [id], {'invoiced_forecast': value}, context=context)
class res_users(osv.Model):
_inherit = 'res.users'

View File

@ -0,0 +1,130 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record model="crm.case.section" id="crm.section_sales_department">
<field name="invoiced_forecast">52700</field>
<field name="invoiced_target">60000</field>
</record>
<record model="crm.case.section" id="crm.crm_case_section_1">
<field name="name">Indirect Sales</field>
<field name="code">IM</field>
<field name="invoiced_forecast">36000</field>
<field name="invoiced_target">40000</field>
</record>
<!-- Invoice for Direct Marketing -->
<record id="test_crm_sale_invoice_1" model="account.invoice">
<field name="currency_id" ref="base.EUR"/>
<field name="company_id" ref="base.main_company"/>
<field name="partner_id" ref="base.res_partner_1"/>
<field name="journal_id" ref="account.sales_journal"/>
<field name="section_id" ref="crm.section_sales_department"/>
<field name="state">draft</field>
<field name="type">out_invoice</field>
<field name="account_id" ref="account.a_recv"/>
<field name="name">Test invoice 1</field>
</record>
<record id="test_crm_sale_invoice_1_line_1" model="account.invoice.line">
<field name="name">Basic computer with Dvorak keyboard and left-handed mouse</field>
<field name="invoice_id" ref="test_crm_sale_invoice_1"/>
<field name="price_unit">250</field>
<field name="quantity">1</field>
<field name="account_id" ref="account.a_sale"/>
</record>
<record id="test_crm_sale_invoice_1_line_2" model="account.invoice.line">
<field name="name">Little server with raid 1 and 512ECC ram</field>
<field name="invoice_id" ref="test_crm_sale_invoice_1"/>
<field name="price_unit">800</field>
<field name="quantity">2</field>
<field name="account_id" ref="account.a_sale"/>
</record>
<record id="test_crm_sale_invoice_1_line_2" model="account.invoice.line">
<field name="name">Server with raid 10 and 2048ECC ram</field>
<field name="invoice_id" ref="test_crm_sale_invoice_1"/>
<field name="price_unit">4800</field>
<field name="quantity">4</field>
<field name="account_id" ref="account.a_sale"/>
</record>
<workflow action="invoice_open" model="account.invoice" ref="test_crm_sale_invoice_1"/>
<function model="account.invoice" name="pay_and_reconcile">
<!-- ids = --> <value eval="[ref('test_crm_sale_invoice_1')]"/>
<!-- pay_amount = --> <value eval="1850"/>
<!-- pay_account_id = --> <value eval="ref('account.cash')"/>
<!-- period_id = --> <value eval="ref('account.period_' + str(int(time.strftime('%m'))))"/>
<!-- pay_journal_id = --> <value eval="ref('account.bank_journal')"/>
<!-- writeoff_acc_id = --> <value eval="ref('account.cash')"/>
<!-- writeoff_period_id = --> <value eval="ref('account.period_' + str(int(time.strftime('%m'))))"/>
<!-- writeoff_journal_id = --> <value eval="ref('account.bank_journal')"/>
<!-- context = --> <value eval="{}"/>
<!-- name = --> <value eval="str('Payment from ASUStek')"/>
</function>
<!-- Invoice for Indirect Marketing -->
<record id="test_crm_sale_invoice_2" model="account.invoice">
<field name="currency_id" ref="base.EUR"/>
<field name="company_id" ref="base.main_company"/>
<field name="partner_id" ref="base.res_partner_1"/>
<field name="journal_id" ref="account.sales_journal"/>
<field name="section_id" ref="crm.crm_case_section_1"/>
<field name="state">draft</field>
<field name="type">out_invoice</field>
<field name="account_id" ref="account.a_recv"/>
<field name="name">Test invoice 1</field>
</record>
<record id="test_crm_sale_invoice_2_line_1" model="account.invoice.line">
<field name="name">Basic formation with Dvorak</field>
<field name="invoice_id" ref="test_crm_sale_invoice_2"/>
<field name="price_unit">500</field>
<field name="quantity">1</field>
<field name="account_id" ref="account.a_sale"/>
</record>
<workflow action="invoice_open" model="account.invoice" ref="test_crm_sale_invoice_2"/>
<function model="account.invoice" name="pay_and_reconcile">
<!-- ids = --> <value eval="[ref('test_crm_sale_invoice_2')]"/>
<!-- pay_amount = --> <value eval="500"/>
<!-- pay_account_id = --> <value eval="ref('account.cash')"/>
<!-- period_id = --> <value eval="ref('account.period_' + str(int(time.strftime('%m'))))"/>
<!-- pay_journal_id = --> <value eval="ref('account.bank_journal')"/>
<!-- writeoff_acc_id = --> <value eval="ref('account.cash')"/>
<!-- writeoff_period_id = --> <value eval="ref('account.period_' + str(int(time.strftime('%m'))))"/>
<!-- writeoff_journal_id = --> <value eval="ref('account.bank_journal')"/>
<!-- context = --> <value eval="{}"/>
<!-- name = --> <value eval="str('Payment from ASUStek')"/>
</function>
<record id="sale.sale_order_1" model="sale.order">
<field name="section_id" ref="crm.section_sales_department"/>
<field name="date_order" eval="(DateTime.today() - relativedelta(months=1)).strftime('%Y-%m-%d %H:%M')"/>
</record>
<record id="sale.sale_order_2" model="sale.order">
<field name="section_id" ref="crm.section_sales_department"/>
<field name="date_order" eval="(DateTime.today() - relativedelta(months=1)).strftime('%Y-%m-%d %H:%M')"/>
</record>
<record id="sale.sale_order_3" model="sale.order">
<field name="section_id" ref="crm.section_sales_department"/>
</record>
<record id="sale.sale_order_4" model="sale.order">
<field name="section_id" ref="crm.section_sales_department"/>
</record>
<record id="sale.sale_order_5" model="sale.order">
<field name="section_id" ref="crm.crm_case_section_1"/>
<field name="date_order" eval="(DateTime.today() - relativedelta(months=1)).strftime('%Y-%m-%d %H:%M')"/>
</record>
<record id="sale.sale_order_6" model="sale.order">
<field name="section_id" ref="crm.crm_case_section_1"/>
</record>
<record id="sale.sale_order_7" model="sale.order">
<field name="section_id" ref="crm.section_sales_department"/>
<field name="date_confirm" eval="(DateTime.today() - relativedelta(months=1)).strftime('%Y-%m-%d %H:%M')"/>
</record>
<record id="sale.sale_order_8" model="sale.order">
<field name="section_id" ref="crm.crm_case_section_1"/>
</record>
</data>
</openerp>

View File

@ -43,7 +43,7 @@
<filter string="My Sales Team(s)"
icon="terp-personal+"
domain="[('section_id.user_id','=',uid)]"
help="My Sales Team(s)"/>
help="My Sales Team(s)" groups="base.group_multi_salesteams"/>
</xpath>
<xpath expr="//field[@name='user_id']" position="after">
<field name="section_id" string="Sales Team" groups="base.group_multi_salesteams"/>
@ -75,7 +75,7 @@
<field name="section_id"/>
</xpath>
<xpath expr="//group/filter[@string='Due Date']" position="after">
<filter string="Sales Team" domain="[]" context="{'group_by':'section_id'}"/>
<filter string="Sales Team" domain="[]" context="{'group_by':'section_id'}" groups="base.group_multi_salesteams"/>
</xpath>
</field>
</record>
@ -101,7 +101,7 @@
<field name="inherit_id" ref="base.view_users_form"/>
<field name="arch" type="xml">
<data>
<xpath expr="//field[@name='email']" position="after">
<xpath expr="//field[@name='tz']" position="after">
<field name="default_section_id"/>
</xpath>
</data>
@ -135,7 +135,6 @@
<field name="context">{
'search_default_section_id': [active_id],
'default_section_id': active_id,
'search_default_my_sale_orders_filter': 1,
}
</field>
<field name="help" type="html">
@ -158,8 +157,8 @@
<field name="view_mode">tree,form,calendar,graph</field>
<field name="context">{
'search_default_section_id': [active_id],
'default_section_id': active_id, 'show_address': 1,
'search_default_my_sale_orders_filter': 1
'default_section_id': active_id,
'show_address': 1,
}
</field>
<field name="domain">[('state','in',('draft','sent','cancel'))]</field>
@ -190,7 +189,7 @@
('type', '=', 'out_invoice')]</field>
<field name="context">{
'search_default_section_id': [active_id],
'default_section_id': active_id},
'default_section_id': active_id,
'default_type':'out_invoice',
'type':'out_invoice',
'journal_type': 'sale',
@ -212,38 +211,68 @@
<field name="act_window_id" ref="sale_crm.action_invoice_salesteams"/>
</record>
<record id="crm_case_section_salesteams_view_form" model="ir.ui.view">
<field name="name">crm.case.section.form</field>
<field name="model">crm.case.section</field>
<field name="inherit_id" ref="crm.crm_case_section_view_form"/>
<field name="arch" type="xml">
<data>
<xpath expr="//field[@name='code']" position="after">
<field name="invoiced_target"/>
<field name="invoiced_forecast"/>
</xpath>
</data>
</field>
</record>
<record id="crm_case_section_salesteams_view_kanban" model="ir.ui.view">
<field name="name">crm.case.section.kanban</field>
<field name="model">crm.case.section</field>
<field name="inherit_id" ref="crm.crm_case_section_salesteams_view_kanban"/>
<field name="arch" type="xml">
<data>
<xpath expr="//field[@name='open_opportunity_ids']" position="after">
<field name="quotation_ids"/>
<field name="sale_order_ids"/>
<field name="invoice_ids"/>
<xpath expr="//field[@name='name']" position="after">
<field name="monthly_quoted"/>
<field name="monthly_confirmed"/>
<field name="monthly_invoiced"/>
<field name="invoiced_forecast"/>
<field name="invoiced_target"/>
</xpath>
<xpath expr="//div[@class='oe_items_list']" position="inside">
<a name="%(action_quotations_salesteams)d" type="action">
<t t-raw="record.quotation_ids.raw_value.length"/>
<t t-if="record.quotation_ids.raw_value.length &gt;= 2">Quotations</t>
<t t-if="record.quotation_ids.raw_value.length &lt; 2">Quotation</t>
</a>
<a name="%(action_orders_salesteams)d" type="action">
<t t-raw="record.sale_order_ids.raw_value.length"/>
<t t-if="record.sale_order_ids.raw_value.length &gt;= 2">Sales Orders</t>
<t t-if="record.sale_order_ids.raw_value.length &lt; 2">Sales Order</t>
</a>
<a name="%(action_invoice_salesteams)d" type="action" groups="account.group_account_invoice">
<t t-raw="record.invoice_ids.raw_value.length"/>
<t t-if="record.invoice_ids.raw_value.length &gt;= 2">Invoices</t>
<t t-if="record.invoice_ids.raw_value.length &lt; 2">Invoice</t>
</a>
<xpath expr="//div[@class='oe_salesteams_leads']" position="after">
<div class="oe_salesteams_orders">
<a name="%(action_orders_salesteams)d" type="action">Sales Orders</a>
<a name="%(sale.action_order_report_all)d" type="action" class="oe_sparkline_bar_link">
<field name="monthly_confirmed" widget="sparkline_bar">
Revenue of confirmed sales orders per month.<br/>Click the acces to Sales Analysis
</field>
</a>
</div>
</xpath>
<xpath expr="//div[@class='oe_salesteams_opportunities']" position="after">
<div class="oe_salesteams_invoices">
<a name="%(action_invoice_salesteams)d" type="action" groups="account.group_account_invoice">Invoices</a>
<a name="%(account.action_account_invoice_report_all)d" type="action" class="oe_sparkline_bar_link">
<field name="monthly_invoiced" widget="sparkline_bar">
Revenue of sent invoices per month.<br/>Click to see a detailed analysis of invoices.
</field>
</a>
</div>
<div class="oe_salesteams_quotations">
<a name="%(action_quotations_salesteams)d" type="action" class="oe_sparkline_bar_link">Quotations</a>
<a name="%(sale.action_order_report_all)d" type="action" class="oe_sparkline_bar_link">
<field name="monthly_quoted" widget="sparkline_bar">
Revenue of created quotation per month.<br/>Click to see a detailed analysis of sales.
</field>
</a>
</div>
</xpath>
<xpath expr="//div[@class='oe_items_list']" position="after">
<div class="oe_center">
<div class="oe_sum"><field name="sum_month_invoice"/></div>
<div class="oe_subsum">Invoiced this month</div>
<div class="oe_center" t-if="record.invoiced_target.raw_value">
<field name="monthly_invoiced" widget="gage" style="width:160px; height: 120px;" options="{'max_field': 'invoiced_target'}">Invoiced</field>
<field name="invoiced_forecast" widget="gage" style="width:160px; height: 120px;" options="{'max_field': 'invoiced_target', 'action_change': 'action_forecast'}">Forecast</field>
</div>
<div class="oe_center" style="color:#bbbbbb;" t-if="!record.invoiced_target.raw_value">
<br/>Define an invoicing target in the sales team settings to see the period's achievement and forecast at a glance.
</div>
</xpath>
</data>

View File

@ -0,0 +1,883 @@
/**
* JustGage - this is work-in-progress, unreleased, unofficial code, so it might not work top-notch :)
* Check http://www.justgage.com for official releases
* Licensed under MIT.
* @author Bojan Djuricic (@Toorshia)
*
* LATEST UPDATES
* -----------------------------
* April 01, 2013.
* -----------------------------
* fix - https://github.com/toorshia/justgage/issues/46
* -----------------------------
* March 26, 2013.
* -----------------------------
* customSectors - define specific color for value range (0-10 : red, 10-30 : blue etc.)
* -----------------------------
* March 23, 2013.
* -----------------------------
* counter - option to animate value in counting fashion
* fix - https://github.com/toorshia/justgage/issues/45
* -----------------------------
* March 13, 2013.
* -----------------------------
* refresh method - added optional 'max' parameter to use when you need to update max value
* -----------------------------
* February 26, 2013.
* -----------------------------
* decimals - option to define/limit number of decimals when not using humanFriendly or customRenderer to display value
* fixed a missing parameters bug when calling generateShadow() for IE < 9
* -----------------------------
* December 31, 2012.
* -----------------------------
* fixed text y-position for hidden divs - workaround for Raphael <tspan> 'dy' bug - https://github.com/DmitryBaranovskiy/raphael/issues/491
* 'show' parameters, like showMinMax are now 'hide' because I am lame developer - please update these in your setups
* Min and Max labels are now auto-off when in donut mode
* Start angle in donut mode is now 90
* donutStartAngle - option to define start angle for donut
* -----------------------------
* November 25, 2012.
* -----------------------------
* Option to define custom rendering function for displayed value
* -----------------------------
* November 19, 2012.
* -----------------------------
* Config.value is now updated after gauge refresh
* -----------------------------
* November 13, 2012.
* -----------------------------
* Donut display mode added
* Option to hide value label
* Option to enable responsive gauge size
* Removed default title attribute
* Option to accept min and max defined as string values
* Option to configure value symbol
* Fixed bad aspect ratio calculations
* Option to configure minimum font size for all texts
* Option to show shorthand big numbers (human friendly)
*/
JustGage = function(config) {
if (!config.id) {alert("Missing id parameter for gauge!"); return false;}
if (!config.node) {
if (!document.getElementById(config.id)) {alert("No element with id: \""+config.id+"\" found!"); return false;}
config.node = document.getElementById(config.id);
}
var obj = this;
// configurable parameters
obj.config =
{
// id : string
// this is container element id
id : config.id,
// node : string
// the node to use instead of DOM
node : config.node,
// title : string
// gauge title
title : (config.title) ? config.title : "",
// titleFontColor : string
// color of gauge title
titleFontColor : (config.titleFontColor) ? config.titleFontColor : "#999999",
// value : int
// value gauge is showing
value : (config.value) ? config.value : 0,
// valueFontColor : string
// color of label showing current value
valueFontColor : (config.valueFontColor) ? config.valueFontColor : "#010101",
// symbol : string
// special symbol to show next to value
symbol : (config.symbol) ? config.symbol : "",
// min : int
// min value
min : (config.min) ? parseFloat(config.min) : 0,
// max : int
// max value
max : (config.max) ? parseFloat(config.max) : 100,
// humanFriendlyDecimal : int
// number of decimal places for our human friendly number to contain
humanFriendlyDecimal : (config.humanFriendlyDecimal) ? config.humanFriendlyDecimal : 0,
// textRenderer: func
// function applied before rendering text
textRenderer : (config.textRenderer) ? config.textRenderer : null,
// gaugeWidthScale : float
// width of the gauge element
gaugeWidthScale : (config.gaugeWidthScale) ? config.gaugeWidthScale : 1.0,
// gaugeColor : string
// background color of gauge element
gaugeColor : (config.gaugeColor) ? config.gaugeColor : "#edebeb",
// label : string
// text to show below value
label : (config.label) ? config.label : "",
// labelFontColor : string
// color of label showing label under value
labelFontColor : (config.labelFontColor) ? config.labelFontColor : "#b3b3b3",
// shadowOpacity : int
// 0 ~ 1
shadowOpacity : (config.shadowOpacity) ? config.shadowOpacity : 0.2,
// shadowSize: int
// inner shadow size
shadowSize : (config.shadowSize) ? config.shadowSize : 5,
// shadowVerticalOffset : int
// how much shadow is offset from top
shadowVerticalOffset : (config.shadowVerticalOffset) ? config.shadowVerticalOffset : 3,
// levelColors : string[]
// colors of indicator, from lower to upper, in RGB format
levelColors : (config.levelColors) ? config.levelColors : [
"#a9d70b",
"#f9c802",
"#ff0000"
],
// startAnimationTime : int
// length of initial animation
startAnimationTime : (config.startAnimationTime) ? config.startAnimationTime : 700,
// startAnimationType : string
// type of initial animation (linear, >, <, <>, bounce)
startAnimationType : (config.startAnimationType) ? config.startAnimationType : ">",
// refreshAnimationTime : int
// length of refresh animation
refreshAnimationTime : (config.refreshAnimationTime) ? config.refreshAnimationTime : 700,
// refreshAnimationType : string
// type of refresh animation (linear, >, <, <>, bounce)
refreshAnimationType : (config.refreshAnimationType) ? config.refreshAnimationType : ">",
// donutStartAngle : int
// angle to start from when in donut mode
donutStartAngle : (config.donutStartAngle) ? config.donutStartAngle : 90,
// valueMinFontSize : int
// absolute minimum font size for the value
valueMinFontSize : config.valueMinFontSize || 16,
// titleMinFontSize
// absolute minimum font size for the title
titleMinFontSize : config.titleMinFontSize || 10,
// labelMinFontSize
// absolute minimum font size for the label
labelMinFontSize : config.labelMinFontSize || 10,
// minLabelMinFontSize
// absolute minimum font size for the minimum label
minLabelMinFontSize : config.minLabelMinFontSize || 10,
// maxLabelMinFontSize
// absolute minimum font size for the maximum label
maxLabelMinFontSize : config.maxLabelMinFontSize || 10,
// hideValue : bool
// hide value text
hideValue : (config.hideValue) ? config.hideValue : false,
// hideMinMax : bool
// hide min and max values
hideMinMax : (config.hideMinMax) ? config.hideMinMax : false,
// hideInnerShadow : bool
// hide inner shadow
hideInnerShadow : (config.hideInnerShadow) ? config.hideInnerShadow : false,
// humanFriendly : bool
// convert large numbers for min, max, value to human friendly (e.g. 1234567 -> 1.23M)
humanFriendly : (config.humanFriendly) ? config.humanFriendly : false,
// noGradient : bool
// whether to use gradual color change for value, or sector-based
noGradient : (config.noGradient) ? config.noGradient : false,
// donut : bool
// show full donut gauge
donut : (config.donut) ? config.donut : false,
// relativeGaugeSize : bool
// whether gauge size should follow changes in container element size
relativeGaugeSize : (config.relativeGaugeSize) ? config.relativeGaugeSize : false,
// counter : bool
// animate level number change
counter : (config.counter) ? config.counter : false,
// decimals : int
// number of digits after floating point
decimals : (config.decimals) ? config.decimals : 0,
// customSectors : [] of objects
// number of digits after floating point
customSectors : (config.customSectors) ? config.customSectors : []
};
// variables
var
canvasW,
canvasH,
widgetW,
widgetH,
aspect,
dx,
dy,
titleFontSize,
titleX,
titleY,
valueFontSize,
valueX,
valueY,
labelFontSize,
labelX,
labelY,
minFontSize,
minX,
minY,
maxFontSize,
maxX,
maxY;
// overflow values
if (this.config.value > this.config.max) this.config.value = this.config.max;
if (this.config.value < this.config.min) this.config.value = this.config.min;
this.originalValue = config.value;
// canvas
this.canvas = Raphael(this.config.node, "100%", "100%");
if (this.config.relativeGaugeSize === true) {
this.canvas.setViewBox(0, 0, 200, 150, true);
}
// canvas dimensions
if (this.config.relativeGaugeSize === true) {
canvasW = 200;
canvasH = 150;
} else {
canvasW = getStyle(this.config.node, "width").slice(0, -2) * 1;
canvasH = getStyle(this.config.node, "height").slice(0, -2) * 1;
}
// widget dimensions
if (this.config.donut === true) {
// DONUT *******************************
// width more than height
if(canvasW > canvasH) {
widgetH = canvasH;
widgetW = widgetH;
// width less than height
} else if (canvasW < canvasH) {
widgetW = canvasW;
widgetH = widgetW;
// if height don't fit, rescale both
if(widgetH > canvasH) {
aspect = widgetH / canvasH;
widgetH = widgetH / aspect;
widgetW = widgetH / aspect;
}
// equal
} else {
widgetW = canvasW;
widgetH = widgetW;
}
// delta
dx = (canvasW - widgetW)/2;
dy = (canvasH - widgetH)/2;
// title
titleFontSize = ((widgetH / 8) > 10) ? (widgetH / 10) : 10;
titleX = dx + widgetW / 2;
titleY = dy + widgetH / 11;
// value
valueFontSize = ((widgetH / 6.4) > 16) ? (widgetH / 5.4) : 18;
valueX = dx + widgetW / 2;
if(this.config.label !== '') {
valueY = dy + widgetH / 1.85;
} else {
valueY = dy + widgetH / 1.7;
}
// label
labelFontSize = ((widgetH / 16) > 10) ? (widgetH / 16) : 10;
labelX = dx + widgetW / 2;
labelY = valueY + labelFontSize;
// min
minFontSize = ((widgetH / 16) > 10) ? (widgetH / 16) : 10;
minX = dx + (widgetW / 10) + (widgetW / 6.666666666666667 * this.config.gaugeWidthScale) / 2 ;
minY = labelY;
// max
maxFontSize = ((widgetH / 16) > 10) ? (widgetH / 16) : 10;
maxX = dx + widgetW - (widgetW / 10) - (widgetW / 6.666666666666667 * this.config.gaugeWidthScale) / 2 ;
maxY = labelY;
} else {
// HALF *******************************
// width more than height
if(canvasW > canvasH) {
widgetH = canvasH;
widgetW = widgetH * 1.25;
//if width doesn't fit, rescale both
if(widgetW > canvasW) {
aspect = widgetW / canvasW;
widgetW = widgetW / aspect;
widgetH = widgetH / aspect;
}
// width less than height
} else if (canvasW < canvasH) {
widgetW = canvasW;
widgetH = widgetW / 1.25;
// if height don't fit, rescale both
if(widgetH > canvasH) {
aspect = widgetH / canvasH;
widgetH = widgetH / aspect;
widgetW = widgetH / aspect;
}
// equal
} else {
widgetW = canvasW;
widgetH = widgetW * 0.75;
}
// delta
dx = (canvasW - widgetW)/2;
dy = (canvasH - widgetH)/2;
// title
titleFontSize = ((widgetH / 8) > this.config.titleMinFontSize) ? (widgetH / 10) : this.config.titleMinFontSize;
titleX = dx + widgetW / 2;
titleY = dy + widgetH / 6.4;
// value
valueFontSize = ((widgetH / 6.5) > this.config.valueMinFontSize) ? (widgetH / 6.5) : this.config.valueMinFontSize;
valueX = dx + widgetW / 2;
valueY = dy + widgetH / 1.275;
// label
labelFontSize = ((widgetH / 16) > this.config.labelMinFontSize) ? (widgetH / 16) : this.config.labelMinFontSize;
labelX = dx + widgetW / 2;
labelY = valueY + valueFontSize / 2 + 5;
// min
minFontSize = ((widgetH / 16) > this.config.minLabelMinFontSize) ? (widgetH / 16) : this.config.minLabelMinFontSize;
minX = dx + (widgetW / 10) + (widgetW / 6.666666666666667 * this.config.gaugeWidthScale) / 2 ;
minY = labelY;
// max
maxFontSize = ((widgetH / 16) > this.config.maxLabelMinFontSize) ? (widgetH / 16) : this.config.maxLabelMinFontSize;
maxX = dx + widgetW - (widgetW / 10) - (widgetW / 6.666666666666667 * this.config.gaugeWidthScale) / 2 ;
maxY = labelY;
}
// parameters
this.params = {
canvasW : canvasW,
canvasH : canvasH,
widgetW : widgetW,
widgetH : widgetH,
dx : dx,
dy : dy,
titleFontSize : titleFontSize,
titleX : titleX,
titleY : titleY,
valueFontSize : valueFontSize,
valueX : valueX,
valueY : valueY,
labelFontSize : labelFontSize,
labelX : labelX,
labelY : labelY,
minFontSize : minFontSize,
minX : minX,
minY : minY,
maxFontSize : maxFontSize,
maxX : maxX,
maxY : maxY
};
// var clear
canvasW, canvasH, widgetW, widgetH, aspect, dx, dy, titleFontSize, titleX, titleY, valueFontSize, valueX, valueY, labelFontSize, labelX, labelY, minFontSize, minX, minY, maxFontSize, maxX, maxY = null
// pki - custom attribute for generating gauge paths
this.canvas.customAttributes.pki = function (value, min, max, w, h, dx, dy, gws, donut) {
var alpha, Ro, Ri, Cx, Cy, Xo, Yo, Xi, Yi, path;
if (donut) {
alpha = (1 - 2 * (value - min) / (max - min)) * Math.PI;
Ro = w / 2 - w / 7;
Ri = Ro - w / 6.666666666666667 * gws;
Cx = w / 2 + dx;
Cy = h / 1.95 + dy;
Xo = w / 2 + dx + Ro * Math.cos(alpha);
Yo = h - (h - Cy) + 0 - Ro * Math.sin(alpha);
Xi = w / 2 + dx + Ri * Math.cos(alpha);
Yi = h - (h - Cy) + 0 - Ri * Math.sin(alpha);
path += "M" + (Cx - Ri) + "," + Cy + " ";
path += "L" + (Cx - Ro) + "," + Cy + " ";
if (value > ((max - min) / 2)) {
path += "A" + Ro + "," + Ro + " 0 0 1 " + (Cx + Ro) + "," + Cy + " ";
}
path += "A" + Ro + "," + Ro + " 0 0 1 " + Xo + "," + Yo + " ";
path += "L" + Xi + "," + Yi + " ";
if (value > ((max - min) / 2)) {
path += "A" + Ri + "," + Ri + " 0 0 0 " + (Cx + Ri) + "," + Cy + " ";
}
path += "A" + Ri + "," + Ri + " 0 0 0 " + (Cx - Ri) + "," + Cy + " ";
path += "Z ";
return { path: path };
} else {
alpha = (1 - (value - min) / (max - min)) * Math.PI;
Ro = w / 2 - w / 10;
Ri = Ro - w / 6.666666666666667 * gws;
Cx = w / 2 + dx;
Cy = h / 1.25 + dy;
Xo = w / 2 + dx + Ro * Math.cos(alpha);
Yo = h - (h - Cy) + 0 - Ro * Math.sin(alpha);
Xi = w / 2 + dx + Ri * Math.cos(alpha);
Yi = h - (h - Cy) + 0 - Ri * Math.sin(alpha);
path += "M" + (Cx - Ri) + "," + Cy + " ";
path += "L" + (Cx - Ro) + "," + Cy + " ";
path += "A" + Ro + "," + Ro + " 0 0 1 " + Xo + "," + Yo + " ";
path += "L" + Xi + "," + Yi + " ";
path += "A" + Ri + "," + Ri + " 0 0 0 " + (Cx - Ri) + "," + Cy + " ";
path += "Z ";
return { path: path };
}
// var clear
alpha, Ro, Ri, Cx, Cy, Xo, Yo, Xi, Yi, path = null;
};
// gauge
this.gauge = this.canvas.path().attr({
"stroke": "none",
"fill": this.config.gaugeColor,
pki: [this.config.max, this.config.min, this.config.max, this.params.widgetW, this.params.widgetH, this.params.dx, this.params.dy, this.config.gaugeWidthScale, this.config.donut]
});
this.gauge.id = this.config.id+"-gauge";
// level
this.level = this.canvas.path().attr({
"stroke": "none",
"fill": getColor(this.config.value, (this.config.value - this.config.min) / (this.config.max - this.config.min), this.config.levelColors, this.config.noGradient, this.config.customSectors),
pki: [this.config.min, this.config.min, this.config.max, this.params.widgetW, this.params.widgetH, this.params.dx, this.params.dy, this.config.gaugeWidthScale, this.config.donut]
});
if(this.config.donut) {
this.level.transform("r" + this.config.donutStartAngle + ", " + (this.params.widgetW/2 + this.params.dx) + ", " + (this.params.widgetH/1.95 + this.params.dy));
}
this.level.id = this.config.id+"-level";
// title
this.txtTitle = this.canvas.text(this.params.titleX, this.params.titleY, this.config.title);
this.txtTitle.attr({
"font-size":this.params.titleFontSize,
"font-weight":"bold",
"font-family":"Arial",
"fill":this.config.titleFontColor,
"fill-opacity":"1"
});
setDy(this.txtTitle, this.params.titleFontSize, this.params.titleY);
this.txtTitle.id = this.config.id+"-txttitle";
// value
this.txtValue = this.canvas.text(this.params.valueX, this.params.valueY, 0);
this.txtValue.attr({
"font-size":this.params.valueFontSize,
"font-weight":"bold",
"font-family":"Arial",
"fill":this.config.valueFontColor,
"fill-opacity":"0"
});
setDy(this.txtValue, this.params.valueFontSize, this.params.valueY);
this.txtValue.id = this.config.id+"-txtvalue";
// label
this.txtLabel = this.canvas.text(this.params.labelX, this.params.labelY, this.config.label);
this.txtLabel.attr({
"font-size":this.params.labelFontSize,
"font-weight":"normal",
"font-family":"Arial",
"fill":this.config.labelFontColor,
"fill-opacity":"0"
});
setDy(this.txtLabel, this.params.labelFontSize, this.params.labelY);
this.txtLabel.id = this.config.id+"-txtlabel";
// min
this.txtMinimum = this.config.min;
if( this.config.humanFriendly ) this.txtMinimum = humanFriendlyNumber( this.config.min, this.config.humanFriendlyDecimal );
this.txtMin = this.canvas.text(this.params.minX, this.params.minY, this.txtMinimum);
this.txtMin.attr({
"font-size":this.params.minFontSize,
"font-weight":"normal",
"font-family":"Arial",
"fill":this.config.labelFontColor,
"fill-opacity": (this.config.hideMinMax || this.config.donut)? "0" : "1"
});
setDy(this.txtMin, this.params.minFontSize, this.params.minY);
this.txtMin.id = this.config.id+"-txtmin";
// max
this.txtMaximum = this.config.max;
if( this.config.humanFriendly ) this.txtMaximum = humanFriendlyNumber( this.config.max, this.config.humanFriendlyDecimal );
this.txtMax = this.canvas.text(this.params.maxX, this.params.maxY, this.txtMaximum);
this.txtMax.attr({
"font-size":this.params.maxFontSize,
"font-weight":"normal",
"font-family":"Arial",
"fill":this.config.labelFontColor,
"fill-opacity": (this.config.hideMinMax || this.config.donut)? "0" : "1"
});
setDy(this.txtMax, this.params.maxFontSize, this.params.maxY);
this.txtMax.id = this.config.id+"-txtmax";
var defs = this.canvas.canvas.childNodes[1];
var svg = "http://www.w3.org/2000/svg";
if (ie < 9) {
onCreateElementNsReady(function() {
this.generateShadow(svg, defs);
});
} else {
this.generateShadow(svg, defs);
}
// var clear
defs, svg = null;
// execute on each animation frame
function onAnimate() {
if (obj.config.counter) {
var currentValue = obj.level.attr("pki");
if(obj.config.textRenderer) {
// this.originalValue = this.config.textRenderer(this.originalValue);
obj.txtValue.attr("text", obj.config.textRenderer(Math.floor(currentValue[0])));
} else if(obj.config.humanFriendly) {
// this.originalValue = humanFriendlyNumber( this.originalValue, this.config.humanFriendlyDecimal ) + this.config.symbol;
obj.txtValue.attr("text", humanFriendlyNumber( Math.floor(currentValue[0]), obj.config.humanFriendlyDecimal ) + obj.config.symbol);
} else {
// this.originalValue += this.config.symbol;
obj.txtValue.attr("text", (currentValue[0] * 1).toFixed(obj.config.decimals) + obj.config.symbol);
}
setDy(obj.txtValue, obj.params.valueFontSize, obj.params.valueY);
currentValue = null;
}
}
if(!obj.config.counter) {
if(obj.config.textRenderer) {
obj.originalValue = obj.config.textRenderer(obj.originalValue);
} else if(obj.config.humanFriendly) {
obj.originalValue = humanFriendlyNumber( obj.originalValue, obj.config.humanFriendlyDecimal ) + obj.config.symbol;
} else {
obj.originalValue = (obj.originalValue * 1).toFixed(obj.config.decimals) + obj.config.symbol;
}
obj.txtValue.attr("text", obj.originalValue);
setDy(obj.txtValue, obj.params.valueFontSize, obj.params.valueY);
}
//event fired on each animation frame
eve.on("raphael.anim.frame.*", onAnimate);
// animate gauge level
this.level.animate({pki: [this.config.value, this.config.min, this.config.max, this.params.widgetW, this.params.widgetH, this.params.dx, this.params.dy, this.config.gaugeWidthScale, this.config.donut]}, this.config.startAnimationTime, this.config.startAnimationType);
// animate value
this.txtValue.animate({"fill-opacity":"1"}, this.config.startAnimationTime, this.config.startAnimationType);
// animate label
this.txtLabel.animate({"fill-opacity":"1"}, this.config.startAnimationTime, this.config.startAnimationType);
};
/** Refresh gauge level */
JustGage.prototype.refresh = function(val, max) {
var originalVal, displayVal, color, max = max || null;
// set new max
if(max !== null) {
this.config.max = max;
this.txtMaximum = this.config.max;
if( this.config.humanFriendly ) this.txtMaximum = humanFriendlyNumber( this.config.max, this.config.humanFriendlyDecimal );
this.txtMax.attr({"text" : this.config.max});
setDy(this.txtMax, this.params.maxFontSize, this.params.maxY);
}
// overflow values
originalVal = val;
displayVal = val;
if ((val * 1) > (this.config.max * 1)) {val = (this.config.max * 1);}
if ((val * 1) < (this.config.min * 1)) {val = (this.config.min * 1);}
color = getColor(val, (val - this.config.min) / (this.config.max - this.config.min), this.config.levelColors, this.config.noGradient, this.config.customSectors);
if(this.config.textRenderer) {
displayVal = this.config.textRenderer(displayVal);
} else if( this.config.humanFriendly ) {
displayVal = humanFriendlyNumber( displayVal, this.config.humanFriendlyDecimal ) + this.config.symbol;
} else {
displayVal = (displayVal * 1).toFixed(this.config.decimals) + this.config.symbol;
}
if(!this.config.counter) {
this.canvas.getById(this.config.id+"-txtvalue").attr({"text":displayVal});
setDy(this.canvas.getById(this.config.id+"-txtvalue"), this.params.valueFontSize, this.params.valueY);
}
this.canvas.getById(this.config.id+"-level").animate({pki: [val, this.config.min, this.config.max, this.params.widgetW, this.params.widgetH, this.params.dx, this.params.dy, this.config.gaugeWidthScale, this.config.donut], "fill":color}, this.config.refreshAnimationTime, this.config.refreshAnimationType);
this.config.value = val * 1;
// var clear
originalVal, displayVal, color, max = null;
};
/** Generate shadow */
JustGage.prototype.generateShadow = function(svg, defs) {
var gaussFilter, feOffset, feGaussianBlur, feComposite1, feFlood, feComposite2, feComposite3;
// FILTER
gaussFilter=document.createElementNS(svg,"filter");
gaussFilter.setAttribute("id", this.config.id + "-inner-shadow");
defs.appendChild(gaussFilter);
// offset
feOffset = document.createElementNS(svg,"feOffset");
feOffset.setAttribute("dx", 0);
feOffset.setAttribute("dy", this.config.shadowVerticalOffset);
gaussFilter.appendChild(feOffset);
// blur
feGaussianBlur = document.createElementNS(svg,"feGaussianBlur");
feGaussianBlur.setAttribute("result","offset-blur");
feGaussianBlur.setAttribute("stdDeviation", this.config.shadowSize);
gaussFilter.appendChild(feGaussianBlur);
// composite 1
feComposite1 = document.createElementNS(svg,"feComposite");
feComposite1.setAttribute("operator","out");
feComposite1.setAttribute("in", "SourceGraphic");
feComposite1.setAttribute("in2","offset-blur");
feComposite1.setAttribute("result","inverse");
gaussFilter.appendChild(feComposite1);
// flood
feFlood = document.createElementNS(svg,"feFlood");
feFlood.setAttribute("flood-color","black");
feFlood.setAttribute("flood-opacity", this.config.shadowOpacity);
feFlood.setAttribute("result","color");
gaussFilter.appendChild(feFlood);
// composite 2
feComposite2 = document.createElementNS(svg,"feComposite");
feComposite2.setAttribute("operator","in");
feComposite2.setAttribute("in", "color");
feComposite2.setAttribute("in2","inverse");
feComposite2.setAttribute("result","shadow");
gaussFilter.appendChild(feComposite2);
// composite 3
feComposite3 = document.createElementNS(svg,"feComposite");
feComposite3.setAttribute("operator","over");
feComposite3.setAttribute("in", "shadow");
feComposite3.setAttribute("in2","SourceGraphic");
gaussFilter.appendChild(feComposite3);
// set shadow
if (!this.config.hideInnerShadow) {
this.canvas.canvas.childNodes[2].setAttribute("filter", "url(#" + this.config.id + "-inner-shadow)");
this.canvas.canvas.childNodes[3].setAttribute("filter", "url(#" + this.config.id + "-inner-shadow)");
}
// var clear
gaussFilter, feOffset, feGaussianBlur, feComposite1, feFlood, feComposite2, feComposite3 = null;
};
/** Get color for value */
function getColor(val, pct, col, noGradient, custSec) {
var no, inc, colors, percentage, rval, gval, bval, lower, upper, range, rangePct, pctLower, pctUpper, color;
var noGradient = noGradient || custSec.length > 0;
if(custSec.length > 0) {
for(var i = 0; i < custSec.length; i++) {
if(val > custSec[i].lo && val <= custSec[i].hi) {
return custSec[i].color;
}
}
}
no = col.length;
if (no === 1) return col[0];
inc = (noGradient) ? (1 / no) : (1 / (no - 1));
colors = [];
for (var i = 0; i < col.length; i++) {
percentage = (noGradient) ? (inc * (i + 1)) : (inc * i);
rval = parseInt((cutHex(col[i])).substring(0,2),16);
gval = parseInt((cutHex(col[i])).substring(2,4),16);
bval = parseInt((cutHex(col[i])).substring(4,6),16);
colors[i] = { pct: percentage, color: { r: rval, g: gval, b: bval } };
}
if(pct === 0) {
return 'rgb(' + [colors[0].color.r, colors[0].color.g, colors[0].color.b].join(',') + ')';
}
for (var j = 0; j < colors.length; j++) {
if (pct <= colors[j].pct) {
if (noGradient) {
return 'rgb(' + [colors[j].color.r, colors[j].color.g, colors[j].color.b].join(',') + ')';
} else {
lower = colors[j - 1];
upper = colors[j];
range = upper.pct - lower.pct;
rangePct = (pct - lower.pct) / range;
pctLower = 1 - rangePct;
pctUpper = rangePct;
color = {
r: Math.floor(lower.color.r * pctLower + upper.color.r * pctUpper),
g: Math.floor(lower.color.g * pctLower + upper.color.g * pctUpper),
b: Math.floor(lower.color.b * pctLower + upper.color.b * pctUpper)
};
return 'rgb(' + [color.r, color.g, color.b].join(',') + ')';
}
}
}
}
/** Fix Raphael display:none tspan dy attribute bug */
function setDy(elem, fontSize, txtYpos) {
if ((!ie || ie > 9) && (elem.node.firstChild.attributes.dy)) {
elem.node.firstChild.attributes.dy.value = 0;
}
}
/** Random integer */
function getRandomInt (min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
/** Cut hex */
function cutHex(str) {
return (str.charAt(0)=="#") ? str.substring(1,7):str;
}
/** Human friendly number suffix - From: http://stackoverflow.com/questions/2692323/code-golf-friendly-number-abbreviator */
function humanFriendlyNumber( n, d ) {
var p, d2, i, s;
p = Math.pow;
d2 = p(10, d);
i = 7;
while( i ) {
s = p(10,i--*3);
if( s <= n ) {
n = Math.round(n*d2/s)/d2+"KMGTPE"[i];
}
}
return n;
}
/** Get style */
function getStyle(oElm, strCssRule){
var strValue = "";
if(document.defaultView && document.defaultView.getComputedStyle){
strValue = document.defaultView.getComputedStyle(oElm).getPropertyValue(strCssRule);
}
else if(oElm.currentStyle){
strCssRule = strCssRule.replace(/\-(\w)/g, function (strMatch, p1){
return p1.toUpperCase();
});
strValue = oElm.currentStyle[strCssRule];
}
return strValue;
}
/** Create Element NS Ready */
function onCreateElementNsReady(func) {
if (document.createElementNS !== undefined) {
func();
} else {
setTimeout(function() { onCreateElementNsReady(func); }, 100);
}
}
/** Get IE version */
// ----------------------------------------------------------
// A short snippet for detecting versions of IE in JavaScript
// without resorting to user-agent sniffing
// ----------------------------------------------------------
// If you're not in IE (or IE version is less than 5) then:
// ie === undefined
// If you're in IE (>=5) then you can determine which version:
// ie === 7; // IE7
// Thus, to detect IE:
// if (ie) {}
// And to detect the version:
// ie === 6 // IE6
// ie > 7 // IE8, IE9 ...
// ie < 9 // Anything less than IE9
// ----------------------------------------------------------
// UPDATE: Now using Live NodeList idea from @jdalton
var ie = (function(){
var undef,
v = 3,
div = document.createElement('div'),
all = div.getElementsByTagName('i');
while (
div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->',
all[0]
);
return v > 4 ? v : undef;
}());

View File

@ -0,0 +1,108 @@
openerp.sale_crm = function(openerp) {
var _t = openerp.web._t;
openerp.sale_crm.GaugeWidget = openerp.web_kanban.AbstractField.extend({
className: "oe_gage",
start: function() {
var self = this;
var parent = this.getParent();
var max = this.options.max_field ? parent.record[this.options.max_field].raw_value : 100;
var label = this.options.label_field ? parent.record[this.options.label_field].raw_value : "";
var title = this.$node.html();
var val = this.field.value;
var value = _.isArray(val) && val.length ? val[val.length-1]['value'] : val;
var unique_id = _.uniqueId("JustGage");
this.$el.empty()
.attr('style', this.$node.attr('style') + ';position:relative; display:inline-block;')
.attr('id', unique_id);
this.gage = new JustGage({
id: unique_id,
node: this.$el[0],
title: title,
value: value,
min: 0,
max: max,
relativeGaugeSize: true,
humanFriendly: true,
titleFontColor: '#333333',
valueFontColor: '#333333',
labelFontColor: '#000',
label: label,
levelColors: [
"#ff0000",
"#f9c802",
"#a9d70b"
],
});
var flag_open = false;
if (self.options.action_change) {
var $svg = self.$el.find('svg');
var css = {
'text-align': 'center',
'position': 'absolute',
'width': self.$el.outerWidth() + 'px',
'top': (self.$el.outerHeight()/2-5) + 'px'
};
self.$el.click(function (event) {
event.stopPropagation();
flag_open = false;
if (!parent.view.is_action_enabled('edit')) {
return;
}
if (!self.$el.find(".oe_justgage_edit").size()) {
$div = $('<div class="oe_justgage_edit" style="z-index:1"/>');
$div.css(css);
$input = $('<input/>').val(value);
$input.css({
'text-align': 'center',
'margin': 'auto',
'width': ($svg.outerWidth()-40) + 'px'
});
$div.append($input);
self.$el.prepend($div)
$input.focus()
.keydown(function (event) {
event.stopPropagation();
if (event.keyCode == 13 || event.keyCode == 9) {
if ($input.val() != value) {
parent.view.dataset.call(self.options.action_change, [parent.id, $input.val()]).then(function () {
parent.do_reload();
});
} else {
$div.remove();
}
}
})
.click(function (event) {
event.stopPropagation();
flag_open = false;
})
.blur(function (event) {
if(!flag_open) {
self.$el.find(".oe_justgage_edit").remove();
} else {
flag_open = false;
setTimeout(function () {$input.focus();}, 0);
}
});
}
}).mousedown(function () {
flag_open = true;
});
if (!+value) {
$svg.fadeTo(0, 0.3);
$div = $('<div/>').text(_t("Click to change value"));
$div.css(css);
self.$el.append($div);
}
}
},
});
openerp.web_kanban.fields_registry.add("gage", "openerp.sale_crm.GaugeWidget");
};

View File

@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:06+0000\n"
"PO-Revision-Date: 2011-01-20 08:51+0000\n"
"PO-Revision-Date: 2013-06-04 13:50+0000\n"
"Last-Translator: Chertykov Denis <chertykov@gmail.com>\n"
"Language-Team: Russian <ru@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-03-16 05:40+0000\n"
"X-Generator: Launchpad (build 16532)\n"
"X-Launchpad-Export-Date: 2013-06-05 05:32+0000\n"
"X-Generator: Launchpad (build 16660)\n"
#. module: sale_margin
#: field:sale.order.line,purchase_price:0
@ -44,6 +44,7 @@ msgid ""
"It gives profitability by calculating the difference between the Unit Price "
"and the cost price."
msgstr ""
"Дает прибыльность как разницу между стоимостью единицы и себестоимостью."
#~ msgid "Customer Invoice"
#~ msgstr "Счета клиентам"

View File

@ -230,6 +230,7 @@ class share_wizard(osv.TransientModel):
current_user = user_obj.browse(cr, UID_ROOT, uid, context=context)
# modify context to disable shortcuts when creating share users
context['noshortcut'] = True
context['no_reset_password'] = True
created_ids = []
existing_ids = []
if wizard_data.user_type == 'emails':

View File

@ -7,13 +7,13 @@ 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-06-03 12:48+0000\n"
"PO-Revision-Date: 2013-06-04 12:52+0000\n"
"Last-Translator: Chertykov Denis <chertykov@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-06-04 05:20+0000\n"
"X-Launchpad-Export-Date: 2013-06-05 05:32+0000\n"
"X-Generator: Launchpad (build 16660)\n"
#. module: stock
@ -1977,7 +1977,7 @@ msgstr "Полка 2"
#: code:addons/stock/stock.py:529
#, python-format
msgid "You cannot remove a lot line."
msgstr ""
msgstr "Нельзя удалить позицию партии"
#. module: stock
#: help:stock.location,posx:0
@ -2406,7 +2406,7 @@ msgstr "Место хранения поставщика"
#. module: stock
#: view:stock.location.product:0
msgid "View Products Inventory"
msgstr ""
msgstr "Новая инвентаризация продукции"
#. module: stock
#: view:stock.move:0
@ -2539,6 +2539,7 @@ msgid ""
"Current quantity of products with this Serial Number available in company "
"warehouses"
msgstr ""
"Текущее количество продукции с этим серийный номером на складах компании"
#. module: stock
#: view:stock.inventory:0

View File

@ -191,7 +191,8 @@ Thanks,''') % (name, self.pool.get('ir.config_parameter').get_param(cr, uid, 'we
'action_id': act_id[0],
'survey_id': [[6, 0, survey_ids]]
}
user = user_ref.create(cr, uid, res_data)
create_ctx = dict(context, no_reset_password=True)
user = user_ref.create(cr, uid, res_data, context=create_ctx)
if user not in new_user:
new_user.append(user)
created+= "- %s (Login: %s, Password: %s)\n" % (partner.name or _('Unknown'),\