Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 21 additions & 22 deletions mail_activity_team/README.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
.. image:: https://odoo-community.org/readme-banner-image
:target: https://odoo-community.org/get-involved?utm_source=readme
:alt: Odoo Community Association

==================
Mail Activity Team
==================
Expand All @@ -17,7 +13,7 @@ Mail Activity Team
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fmail-lightgray.png?logo=github
Expand Down Expand Up @@ -84,35 +80,38 @@ Authors
Contributors
------------

- `ForgeFlow <https://www.forgeflow.com>`__:
- `ForgeFlow <https://www.forgeflow.com>`__:

- Jordi Ballester Alomar (jordi.ballester@forgeflow.com)
- Miquel Raïch (miquel.raich@forgeflow.com)
- Bernat Puig Font (bernat.puig@forgeflow.com)

- Jordi Ballester Alomar (jordi.ballester@forgeflow.com)
- Miquel Raïch (miquel.raich@forgeflow.com)
- Bernat Puig Font (bernat.puig@forgeflow.com)
- Pedro Gonzalez (pedro.gonzalez@pesol.es)
- `Tecnativa <https://www.tecnativa.com>`__:

- Pedro Gonzalez (pedro.gonzalez@pesol.es)
- `Tecnativa <https://www.tecnativa.com>`__:
- David Vidal

- David Vidal
- `Dynapps <https://www.dynapps.eu>`__:

- `Dynapps <https://www.dynapps.eu>`__:
- Raf Ven

- Raf Ven
- [Trobz] (https://trobz.com):

- [Trobz] (https://trobz.com):
- Son Ho sonhd@trobz.com

- Son Ho sonhd@trobz.com
- [Camptocamp] (https://camptocamp.com):

- [Camptocamp] (https://camptocamp.com):
- Vincent Van Rossem vincent.vanrossem@camptocamp.com
- Italo Lopes italo.lopes@camptocamp.com

- Vincent Van Rossem vincent.vanrossem@camptocamp.com
- Italo Lopes italo.lopes@camptocamp.com
- `CorporateHub <https://corporatehub.eu/>`__

- `CorporateHub <https://corporatehub.eu/>`__
- Alexey Pelykh alexey.pelykh@corphub.eu

- Alexey Pelykh alexey.pelykh@corphub.eu
- Stefan Rijnhart (stefan@opener.amsterdam)
- `glueckkanja AG <https://glueckkanja.com/>`__

- Stefan Rijnhart (stefan@opener.amsterdam)
- Christopher Rogos (crogos@gmail.com)

Other credits
-------------
Expand Down
145 changes: 139 additions & 6 deletions mail_activity_team/models/mail_activity.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Copyright 2018-22 ForgeFlow S.L.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

from odoo import SUPERUSER_ID, _, api, fields, models
from odoo import SUPERUSER_ID, api, fields, models
from odoo.exceptions import ValidationError
from odoo.tools.misc import get_lang


class MailActivity(models.Model):
Expand Down Expand Up @@ -73,15 +74,70 @@ def create(self, vals_list):
# odoo import api, fields, models the default one linked to the activity type.
# We don't want this behavior because using the team_id, we want to assign the
# activity to the whole team.
new_vals_list = []
for vals in vals_list:
new_vals = vals.copy()
# we need to be sure that we are in a context where the team_id is set,
# and we don't want to use user_id
if vals.get("team_id"):
if new_vals.get("team_id"):
# using team, we have user_id = team_user_id,
# so if we don't have a user_team_id we don't want user_id too
if "user_id" in vals and not vals.get("team_user_id"):
del vals["user_id"]
return super().create(vals_list)
if "user_id" in new_vals and not new_vals.get("team_user_id"):
del new_vals["user_id"]
# team_user_id is a related field pointing to user_id (readonly=False).
# If left in vals, the ORM places it in the 'inversed' bucket and
# calls _inverse_related *after* the initial INSERT while user_id is
# still NULL. That triggers Model.write({'user_id': …}) which in turn
# fires action_notify() → action_notify_team() a first time, and then
# core mail.activity.create()'s post-hook fires action_notify() a
# second time — causing duplicate notifications for team members.
# Eagerly resolving team_user_id to user_id here ensures user_id is
# stored in the INSERT so no inverse write occurs.
if "team_user_id" in new_vals:
team_user_id_val = new_vals.pop("team_user_id")
new_vals.setdefault("user_id", team_user_id_val)
new_vals_list.append(new_vals)
activities = super().create(new_vals_list)
if not self.env.context.get("mail_activity_quick_update"):
# Core create() triggers action_notify() only for activities assigned to
# users different from the current one. Notify team members here only for
# activities not covered by that path to avoid duplicate notifications.
activities_without_core_notify = activities.filtered(
lambda activity: activity.user_id == self.env.user
)
activities_without_core_notify.action_notify_team()
return activities

def write(self, values):
Comment thread
StefanRijnhart marked this conversation as resolved.
# Notify the new team, but prevent duplicate notifications by excluding
# activities that will be notified by core write()->action_notify()
# when the user changes.
team_notify_activities = self.env["mail.activity"]
core_notified_activities = self.env["mail.activity"]
if not self.env.context.get("mail_activity_quick_update", False):
new_team_id = values.get("team_id", False)
team_notify_activities = self.filtered(
lambda activity, new_team_id=new_team_id: new_team_id
and activity.team_id.id != new_team_id
)
new_user_id = values.get("user_id", False)
user_changed_activities = self.filtered(
lambda activity, new_user_id=new_user_id: new_user_id
and activity.user_id.id != new_user_id
)
team_notify_activities |= user_changed_activities

# Core write() calls action_notify() for user changes except when
# assigning to the current user; avoid re-sending team notifications.
if new_user_id != self.env.uid:
core_notified_activities = user_changed_activities

res = super().write(values)
# notify new responsibles

if not self.env.context.get("mail_activity_quick_update", False):
(team_notify_activities - core_notified_activities).action_notify_team()
return res

@api.onchange("team_id")
def _onchange_team_id(self):
Expand Down Expand Up @@ -112,7 +168,7 @@ def _check_team_and_user(self):
not in activity.team_id.with_context(active_test=False).member_ids
):
raise ValidationError(
_(
self.env._(
"The assigned user %(user_name)s is "
"not member of the team %(team_name)s.",
user_name=activity.user_id.name,
Expand All @@ -129,3 +185,80 @@ def _onchange_activity_type_id(self):
if self.user_id not in members and members:
self.user_id = members[:1]
return res

def action_notify_team(self):
Comment thread
StefanRijnhart marked this conversation as resolved.
# Like action_notify(), but for team members.
classified = self._classify_by_model()
for model, activity_data in classified.items():
records_sudo = self.env[model].sudo().browse(activity_data["record_ids"])
# in case record was cascade-deleted in DB, skipping unlink override
activity_data["record_ids"] = records_sudo.exists().ids

for activity in self:
if activity.res_id not in classified[activity.res_model]["record_ids"]:
continue
if not activity.team_id.notify_members:
continue

record = activity.env[activity.res_model].browse(activity.res_id)
# Notify each team member except the assigned user and the current user
members = activity.team_id.member_ids.filtered(
lambda member, assigned_user_id=activity.user_id: self.env.uid
not in member.user_ids.ids
and (
not assigned_user_id
or (assigned_user_id and assigned_user_id not in member.user_ids)
)
)
for member in members:
activity_ctx = (
activity.with_context(lang=member.lang) if member.lang else activity
)
model_description = (
activity_ctx.env["ir.model"]
._get(activity_ctx.res_model)
.display_name
)
body = activity_ctx.env["ir.qweb"]._render(
"mail.message_activity_assigned",
{
"activity": activity_ctx,
"model_description": model_description,
"is_html_empty": lambda value: not value
or value == "<p><br></p>",
},
minimal_qcontext=True,
)
record.message_notify(
partner_ids=member.sudo().partner_id.ids,
body=body,
record_name=activity_ctx.res_name,
model_description=model_description,
email_layout_xmlid="mail.mail_notification_layout",
subject=self.env._(
"%(activity_name)s: %(summary)s (Team Activity)",
activity_name=activity_ctx.res_name,
summary=activity_ctx.summary
or activity_ctx.activity_type_id.name,
),
subtitles=[
self.env._("Activity: %s", activity_ctx.activity_type_id.name),
self.env._("Team: %s", activity_ctx.team_id.name),
self.env._(
"Deadline: %s",
(
activity_ctx.date_deadline.strftime(
get_lang(activity_ctx.env).date_format
)
if hasattr(activity_ctx.date_deadline, "strftime")
else str(activity_ctx.date_deadline)
),
),
],
)

def action_notify(self):
"""Override to notify team members when notify_members is enabled."""
result = super().action_notify()
self.action_notify_team()
return result
5 changes: 5 additions & 0 deletions mail_activity_team/models/mail_activity_team.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ def _compute_missing_activities(self):
string="Team Members",
)
user_id = fields.Many2one(comodel_name="res.users", string="Team Leader")
notify_members = fields.Boolean(
default=False,
help="When enabled, all team members will be notified "
"when an activity is assigned to this team.",
)
count_missing_activities = fields.Integer(
string="Missing Activities", compute="_compute_missing_activities", default=0
)
Expand Down
2 changes: 2 additions & 0 deletions mail_activity_team/readme/CONTRIBUTORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@
- [CorporateHub](https://corporatehub.eu/)
- Alexey Pelykh <alexey.pelykh@corphub.eu>
- Stefan Rijnhart (<stefan@opener.amsterdam>)
- [glueckkanja AG](https://glueckkanja.com/)
- Christopher Rogos (<crogos@gmail.com>)
32 changes: 15 additions & 17 deletions mail_activity_team/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>README.rst</title>
<title>Mail Activity Team</title>
<style type="text/css">

/*
Expand Down Expand Up @@ -360,21 +360,16 @@
</style>
</head>
<body>
<div class="document">
<div class="document" id="mail-activity-team">
<h1 class="title">Mail Activity Team</h1>


<a class="reference external image-reference" href="https://odoo-community.org/get-involved?utm_source=readme">
<img alt="Odoo Community Association" src="https://odoo-community.org/readme-banner-image" />
</a>
<div class="section" id="mail-activity-team">
<h1>Mail Activity Team</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:d6186630bb2bcead6b33ef289ce1d21d47a0da189cc8709d637007d325eab1b2
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/license-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/mail/tree/18.0/mail_activity_team"><img alt="OCA/mail" src="https://img.shields.io/badge/github-OCA%2Fmail-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/mail-18-0/mail-18-0-mail_activity_team"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/mail&amp;target_branch=18.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/mail/tree/18.0/mail_activity_team"><img alt="OCA/mail" src="https://img.shields.io/badge/github-OCA%2Fmail-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/mail-18-0/mail-18-0-mail_activity_team"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/mail&amp;target_branch=18.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>This module adds the possibility to assign teams to activities.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
Expand All @@ -391,7 +386,7 @@ <h1>Mail Activity Team</h1>
</ul>
</div>
<div class="section" id="usage">
<h2><a class="toc-backref" href="#toc-entry-1">Usage</a></h2>
<h1><a class="toc-backref" href="#toc-entry-1">Usage</a></h1>
<p>To set up new teams:</p>
<ol class="arabic simple">
<li>Go to <em>Settings / Activate developer mode</em></li>
Expand All @@ -410,24 +405,24 @@ <h2><a class="toc-backref" href="#toc-entry-1">Usage</a></h2>
/ Activities</em>, and then filter by a specific team or group by teams.</p>
</div>
<div class="section" id="bug-tracker">
<h2><a class="toc-backref" href="#toc-entry-2">Bug Tracker</a></h2>
<h1><a class="toc-backref" href="#toc-entry-2">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/mail/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/mail/issues/new?body=module:%20mail_activity_team%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h2><a class="toc-backref" href="#toc-entry-3">Credits</a></h2>
<h1><a class="toc-backref" href="#toc-entry-3">Credits</a></h1>
<div class="section" id="authors">
<h3><a class="toc-backref" href="#toc-entry-4">Authors</a></h3>
<h2><a class="toc-backref" href="#toc-entry-4">Authors</a></h2>
<ul class="simple">
<li>ForgeFlow</li>
<li>Sodexis</li>
</ul>
</div>
<div class="section" id="contributors">
<h3><a class="toc-backref" href="#toc-entry-5">Contributors</a></h3>
<h2><a class="toc-backref" href="#toc-entry-5">Contributors</a></h2>
<ul class="simple">
<li><a class="reference external" href="https://www.forgeflow.com">ForgeFlow</a>:<ul>
<li>Jordi Ballester Alomar (<a class="reference external" href="mailto:jordi.ballester&#64;forgeflow.com">jordi.ballester&#64;forgeflow.com</a>)</li>
Expand Down Expand Up @@ -458,15 +453,19 @@ <h3><a class="toc-backref" href="#toc-entry-5">Contributors</a></h3>
</ul>
</li>
<li>Stefan Rijnhart (<a class="reference external" href="mailto:stefan&#64;opener.amsterdam">stefan&#64;opener.amsterdam</a>)</li>
<li><a class="reference external" href="https://glueckkanja.com/">glueckkanja AG</a><ul>
<li>Christopher Rogos (<a class="reference external" href="mailto:crogos&#64;gmail.com">crogos&#64;gmail.com</a>)</li>
</ul>
</li>
</ul>
</div>
<div class="section" id="other-credits">
<h3><a class="toc-backref" href="#toc-entry-6">Other credits</a></h3>
<h2><a class="toc-backref" href="#toc-entry-6">Other credits</a></h2>
<p>The migration of this module from 16.0 to 17.0 was financially supported
by Camptocamp</p>
</div>
<div class="section" id="maintainers">
<h3><a class="toc-backref" href="#toc-entry-7">Maintainers</a></h3>
<h2><a class="toc-backref" href="#toc-entry-7">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
Expand All @@ -479,6 +478,5 @@ <h3><a class="toc-backref" href="#toc-entry-7">Maintainers</a></h3>
</div>
</div>
</div>
</div>
</body>
</html>
Loading
Loading