[CLEAN] crm, sale_crm: cleaned code for new fields. Fixed some bugs in calculation...
authorThibault Delavallée <tde@openerp.com>
Fri, 31 May 2013 15:31:00 +0000 (17:31 +0200)
committerThibault Delavallée <tde@openerp.com>
Fri, 31 May 2013 15:31:00 +0000 (17:31 +0200)
bzr revid: tde@openerp.com-20130531153100-q1cmpx0kjb02cbtu

addons/crm/crm.py
addons/crm/crm_case_section_view.xml
addons/crm/static/src/js/crm_case_section.js
addons/sale_crm/sale_crm.py
addons/sale_crm/sale_crm_view.xml
addons/sale_crm/static/src/js/sale_crm.js

index 5866167..4c9a606 100644 (file)
 #
 ##############################################################################
 
+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 = [
@@ -105,34 +105,55 @@ 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_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 = {
@@ -158,8 +179,12 @@ class crm_case_section(osv.osv):
         '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):
index 7f33d7c..c73390c 100644 (file)
@@ -78,8 +78,8 @@
                     <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">
index 656e368..2b4ca74 100644 (file)
@@ -15,7 +15,16 @@ openerp.crm = function(openerp) {
             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);
         },
index b753438..6a3e13f 100644 (file)
 #
 ##############################################################################
 
-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
 
 
@@ -48,65 +49,51 @@ class sale_order(osv.osv):
 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'
index 32ddad3..2ae8bb3 100644 (file)
             <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>
index c0dc4dc..207ac95 100644 (file)
@@ -11,7 +11,7 @@ openerp.sale_crm.GaugeWidget = openerp.web_kanban.AbstractField.extend({
         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()