Skip to content
Draft
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
3 changes: 3 additions & 0 deletions base_tier_validation/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ To configure this module, you need to:

**Note:**

- With *Validated by: Any of specific users*, list the users under
*Reviewers*: any one of them can validate, like a member of a reviewer
group, without creating a group for them.
- If check *Notify Reviewers on Creation*, all possible reviewers will
be notified by email when this definition is triggered.
- If check *Notify reviewers on reaching pending* if you want to send a
Expand Down
28 changes: 25 additions & 3 deletions base_tier_validation/models/tier_definition.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def _get_tier_validation_model_names(self):
default="individual",
selection=[
("individual", "Specific user"),
("users", "Any of specific users"),
("group", "Any user in a specific group"),
("field", "Field in related record"),
],
Expand All @@ -48,6 +49,16 @@ def _get_tier_validation_model_names(self):
reviewer_group_id = fields.Many2one(
comodel_name="res.groups", string="Reviewer group"
)
reviewer_user_ids = fields.Many2many(
comodel_name="res.users",
relation="tier_definition_reviewer_user_rel",
column1="definition_id",
column2="user_id",
string="Reviewers",
domain="[('share', '=', False)]",
help="Any one of these users can validate, like a member of a reviewer "
"group, without having to create a group for them.",
)
reviewer_field_id = fields.Many2one(
comodel_name="ir.model.fields",
string="Reviewer field",
Expand Down Expand Up @@ -122,14 +133,15 @@ def _get_tier_validation_model_names(self):
"group can validate -- except the requester themselves. Without "
"this flag, a requester who happens to be in the same group as "
"the configured reviewers can auto-validate their own request. "
"Only meaningful for review_type='group'; the form hides this "
"field for the other review types.",
"Only meaningful when several users can review (a group or a list "
"of users); the form hides this field for the other review types.",
)

@api.onchange("review_type")
def onchange_review_type(self):
self.reviewer_id = None
self.reviewer_group_id = None
self.reviewer_user_ids = False

def _reviewers_without_model_access(self, model_name, users):
"""Return the subset of ``users`` that cannot read ``model_name``.
Expand Down Expand Up @@ -160,9 +172,19 @@ def _reviewers_to_check_for_access(self):
return self.reviewer_id
if self.review_type == "group":
return self.reviewer_group_id.user_ids
if self.review_type == "users":
# In a form, the lines of an x2many are new records; check the
# users themselves.
return self.reviewer_user_ids._origin
return self.env["res.users"]

@api.onchange("review_type", "reviewer_id", "reviewer_group_id", "model_id")
@api.onchange(
"review_type",
"reviewer_id",
"reviewer_group_id",
"reviewer_user_ids",
"model_id",
)
def _onchange_warn_reviewer_access(self):
"""Advisory warning when an assigned reviewer cannot read the model.

Expand Down
9 changes: 8 additions & 1 deletion base_tier_validation/models/tier_review.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ class TierReview(models.Model):
review_type = fields.Selection(related="definition_id.review_type")
reviewer_id = fields.Many2one(related="definition_id.reviewer_id")
reviewer_group_id = fields.Many2one(related="definition_id.reviewer_group_id")
reviewer_user_ids = fields.Many2many(
related="definition_id.reviewer_user_ids", string="Listed Reviewers"
)
reviewer_field_id = fields.Many2one(related="definition_id.reviewer_field_id")
reviewer_ids = fields.Many2many(
string="Reviewers",
Expand Down Expand Up @@ -152,6 +155,8 @@ def _get_reviewer_fields(self):
"reviewer_id",
"reviewer_group_id",
"reviewer_group_id.user_ids",
"review_type",
"reviewer_user_ids",
"definition_id.exclude_requester",
"requested_by",
]
Expand All @@ -178,7 +183,9 @@ def _compute_todo_by(self):

def _get_reviewers(self):
reviewers = self.env["res.users"]
if self.reviewer_id or self.reviewer_group_id.user_ids:
if self.review_type == "users":
reviewers = self.reviewer_user_ids
elif self.reviewer_id or self.reviewer_group_id.user_ids:
reviewers = self.reviewer_id + self.reviewer_group_id.user_ids
elif self.reviewer_field_id:
resource = self.env[self.model].browse(self.res_id)
Expand Down
3 changes: 3 additions & 0 deletions base_tier_validation/readme/CONFIGURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ To configure this module, you need to:

**Note:**

- With *Validated by: Any of specific users*, list the users under
*Reviewers*: any one of them can validate, like a member of a reviewer
group, without creating a group for them.
- If check *Notify Reviewers on Creation*, all possible reviewers will
be notified by email when this definition is triggered.
- If check *Notify reviewers on reaching pending* if you want to send a notification when pending status is reached.
Expand Down
1 change: 1 addition & 0 deletions base_tier_validation/readme/newsfragments/91.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
New *Validated by* option *Any of specific users*: any one of the listed users can validate, without creating a group for them.
5 changes: 4 additions & 1 deletion base_tier_validation/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>Base Tier Validation</title>
<title>README.rst</title>
<style type="text/css">

/*
Expand Down Expand Up @@ -438,6 +438,9 @@ <h2><a class="toc-backref" href="#toc-entry-1">Configuration</a></h2>
</ol>
<p><strong>Note:</strong></p>
<ul class="simple">
<li>With <em>Validated by: Any of specific users</em>, list the users under
<em>Reviewers</em>: any one of them can validate, like a member of a reviewer
group, without creating a group for them.</li>
<li>If check <em>Notify Reviewers on Creation</em>, all possible reviewers will
be notified by email when this definition is triggered.</li>
<li>If check <em>Notify reviewers on reaching pending</em> if you want to send a
Expand Down
1 change: 1 addition & 0 deletions base_tier_validation/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@

from . import test_tier_validation
from . import test_tier_validation_reminder
from . import test_tier_validation_users
56 changes: 56 additions & 0 deletions base_tier_validation/tests/test_tier_validation_users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo.tests import Form, new_test_user, tagged

from .common import CommonTierValidation


@tagged("post_install", "-at_install")
class TierValidationAnyOfUsers(CommonTierValidation):
"""Validated by "Any of specific users": a list of users, no group."""

def _setup_tier_definitions(self):
self.reviewer_a = new_test_user(self.env, login="reviewer_a", name="Anna")
self.reviewer_b = new_test_user(self.env, login="reviewer_b", name="Bert")
self.definition = self.tier_def_obj.create(
{
"model_id": self.tester_model.id,
"review_type": "users",
"reviewer_user_ids": [(6, 0, (self.reviewer_a | self.reviewer_b).ids)],
"definition_domain": "[('test_field', '>', 1.0)]",
}
)

def test_any_listed_user_validates(self):
record = self.test_model.create({"test_field": 2.0})
review = record.with_user(self.test_user_2).request_validation()
self.assertEqual(review.reviewer_ids, self.reviewer_a | self.reviewer_b)
self.assertEqual(review.todo_by, "Anna, Bert")
self.assertNotIn(review, self.test_user_1.review_ids)
record.with_user(self.reviewer_b).validate_tier()
self.assertEqual(review.status, "approved")
self.assertEqual(review.done_by, self.reviewer_b)
self.assertEqual(record.validation_status, "validated")

def test_changing_the_list_updates_open_reviews(self):
record = self.test_model.create({"test_field": 2.0})
review = record.with_user(self.test_user_2).request_validation()
self.definition.reviewer_user_ids = [(4, self.test_user_1.id)]
self.assertIn(self.test_user_1, review.reviewer_ids)

def test_four_eyes_principle(self):
self.definition.exclude_requester = True
record = self.test_model.create({"test_field": 2.0})
review = record.with_user(self.reviewer_a).request_validation()
self.assertEqual(review.reviewer_ids, self.reviewer_b)

def test_form(self):
with Form(self.tier_def_obj) as form:
form.model_id = self.tester_model
form.review_type = "users"
form.reviewer_user_ids.add(self.reviewer_a)
self.assertEqual(form.record.reviewer_user_ids, self.reviewer_a)
self.assertEqual(form.record._reviewers_to_check_for_access(), self.reviewer_a)
with Form(form.record) as form:
form.review_type = "individual"
form.reviewer_id = self.test_user_1
self.assertFalse(form.record.reviewer_user_ids)
14 changes: 13 additions & 1 deletion base_tier_validation/views/tier_definition_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
<field name="review_type" />
<field name="reviewer_id" />
<field name="reviewer_group_id" />
<field
name="reviewer_user_ids"
widget="many2many_avatar_user"
optional="show"
/>
<field name="reviewer_field_id" />
<field name="notify_on_create" optional="show" />
<field name="notify_on_pending" optional="show" />
Expand Down Expand Up @@ -63,6 +68,13 @@
name="reviewer_group_id"
invisible="review_type != 'group'"
/>
<field
name="reviewer_user_ids"
widget="many2many_avatar_user"
invisible="review_type != 'users'"
required="review_type == 'users'"
options="{'no_create': True}"
/>
<field
name="reviewer_field_id"
invisible="review_type != 'field'"
Expand Down Expand Up @@ -93,7 +105,7 @@
</div>
<field
name="exclude_requester"
invisible="review_type != 'group'"
invisible="review_type not in ('group', 'users')"
/>
</group>
</group>
Expand Down
1 change: 1 addition & 0 deletions base_tier_validation_forward/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Copyright 2020 Ecosoft Co., Ltd. (http://ecosoft.co.th)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from . import test_tier_validation
from . import test_forward_any_of_users
37 changes: 37 additions & 0 deletions base_tier_validation_forward/tests/test_forward_any_of_users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo.tests import Form, new_test_user, tagged

from odoo.addons.base_tier_validation.tests.common import CommonTierValidation


@tagged("post_install", "-at_install")
class TestForwardAnyOfUsers(CommonTierValidation):
def _setup_tier_definitions(self):
self.colleague = new_test_user(self.env, login="colleague", name="Clara")
self.tier_def_obj.create(
{
"model_id": self.tester_model.id,
"review_type": "users",
"reviewer_user_ids": [(6, 0, (self.test_user_2 | self.colleague).ids)],
"definition_domain": "[('test_field', '>', 1.0)]",
"has_forward": True,
}
)

def test_forward_keeps_only_the_new_reviewer(self):
"""A forwarded review is for that user alone, not for the whole list."""
record = self.test_model.create({"test_field": 2.0})
record.with_user(self.test_user_1).request_validation()
record = record.with_user(self.test_user_2)
res = record.forward_tier()
wizard = Form(
self.env["tier.validation.forward.wizard"]
.with_user(self.test_user_2)
.with_context(**res["context"])
)
wizard.forward_reviewer_id = self.test_user_1
wizard.forward_description = "Please have a look"
wizard.save().add_forward()
record.invalidate_recordset()
forwarded = record.review_ids.filtered(lambda r: not r.definition_id)
self.assertEqual(forwarded.reviewer_ids, self.test_user_1)
Loading