#
##############################################################################
+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.tools.translate import _
-from datetime import date, datetime
-from dateutil.relativedelta import relativedelta
MAX_LEVEL = 15
AVAILABLE_STATES = [
_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_open_lead_per_duration(self, cr, uid, ids, field_name, arg, context=None):
- res = dict.fromkeys(ids, [])
- obj = self.pool.get('crm.lead')
- today = date.today().replace(day=1)
- begin = (today + relativedelta(months=-5)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
- for section in self.browse(cr, uid, ids, context=context):
- domain = [("section_id", "=", section.id), '|', ('type', '=', 'lead'), ('date_open', '!=', None), ('create_date', '>=', begin)]
- group_obj = obj.read_group(cr, uid, domain, ["create_date"], "create_date", context=context)
- group_list = [group['create_date_count'] for group in group_obj]
- nb_month = group_obj and relativedelta(today, datetime.strptime(group_obj[-1]['__domain'][0][2], '%Y-%m-%d')).months or 0
- res[section.id] = [0]*(5 - len(group_list) - nb_month) + group_list + [0]*nb_month
- return res
+ 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_won_opportunity_per_duration(self, cr, uid, ids, field_name, arg, context=None):
- res = dict.fromkeys(ids, [])
+ 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')
- today = date.today().replace(day=1)
- begin = (today + relativedelta(months=-5)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
- for section in self.browse(cr, uid, ids, context=context):
- domain = [("section_id", "=", section.id), '|', ('type', '=', 'opportunity'), ('date_open', '!=', None), ('create_date', '>=', begin)]
- group_obj = obj.read_group(cr, uid, domain, ['planned_revenue', "create_date"], "create_date", context=context)
- group_list = [group['planned_revenue'] for group in group_obj]
- nb_month = group_obj and relativedelta(today, datetime.strptime(group_obj[-1]['__domain'][0][2], '%Y-%m-%d')).months or 0
- res[section.id] = [0]*(5 - len(group_list) - nb_month) + group_list + [0]*nb_month
+ 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 = {
'use_leads': fields.boolean('Leads',
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."),
- 'open_lead_per_duration': fields.function(_get_open_lead_per_duration, string='Open Leads per duration', type="string", readonly=True),
- 'won_opportunity_per_duration': fields.function(_get_won_opportunity_per_duration, string='Revenue of opporunities whon per duration', type="string", readonly=True)
+ '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):
<field name="note"/>
<field name="alias_id"/>
<field name="color"/>
- <field name="open_lead_per_duration"/>
- <field name="won_opportunity_per_duration"/>
+ <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_global_click oe_kanban_crm_salesteams">
<div class="oe_items_list">
<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 month.<br/>Click to see a detailed analysis of leads.</field></a>
- </div><div class="oe_salesteams_opportunities">
+ <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="won_opportunity_per_duration" widget="sparkline_bar">Revenue of won opportunities per month.<br/>Click to see a detailed analysis of opportunities.</field></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>
<group>
<group>
- <field name="parent_id"/>
- <field name="active"/>
+ <field name="user_id"/>
+ <field name="code"/>
</group>
<group>
- <field name="user_id"/>
+ <field name="parent_id"/>
<field name="change_responsible"/>
- </group>
- <group colspan="4" attrs="{'invisible': [('use_leads', '=', False)]}">
-
+ <field name="active"/>
</group>
</group>
<notebook colspan="4">
var self = this;
var title = this.$node.html();
setTimeout(function () {
- self.$el.sparkline(self.field.value, {type: 'bar', barWidth: 5} );
+ 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);
},
#
##############################################################################
-from datetime import date, datetime
+from datetime import date
+from dateutil import relativedelta
+
from openerp import tools
-from dateutil.relativedelta import relativedelta
from openerp.osv import osv, fields
class crm_case_section(osv.osv):
_inherit = 'crm.case.section'
- def _get_created_quotation_per_duration(self, cr, uid, ids, field_name, arg, context=None):
- res = dict.fromkeys(ids, [])
+ def _get_sale_orders_data(self, cr, uid, ids, field_name, arg, context=None):
obj = self.pool.get('sale.order')
- today = date.today().replace(day=1)
- begin = (today + relativedelta(months=-5)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
- for section in self.browse(cr, uid, ids, context=context):
- domain = [("section_id", "=", section.id), ('state', 'in', ['draft', 'sent']), ('date_order', '>=', begin)]
- group_obj = obj.read_group(cr, uid, domain, ['amount_total', "date_order"], "date_order", context=context)
- group_list = [group['amount_total'] for group in group_obj]
- nb_month = group_obj and relativedelta(today, datetime.strptime(group_obj[-1]['__domain'][0][2], '%Y-%m-%d')).months or 0
- res[section.id] = [0]*(5 - len(group_list) - nb_month) + group_list + [0]*nb_month
+ 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_validate_saleorder_per_duration(self, cr, uid, ids, field_name, arg, context=None):
- res = dict.fromkeys(ids, [])
- obj = self.pool.get('sale.order')
- today = date.today().replace(day=1)
- begin = (today + relativedelta(months=-5)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
- for section in self.browse(cr, uid, ids, context=context):
- domain = [("section_id", "=", section.id), ('state', 'not in', ['draft', 'sent']), ('date_confirm', '>=', begin)]
- group_obj = obj.read_group(cr, uid, domain, ['amount_total', "date_confirm"], "date_confirm", context=context)
- group_list = [group['amount_total'] for group in group_obj]
- nb_month = group_obj and relativedelta(today, datetime.strptime(group_obj[-1]['__domain'][0][2], '%Y-%m-%d')).months or 0
- res[section.id] = [0]*(5 - len(group_list) - nb_month) + group_list + [0]*nb_month
- return res
-
- def _get_sent_invoice_per_duration(self, cr, uid, ids, field_name, arg, context=None):
- res = dict.fromkeys(ids, [])
+ def _get_invoices_data(self, cr, uid, ids, field_name, arg, context=None):
obj = self.pool.get('account.invoice.report')
- today = date.today().replace(day=1)
- begin = (today + relativedelta(months=-5)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
- for section in self.browse(cr, uid, ids, context=context):
- domain = [("section_id", "=", section.id), ('state', 'not in', ['draft', 'cancel']), ('date', '>=', begin)]
- group_obj = obj.read_group(cr, uid, domain, ['price_total', "date"], "date", context=context)
- group_list = [group['price_total'] for group in group_obj]
- nb_month = group_obj and relativedelta(today, datetime.strptime(group_obj[-1]['__domain'][0][2], '%Y-%m-%d')).months or 0
- res[section.id] = [0]*(5 - len(group_list) - nb_month) + group_list + [0]*nb_month
+ 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'])]),
-
- '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),
+ '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], {'forecast': value}, context=context)
+ return self.write(cr, uid, [id], {'invoiced_forecast': value}, context=context)
+
class res_users(osv.Model):
_inherit = 'res.users'
<field name="inherit_id" ref="crm.crm_case_section_view_form"/>
<field name="arch" type="xml">
<data>
- <xpath expr="//group" position="inside">
- <group>
- <field name="target_invoice"/>
- </group>
+ <xpath expr="//field[@name='code']" position="after">
+ <field name="invoiced_target"/>
+ <field name="invoiced_forecast"/>
</xpath>
</data>
</field>
<field name="arch" type="xml">
<data>
<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"/>
+ <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_salesteams_leads']" position="after"><div class="oe_salesteams_orders">
+ <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 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="%(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="sent_invoice_per_duration" 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="%(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="created_quotation_per_duration" widget="sparkline_bar">Revenue of created quotation per month.<br/>Click to see a detailed analysis of sales.</field></a>
- </div></xpath>
+ <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" 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'}">Invoiced</field>
- <field name="forecast" widget="gage" style="width:160px; height: 120px;" options="{'max_field': 'target_invoice', 'action_change': 'action_forecast'}">Forecast</field>
+ <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.target_invoice.raw_value">
+ <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>
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] : val;
+ var value = _.isArray(val) && val.length ? val[val.length-1]['value'] : val;
var unique_id = _.uniqueId("JustGage");
this.$el.empty()