diff --git a/addons/crm/__init__.py b/addons/crm/__init__.py index 3398a4b470d..853b0b01f31 100644 --- a/addons/crm/__init__.py +++ b/addons/crm/__init__.py @@ -8,8 +8,3 @@ from . import wizard from odoo import api, SUPERUSER_ID - -def uninstall_hook(cr, registry): - env = api.Environment(cr, SUPERUSER_ID, {}) - teams = env['crm.team'].search([('dashboard_graph_model', '=', 'crm.opportunity.report')]) - teams.update({'dashboard_graph_model': None}) diff --git a/addons/crm/__manifest__.py b/addons/crm/__manifest__.py index 4223c2c1ad6..61b2e19d0a0 100644 --- a/addons/crm/__manifest__.py +++ b/addons/crm/__manifest__.py @@ -54,6 +54,5 @@ 'css': ['static/src/css/crm.css'], 'installable': True, 'application': True, - 'auto_install': False, - 'uninstall_hook': 'uninstall_hook', + 'auto_install': False } diff --git a/addons/crm/data/crm_data.xml b/addons/crm/data/crm_data.xml index a2e1f84498a..006c982c03d 100644 --- a/addons/crm/data/crm_data.xml +++ b/addons/crm/data/crm_data.xml @@ -1,13 +1,16 @@ - - True - True - sales - crm.lead - - + + + + + + + + + + @@ -29,5 +32,4 @@ Not enough stock - diff --git a/addons/crm/data/crm_demo.xml b/addons/crm/data/crm_demo.xml index 1d5e38c586e..d57758c9106 100644 --- a/addons/crm/data/crm_demo.xml +++ b/addons/crm/data/crm_demo.xml @@ -6,14 +6,9 @@ - - - - - True - True - crm.lead - + sales + + diff --git a/addons/crm/data/crm_lead_data.xml b/addons/crm/data/crm_lead_data.xml index 4a287afb4e8..c5b83c47682 100644 --- a/addons/crm/data/crm_lead_data.xml +++ b/addons/crm/data/crm_lead_data.xml @@ -58,12 +58,7 @@ team_id - - - - + Share diff --git a/addons/sale/data/sale_demo.xml b/addons/sale/data/sale_demo.xml index acb8e8b688b..9f7106b6e71 100644 --- a/addons/sale/data/sale_demo.xml +++ b/addons/sale/data/sale_demo.xml @@ -17,10 +17,7 @@ - - 40000 - sale.report @@ -733,11 +730,6 @@ Thanks! - - - Chair floor protection diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index 0945fade787..43caf3a306c 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -734,7 +734,7 @@ class SaleOrder(models.Model): return report_pages def has_to_be_signed(self, also_in_draft=False): - return (self.state == 'sent' or (self.state == 'draft' and also_in_draft)) and not self.is_expired and self.require_signature and not self.signature and self.team_id.team_type != 'website' + return (self.state == 'sent' or (self.state == 'draft' and also_in_draft)) and not self.is_expired and self.require_signature and not self.signature def has_to_be_paid(self, also_in_draft=False): transaction = self.get_portal_last_transaction() diff --git a/addons/sale/models/sales_team.py b/addons/sale/models/sales_team.py index bb0686c50c9..852162bfd8c 100644 --- a/addons/sale/models/sales_team.py +++ b/addons/sale/models/sales_team.py @@ -9,9 +9,6 @@ from odoo import api, fields, models, _ class CrmTeam(models.Model): _inherit = 'crm.team' - use_quotations = fields.Boolean(string='Quotations', help="Check this box if you send quotations to your customers rather than confirming orders straight away. " - "This will add specific action buttons to your dashboard.") - use_invoices = fields.Boolean('Set Invoicing Target', help="Check this box to set an invoicing target for this Sales Team.") invoiced = fields.Integer( compute='_compute_invoiced', string='Invoiced This Month', readonly=True, @@ -31,36 +28,31 @@ class CrmTeam(models.Model): sales_to_invoice_count = fields.Integer( compute='_compute_sales_to_invoice', string='Number of sales to invoice', readonly=True) - dashboard_graph_model = fields.Selection(selection_add=[ - ('sale.report', 'Sales'), - ('account.invoice.report', 'Invoices'), - ]) + def _compute_quotations_to_invoice(self): - non_website_teams = self.filtered(lambda team: team.team_type != 'website') - if non_website_teams: - query = self.env['sale.order']._where_calc([ - ('team_id', 'in', non_website_teams.ids), - ('state', 'in', ['draft', 'sent']), - ]) - self.env['sale.order']._apply_ir_rules(query, 'read') - _, where_clause, where_clause_args = query.get_sql() - select_query = """ - SELECT team_id, count(*), sum(amount_total / - CASE COALESCE(currency_rate, 0) - WHEN 0 THEN 1.0 - ELSE currency_rate - END - ) as amount_total - FROM sale_order - WHERE %s - GROUP BY team_id - """ % where_clause - self.env.cr.execute(select_query, where_clause_args) - quotation_data = self.env.cr.dictfetchall() - for datum in quotation_data: - self.browse(datum['team_id']).quotations_amount = datum['amount_total'] - self.browse(datum['team_id']).quotations_count = datum['count'] + query = self.env['sale.order']._where_calc([ + ('team_id', 'in', self.ids), + ('state', 'in', ['draft', 'sent']), + ]) + self.env['sale.order']._apply_ir_rules(query, 'read') + _, where_clause, where_clause_args = query.get_sql() + select_query = """ + SELECT team_id, count(*), sum(amount_total / + CASE COALESCE(currency_rate, 0) + WHEN 0 THEN 1.0 + ELSE currency_rate + END + ) as amount_total + FROM sale_order + WHERE %s + GROUP BY team_id + """ % where_clause + self.env.cr.execute(select_query, where_clause_args) + quotation_data = self.env.cr.dictfetchall() + for datum in quotation_data: + self.browse(datum['team_id']).quotations_amount = datum['amount_total'] + self.browse(datum['team_id']).quotations_count = datum['count'] @api.multi def _compute_sales_to_invoice(self): @@ -68,8 +60,9 @@ class CrmTeam(models.Model): ('team_id', 'in', self.ids), ('order_line.qty_to_invoice', '>', 0), ], ['team_id'], ['team_id']) - for datum in sale_order_data: - self.browse(datum['team_id'][0]).invoiced = datum['team_id_count'] + data_map = {datum['team_id'][0]: datum['team_id_count'] for datum in sale_order_data } + for team in self: + team.sales_to_invoice_count = data_map.get(team.id,0.0) @api.multi def _compute_invoiced(self): @@ -80,67 +73,45 @@ class CrmTeam(models.Model): ('date', '>=', date.today().replace(day=1)), ('type', 'in', ['out_invoice', 'out_refund']), ], ['amount_untaxed_signed', 'team_id'], ['team_id']) - for datum in invoice_data: - self.browse(datum['team_id'][0]).invoiced = datum['amount_untaxed_signed'] + data_map = { datum['team_id'][0]: datum['amount_untaxed_signed'] for datum in invoice_data} + for team in self: + team.invoiced = data_map.get(team.id,0.0) + + def _graph_get_model(self): + if self._context.get('in_sales_app'): + return 'sale.report' + return super(CrmTeam,self)._graph_get_model() def _graph_date_column(self): - if self.dashboard_graph_model == 'sale.report': + if self._context.get('in_sales_app'): return 'confirmation_date' - elif self.dashboard_graph_model == 'account.invoice.report': - return 'date' - return super(CrmTeam, self)._graph_date_column() + return super(CrmTeam,self)._graph_date_column() def _graph_y_query(self): - if self.dashboard_graph_model == 'sale.report': + if self._context.get('in_sales_app'): return 'SUM(price_subtotal)' - elif self.dashboard_graph_model == 'account.invoice.report': - return 'SUM(price_total)' - return super(CrmTeam, self)._graph_y_query() + return super(CrmTeam,self)._graph_y_query() def _extra_sql_conditions(self): - if self.dashboard_graph_model == 'sale.report': - return "AND state in ('sale', 'done')" - elif self.dashboard_graph_model == 'account.invoice.report': - return "AND state in ('open', 'in_payment', 'paid')" - return super(CrmTeam, self)._extra_sql_conditions() + if self._context.get('in_sales_app'): + return "AND state in ('sale', 'done', 'pos_done')" + return super(CrmTeam,self)._extra_sql_conditions() def _graph_title_and_key(self): - if self.dashboard_graph_model == 'sale.report': + if self._context.get('in_sales_app'): return ['', _('Sales: Untaxed Total')] # no more title - elif self.dashboard_graph_model == 'account.invoice.report': - return ['', _('Invoices: Untaxed Total')] return super(CrmTeam, self)._graph_title_and_key() def _compute_dashboard_button_name(self): - quotation_teams = self.filtered('use_quotations') - quotation_teams.update({'dashboard_button_name': _("Quotations")}) - (self - quotation_teams).update({'dashboard_button_name': _("Sales Orders")}) + super(CrmTeam,self)._compute_dashboard_button_name() + if self._context.get('in_sales_app'): + self.update({'dashboard_button_name': _("Sales Analysis")}) def action_primary_channel_button(self): - if hasattr(self, 'use_opportunities') and self.use_opportunities: - return super(CrmTeam, self).action_primary_channel_button() - elif self.use_quotations: - action = self.env.ref('sale.action_quotations_salesteams').read()[0] - action['context'] = {'search_default_team_id': self.id} - return action - else: - action = self.env.ref('sale.action_orders_salesteams').read()[0] - action['context'] = {'search_default_team_id': self.id} - return action - - @api.onchange('team_type') - def _onchange_team_type(self): - if self.team_type == 'sales': - self.use_quotations = True - self.use_invoices = True - if not self.dashboard_graph_model: - self.dashboard_graph_model = 'sale.report' - else: - self.use_quotations = False - self.use_invoices = False - self.dashboard_graph_model = 'sale.report' - return super(CrmTeam, self)._onchange_team_type() - + if self._context.get('in_sales_app'): + return self.env.ref('sale.action_order_report_so_salesteam').read()[0] + return super(CrmTeam, self).action_primary_channel_button() + @api.multi def update_invoiced_target(self, value): return self.write({'invoiced_target': round(float(value or 0))}) diff --git a/addons/sale/tests/test_access_rights.py b/addons/sale/tests/test_access_rights.py index 11f9faa3a57..a58a6b94c03 100644 --- a/addons/sale/tests/test_access_rights.py +++ b/addons/sale/tests/test_access_rights.py @@ -86,8 +86,8 @@ class TestAccessRights(TestCommonSaleNoChart): }) self.assertIn(india_channel.id, self.env['crm.team'].search([]).ids, 'Sales manager should be able to create a Sales Team') # Manager can edit a Sales Team - india_channel.sudo(self.user_manager).write({'dashboard_graph_group': 'week'}) - self.assertEquals(india_channel.dashboard_graph_group, 'week', 'Sales manager should be able to edit a Sales Team') + india_channel.sudo(self.user_manager).write({'name': 'new_india'}) + self.assertEquals(india_channel.name, 'new_india', 'Sales manager should be able to edit a Sales Team') # Manager can delete a Sales Team india_channel.sudo(self.user_manager).unlink() self.assertNotIn(india_channel.id, self.env['crm.team'].search([]).ids, 'Sales manager should be able to delete a Sales Team') diff --git a/addons/sale/views/sale_views.xml b/addons/sale/views/sale_views.xml index 3b6372a111e..9fd93e2b419 100644 --- a/addons/sale/views/sale_views.xml +++ b/addons/sale/views/sale_views.xml @@ -1239,8 +1239,8 @@ Sales Analysis sale.report graph - [('state','not in',('draft','cancel')),('team_id', '=', active_id)] - {'search_default_order_month':1} + [('state','not in',('draft','cancel'))] + {'search_default_Sales': 1, 'time_ranges': {'field':'confirmation_date', 'range':'last_30_days'}, 'search_default_team_id': [active_id]} This report performs analysis on your sales orders. Analysis check your sales revenues and sort it by different group criteria (salesman, partner, product, etc.) Use this report to perform analysis on sales not having invoiced yet. If you want to analyse your turnover, you should use the Invoice Analysis report in the Accounting application. @@ -1248,8 +1248,8 @@ Invoices Analysis account.invoice.report graph - [('team_id', '=', active_id),('state', 'not in', ['draft', 'cancel'])] - {'search_default_month':1} + [('state', 'not in', ['draft', 'cancel'])] + {'search_default_month':1, 'search_default_team_id': [active_id]} From this report, you can have an overview of the amount invoiced to your customer. The search tool can also be used to personalise your Invoices reports and so, match this analysis to your needs. diff --git a/addons/sale/views/sales_team_views.xml b/addons/sale/views/sales_team_views.xml index 14324d170a2..49b2963217f 100644 --- a/addons/sale/views/sales_team_views.xml +++ b/addons/sale/views/sales_team_views.xml @@ -6,17 +6,8 @@ 9 - -
-
-
- - - - - - + +
@@ -58,8 +49,6 @@ - - @@ -89,17 +78,17 @@ - + - - Click to define a target + + Click to define an invoicing target -
+
@@ -111,7 +100,7 @@ -
+
Quotation @@ -119,11 +108,6 @@ -
Sales diff --git a/addons/sale_crm/__init__.py b/addons/sale_crm/__init__.py index dc5e6b693d1..e3e519761b8 100644 --- a/addons/sale_crm/__init__.py +++ b/addons/sale_crm/__init__.py @@ -2,3 +2,10 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from . import models + +from odoo import api, SUPERUSER_ID + +def uninstall_hook(cr, registry): + env = api.Environment(cr, SUPERUSER_ID, {}) + teams = env['crm.team'].search([('use_opportunities', '=', False)]) + teams.write({'use_opportunities': True}) \ No newline at end of file diff --git a/addons/sale_crm/__manifest__.py b/addons/sale_crm/__manifest__.py index 4fb6af3d937..9620ea428e7 100644 --- a/addons/sale_crm/__manifest__.py +++ b/addons/sale_crm/__manifest__.py @@ -22,6 +22,8 @@ modules. 'views/partner_views.xml', 'views/sale_order_views.xml', 'views/crm_lead_views.xml', + 'views/crm_team_views.xml' ], 'auto_install': True, + 'uninstall_hook': 'uninstall_hook' } diff --git a/addons/sale_crm/models/crm_team.py b/addons/sale_crm/models/crm_team.py index b7e91e1b697..183f6cc03a2 100644 --- a/addons/sale_crm/models/crm_team.py +++ b/addons/sale_crm/models/crm_team.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo import models, _ +from odoo import models,fields, api, _ class CrmTeam(models.Model): @@ -9,4 +9,36 @@ class CrmTeam(models.Model): def _compute_dashboard_button_name(self): super(CrmTeam, self)._compute_dashboard_button_name() - self.filtered(lambda team: team.use_opportunities and team.team_type in ('sales', 'website')).update({'dashboard_button_name': _("Pipeline")}) + teams_with_opp = self.filtered(lambda team: team.use_opportunities) + if self._context.get('in_sales_app'): + teams_with_opp.update({'dashboard_button_name': _("Sales Analysis")}) + + def action_primary_channel_button(self): + if self._context.get('in_sales_app') and self.use_opportunities: + return self.env.ref('sale.action_order_report_so_salesteam').read()[0] + return super(CrmTeam,self).action_primary_channel_button() + + def _graph_get_model(self): + if self.use_opportunities and self._context.get('in_sales_app') : + return 'sale.report' + return super(CrmTeam,self)._graph_get_model() + + def _graph_date_column(self): + if self.use_opportunities and self._context.get('in_sales_app'): + return 'confirmation_date' + return super(CrmTeam,self)._graph_date_column() + + def _graph_y_query(self): + if self.use_opportunities and self._context.get('in_sales_app'): + return 'SUM(price_subtotal)' + return super(CrmTeam,self)._graph_y_query() + + def _graph_title_and_key(self): + if self.use_opportunities and self._context.get('in_sales_app'): + return ['', _('Sales: Untaxed Total')] + return super(CrmTeam,self)._graph_title_and_key() + + def _extra_sql_conditions(self): + if self.use_opportunities and self._context.get('in_sales_app'): + return "AND state in ('sale', 'done', 'pos_done')" + return super(CrmTeam,self)._extra_sql_conditions() diff --git a/addons/sale_crm/views/crm_team_views.xml b/addons/sale_crm/views/crm_team_views.xml new file mode 100644 index 00000000000..7cd6d72129c --- /dev/null +++ b/addons/sale_crm/views/crm_team_views.xml @@ -0,0 +1,23 @@ + + + + crm.team.form + crm.team + + + + + + + + + + + + {'in_sales_app': True} + + + + [('use_opportunities', '=', True)] + + diff --git a/addons/sales_team/data/sales_team_data.xml b/addons/sales_team/data/sales_team_data.xml index 133233e2963..12d92efd92e 100644 --- a/addons/sales_team/data/sales_team_data.xml +++ b/addons/sales_team/data/sales_team_data.xml @@ -11,8 +11,25 @@ Website - + + + Point of Sale + + + + + eBay + + + + diff --git a/addons/sales_team/data/sales_team_demo.xml b/addons/sales_team/data/sales_team_demo.xml index 91ea15a5470..cbe77090d97 100644 --- a/addons/sales_team/data/sales_team_demo.xml +++ b/addons/sales_team/data/sales_team_demo.xml @@ -4,7 +4,7 @@ Europe - week + @@ -17,8 +17,8 @@ America - week + diff --git a/addons/sales_team/models/crm_team.py b/addons/sales_team/models/crm_team.py index 476f8a9e991..19ed667dea4 100644 --- a/addons/sales_team/models/crm_team.py +++ b/addons/sales_team/models/crm_team.py @@ -38,10 +38,11 @@ class CrmTeam(models.Model): default_team_id.check_access_rule('read') except AccessError: return self.env['crm.team'] - if self.env.context.get('default_type') != 'lead' or default_team_id.use_leads and default_team_id.active: + if self.env.context.get('default_type') != 'lead' and default_team_id.active: team_id = default_team_id return team_id + def _get_default_favorite_user_ids(self): return [(6, 0, [self.env.uid])] @@ -53,7 +54,7 @@ class CrmTeam(models.Model): "res.currency", related='company_id.currency_id', string="Currency", readonly=True) user_id = fields.Many2one('res.users', string='Team Leader') - member_ids = fields.One2many('res.users', 'sale_team_id', string='Channel Members') + member_ids = fields.One2many('res.users', 'sale_team_id', string='Channel Members', domain= lambda self: [('groups_id', 'in', self.env.ref('base.group_user').id)], help="Add members to automatically assign their documents to this sales team. You can only be member of one team.") favorite_user_ids = fields.Many2many( 'res.users', 'team_favorite_user_rel', 'team_id', 'user_id', string='Favorite Members', @@ -65,35 +66,11 @@ class CrmTeam(models.Model): reply_to = fields.Char(string='Reply-To', help="The email address put in the 'Reply-To' of all emails sent by Odoo about cases in this Sales Team") color = fields.Integer(string='Color Index', help="The color of the channel") - team_type = fields.Selection([('sales', 'Sales'), ('website', 'Website')], string='Team Type', default='sales', required=True, - help="The type of this channel, it will define the resources this channel uses.") dashboard_button_name = fields.Char(string="Dashboard Button", compute='_compute_dashboard_button_name') dashboard_graph_data = fields.Text(compute='_compute_dashboard_graph') - dashboard_graph_type = fields.Selection([ - ('line', 'Line'), - ('bar', 'Bar'), - ], string='Type', compute='_compute_dashboard_graph', help='The type of graph this channel will display in the dashboard.') - dashboard_graph_model = fields.Selection([], string="Content", help='The graph this channel will display in the Dashboard.\n') - dashboard_graph_group = fields.Selection([ - ('day', 'Day'), - ('week', 'Week'), - ('month', 'Month'), - ('user', 'Salesperson'), - ], string='Group by', default='day', help="How this channel's dashboard graph will group the results.") - dashboard_graph_period = fields.Selection([ - ('week', 'Last Week'), - ('month', 'Last Month'), - ('year', 'Last Year'), - ], string='Scale', default='month', help="The time period this channel's dashboard graph will consider.") - - @api.depends('dashboard_graph_group', 'dashboard_graph_model', 'dashboard_graph_period') + def _compute_dashboard_graph(self): - for team in self.filtered('dashboard_graph_model'): - if team.dashboard_graph_group in (False, 'user') or team.dashboard_graph_period == 'week' and team.dashboard_graph_group != 'day' \ - or team.dashboard_graph_period == 'month' and team.dashboard_graph_group != 'day': - team.dashboard_graph_type = 'bar' - else: - team.dashboard_graph_type = 'line' + for team in self: team.dashboard_graph_data = json.dumps(team._get_graph()) def _compute_is_favorite(self): @@ -107,45 +84,26 @@ class CrmTeam(models.Model): (sudoed_self - to_fav).write({'favorite_user_ids': [(3, self.env.uid)]}) return True - def _graph_get_dates(self, today): - """ return a coherent start and end date for the dashboard graph according to the graph settings. + def _graph_get_model(self): + """ skeleton function defined here because it'll be called by crm and/or sale """ - if self.dashboard_graph_period == 'week': - start_date = today - relativedelta(weeks=1) - elif self.dashboard_graph_period == 'year': - start_date = today - relativedelta(years=1) - else: - start_date = today - relativedelta(months=1) - - # we take the start of the following month/week/day if we group by month/week/day - # (to avoid having twice the same month/week/day from different years/month/week) - if self.dashboard_graph_group == 'month': - start_date = date(start_date.year + start_date.month // 12, start_date.month % 12 + 1, 1) - # handle period=week, grouping=month for silly managers - if self.dashboard_graph_period == 'week': - start_date = today.replace(day=1) - elif self.dashboard_graph_group == 'week': - start_date += relativedelta(days=8 - start_date.isocalendar()[2]) - # add a week to make sure no overlapping is possible in case of year period (will display max 52 weeks, avoid case of 53 weeks in a year) - if self.dashboard_graph_period == 'year': - start_date += relativedelta(weeks=1) - else: - start_date += relativedelta(days=1) + raise UserError(_('Undefined graph model for Sales Team: %s') % self.name) + + def _graph_get_dates(self, today): + """ return a coherent start and end date for the dashboard graph covering a month period grouped by week. + """ + start_date = today - relativedelta(months=1) + # we take the start of the following week if we group by week + # (to avoid having twice the same week from different month) + start_date += relativedelta(days=8 - start_date.isocalendar()[2]) return [start_date, today] def _graph_date_column(self): return 'create_date' def _graph_x_query(self): - if self.dashboard_graph_group == 'user': - return 'user_id' - elif self.dashboard_graph_group == 'week': - return 'EXTRACT(WEEK FROM %s)' % self._graph_date_column() - elif self.dashboard_graph_group == 'month': - return 'EXTRACT(MONTH FROM %s)' % self._graph_date_column() - else: - return 'DATE(%s)' % self._graph_date_column() + return 'EXTRACT(WEEK FROM %s)' % self._graph_date_column() def _graph_y_query(self): raise UserError(_('Undefined graph model for Sales Team: %s') % self.name) @@ -162,7 +120,7 @@ class CrmTeam(models.Model): def _graph_data(self, start_date, end_date): """ return format should be an iterable of dicts that contain {'x_value': ..., 'y_value': ...} - x_values should either be dates, weeks, months or user_ids depending on the self.dashboard_graph_group value. + x_values should be weeks. y_values are floats. """ query = """SELECT %(x_query)s as x_value, %(y_query)s as y_value @@ -174,9 +132,8 @@ class CrmTeam(models.Model): GROUP BY x_value;""" # apply rules - if not self.dashboard_graph_model: - raise UserError(_('Undefined graph model for Sales Team: %s') % self.name) - GraphModel = self.env[self.dashboard_graph_model] + dashboard_graph_model = self._graph_get_model() + GraphModel = self.env[dashboard_graph_model] graph_table = GraphModel._table extra_conditions = self._extra_sql_conditions() where_query = GraphModel._where_calc([]) @@ -195,6 +152,7 @@ class CrmTeam(models.Model): 'end_date': "%s", 'extra_conditions': extra_conditions } + self._cr.execute(query, [self.id, start_date, end_date] + where_clause_params) return self.env.cr.dictfetchall() @@ -217,51 +175,20 @@ class CrmTeam(models.Model): today = fields.Date.from_string(fields.Date.context_today(self)) start_date, end_date = self._graph_get_dates(today) graph_data = self._graph_data(start_date, end_date) - - # line graphs and bar graphs require different labels - if self.dashboard_graph_type == 'line': - x_field = 'x' - y_field = 'y' - else: - x_field = 'label' - y_field = 'value' + x_field = 'label' + y_field = 'value' # generate all required x_fields and update the y_values where we have data for them locale = self._context.get('lang') or 'en_US' - if self.dashboard_graph_group == 'day': - for day in range(0, (end_date - start_date).days + 1): - short_name = format_date(start_date + relativedelta(days=day), 'd MMM', locale=locale) - values.append({x_field: short_name, y_field: 0}) - for data_item in graph_data: - index = (data_item.get('x_value') - start_date).days - values[index][y_field] = data_item.get('y_value') + + weeks_in_start_year = int(date(start_date.year, 12, 28).isocalendar()[1]) # This date is always in the last week of ISO years + for week in range(0, (end_date.isocalendar()[1] - start_date.isocalendar()[1]) % weeks_in_start_year + 1): + short_name = get_week_name(start_date + relativedelta(days=7 * week), locale) + values.append({x_field: short_name, y_field: 0}) - elif self.dashboard_graph_group == 'week': - weeks_in_start_year = int(date(start_date.year, 12, 28).isocalendar()[1]) # This date is always in the last week of ISO years - for week in range(0, (end_date.isocalendar()[1] - start_date.isocalendar()[1]) % weeks_in_start_year + 1): - short_name = get_week_name(start_date + relativedelta(days=7 * week), locale) - values.append({x_field: short_name, y_field: 0}) - - for data_item in graph_data: - index = int((data_item.get('x_value') - start_date.isocalendar()[1]) % weeks_in_start_year) - values[index][y_field] = data_item.get('y_value') - - elif self.dashboard_graph_group == 'month': - for month in range(0, (end_date.month - start_date.month) % 12 + 1): - short_name = format_date(start_date + relativedelta(months=month), 'MMM', locale=locale) - values.append({x_field: short_name, y_field: 0}) - - for data_item in graph_data: - index = int((data_item.get('x_value') - start_date.month) % 12) - values[index][y_field] = data_item.get('y_value') - - elif self.dashboard_graph_group == 'user': - for data_item in graph_data: - values.append({x_field: self.env['res.users'].browse(data_item.get('x_value')).name or _('Not Defined'), y_field: data_item.get('y_value')}) - - else: - for data_item in graph_data: - values.append({x_field: data_item.get('x_value'), y_field: data_item.get('y_value')}) + for data_item in graph_data: + index = int((data_item.get('x_value') - start_date.isocalendar()[1]) % weeks_in_start_year) + values[index][y_field] = data_item.get('y_value') [graph_title, graph_key] = self._graph_title_and_key() color = '#875A7B' if '+e' in version else '#7c7bad' @@ -279,11 +206,6 @@ class CrmTeam(models.Model): """ return False - def _onchange_team_type(self): - """ skeleton function defined here because it'll be called by crm and/or sale - """ - self.ensure_one() - @api.model def create(self, values): team = super(CrmTeam, self.with_context(mail_create_nosubscribe=True)).create(values) @@ -298,6 +220,18 @@ class CrmTeam(models.Model): self._add_members_to_favorites() return res + @api.multi + def unlink(self): + default_teams = [ + self.env.ref('sales_team.salesteam_website_sales'), + self.env.ref('sales_team.pos_sales_team'), + self.env.ref('sales_team.ebay_sales_team') + ] + for team in self: + if team in default_teams: + raise UserError(_('Cannot delete default team "%s"' % (team.name))) + return super(CrmTeam,self).unlink() + def _add_members_to_favorites(self): for team in self: team.favorite_user_ids = [(4, member.id) for member in team.member_ids] diff --git a/addons/sales_team/models/res_users.py b/addons/sales_team/models/res_users.py index 7a31376f887..0714af03dce 100644 --- a/addons/sales_team/models/res_users.py +++ b/addons/sales_team/models/res_users.py @@ -9,14 +9,4 @@ class ResUsers(models.Model): sale_team_id = fields.Many2one( 'crm.team', "User's Sales Team", - help='Sales Team the user is member of. Used to compute the members of a Sales Team through the inverse one2many') - - @api.model - def create(self, vals): - # Assign the new user in the sales team if there's only one sales team of type `Sales` - user = super(ResUsers, self).create(vals) - if user.has_group('sales_team.group_sale_salesman') and not user.sale_team_id: - teams = self.env['crm.team'].search([('team_type', '=', 'sales')]) - if len(teams.ids) == 1: - user.sale_team_id = teams.id - return user + help='Sales Team the user is member of. Used to compute the members of a Sales Team through the inverse one2many') \ No newline at end of file diff --git a/addons/sales_team/security/sales_team_security.xml b/addons/sales_team/security/sales_team_security.xml index 0633f75c0c0..2ca3879d012 100644 --- a/addons/sales_team/security/sales_team_security.xml +++ b/addons/sales_team/security/sales_team_security.xml @@ -25,7 +25,6 @@ All Salesteam - [('team_type','in', ['sales', 'website'])] diff --git a/addons/sales_team/views/crm_team_views.xml b/addons/sales_team/views/crm_team_views.xml index 285390fd16c..8c3a0d8433c 100644 --- a/addons/sales_team/views/crm_team_views.xml +++ b/addons/sales_team/views/crm_team_views.xml @@ -8,8 +8,6 @@ crm.team - - @@ -22,11 +20,27 @@ + Sales Teams + crm.team + form + kanban,form + + {'in_sales_app': True} + +

+ Define a new Sales Team +

+ Use Sales Teams to organize your sales departments. + Each channel will work with a separate pipeline. +

+
+
+ + Team Pipelines crm.team form kanban,form - {'search_default_my_favorites': 1}

@@ -56,11 +70,10 @@

-
+
- @@ -68,7 +81,7 @@ - + @@ -88,13 +101,6 @@ - - - - - - -
diff --git a/addons/sales_team/views/sales_team_dashboard.xml b/addons/sales_team/views/sales_team_dashboard.xml index 6d9dcb28454..c940e235073 100644 --- a/addons/sales_team/views/sales_team_dashboard.xml +++ b/addons/sales_team/views/sales_team_dashboard.xml @@ -9,7 +9,6 @@ - @@ -25,10 +24,10 @@
- +
diff --git a/addons/website_crm/models/crm_lead.py b/addons/website_crm/models/crm_lead.py index b680e8599fc..f3b739101da 100644 --- a/addons/website_crm/models/crm_lead.py +++ b/addons/website_crm/models/crm_lead.py @@ -22,10 +22,9 @@ class Website(models.Model): _inherit = 'website' def _get_crm_default_team_domain(self): - if self.env.user.has_group('crm.group_use_lead'): - return [('use_leads', '=', True)] - else: + if not self.env.user.has_group('crm.group_use_lead'): return [('use_opportunities', '=', True)] + return [] crm_default_team_id = fields.Many2one( 'crm.team', string='Default Sales Teams', diff --git a/addons/website_crm/models/res_config_settings.py b/addons/website_crm/models/res_config_settings.py index f9b3f1c2c33..11cd082db8c 100644 --- a/addons/website_crm/models/res_config_settings.py +++ b/addons/website_crm/models/res_config_settings.py @@ -7,10 +7,9 @@ class ResConfigSettings(models.TransientModel): _inherit = 'res.config.settings' def _get_crm_default_team_domain(self): - if self.env.user.has_group('crm.group_use_lead'): - return [('use_leads', '=', True)] - else: + if not self.env.user.has_group('crm.group_use_lead'): return [('use_opportunities', '=', True)] + return [] crm_default_team_id = fields.Many2one( 'crm.team', string='Default Sales Team', related='website_id.crm_default_team_id', readonly=False, diff --git a/addons/website_crm_partner_assign/tests/test_partner_assign.py b/addons/website_crm_partner_assign/tests/test_partner_assign.py index 52ddbe427e1..dde78e0d410 100644 --- a/addons/website_crm_partner_assign/tests/test_partner_assign.py +++ b/addons/website_crm_partner_assign/tests/test_partner_assign.py @@ -105,8 +105,6 @@ class TestPartnerLeadPortal(TestCrmCases): # Sales Team of crm_salesman self.team = self.env['crm.team'].with_context(mail_notrack=True).create({ 'name': 'Test Team FOR THE WIN', - 'use_leads': True, - 'use_opportunities': True, 'member_ids': [(6, 0, [self.crm_salesman.id])], }) diff --git a/addons/website_sale/controllers/backend.py b/addons/website_sale/controllers/backend.py index ea828d289fc..deaf5e9786e 100644 --- a/addons/website_sale/controllers/backend.py +++ b/addons/website_sale/controllers/backend.py @@ -43,7 +43,6 @@ class WebsiteSaleBackend(WebsiteBackend): report_product_lines = request.env['sale.report'].read_group( domain=[ ('website_id', '=', current_website.id), - ('team_id.team_type', '=', 'website'), ('state', 'in', ['sale', 'done']), ('confirmation_date', '>=', date_from), ('confirmation_date', '<=', fields.Datetime.now())], @@ -61,7 +60,7 @@ class WebsiteSaleBackend(WebsiteBackend): # Sale-based results computation sale_order_domain = [ ('website_id', '=', current_website.id), - ('team_id', 'in', request.env['crm.team'].search([('team_type', '=', 'website')]).ids), + ('team_id', 'in', request.env['crm.team'].search([('website_ids', '!=', False)]).ids), ('date_order', '>=', fields.Datetime.to_string(datetime_from)), ('date_order', '<=', fields.Datetime.to_string(datetime_to))] so_group_data = request.env['sale.order'].read_group(sale_order_domain, fields=['state'], groupby='state') @@ -75,7 +74,6 @@ class WebsiteSaleBackend(WebsiteBackend): report_price_lines = request.env['sale.report'].read_group( domain=[ ('website_id', '=', current_website.id), - ('team_id.team_type', '=', 'website'), ('state', 'in', ['sale', 'done']), ('date', '>=', date_from), ('date', '<=', date_to)], @@ -116,7 +114,6 @@ class WebsiteSaleBackend(WebsiteBackend): sales_domain = [ ('website_id', '=', current_website.id), - ('team_id.team_type', '=', 'website'), ('state', 'in', ['sale', 'done']), ('confirmation_date', '>=', date_from), ('confirmation_date', '<=', fields.Datetime.now()) @@ -132,7 +129,6 @@ class WebsiteSaleBackend(WebsiteBackend): return results def _compute_sale_graph(self, date_from, date_to, sales_domain, previous=False): - days_between = (date_to - date_from).days date_list = [(date_from + timedelta(days=x)) for x in range(0, days_between + 1)] diff --git a/addons/website_sale/data/data.xml b/addons/website_sale/data/data.xml index 81639c17cd0..4c56e978edb 100644 --- a/addons/website_sale/data/data.xml +++ b/addons/website_sale/data/data.xml @@ -27,11 +27,8 @@ oe_image_full - + - sale.report - week - diff --git a/addons/website_sale/models/crm_team.py b/addons/website_sale/models/crm_team.py index 85206db7b3a..ca6429c9e20 100644 --- a/addons/website_sale/models/crm_team.py +++ b/addons/website_sale/models/crm_team.py @@ -4,7 +4,8 @@ from datetime import datetime from dateutil.relativedelta import relativedelta -from odoo import fields, models, _ +from odoo import fields,api, models, _ +from odoo.exceptions import UserError, ValidationError class CrmTeam(models.Model): @@ -22,7 +23,7 @@ class CrmTeam(models.Model): # abandoned carts to recover are draft sales orders that have no order lines, # a partner other than the public user, and created over an hour ago # and the recovery mail was not yet sent - website_teams = self.filtered(lambda team: team.team_type == 'website') + website_teams = self.filtered(lambda team: team.website_ids) if website_teams: abandoned_carts_data = self.env['sale.order'].read_group([ ('is_abandoned_cart', '=', True), @@ -34,11 +35,6 @@ class CrmTeam(models.Model): team.abandoned_carts_count = counts.get(team.id, 0) team.abandoned_carts_amount = amounts.get(team.id, 0) - def _compute_dashboard_button_name(self): - website_teams = self.filtered(lambda team: team.team_type == 'website' and not team.use_quotations) - website_teams.update({'dashboard_button_name': _("Online Sales")}) - super(CrmTeam, self - website_teams)._compute_dashboard_button_name() - def get_abandoned_carts(self): self.ensure_one() return { diff --git a/addons/website_sale/models/digest.py b/addons/website_sale/models/digest.py index 70ae0b9bfb4..aed881ce5fb 100644 --- a/addons/website_sale/models/digest.py +++ b/addons/website_sale/models/digest.py @@ -20,7 +20,7 @@ class Digest(models.Model): ('confirmation_date', '>=', start), ('confirmation_date', '<', end), ('state', 'not in', ['draft', 'cancel', 'sent']), - ('team_id.team_type', '=', 'website'), + ('website_id', '!=', False), ('company_id', '=', company.id) ]) record.kpi_website_sale_total_value = sum(confirmed_website_sales.mapped('amount_total')) diff --git a/addons/website_sale/models/res_config_settings.py b/addons/website_sale/models/res_config_settings.py index a3613339aed..2322645b4e1 100644 --- a/addons/website_sale/models/res_config_settings.py +++ b/addons/website_sale/models/res_config_settings.py @@ -16,7 +16,7 @@ class ResConfigSettings(models.TransientModel): return False salesperson_id = fields.Many2one('res.users', related='website_id.salesperson_id', string='Salesperson', readonly=False) - salesteam_id = fields.Many2one('crm.team', related='website_id.salesteam_id', string='Sales Team', domain=[('team_type', '!=', 'pos')], readonly=False) + salesteam_id = fields.Many2one('crm.team', related='website_id.salesteam_id', string='Sales Team', readonly=False) module_website_sale_delivery = fields.Boolean("eCommerce Shipping Costs") # field used to have a nice radio in form view, resuming the 2 fields above sale_delivery_settings = fields.Selection([ diff --git a/addons/website_sale/models/res_partner.py b/addons/website_sale/models/res_partner.py index 8c5119db323..8a9980240c7 100644 --- a/addons/website_sale/models/res_partner.py +++ b/addons/website_sale/models/res_partner.py @@ -19,7 +19,6 @@ class ResPartner(models.Model): if request and hasattr(request, 'website') and not is_public: partner.last_website_so_id = SaleOrder.search([ ('partner_id', '=', partner.id), - ('team_id.team_type', '=', 'website'), ('website_id', '=', request.website.id), ('state', '=', 'draft'), ], order='write_date desc', limit=1) diff --git a/addons/website_sale/models/sale_order.py b/addons/website_sale/models/sale_order.py index 48173deecd2..dba1a992d58 100644 --- a/addons/website_sale/models/sale_order.py +++ b/addons/website_sale/models/sale_order.py @@ -41,12 +41,12 @@ class SaleOrder(models.Model): order.only_services = all(l.product_id.type in ('service', 'digital') for l in order.website_order_line) @api.multi - @api.depends('team_id.team_type', 'date_order', 'order_line', 'state', 'partner_id') + @api.depends('website_id', 'date_order', 'order_line', 'state', 'partner_id') def _compute_abandoned_cart(self): abandoned_delay = self.website_id and self.website_id.cart_abandoned_delay or 1.0 abandoned_datetime = datetime.utcnow() - relativedelta(hours=abandoned_delay) for order in self: - domain = order.date_order and order.date_order <= abandoned_datetime and order.team_id.team_type == 'website' and order.state == 'draft' and order.partner_id.id != self.env.ref('base.public_partner').id and order.order_line + domain = order.date_order and order.date_order <= abandoned_datetime and order.state == 'draft' and order.partner_id.id != self.env.ref('base.public_partner').id and order.order_line order.is_abandoned_cart = bool(domain) def _search_abandoned_cart(self, operator, value): @@ -54,7 +54,7 @@ class SaleOrder(models.Model): abandoned_datetime = fields.Datetime.to_string(datetime.utcnow() - relativedelta(hours=abandoned_delay)) abandoned_domain = expression.normalize_domain([ ('date_order', '<=', abandoned_datetime), - ('team_id.team_type', '=', 'website'), + ('website_id', '!=', False), ('state', '=', 'draft'), ('partner_id', '!=', self.env.ref('base.public_partner').id), ('order_line', '!=', False) diff --git a/addons/website_sale/models/website.py b/addons/website_sale/models/website.py index 50a8ab084ff..e848067f1b2 100644 --- a/addons/website_sale/models/website.py +++ b/addons/website_sale/models/website.py @@ -16,7 +16,17 @@ class Website(models.Model): pricelist_id = fields.Many2one('product.pricelist', compute='_compute_pricelist_id', string='Default Pricelist') currency_id = fields.Many2one('res.currency', related='pricelist_id.currency_id', related_sudo=False, string='Default Currency', readonly=False) salesperson_id = fields.Many2one('res.users', string='Salesperson') - salesteam_id = fields.Many2one('crm.team', string='Sales Team') + + def _get_default_website_team(self): + try: + team = self.env.ref('sales_team.salesteam_website_sales') + return team if team.active else None + except ValueError: + return None + + salesteam_id = fields.Many2one('crm.team', + string='Sales Team', + default=_get_default_website_team) pricelist_ids = fields.One2many('product.pricelist', compute="_compute_pricelist_ids", string='Price list available for this Ecommerce/Website') diff --git a/addons/website_sale/views/crm_team_views.xml b/addons/website_sale/views/crm_team_views.xml index dda9efe8c3e..f3070f10c92 100644 --- a/addons/website_sale/views/crm_team_views.xml +++ b/addons/website_sale/views/crm_team_views.xml @@ -7,11 +7,8 @@ - - - -
+
diff --git a/addons/website_sale/views/res_config_settings_views.xml b/addons/website_sale/views/res_config_settings_views.xml index 04410eb05d0..6a2daeee7da 100644 --- a/addons/website_sale/views/res_config_settings_views.xml +++ b/addons/website_sale/views/res_config_settings_views.xml @@ -288,7 +288,7 @@