diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py index d441953ef5b..65993b87fc9 100644 --- a/addons/crm/models/crm_lead.py +++ b/addons/crm/models/crm_lead.py @@ -114,7 +114,7 @@ class Lead(models.Model): team_id = fields.Many2one( 'crm.team', string='Sales Team', check_company=True, index=True, tracking=True, domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", - compute='_compute_team_id', readonly=False, store=True) + compute='_compute_team_id', ondelete="set null", readonly=False, store=True) company_id = fields.Many2one( 'res.company', string='Company', index=True, compute='_compute_company_id', readonly=False, store=True) diff --git a/addons/crm/models/crm_lead_scoring_frequency.py b/addons/crm/models/crm_lead_scoring_frequency.py index 85b5c77b0f2..390da13a95e 100644 --- a/addons/crm/models/crm_lead_scoring_frequency.py +++ b/addons/crm/models/crm_lead_scoring_frequency.py @@ -10,7 +10,7 @@ class LeadScoringFrequency(models.Model): value = fields.Char('Value') won_count = fields.Float('Won Count', digits=(16, 1)) # Float because we add 0.1 to avoid zero Frequency issue lost_count = fields.Float('Lost Count', digits=(16, 1)) # Float because we add 0.1 to avoid zero Frequency issue - team_id = fields.Many2one('crm.team', 'Sales Team') + team_id = fields.Many2one('crm.team', 'Sales Team', ondelete="cascade") class FrequencyField(models.Model): diff --git a/addons/crm/models/crm_stage.py b/addons/crm/models/crm_stage.py index 5cd1a793f31..43f4f01ac11 100644 --- a/addons/crm/models/crm_stage.py +++ b/addons/crm/models/crm_stage.py @@ -36,7 +36,7 @@ class Stage(models.Model): sequence = fields.Integer('Sequence', default=1, help="Used to order stages. Lower is better.") is_won = fields.Boolean('Is Won Stage?') requirements = fields.Text('Requirements', help="Enter here the internal requirements for this stage (ex: Offer sent to customer). It will appear as a tooltip over the stage's name.") - team_id = fields.Many2one('crm.team', string='Sales Team', ondelete='set null', + team_id = fields.Many2one('crm.team', string='Sales Team', ondelete="set null", help='Specific team that uses this stage. Other teams will not be able to see or use this stage.') fold = fields.Boolean('Folded in Pipeline', help='This stage is folded in the kanban view when there are no records in that stage to display.') diff --git a/addons/crm/models/res_partner.py b/addons/crm/models/res_partner.py index 1110025ed6f..a6ddbf0e568 100644 --- a/addons/crm/models/res_partner.py +++ b/addons/crm/models/res_partner.py @@ -8,7 +8,7 @@ class Partner(models.Model): _name = 'res.partner' _inherit = 'res.partner' - team_id = fields.Many2one('crm.team', string='Sales Team') + team_id = fields.Many2one('crm.team', string='Sales Team', ondelete="set null") opportunity_ids = fields.One2many('crm.lead', 'partner_id', string='Opportunities', domain=[('type', '=', 'opportunity')]) opportunity_count = fields.Integer("Opportunity", compute='_compute_opportunity_count') diff --git a/addons/crm_iap_lead/models/crm_iap_lead_mining_request.py b/addons/crm_iap_lead/models/crm_iap_lead_mining_request.py index 141b7a1ac35..e4888e1446a 100644 --- a/addons/crm_iap_lead/models/crm_iap_lead_mining_request.py +++ b/addons/crm_iap_lead/models/crm_iap_lead_mining_request.py @@ -48,7 +48,7 @@ class CRMLeadMiningRequest(models.Model): lead_type = fields.Selection([('lead', 'Leads'), ('opportunity', 'Opportunities')], string='Type', required=True, default=_default_lead_type) display_lead_label = fields.Char(compute='_compute_display_lead_label') team_id = fields.Many2one( - 'crm.team', string='Sales Team', + 'crm.team', string='Sales Team', ondelete="set null", domain="[('use_opportunities', '=', True)]", readonly=False, compute='_compute_team_id', store=True) user_id = fields.Many2one('res.users', string='Salesperson', default=lambda self: self.env.user) tag_ids = fields.Many2many('crm.tag', string='Tags') diff --git a/addons/crm_iap_lead_website/models/crm_reveal_rule.py b/addons/crm_iap_lead_website/models/crm_reveal_rule.py index a2c824d47e6..38e9d67af7b 100644 --- a/addons/crm_iap_lead_website/models/crm_reveal_rule.py +++ b/addons/crm_iap_lead_website/models/crm_reveal_rule.py @@ -52,7 +52,7 @@ class CRMRevealRule(models.Model): lead_for = fields.Selection([('companies', 'Companies'), ('people', 'Companies and their Contacts')], string='Data Tracking', required=True, default='companies', help='Choose whether to track companies only or companies and their contacts') lead_type = fields.Selection([('lead', 'Lead'), ('opportunity', 'Opportunity')], string='Type', required=True, default='opportunity') suffix = fields.Char(string='Suffix', help='This will be appended in name of generated lead so you can identify lead/opportunity is generated with this rule') - team_id = fields.Many2one('crm.team', string='Sales Team') + team_id = fields.Many2one('crm.team', string='Sales Team', ondelete="set null") tag_ids = fields.Many2many('crm.tag', string='Tags') user_id = fields.Many2one('res.users', string='Salesperson') priority = fields.Selection(crm_stage.AVAILABLE_PRIORITIES, string='Priority') diff --git a/addons/event_crm/models/event_lead_rule.py b/addons/event_crm/models/event_lead_rule.py index f5c9ec1c7e5..0c96bd1e21e 100644 --- a/addons/event_crm/models/event_lead_rule.py +++ b/addons/event_crm/models/event_lead_rule.py @@ -105,7 +105,9 @@ class EventLeadRule(models.Model): ('lead', 'Lead'), ('opportunity', 'Opportunity')], string="Lead Type", required=True, default=lambda self: 'lead' if self.env['res.users'].has_group('crm.group_use_lead') else 'opportunity', help="Default lead type when this rule is applied.") - lead_sales_team_id = fields.Many2one('crm.team', string='Sales Team', help="Automatically assign the created leads to this Sales Team.") + lead_sales_team_id = fields.Many2one( + 'crm.team', string='Sales Team', ondelete="set null", + help="Automatically assign the created leads to this Sales Team.") lead_user_id = fields.Many2one('res.users', string='Salesperson', help="Automatically assign the created leads to this Salesperson.") lead_tag_ids = fields.Many2many('crm.tag', string='Tags', help="Automatically add these tags to the created leads.") diff --git a/addons/pos_sale/models/pos_config.py b/addons/pos_sale/models/pos_config.py index c6a8dd7cc90..26e35bef430 100644 --- a/addons/pos_sale/models/pos_config.py +++ b/addons/pos_sale/models/pos_config.py @@ -10,7 +10,7 @@ class PosConfig(models.Model): crm_team_id = fields.Many2one( - 'crm.team', string="Sales Team", + 'crm.team', string="Sales Team", ondelete="set null", help="This Point of sale's sales will be related to this Sales Team.") @api.onchange('company_id') diff --git a/addons/pos_sale/models/pos_order.py b/addons/pos_sale/models/pos_order.py index eb16cd7272a..596cfc1f57c 100644 --- a/addons/pos_sale/models/pos_order.py +++ b/addons/pos_sale/models/pos_order.py @@ -8,7 +8,7 @@ class PosOrder(models.Model): _inherit = 'pos.order' currency_rate = fields.Float(compute='_compute_currency_rate', store=True, digits=0, readonly=True) - crm_team_id = fields.Many2one('crm.team', string="Sales Team") + crm_team_id = fields.Many2one('crm.team', string="Sales Team", ondelete="set null") @api.model def _complete_values_from_session(self, session, values): diff --git a/addons/sale/models/account_move.py b/addons/sale/models/account_move.py index e5e9e84379d..43ad0b22c8e 100644 --- a/addons/sale/models/account_move.py +++ b/addons/sale/models/account_move.py @@ -14,6 +14,7 @@ class AccountMove(models.Model): team_id = fields.Many2one( 'crm.team', string='Sales Team', default=_get_invoice_default_sale_team, + ondelete="set null", tracking=True, domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]") partner_shipping_id = fields.Many2one( 'res.partner', diff --git a/addons/sale/models/crm_team.py b/addons/sale/models/crm_team.py index a1ac9f1edc9..40b00821a48 100644 --- a/addons/sale/models/crm_team.py +++ b/addons/sale/models/crm_team.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo import fields, models, _ +from odoo import api, fields, models, _ +from odoo.exceptions import UserError class CrmTeam(models.Model): @@ -26,7 +27,7 @@ class CrmTeam(models.Model): sales_to_invoice_count = fields.Integer( compute='_compute_sales_to_invoice', string='Number of sales to invoice', readonly=True) - + sale_order_count = fields.Integer(compute='_compute_sale_order_count', string='# Sale Orders') def _compute_quotations_to_invoice(self): query = self.env['sale.order']._where_calc([ @@ -96,6 +97,17 @@ class CrmTeam(models.Model): for team in self: team.invoiced = data_map.get(team.id, 0.0) + def _compute_sale_order_count(self): + data_map = {} + if self.ids: + sale_order_data = self.env['sale.order'].read_group([ + ('team_id', 'in', self.ids), + ('state', '!=', 'cancel'), + ], ['team_id'], ['team_id']) + data_map = {datum['team_id'][0]: datum['team_id_count'] for datum in sale_order_data} + for team in self: + team.sale_order_count = data_map.get(team.id, 0) + def _graph_get_model(self): if self._context.get('in_sales_app'): return 'sale.report' @@ -133,3 +145,17 @@ class CrmTeam(models.Model): def update_invoiced_target(self, value): return self.write({'invoiced_target': round(float(value or 0))}) + + @api.ondelete(at_uninstall=False) + def _unlink_except_used_for_sales(self): + """ If more than 5 active SOs, we consider this team to be actively used. + 5 is some random guess based on "user testing", aka more than testing + CRM feature and less than use it in real life use cases. """ + SO_COUNT_TRIGGER = 5 + for team in self: + if team.sale_order_count >= SO_COUNT_TRIGGER: + raise UserError( + _('Team %(team_name)s has %(sale_order_count)s active sale orders. Consider canceling them or archiving the team instead.', + team_name=team.name, + sale_order_count=team.sale_order_count + )) diff --git a/addons/sale/models/sale_order.py b/addons/sale/models/sale_order.py index ef1a95d2f6e..f800a16abde 100644 --- a/addons/sale/models/sale_order.py +++ b/addons/sale/models/sale_order.py @@ -246,6 +246,7 @@ class SaleOrder(models.Model): company_id = fields.Many2one('res.company', 'Company', required=True, index=True, default=lambda self: self.env.company) team_id = fields.Many2one( 'crm.team', 'Sales Team', + ondelete="set null", tracking=True, change_default=True, default=_get_default_team, check_company=True, # Unrequired company domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]") diff --git a/addons/sales_team/models/crm_team_member.py b/addons/sales_team/models/crm_team_member.py index 146b6ef632f..f0c53940127 100644 --- a/addons/sales_team/models/crm_team_member.py +++ b/addons/sales_team/models/crm_team_member.py @@ -15,7 +15,7 @@ class CrmTeamMember(models.Model): crm_team_id = fields.Many2one( 'crm.team', string='Sales Team', default=False, # TDE: temporary fix to activate depending computed fields - check_company=True, index=True, required=True) + check_company=True, index=True, ondelete="cascade", required=True) user_id = fields.Many2one( 'res.users', string='Salesperson', # TDE FIXME check responsible field check_company=True, index=True, ondelete='cascade', required=True, diff --git a/addons/website_sale/models/website.py b/addons/website_sale/models/website.py index d012d3dcfc2..568c1a336c0 100644 --- a/addons/website_sale/models/website.py +++ b/addons/website_sale/models/website.py @@ -29,7 +29,7 @@ class Website(models.Model): return None salesteam_id = fields.Many2one('crm.team', - string='Sales Team', + string='Sales Team', ondelete="set null", default=_get_default_website_team) pricelist_ids = fields.One2many('product.pricelist', compute="_compute_pricelist_ids", string='Price list available for this Ecommerce/Website')