[ADD] mail_autosubscribe

This commit is contained in:
Ivàn Todorovich 2021-08-09 15:10:40 -03:00 committed by nicolas
parent 0628e395dd
commit 0926ea7b1c
20 changed files with 458 additions and 0 deletions

View File

@ -0,0 +1 @@
from . import models

View File

@ -0,0 +1,20 @@
# Copyright 2021 Camptocamp (http://www.camptocamp.com).
# @author Iván Todorovich <ivan.todorovich@gmail.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
"name": "Mail Autosubscribe",
"summary": "Automatically subscribe partners to its company's business documents",
"version": "14.0.1.0.0",
"author": "Camptocamp SA, Odoo Community Association (OCA)",
"license": "AGPL-3",
"category": "Marketing",
"depends": ["mail"],
"website": "https://github.com/OCA/social",
"data": [
"security/ir.model.access.csv",
"views/mail_autosubscribe.xml",
"views/mail_template.xml",
"views/res_partner.xml",
],
}

View File

@ -0,0 +1,5 @@
from . import models
from . import res_partner
from . import mail_thread
from . import mail_autosubscribe
from . import mail_template

View File

@ -0,0 +1,42 @@
# Copyright 2021 Camptocamp (http://www.camptocamp.com).
# @author Iván Todorovich <ivan.todorovich@gmail.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models
class MailAutosubscribe(models.Model):
_name = "mail.autosubscribe"
_description = "Mail Autosubscribe"
_sql_constraints = [
(
"model_id_unique",
"UNIQUE(model_id)",
"There's already a rule for this model",
)
]
model_id = fields.Many2one(
"ir.model",
required=True,
index=True,
ondelete="cascade",
)
model = fields.Char(
related="model_id.model",
string="Model Name",
store=True,
index=True,
)
name = fields.Char(
compute="_compute_name",
store=True,
readonly=False,
)
@api.depends("model_id")
def _compute_name(self):
for rec in self:
if not rec.name:
rec.name = rec.model_id.name

View File

@ -0,0 +1,34 @@
# Copyright 2021 Camptocamp (http://www.camptocamp.com).
# @author Iván Todorovich <ivan.todorovich@gmail.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class MailTemplate(models.Model):
_inherit = "mail.template"
use_autosubscribe_followers = fields.Boolean(default=True)
def generate_recipients(self, results, res_ids):
res = super().generate_recipients(results, res_ids)
autosubscribe_followers = (
self.use_autosubscribe_followers
and not self.env.context.get("no_autosubscribe_followers")
# In this case, autosubscribers will be added by
# :func:`_message_get_default_recipients`
and not self.use_default_to
and not self.env.context.get("tpl_force_default_to")
)
if autosubscribe_followers:
for res_id in res.keys():
partners = (
self.env["res.partner"].sudo().browse(res[res_id]["partner_ids"])
)
ResModel = self.env[self.model]
followers = ResModel._message_get_autosubscribe_followers(partners)
follower_ids = [
follower.id for follower in followers if follower not in partners
]
res[res_id]["partner_ids"] += follower_ids
return res

View File

@ -0,0 +1,27 @@
# Copyright 2021 Camptocamp (http://www.camptocamp.com).
# @author Iván Todorovich <ivan.todorovich@gmail.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models
class MailThread(models.AbstractModel):
_inherit = "mail.thread"
def message_subscribe(self, partner_ids=None, channel_ids=None, subtype_ids=None):
# Overload to automatically subscribe autosubscribe followers.
autosubscribe_followers = not self.env.context.get("no_autosubscribe_followers")
if partner_ids and autosubscribe_followers:
partners = self.env["res.partner"].sudo().browse(partner_ids)
followers = self._message_get_autosubscribe_followers(partners)
follower_ids = [
follower.id
for follower in followers
if follower not in partners and follower not in self.message_partner_ids
]
partner_ids += follower_ids
return super().message_subscribe(
partner_ids=partner_ids,
channel_ids=channel_ids,
subtype_ids=subtype_ids,
)

View File

@ -0,0 +1,37 @@
# Copyright 2021 Camptocamp (http://www.camptocamp.com).
# @author Iván Todorovich <ivan.todorovich@gmail.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, models
class BaseModel(models.AbstractModel):
_inherit = "base"
@api.model
def _message_get_autosubscribe_followers_domain(self, partners):
return [
("id", "child_of", partners.commercial_partner_id.ids),
("mail_autosubscribe_ids.model", "=", self._name),
]
@api.model
def _message_get_autosubscribe_followers(self, partners):
domain = self._message_get_autosubscribe_followers_domain(partners)
return self.env["res.partner"].sudo().search(domain)
def _message_get_default_recipients(self):
# Overload to include auto follow document partners in the composer
# Note: This only works if the template is configured with 'Default recipients'
res = super()._message_get_default_recipients()
if self.env.context.get("no_autosubscribe_followers"):
return res
for rec in self:
partner_ids = res[rec.id]["partner_ids"]
partners = self.env["res.partner"].sudo().browse(partner_ids)
followers = rec._message_get_autosubscribe_followers(partners)
follower_ids = [
follower.id for follower in followers if follower not in partners
]
partner_ids += follower_ids
return res

View File

@ -0,0 +1,16 @@
# Copyright 2021 Camptocamp (http://www.camptocamp.com).
# @author Iván Todorovich <ivan.todorovich@gmail.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class ResPartner(models.Model):
_inherit = "res.partner"
mail_autosubscribe_ids = fields.Many2many(
"mail.autosubscribe",
string="Autosubscribe Models",
column1="partner_id",
column2="model_id",
)

View File

@ -0,0 +1,8 @@
Go to Configuration > Technical > Automation > Autosubscribe Models and configure
the models for which you want the feature to work.
Then, on each partner, you can check the company documents subscriptions in the
field `In copy of`.
This feature can be disabled on specific templates, if required, by disabling the
Autosubscribe followers field.

View File

@ -0,0 +1,3 @@
* `Camptocamp <https://www.camptocamp.com>`_
* Iván Todorovich <ivan.todorovich@gmail.com>

View File

@ -0,0 +1,5 @@
This module allows you to configure partners that will be automatically in copy
of their company's business documents.
For example, you can configure an accountant to be in copy of all invoices
sent for a given commercial partner, regardless of the invoicing address.

View File

@ -0,0 +1,3 @@
* Consider implementing domain-based autosubscription rules.
This was considered during first development but it wasn't a requirement at the time.
If pursuit, this has to be done carefully to avoid affecting performance.

View File

@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_mail_autosubscribe_user,access_mail_autosubscribe_user,model_mail_autosubscribe,base.group_user,1,0,0,0
access_mail_autosubscribe_system,access_mail_autosubscribe_system,model_mail_autosubscribe,base.group_system,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_mail_autosubscribe_user access_mail_autosubscribe_user model_mail_autosubscribe base.group_user 1 0 0 0
3 access_mail_autosubscribe_system access_mail_autosubscribe_system model_mail_autosubscribe base.group_system 1 1 1 1

View File

@ -0,0 +1 @@
from . import test_mail_autosubscribe

View File

@ -0,0 +1 @@
from . import fake_order

View File

@ -0,0 +1,13 @@
# Copyright 2021 Camptocamp (http://www.camptocamp.com).
# @author Iván Todorovich <ivan.todorovich@gmail.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class FakeOrder(models.Model):
_name = "fake.order"
_inherit = "mail.thread"
_description = "Fake sale.order like model"
partner_id = fields.Many2one("res.partner", required=True)

View File

@ -0,0 +1,132 @@
# Copyright 2021 Camptocamp (http://www.camptocamp.com).
# @author Iván Todorovich <ivan.todorovich@gmail.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo_test_helper import FakeModelLoader
from odoo.tests.common import Form, SavepointCase, tagged
@tagged("post_install", "-at_install")
class TestMailAutosubscribe(SavepointCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
# Setup env
cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True))
# Load fake order model
cls.loader = FakeModelLoader(cls.env, cls.__module__)
cls.loader.backup_registry()
from .models.fake_order import FakeOrder
cls.loader.update_registry((FakeOrder,))
cls.fake_order_model = cls.env["ir.model"].search(
[("model", "=", "fake.order")]
)
# Email Template
cls.mail_template = cls.env["mail.template"].create(
{
"model_id": cls.fake_order_model.id,
"name": "Fake Order: Send by Mail",
"subject": "Fake Order: ${object.partner_id.name}",
"partner_to": "${object.partner_id.id}",
"body_html": "Hello, this is a fake order",
}
)
# Partners
cls.commercial_partner = cls.env.ref("base.res_partner_4")
cls.partner_1 = cls.env.ref("base.res_partner_address_13")
cls.partner_2 = cls.env.ref("base.res_partner_address_14")
cls.partner_3 = cls.env.ref("base.res_partner_address_24")
# Autosubscribe rules
cls.autosubscribe_fake_order = cls.env["mail.autosubscribe"].create(
{"model_id": cls.fake_order_model.id}
)
cls.partner_3.mail_autosubscribe_ids = [(4, cls.autosubscribe_fake_order.id)]
# Empty fake.order
cls.order = cls.env["fake.order"].create({"partner_id": cls.partner_2.id})
@classmethod
def tearDownClass(cls):
cls.loader.restore_registry()
return super().tearDownClass()
def test_message_subscribe(self):
"""Test autosubscribe on a basic workflow"""
self.assertFalse(self.order.message_partner_ids, "No subscribers yet")
self.order.message_subscribe([self.order.partner_id.id])
self.assertEqual(
self.order.message_partner_ids,
self.partner_2 | self.partner_3,
"Partner 3 is automatically subscribed",
)
def test_message_subscribe_disabled(self):
"""Test autosubscribe on a basic workflow (disabled)"""
self.partner_3.mail_autosubscribe_ids = [(5, False)]
self.assertFalse(self.order.message_partner_ids, "No subscribers yet")
self.order.message_subscribe([self.order.partner_id.id])
self.assertEqual(
self.order.message_partner_ids,
self.partner_2,
"Partner 2 is the only subscriber",
)
def test_mail_template(self):
"""Test autosubscribe when partner is set in the mail.template partners_to"""
self.mail_template.send_mail(self.order.id)
message = self.order.message_ids[0]
self.assertEqual(message.partner_ids, self.partner_2 | self.partner_3)
def test_mail_template_disabled(self):
"""Test autosubscribe when the partner is not an autosubscribe follower"""
self.partner_3.mail_autosubscribe_ids = [(5, False)]
self.mail_template.send_mail(self.order.id)
message = self.order.message_ids[0]
self.assertEqual(message.partner_ids, self.partner_2)
def test_mail_template_no_autosubscribe_followers(self):
"""Test autosubscribe doesn't apply if it's disabled on the template"""
self.mail_template.use_autosubscribe_followers = False
self.mail_template.send_mail(self.order.id)
message = self.order.message_ids[0]
self.assertEqual(message.partner_ids, self.partner_2)
def test_mail_template_default_recipients(self):
"""Test autosubscribe when using default recipients"""
self.mail_template.use_default_to = True
self.mail_template.send_mail(self.order.id)
message = self.order.message_ids[0]
self.assertEqual(message.partner_ids, self.partner_2 | self.partner_3)
def test_mail_message_composer(self):
"""Test autosubscribe when using the mail composer"""
self.assertFalse(self.order.message_partner_ids, "No subscribers yet")
composer = Form(
self.env["mail.compose.message"].with_context(
default_model="fake.order",
default_res_id=self.order.id,
default_use_template=True,
default_template_id=self.mail_template.id,
default_composition_mode="comment",
)
)
composer.save().send_mail()
message = self.order.message_ids[0]
self.assertEqual(message.partner_ids, self.partner_2 | self.partner_3)
def test_mail_message_composer_no_autosubscribe_followers(self):
"""Test autosubscribe when using the mail composer and it's disabled"""
self.mail_template.use_autosubscribe_followers = False
composer = Form(
self.env["mail.compose.message"].with_context(
default_model="fake.order",
default_res_id=self.order.id,
default_use_template=True,
default_template_id=self.mail_template.id,
default_composition_mode="comment",
)
)
composer.save().send_mail()
message = self.order.message_ids[0]
self.assertEqual(message.partner_ids, self.partner_2)

View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright 2021 Camptocamp (http://www.camptocamp.com).
@author Iván Todorovich <ivan.todorovich@gmail.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="view_mail_autosubscribe_form" model="ir.ui.view">
<field name="model">mail.autosubscribe</field>
<field name="arch" type="xml">
<form>
<sheet>
<div class="oe_title">
<h1>
<field name="name" placeholder="Name" />
</h1>
</div>
<group>
<group>
<field name="model_id" options="{'no_create': True}" />
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="view_mail_autosubscribe_tree" model="ir.ui.view">
<field name="model">mail.autosubscribe</field>
<field name="arch" type="xml">
<tree>
<field name="name" />
<field name="model_id" />
</tree>
</field>
</record>
<record id="action_mail_autosubscribe" model="ir.actions.act_window">
<field name="name">Mail Auto Subscribe</field>
<field name="res_model">mail.autosubscribe</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem
id="menu_mail_autosubscribe"
name="Autosubscribe Models"
action="action_mail_autosubscribe"
parent="base.menu_automation"
sequence="50"
/>
</odoo>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright 2021 Camptocamp (http://www.camptocamp.com).
@author Iván Todorovich <ivan.todorovich@gmail.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="email_template_form" model="ir.ui.view">
<field name="model">mail.template</field>
<field name="inherit_id" ref="mail.email_template_form" />
<field name="arch" type="xml">
<field name="use_default_to" position="after">
<field name="use_autosubscribe_followers" />
</field>
</field>
</record>
</odoo>

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright 2021 Camptocamp (http://www.camptocamp.com).
@author Iván Todorovich <ivan.todorovich@gmail.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="view_partner_form" model="ir.ui.view">
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form" />
<field name="arch" type="xml">
<xpath expr="//field[@name='email']/parent::div" position="after">
<field
name="mail_autosubscribe_ids"
string="In copy of"
widget="many2many_checkboxes"
attrs="{'invisible': [('email', '=', False)]}"
/>
</xpath>
<xpath
expr="//field[@name='child_ids']//form//field[@name='email']"
position="after"
>
<field
name="mail_autosubscribe_ids"
string="In copy of"
widget="many2many_checkboxes"
attrs="{'invisible': [('email', '=', False)]}"
/>
</xpath>
</field>
</record>
</odoo>