<field name="search_view_id" ref="crm.view_crm_case_opportunities_filter"/>
<field name="context">{
'search_default_section_id': [active_id],
- 'search_default_new': 1,
- 'search_default_open': 1,
+ 'search_default_assigned_to_me': 1,
'default_section_id': active_id,
'stage_type': 'opportunity',
'default_type': 'opportunity',
<field name="note"/>
<field name="alias_id"/>
<field name="color"/>
- <field name="open_lead_ids"/>
- <field name="open_opportunity_ids"/>
+ <field name="target_duration_txt"/>
+ <field name="open_lead_per_duration"/>
+ <field name="won_opportunity_per_duration"/>
<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>
</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 >= 2">Leads</t>
- <t t-if="record.open_lead_ids.raw_value.length < 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 >= 2">Opportunities</t>
- <t t-if="record.open_opportunity_ids.raw_value.length < 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="open_lead_per_duration" widget="sparkline_bar">Number of opening leads per duration.<br/>(last one is <t t-esc="record.target_duration_txt.value"/>).<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="won_opportunity_per_duration" widget="sparkline_bar">Revenue of won opportunities per duration.<br/>(last one is <t t-esc="record.target_duration_txt.value"/>).<br/>Click to see a detailed analysis of opportunities.</field></a>
+ </div>
</div>
</div>
</div>
</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"/>
+ <filter name="my" string="My" domain="['|', ('member_ids', '=', uid), ('user_id', '=', uid)]"/>
+ <group expand="0" string="Group By...">
+ <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">
<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_my': True}</field>
<field name="view_id" ref="crm_case_section_salesteams_view_kanban"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
<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="Sales team"/>
</h1>
<div name="group_alias"
attrs="{'invisible': [('alias_domain', '=', False)]}">
<group>
<group>
<field name="parent_id"/>
- <field name="resource_calendar_id"/>
<field name="active"/>
</group>
<group>
<field name="user_id"/>
- <field name="code"/>
+ <field name="change_responsible"/>
+ </group>
+ <group colspan="4" col="2">
+ <field name="target_duration" widget="radio"/>
</group>
<group colspan="4" attrs="{'invisible': [('use_leads', '=', False)]}">
</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"/>
import crm
from datetime import datetime
from operator import itemgetter
- from openerp.osv import fields, osv
+ from openerp.osv import fields, osv, orm
import time
+ from openerp import SUPERUSER_ID
from openerp import tools
from openerp.tools.translate import _
from openerp.tools import html2plaintext
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]
def message_get_reply_to(self, cr, uid, ids, context=None):
""" Override to get the reply_to of the parent project. """
return [lead.section_id.message_get_reply_to()[0] if lead.section_id else False
- for lead in self.browse(cr, uid, ids, context=context)]
+ for lead in self.browse(cr, SUPERUSER_ID, ids, context=context)]
+
+ def _get_formview_action(self, cr, uid, id, context=None):
+ action = super(crm_lead, self)._get_formview_action(cr, uid, id, context=context)
+ obj = self.browse(cr, uid, id, context=context)
+ if obj.type == 'opportunity':
+ model, view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'crm', 'crm_case_form_view_oppor')
+ action.update({
+ 'views': [(view_id, 'form')],
+ })
+ return action
def message_get_suggested_recipients(self, cr, uid, ids, context=None):
recipients = super(crm_lead, self).message_get_suggested_recipients(cr, uid, ids, context=context)
- for lead in self.browse(cr, uid, ids, context=context):
- if lead.partner_id:
- self._message_add_suggested_recipient(cr, uid, recipients, lead, partner=lead.partner_id, reason=_('Customer'))
- elif lead.email_from:
- self._message_add_suggested_recipient(cr, uid, recipients, lead, email=lead.email_from, reason=_('Customer Email'))
+ try:
+ for lead in self.browse(cr, uid, ids, context=context):
+ if lead.partner_id:
+ self._message_add_suggested_recipient(cr, uid, recipients, lead, partner=lead.partner_id, reason=_('Customer'))
+ elif lead.email_from:
+ self._message_add_suggested_recipient(cr, uid, recipients, lead, email=lead.email_from, reason=_('Customer Email'))
+ except (osv.except_osv, orm.except_orm): # no read access rights -> just ignore suggested recipients because this imply modifying followers
+ pass
return recipients
def message_new(self, cr, uid, msg, custom_values=None, context=None):
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)]"
<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'}"/>
</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>
<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="Assigned to Me"
+ <filter string="Mine" 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)"
<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>
#
##############################################################################
-from datetime import datetime
+from datetime import date
+from openerp import tools
+from dateutil.relativedelta import relativedelta
from openerp.osv import osv, fields
+MONTHS = {
+ "monthly": 1,
+ "semesterly": 3,
+ "semiannually": 6,
+ "annually": 12
+}
class sale_order(osv.osv):
_inherit = 'sale.order'
domain="['|',('section_id','=',section_id),('section_id','=',False), ('object_id.model', '=', 'crm.lead')]")
}
+ def _make_invoice(self, cr, uid, order, lines, context=None):
+ if order.section_id:
+ context = dict(context or {}, default_section_id= order.section_id.id)
+ return super(sale_order, self)._make_invoice(cr, uid, order, lines, context=context)
+
+ def _prepare_invoice(self, cr, uid, order, lines, context=None):
+ invoice_vals = super(sale_order, self)._prepare_invoice(cr, uid, order, lines, context=context)
+ if order.section_id and order.section_id.id:
+ invoice_vals['section_id'] = order.section_id.id
+ return invoice_vals
+
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_created_quotation_per_duration(self, cr, uid, ids, field_name, arg, context=None):
+ res = dict.fromkeys(ids, [])
+ obj = self.pool.get('sale.order')
+ first_day = date.today().replace(day=1)
+
+ for section in self.browse(cr, uid, ids, context=context):
+ dates = [first_day + relativedelta(months=-(MONTHS[section.target_duration]*(key+1)-1)) for key in range(0, 5)]
+ rate_invoice = []
+ for when in range(0, 5):
+ domain = [("section_id", "=", section.id), ('state', 'in', ['draft', 'sent']), ('date_order', '>=', dates[when].strftime(tools.DEFAULT_SERVER_DATE_FORMAT))]
+ if when:
+ domain += [('date_order', '<', dates[when-1].strftime(tools.DEFAULT_SERVER_DATE_FORMAT))]
+ rate = 0
+ order_ids = obj.search(cr, uid, domain, context=context)
+ for order in obj.browse(cr, uid, order_ids, context=context):
+ rate += order.amount_total
+ rate_invoice.append(rate)
+ rate_invoice.reverse()
+ res[section.id] = rate_invoice
+ return res
+
+ def _get_validate_saleorder_per_duration(self, cr, uid, ids, field_name, arg, context=None):
+ res = dict.fromkeys(ids, [])
+ obj = self.pool.get('sale.order')
+ first_day = date.today().replace(day=1)
+
+ for section in self.browse(cr, uid, ids, context=context):
+ dates = [first_day + relativedelta(months=-(MONTHS[section.target_duration]*(key+1)-1)) for key in range(0, 5)]
+ rate_invoice = []
+ for when in range(0, 5):
+ domain = [("section_id", "=", section.id), ('state', 'not in', ['draft', 'sent']), ('date_confirm', '>=', dates[when].strftime(tools.DEFAULT_SERVER_DATE_FORMAT))]
+ if when:
+ domain += [('date_confirm', '<', dates[when-1].strftime(tools.DEFAULT_SERVER_DATE_FORMAT))]
+ rate = 0
+ order_ids = obj.search(cr, uid, domain, context=context)
+ for order in obj.browse(cr, uid, order_ids, context=context):
+ rate += order.amount_total
+ rate_invoice.append(rate)
+ rate_invoice.reverse()
+ res[section.id] = rate_invoice
+ return res
+
+ def _get_sent_invoice_per_duration(self, cr, uid, ids, field_name, arg, context=None):
+ res = dict.fromkeys(ids, [])
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
+ first_day = date.today().replace(day=1)
+
+ for section in self.browse(cr, uid, ids, context=context):
+ dates = [first_day + relativedelta(months=-(MONTHS[section.target_duration]*(key+1)-1)) for key in range(0, 5)]
+ rate_invoice = []
+ for when in range(0, 5):
+ domain = [("section_id", "=", section.id), ('state', 'not in', ['draft', 'cancel']), ('date', '>=', dates[when].strftime(tools.DEFAULT_SERVER_DATE_FORMAT))]
+ if when:
+ domain += [('date', '<', dates[when-1].strftime(tools.DEFAULT_SERVER_DATE_FORMAT))]
+ rate = 0
+ invoice_ids = obj.search(cr, uid, domain, context=context)
+ for invoice in obj.browse(cr, uid, invoice_ids, context=context):
+ rate += invoice.price_total
+ rate_invoice.append(rate)
+ rate_invoice.reverse()
+ res[section.id] = rate_invoice
return res
_columns = {
'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),
+
+ 'forecast': fields.integer(string='Total forecast'),
+ 'target_invoice': fields.integer(string='Invoicing Target'),
+ 'created_quotation_per_duration': fields.function(_get_created_quotation_per_duration, string='Rate of created quotation per duration', type="string", readonly=True),
+ 'validate_saleorder_per_duration': fields.function(_get_validate_saleorder_per_duration, string='Rate of validate sales orders per duration', type="string", readonly=True),
+ 'sent_invoice_per_duration': fields.function(_get_sent_invoice_per_duration, string='Rate of sent invoices per duration', type="string", readonly=True),
}
+ def action_forecast(self, cr, uid, id, value, context=None):
+ return self.write(cr, uid, [id], {'forecast': value}, context=context)
class res_users(osv.Model):
_inherit = 'res.users'
</field>
</record>
+ <!-- add needaction_menu_ref to reload quotation needaction when opportunity needaction is reloaded -->
+ <record model="ir.actions.act_window" id="crm.crm_case_category_act_oppor11">
+ <field name="context">{'stage_type': 'opportunity', 'default_type': 'opportunity', 'default_user_id': uid, 'needaction_menu_ref': 'sale.menu_sale_quotations'}</field>
+ </record>
+
<record model="ir.ui.view" id="sale_view_inherit123">
<field name="name">sale.order.inherit</field>
<field name="model">sale.order</field>
<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"/>
<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>
<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">
<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>
('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',
<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>
+ <field name="target_duration" position="before">
+ <field name="target_invoice"/>
+ </field>
+ </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>
- <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 >= 2">Quotations</t>
- <t t-if="record.quotation_ids.raw_value.length < 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 >= 2">Sales Orders</t>
- <t t-if="record.sale_order_ids.raw_value.length < 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 >= 2">Invoices</t>
- <t t-if="record.invoice_ids.raw_value.length < 2">Invoice</t>
- </a>
+ <xpath expr="//field[@name='name']" position="after">
+ <field name="created_quotation_per_duration"/>
+ <field name="validate_saleorder_per_duration"/>
+ <field name="sent_invoice_per_duration"/>
+
+ <field name="forecast"/>
+ <field name="target_invoice"/>
</xpath>
+ <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="validate_saleorder_per_duration" widget="sparkline_bar">Revenue of confirmed sales orders per <t t-esc="record.target_duration_txt.value"/>).<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="sent_invoice_per_duration" widget="sparkline_bar">Revenue of sent invoices per <t t-esc="record.target_duration_txt.value"/>.<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="created_quotation_per_duration" widget="sparkline_bar">Revenue of created quotation per <t t-esc="record.target_duration_txt.value"/>.<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.target_invoice.raw_value">
+ <field name="sent_invoice_per_duration" widget="gage" style="width:160px; height: 120px;" options="{'max_field': 'target_invoice', 'label_field': 'target_duration_txt'}">Invoiced</field>
+ <field name="forecast" widget="gage" style="width:160px; height: 120px;" options="{'max_field': 'target_invoice', 'label_field': 'target_duration_txt', 'action_change': 'action_forecast'}">Forecast</field>
+ </div>
+ <div class="oe_center" style="color:#bbbbbb;" t-if="!record.target_invoice.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>