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
6 changes: 6 additions & 0 deletions base_tier_validation/models/tier_definition.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ def _get_tier_validation_model_names(self):
default=False,
help="Approval order by the specified sequence number",
)
allow_bulk_approve = fields.Boolean(
default=False,
help="When set, reviewers can approve several reviews at once from the "
"tier review list view. Reviews whose definition does not have this "
"flag are skipped from a bulk selection.",
)
approve_sequence_bypass = fields.Boolean(
help="Bypassed (auto validated), if previous tier was validated "
"by same reviewer",
Expand Down
40 changes: 40 additions & 0 deletions base_tier_validation/models/tier_review.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class TierReview(models.Model):
approve_sequence_bypass = fields.Boolean(
related="definition_id.approve_sequence_bypass"
)
allow_bulk_approve = fields.Boolean(related="definition_id.allow_bulk_approve")
last_reminder_date = fields.Datetime(readonly=True)

@api.depends("status")
Expand Down Expand Up @@ -212,3 +213,42 @@ def _schedule_review_reminder_activity(self, record):
note=self._notify_review_reminder_body(),
user_id=self.reviewer_ids.id,
)

def _filter_bulk_validatable(self):
"""Return the subset of self that the current user may bulk-approve.

A review is eligible when its definition opts in via
``allow_bulk_approve``, the review is pending and assigned to the
current user, the user is the reviewer-of-the-moment (``can_review``),
and the definition does not require a per-review comment (which
cannot be collected in a bulk action).
"""
return self.filtered(
lambda r: r.allow_bulk_approve
and r.status == "pending"
and r.can_review
and self.env.user in r.reviewer_ids
and not r.has_comment
)

def action_bulk_validate(self):
"""Approve every eligible review in ``self`` in a single call.

Reviews are grouped by their underlying record so that each record's
own ``_validate_tier`` runs once -- preserving notifications, the
approve-sequence promotion logic, and the review counter update.
Ineligible reviews are skipped silently; the action is meant to be
wired to the list view header.
"""
eligible = self._filter_bulk_validatable()
if not eligible:
return False
by_key = {}
for review in eligible:
by_key.setdefault((review.model, review.res_id), self.browse())
by_key[(review.model, review.res_id)] |= review
for (model, res_id), reviews in by_key.items():
record = self.env[model].browse(res_id)
record._validate_tier(reviews)
record._update_counter({"review_deleted": True})
return True
3 changes: 3 additions & 0 deletions base_tier_validation/readme/CONFIGURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ To configure this module, you need to:
Reject.
- If check *Approve by sequence*, reviewers is forced to review by
specified sequence.
- If check *Allow Bulk Approve*, reviewers can validate several reviews at
once from the Tier Review list. Reviews on definitions without this flag
are skipped from a bulk selection.

To configure Tier Validation Exceptions, you need to:

Expand Down
46 changes: 46 additions & 0 deletions base_tier_validation/tests/test_tier_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1457,6 +1457,52 @@ def test_34_test_duplicate_new_user_should_not_have_review_ids(self):

@tagged("at_install")
class TierTierValidationView(CommonTierValidation):
def test_bulk_validate_approves_eligible_reviews(self):
"""Reviews on definitions with allow_bulk_approve=True get approved;
those without are left untouched."""
self.definition_1.allow_bulk_approve = True
rec_a = self.test_model.create({"test_field": 1.0})
rec_b = self.test_model.create({"test_field": 1.0})
rec_a.with_user(self.test_user_2).request_validation()
rec_b.with_user(self.test_user_2).request_validation()
reviews = (rec_a.review_ids | rec_b.review_ids).with_user(self.test_user_1)
self.assertEqual(set(reviews.mapped("status")), {"pending"})
reviews.action_bulk_validate()
self.assertEqual(set(reviews.mapped("status")), {"approved"})
self.assertEqual(rec_a.validation_status, "validated")
self.assertEqual(rec_b.validation_status, "validated")

def test_bulk_validate_skips_definitions_without_flag(self):
"""A review on a definition without allow_bulk_approve stays pending
even when included in a bulk selection."""
self.assertFalse(self.definition_1.allow_bulk_approve)
rec = self.test_model.create({"test_field": 1.0})
rec.with_user(self.test_user_2).request_validation()
review = rec.review_ids.with_user(self.test_user_1)
review.action_bulk_validate()
self.assertEqual(review.status, "pending")

def test_bulk_validate_skips_other_users_reviews(self):
"""The action only touches reviews assigned to the current user."""
self.definition_1.allow_bulk_approve = True
rec = self.test_model.create({"test_field": 1.0})
rec.with_user(self.test_user_2).request_validation()
review = rec.review_ids.with_user(self.test_user_2)
self.assertFalse(review._filter_bulk_validatable())
review.action_bulk_validate()
self.assertEqual(review.status, "pending")

def test_bulk_validate_skips_reviews_requiring_comment(self):
"""A comment-required definition cannot be approved in bulk because
no comment can be collected; such reviews are skipped."""
self.definition_1.allow_bulk_approve = True
self.definition_1.has_comment = True
rec = self.test_model.create({"test_field": 1.0})
rec.with_user(self.test_user_2).request_validation()
review = rec.review_ids.with_user(self.test_user_1)
review.action_bulk_validate()
self.assertEqual(review.status, "pending")

def test_view_manual(self):
view = self.env[self.test_record._name].get_view(False, "form")
with Form(self.test_record) as f:
Expand Down
1 change: 1 addition & 0 deletions base_tier_validation/views/tier_definition_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
options="{'no_create': True}"
/>
<field name="sequence" />
<field name="allow_bulk_approve" />
<field name="approve_sequence" />
<label
for="approve_sequence_bypass"
Expand Down
7 changes: 7 additions & 0 deletions base_tier_validation/views/tier_review_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@
decoration-danger="status=='rejected'"
decoration-success="status=='approved'"
>
<header>
<button
name="action_bulk_validate"
type="object"
string="Validate"
/>
</header>
<field name="sequence" />
<field name="requested_by" />
<field name="review_type" />
Expand Down
Loading