diff --git a/base_tier_validation/models/tier_definition.py b/base_tier_validation/models/tier_definition.py
index 1eb07a86a..a19fcd0ac 100644
--- a/base_tier_validation/models/tier_definition.py
+++ b/base_tier_validation/models/tier_definition.py
@@ -120,6 +120,14 @@ def _get_tier_validation_model_names(self):
"Only meaningful for review_type='group'; the form hides this "
"field for the other review types.",
)
+ allow_reject = fields.Boolean(
+ string="Allow Rejection",
+ default=True,
+ help="When unchecked, reviewers of this tier can only validate "
+ "the record, not reject it. Use this to model 'sign-off' / "
+ "informational tiers where the reviewer is expected to "
+ "acknowledge but not block the workflow.",
+ )
@api.onchange("review_type")
def onchange_review_type(self):
diff --git a/base_tier_validation/models/tier_validation.py b/base_tier_validation/models/tier_validation.py
index 782cca05d..44ec2c552 100644
--- a/base_tier_validation/models/tier_validation.py
+++ b/base_tier_validation/models/tier_validation.py
@@ -67,6 +67,14 @@ class TierValidation(models.AbstractModel):
can_review = fields.Boolean(
compute="_compute_can_review", search="_search_can_review"
)
+ can_reject = fields.Boolean(
+ compute="_compute_can_reject",
+ help="True when the current user can act on this record AND at "
+ "least one of their actionable tier reviews has "
+ "``tier.definition.allow_reject`` set. Drives the visibility "
+ "of the Reject button on the validation banner so that "
+ "sign-off / informational tiers don't expose a Reject action.",
+ )
has_comment = fields.Boolean(
compute="_compute_has_comment",
help="If set, Allow the reviewer to leave a comment on the review.",
@@ -115,6 +123,29 @@ def _compute_can_review(self):
for rec in self:
rec.can_review = rec._get_sequences_to_approve(self.env.user)
+ @api.depends_context("uid")
+ @api.depends(
+ "review_ids.approve_sequence",
+ "review_ids.reviewer_ids",
+ "review_ids.sequence",
+ "review_ids.status",
+ "review_ids.definition_id.allow_reject",
+ )
+ def _compute_can_reject(self):
+ user = self.env.user
+ for rec in self:
+ sequences = rec._get_sequences_to_approve(user)
+ if not sequences:
+ rec.can_reject = False
+ continue
+ # The user has actionable tiers; check whether at least one
+ # of them is configured to allow rejection.
+ rec.can_reject = any(
+ r.definition_id.allow_reject
+ for r in rec.review_ids
+ if r.sequence in sequences
+ )
+
@api.model
def _search_can_review(self, operator, value):
domain = Domain(
@@ -660,6 +691,13 @@ def reject_tier(self):
self.ensure_one()
sequences = self._get_sequences_to_approve(self.env.user)
reviews = self.review_ids.filtered(lambda x: x.sequence in sequences)
+ # Honor per-definition ``allow_reject``: tiers flagged as
+ # sign-off / informational only let the reviewer validate, not
+ # reject. Filter them out so calling reject_tier on a mixed
+ # batch only rejects the tiers that actually permit it.
+ reviews = reviews.filtered(lambda r: r.definition_id.allow_reject)
+ if not reviews:
+ return
if self.has_comment:
return self._add_comment("reject", reviews)
self._rejected_tier(reviews)
diff --git a/base_tier_validation/templates/tier_validation_templates.xml b/base_tier_validation/templates/tier_validation_templates.xml
index 288709562..c837eedf8 100644
--- a/base_tier_validation/templates/tier_validation_templates.xml
+++ b/base_tier_validation/templates/tier_validation_templates.xml
@@ -42,7 +42,7 @@
+