[IMP] mail, test_mail, *: add new email.cc.mixin on main models
*: crm, project, maintenance, hr_recruitment When a record is created from an email, cc can be lost. This commit proposes a new mixin to keep cc on the record and allows to create partner for each of them when sending a message from the chatter. The mixin is added on the mail recors that can be created from mail.alias: document helpdesk.ticket mrp.eco quality.alert hr.applicant crm.lead project.task mail.channel maintenance.equipment Task: 1925001
This commit is contained in:
@@ -50,7 +50,7 @@ class Lead(models.Model):
|
||||
_name = "crm.lead"
|
||||
_description = "Lead/Opportunity"
|
||||
_order = "priority desc,activity_date_deadline,id desc"
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin', 'utm.mixin', 'format.address.mixin', 'mail.blacklist.mixin']
|
||||
_inherit = ['mail.thread.cc', 'mail.activity.mixin', 'utm.mixin', 'format.address.mixin', 'mail.blacklist.mixin']
|
||||
_primary_email = 'email_from'
|
||||
|
||||
def _default_probability(self):
|
||||
@@ -74,7 +74,6 @@ class Lead(models.Model):
|
||||
index=True, tracking=True, help='When sending mails, the default email address is taken from the Sales Team.')
|
||||
kanban_state = fields.Selection([('grey', 'No next activity planned'), ('red', 'Next activity late'), ('green', 'Next activity is planned')],
|
||||
string='Kanban State', compute='_compute_kanban_state')
|
||||
email_cc = fields.Text('Global CC', help="These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma")
|
||||
description = fields.Text('Notes')
|
||||
tag_ids = fields.Many2many('crm.lead.tag', 'crm_lead_tag_rel', 'lead_id', 'tag_id', string='Tags', help="Classify and analyze your lead/opportunity categories like: Training, Service")
|
||||
contact_name = fields.Char('Contact Name', tracking=30)
|
||||
@@ -1204,7 +1203,6 @@ class Lead(models.Model):
|
||||
defaults = {
|
||||
'name': msg_dict.get('subject') or _("No Subject"),
|
||||
'email_from': msg_dict.get('from'),
|
||||
'email_cc': msg_dict.get('cc'),
|
||||
'partner_id': msg_dict.get('author_id', False),
|
||||
}
|
||||
if msg_dict.get('author_id'):
|
||||
|
||||
@@ -107,6 +107,7 @@
|
||||
aria-label="Blacklisted" attrs="{'invisible': [('is_blacklisted', '=', False)]}" groups="base.group_user"></i>
|
||||
<field name="email_from" widget="email"/>
|
||||
</div>
|
||||
<field name="email_cc" groups="base.group_no_one"/>
|
||||
<field name="function"/>
|
||||
<field name="phone" widget="phone"/>
|
||||
<field name="mobile"/>
|
||||
|
||||
@@ -92,7 +92,7 @@ class Applicant(models.Model):
|
||||
_name = "hr.applicant"
|
||||
_description = "Applicant"
|
||||
_order = "priority desc, id desc"
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin', 'utm.mixin']
|
||||
_inherit = ['mail.thread.cc', 'mail.activity.mixin', 'utm.mixin']
|
||||
|
||||
def _default_stage_id(self):
|
||||
if self._context.get('default_job_id'):
|
||||
@@ -117,8 +117,6 @@ class Applicant(models.Model):
|
||||
active = fields.Boolean("Active", default=True, help="If the active field is set to false, it will allow you to hide the case without removing it.")
|
||||
description = fields.Text("Description")
|
||||
email_from = fields.Char("Email", size=128, help="Applicant email")
|
||||
email_cc = fields.Text("Watchers Emails", size=252,
|
||||
help="These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma")
|
||||
probability = fields.Float("Probability")
|
||||
partner_id = fields.Many2one('res.partner', "Contact")
|
||||
create_date = fields.Datetime("Creation Date", readonly=True, index=True)
|
||||
@@ -407,7 +405,6 @@ class Applicant(models.Model):
|
||||
'name': msg.get('subject') or _("No Subject"),
|
||||
'partner_name': val,
|
||||
'email_from': msg.get('from'),
|
||||
'email_cc': msg.get('cc'),
|
||||
'partner_id': msg.get('author_id', False),
|
||||
}
|
||||
if msg.get('priority'):
|
||||
|
||||
@@ -131,6 +131,7 @@
|
||||
<group>
|
||||
<field name="partner_id"/>
|
||||
<field name="email_from" widget="email"/>
|
||||
<field name="email_cc" groups="base.group_no_one"/>
|
||||
<field name="partner_phone"/>
|
||||
<field name="partner_mobile"/>
|
||||
<field name="type_id" placeholder="Degree"/>
|
||||
|
||||
@@ -10,6 +10,7 @@ from . import mail_message
|
||||
from . import mail_activity
|
||||
from . import mail_mail
|
||||
from . import mail_thread
|
||||
from . import mail_cc_mixin
|
||||
from . import mail_address_mixin
|
||||
from . import mail_blacklist
|
||||
from . import mail_channel
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import _, api, fields, models, tools
|
||||
from email.utils import formataddr
|
||||
|
||||
|
||||
class MailCCMixin(models.AbstractModel):
|
||||
_name = 'mail.thread.cc'
|
||||
_inherit = 'mail.thread'
|
||||
_description = 'Email CC management'
|
||||
|
||||
email_cc = fields.Char('Email cc', help='List of cc from incoming emails.')
|
||||
|
||||
def _mail_cc_sanitized_raw_dict(self, cc_string):
|
||||
'''return a dict of sanitize_email:raw_email from a string of cc'''
|
||||
if not cc_string:
|
||||
return {}
|
||||
return {tools.email_normalize(email): formataddr((name, tools.email_normalize(email)))
|
||||
for (name, email) in tools.email_split_tuples(cc_string)}
|
||||
|
||||
@api.model
|
||||
def message_new(self, msg_dict, custom_values=None):
|
||||
if custom_values is None:
|
||||
custom_values = {}
|
||||
cc_values = {
|
||||
'email_cc': ", ".join(self._mail_cc_sanitized_raw_dict(msg_dict.get('cc')).values()),
|
||||
}
|
||||
cc_values.update(custom_values)
|
||||
return super(MailCCMixin, self).message_new(msg_dict, cc_values)
|
||||
|
||||
@api.multi
|
||||
def message_update(self, msg_dict, update_vals=None):
|
||||
'''Adds cc email to self.email_cc while trying to keep email as raw as possible but unique'''
|
||||
if update_vals is None:
|
||||
update_vals = {}
|
||||
cc_values = {}
|
||||
new_cc = self._mail_cc_sanitized_raw_dict(msg_dict.get('cc'))
|
||||
if new_cc:
|
||||
old_cc = self._mail_cc_sanitized_raw_dict(self.email_cc)
|
||||
new_cc.update(old_cc)
|
||||
cc_values['email_cc'] = ", ".join(new_cc.values())
|
||||
cc_values.update(update_vals)
|
||||
return super(MailCCMixin, self).message_update(msg_dict, cc_values)
|
||||
|
||||
@api.multi
|
||||
def message_get_suggested_recipients(self):
|
||||
recipients = super(MailCCMixin, self).message_get_suggested_recipients()
|
||||
for record in self:
|
||||
if record.email_cc:
|
||||
for email in tools.email_split_and_format(record.email_cc):
|
||||
record._message_add_suggested_recipient(recipients, email=email, reason=_('CC Email'))
|
||||
return recipients
|
||||
@@ -81,7 +81,7 @@ class MaintenanceEquipmentCategory(models.Model):
|
||||
return res
|
||||
|
||||
def get_alias_model_name(self, vals):
|
||||
return vals.get('alias_model', 'maintenance.equipment')
|
||||
return vals.get('alias_model', 'maintenance.request')
|
||||
|
||||
def get_alias_values(self):
|
||||
values = super(MaintenanceEquipmentCategory, self).get_alias_values()
|
||||
@@ -253,9 +253,10 @@ class MaintenanceEquipment(models.Model):
|
||||
if not next_requests:
|
||||
equipment._create_new_request(equipment.next_action_date)
|
||||
|
||||
|
||||
class MaintenanceRequest(models.Model):
|
||||
_name = 'maintenance.request'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
_inherit = ['mail.thread.cc', 'mail.activity.mixin']
|
||||
_description = 'Maintenance Request'
|
||||
_order = "id desc"
|
||||
|
||||
|
||||
@@ -94,6 +94,7 @@
|
||||
class="oe_inline"/> <span class="ml8">hours</span>
|
||||
</div>
|
||||
<field name="priority" widget="priority"/>
|
||||
<field name="email_cc" string="Email cc" groups="base.group_no_one"/>
|
||||
</group>
|
||||
</group>
|
||||
<field name='description' placeholder="Internal Note ......."/>
|
||||
|
||||
@@ -399,7 +399,7 @@ class Task(models.Model):
|
||||
_name = "project.task"
|
||||
_description = "Task"
|
||||
_date_name = "date_assign"
|
||||
_inherit = ['portal.mixin', 'mail.thread', 'mail.activity.mixin', 'rating.mixin']
|
||||
_inherit = ['portal.mixin', 'mail.thread.cc', 'mail.activity.mixin', 'rating.mixin']
|
||||
_mail_post_access = 'read'
|
||||
_order = "priority desc, sequence, id desc"
|
||||
|
||||
@@ -496,8 +496,6 @@ class Task(models.Model):
|
||||
subtask_project_id = fields.Many2one('project.project', related="project_id.subtask_project_id", string='Sub-task Project', readonly=True)
|
||||
subtask_count = fields.Integer("Sub-task count", compute='_compute_subtask_count')
|
||||
email_from = fields.Char(string='Email', help="These people will receive email.", index=True)
|
||||
email_cc = fields.Char(string='Watchers Emails', help="""These email addresses will be added to the CC field of all inbound
|
||||
and outbound emails for this record before being sent. Separate multiple email addresses with a comma""")
|
||||
# Computed field about working time elapsed between record creation and assignation/closing.
|
||||
working_hours_open = fields.Float(compute='_compute_elapsed', string='Working hours to assign', store=True, group_operator="avg")
|
||||
working_hours_close = fields.Float(compute='_compute_elapsed', string='Working hours to close', store=True, group_operator="avg")
|
||||
@@ -835,7 +833,6 @@ class Task(models.Model):
|
||||
defaults = {
|
||||
'name': msg.get('subject') or _("No Subject"),
|
||||
'email_from': msg.get('from'),
|
||||
'email_cc': msg.get('cc'),
|
||||
'planned_hours': 0.0,
|
||||
'partner_id': msg.get('author_id')
|
||||
}
|
||||
|
||||
@@ -124,3 +124,10 @@ class MailModel(models.Model):
|
||||
def _value_pc(self):
|
||||
for record in self:
|
||||
record.value_pc = float(record.value) / 100
|
||||
|
||||
|
||||
class MailCC(models.Model):
|
||||
_name = 'mail.test.cc'
|
||||
_inherit = ['mail.thread.cc']
|
||||
|
||||
name = fields.Char()
|
||||
|
||||
@@ -10,3 +10,6 @@ access_mail_test_full_portal,mail.test.full.portal,model_mail_test_full,base.gro
|
||||
access_mail_test_full_user,mail.test.full.user,model_mail_test_full,base.group_user,1,1,1,1
|
||||
access_mail_test_portal,mail.test.portal,model_mail_test,base.group_portal,1,1,0,0
|
||||
access_mail_test_user,mail.test.user,model_mail_test,base.group_user,1,1,1,1
|
||||
access_mail_test_cc_portal,mail.test.cc.portal,model_mail_test_cc,base.group_portal,1,0,0,0
|
||||
access_mail_test_cc_user,mail.test.cc.user,model_mail_test_cc,base.group_user,1,1,1,1
|
||||
|
||||
|
||||
|
@@ -6,6 +6,7 @@ from . import test_mail_message
|
||||
from . import test_mail_mail
|
||||
from . import test_mail_race
|
||||
from . import test_mail_resend
|
||||
from . import test_mail_cc
|
||||
from . import test_mail_channel
|
||||
from . import test_mail_gateway
|
||||
from . import test_mail_template
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.test_mail.tests import common
|
||||
from odoo.tests import tagged
|
||||
from odoo.tools import mute_logger, email_split_and_format
|
||||
from odoo.addons.test_mail.data.test_mail_data import MAIL_TEMPLATE
|
||||
|
||||
|
||||
@tagged('cc_test')
|
||||
class TestMailResend(common.BaseFunctionalTest, common.MockEmails):
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_thread')
|
||||
def test_message_cc_new(self):
|
||||
alias = self.env['mail.alias'].create({
|
||||
'alias_name': 'cc_record',
|
||||
'alias_user_id': False,
|
||||
'alias_model_id': self.env['ir.model']._get('mail.test.cc').id,
|
||||
'alias_contact': 'everyone'})
|
||||
record = self.format_and_process(MAIL_TEMPLATE, target_model='mail.test.cc', to='cc_record@example.com',
|
||||
cc='cc1@example.com, cc2@example.com')
|
||||
cc = email_split_and_format(record.email_cc)
|
||||
self.assertEqual(sorted(cc), ['cc1@example.com', 'cc2@example.com'], 'cc should contains exactly 2 cc')
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_thread')
|
||||
def test_message_cc_update_with_old(self):
|
||||
record = self.env['mail.test.cc'].create({'email_cc': 'cc1 <cc1@example.com>, cc2@example.com'})
|
||||
alias = self.env['mail.alias'].create({
|
||||
'alias_name': 'cc_record',
|
||||
'alias_user_id': False,
|
||||
'alias_model_id': self.env['ir.model']._get('mail.test.cc').id,
|
||||
'alias_contact': 'everyone',
|
||||
'alias_force_thread_id': record.id})
|
||||
|
||||
self.format_and_process(MAIL_TEMPLATE, subject='Re: Frogs', target_model='mail.test.cc',
|
||||
msg_id='1198923581.41972151344608186799.JavaMail.diff1@agrolait.com',
|
||||
to='cc_record@example.com',
|
||||
cc='cc2 <cc2@example.com>, cc3@example.com')
|
||||
cc = email_split_and_format(record.email_cc)
|
||||
self.assertEqual(sorted(cc), ['cc1 <cc1@example.com>', 'cc2@example.com', 'cc3@example.com'], 'new cc should have been added on record (unique)')
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_thread')
|
||||
def test_message_cc_update_no_old(self):
|
||||
record = self.env['mail.test.cc'].create({})
|
||||
alias = self.env['mail.alias'].create({
|
||||
'alias_name': 'cc_record',
|
||||
'alias_user_id': False,
|
||||
'alias_model_id': self.env['ir.model']._get('mail.test.cc').id,
|
||||
'alias_contact': 'everyone',
|
||||
'alias_force_thread_id': record.id})
|
||||
self.format_and_process(MAIL_TEMPLATE, subject='Re: Frogs', target_model='mail.test.cc',
|
||||
msg_id='1198923581.41972151344608186799.JavaMail.diff1@agrolait.com',
|
||||
to='cc_record@example.com',
|
||||
cc='cc2 <cc2@example.com>, cc3@example.com')
|
||||
cc = email_split_and_format(record.email_cc)
|
||||
self.assertEqual(sorted(cc), ['cc2 <cc2@example.com>', 'cc3@example.com'], 'new cc should have been added on record (unique)')
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_mail_cc_recipient_suggestion(self):
|
||||
record = self.env['mail.test.cc'].create({'email_cc': 'cc1@example.com, cc2@example.com, cc3 <cc3@example.com>'})
|
||||
suggestions = record.message_get_suggested_recipients()[record.id]
|
||||
self.assertEqual(sorted(suggestions), [
|
||||
(False, 'cc1@example.com', 'CC Email'),
|
||||
(False, 'cc2@example.com', 'CC Email'),
|
||||
(False, 'cc3 <cc3@example.com>', 'CC Email')
|
||||
], 'cc should be in suggestions')
|
||||
+10
-9
@@ -474,28 +474,29 @@ def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=Non
|
||||
cr.close()
|
||||
return res
|
||||
|
||||
def email_split(text):
|
||||
""" Return a list of the email addresses found in ``text`` """
|
||||
def email_split_tuples(text):
|
||||
""" Return a list of (name, email) addresse tuples found in ``text``"""
|
||||
if not text:
|
||||
return []
|
||||
return [addr[1] for addr in getaddresses([text])
|
||||
return [(addr[0], addr[1]) for addr in getaddresses([text])
|
||||
# getaddresses() returns '' when email parsing fails, and
|
||||
# sometimes returns emails without at least '@'. The '@'
|
||||
# is strictly required in RFC2822's `addr-spec`.
|
||||
if addr[1]
|
||||
if '@' in addr[1]]
|
||||
|
||||
def email_split(text):
|
||||
""" Return a list of the email addresses found in ``text`` """
|
||||
if not text:
|
||||
return []
|
||||
return [email for (name, email) in email_split_tuples(text)]
|
||||
|
||||
def email_split_and_format(text):
|
||||
""" Return a list of email addresses found in ``text``, formatted using
|
||||
formataddr. """
|
||||
if not text:
|
||||
return []
|
||||
return [formataddr((addr[0], addr[1])) for addr in getaddresses([text])
|
||||
# getaddresses() returns '' when email parsing fails, and
|
||||
# sometimes returns emails without at least '@'. The '@'
|
||||
# is strictly required in RFC2822's `addr-spec`.
|
||||
if addr[1]
|
||||
if '@' in addr[1]]
|
||||
return [formataddr((name, email)) for (name, email) in email_split_tuples(text)]
|
||||
|
||||
def email_normalize(text):
|
||||
""" Sanitize and standardize email address entries.
|
||||
|
||||
Reference in New Issue
Block a user