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: 3 additions & 3 deletions base_tier_validation/models/tier_definition.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,9 @@ def _get_tier_validation_model_names(self):
help="Default comment prefilled when approval comments are enabled.",
)
notify_reminder_delay = fields.Integer(
string="Send reminder message on pending reviews",
help="Number of days after which a message must be posted to remind about "
"pending validation (0 = no reminder)",
string="Remind Every (Days)",
help="Remind the reviewers every this many days until the review is "
"done. Leave at 0 to send no reminder.",
)
approve_sequence = fields.Boolean(
string="Approve by sequence",
Expand Down
5 changes: 3 additions & 2 deletions base_tier_validation/models/tier_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,13 +200,13 @@ def _get_to_validate_message(self):
)

def _get_validated_message(self):
msg = f"""<i class="fa fa-thumbs-up"></i> {
msg = f"""<i class="fa fa-check"></i> {
self.env._("Operation has been <b>validated</b>!")
}"""
return self.validation_status == "validated" and msg or ""

def _get_rejected_message(self):
msg = f"""<i class="fa fa-thumbs-down"></i> {
msg = f"""<i class="fa fa-times"></i> {
self.env._("Operation has been <b>rejected</b>.")
}"""
return self.validation_status == "rejected" and msg or ""
Expand Down Expand Up @@ -305,6 +305,7 @@ def _get_exception_fields(self, extra_domain=None):
return (
self.env["tier.validation.exception"]
.sudo()
.with_context(active_test=True)
.search(domain)
.mapped("field_ids.name")
)
Expand Down
1 change: 1 addition & 0 deletions base_tier_validation/models/tier_validation_exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ def _get_tier_validation_model_names(self):
required=True,
default="New Tier Validation Exception",
)
active = fields.Boolean(default=True)
model_id = fields.Many2one(
comodel_name="ir.model",
string="Model",
Expand Down
3 changes: 3 additions & 0 deletions base_tier_validation/readme/newsfragments/23.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Clearer tier definition form: who reviews and in what order at the top, a *Review* tab (reviewer permissions, what happens when reviewing, after review) and a *Notifications* tab, where the reminder reads *Remind every [ ] days*. The definition list hides archived definitions by default, shows reviewer avatars and has an optional *Reviewer field* column. The review lists show avatars and status badges, the definition action has an empty-state help, and the reviewer menu sits with the other notification menus in the systray.

Tier validation exceptions can be archived; archived exceptions no longer apply. Their form groups the write permissions and the groups they apply to.
2 changes: 1 addition & 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
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@ export const systrayItem = {
Component: TierReviewMenu,
};

// Systray items render right-to-left by descending sequence (higher =
// further left; the user menu at sequence 0 is the right-most). A low
// sequence keeps the reviewer menu grouped with the standard
// notification menus (messaging 25, activities 20) on the right, rather
// than off on the far left -- notably to the right of the Enterprise AI
// button (which sits left of the messaging menu, i.e. sequence > 25).
registry
.category("systray")
.add("base_tier_validation.ReviewerMenu", systrayItem, {sequence: 99});
.add("base_tier_validation.ReviewerMenu", systrayItem, {sequence: 24});
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,7 @@
menuClass="discussSystray.menuClass"
>
<button>
<i
class="fa fa-lg fa-pencil-square-o"
role="img"
aria-label="Reviews"
/>
<i class="fa fa-lg fa-check-square-o" role="img" aria-label="Reviews" />
<span
t-if="store.tierReviewCounter"
class="o-mail-ActivityMenu-counter badge rounded-pill"
Expand Down
9 changes: 5 additions & 4 deletions base_tier_validation/templates/tier_validation_templates.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
string="Restart Validation"
invisible="not review_ids or hide_reviews"
type="object"
icon="fa-rotate-left"
/>
</div>
</template>
Expand All @@ -37,15 +38,15 @@
invisible="not can_review"
type="object"
class="btn-icon me-1 btn-success"
icon="fa-thumbs-up"
icon="fa-check fa-lg"
/>
<button
name="reject_tier"
string="Reject"
invisible="not can_review"
type="object"
class="btn-icon btn-danger"
icon="fa-thumbs-down"
icon="fa-times fa-lg"
/>
<br />
<field name="next_review" readonly="1" />
Expand All @@ -57,7 +58,7 @@
t-attf-invisible="validation_status != 'validated' or hide_reviews or not review_ids"
>
<p class="mb-1">
<i class="fa fa-lg fa-thumbs-up" />
<i class="fa fa-lg fa-check" />
Operation has been
<b>validated</b>
!
Expand All @@ -69,7 +70,7 @@
t-attf-invisible="validation_status != 'rejected' or hide_reviews or not review_ids"
>
<p class="mb-1">
<i class="fa fa-lg fa-thumbs-down" />
<i class="fa fa-lg fa-times" />
Operation has been
<b>rejected</b>
.
Expand Down
43 changes: 43 additions & 0 deletions base_tier_validation/tests/test_tier_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,30 @@ def test_04_request_validation_rejected(self):
record.restart_validation()
self.assertFalse(record.review_ids)

def test_validated_and_rejected_message_icons(self):
"""`_get_validated_message` / `_get_rejected_message` render the
per-status banner HTML using the check / xmark icons consistent
with the rest of the module's iconography. Empty until the
record actually reaches that state."""
# Until a state is reached, the banner is empty.
self.assertFalse(self.test_record.validated_message)
self.assertFalse(self.test_record.rejected_message)
# Validate -> banner reflects the validated state.
reviews = self.test_record.with_user(self.test_user_2).request_validation()
self.assertTrue(reviews)
self.test_record.with_user(self.test_user_1).validate_tier()
self.assertEqual(self.test_record.validation_status, "validated")
self.assertIn("fa-check", self.test_record.validated_message)
self.assertIn("validated", self.test_record.validated_message)
self.assertFalse(self.test_record.rejected_message)
# Restart + reject -> the rejected banner picks up its icon too.
self.test_record.restart_validation()
self.test_record.with_user(self.test_user_2).request_validation()
self.test_record.with_user(self.test_user_1).reject_tier()
self.assertEqual(self.test_record.validation_status, "rejected")
self.assertIn("fa-times", self.test_record.rejected_message)
self.assertIn("rejected", self.test_record.rejected_message)

def test_05_under_validation(self):
"""Write is forbidden in a record under validation."""
self.assertFalse(self.test_record.review_ids)
Expand Down Expand Up @@ -1854,6 +1878,25 @@ def test_34_test_duplicate_new_user_should_not_have_review_ids(self):
# Review_ids should not be copied when duplicating a user
self.assertFalse(new_user.review_ids.ids)

def test_35_archived_exception_not_applied(self):
exception = self.env["tier.validation.exception"].create(
{
"model_id": self.tester_model.id,
"field_ids": [
Command.set(
self.tester_model.field_id.filtered(
lambda f: f.name == "test_validation_field"
).ids
)
],
}
)
self.assertIn("test_validation_field", self.test_record._get_exception_fields())
exception.action_archive()
self.assertNotIn(
"test_validation_field", self.test_record._get_exception_fields()
)


@tagged("at_install")
class TierTierValidationView(CommonTierValidation):
Expand Down
83 changes: 63 additions & 20 deletions base_tier_validation/views/tier_definition_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
<list>
<field name="sequence" widget="handle" />
<field name="model_id" />
<field name="name" />
<field name="name" decoration-bf="1" />
<field name="review_type" />
<field name="reviewer_id" />
<field name="reviewer_id" widget="many2one_avatar_user" />
<field name="reviewer_group_id" />
<field name="reviewer_field_id" />
<field name="reviewer_field_id" optional="hide" />
<field name="notify_on_create" optional="show" />
<field name="notify_on_pending" optional="show" />
<field name="has_comment" optional="hide" />
Expand All @@ -22,7 +22,6 @@
groups="base.group_multi_company"
optional="hide"
/>
<field name="active" />
</list>
</field>
</record>
Expand All @@ -40,7 +39,6 @@
invisible="active"
/>
<div class="oe_title">
<span class="oe_edit_only">Name</span>
<h1>
<field
name="name"
Expand All @@ -54,15 +52,19 @@
<field name="model_id" options="{'no_create': True}" />
<field name="model" invisible="1" />
<field name="review_type" />
<field name="allow_write_for_reviewer" />
<field
name="reviewer_id"
widget="many2one_avatar_user"
invisible="review_type != 'individual'"
/>
<field
name="reviewer_group_id"
invisible="review_type != 'group'"
/>
<field
name="exclude_requester"
invisible="review_type != 'group'"
/>
<field
name="reviewer_field_id"
invisible="review_type != 'field'"
Expand All @@ -75,7 +77,7 @@
groups="base.group_multi_company"
options="{'no_create': True}"
/>
<field name="sequence" />
<field name="sequence" class="oe_inline o_input_4ch" />
<field name="approve_sequence" />
<label
for="approve_sequence_bypass"
Expand All @@ -91,10 +93,6 @@
<span
>Bypass, if previous tier was validated by same reviewer</span>
</div>
<field
name="exclude_requester"
invisible="review_type != 'group'"
/>
</group>
</group>
<notebook>
Expand All @@ -109,20 +107,42 @@
/>
</group>
</page>
<page name="options" string="More Options">
<page name="review" string="Review">
<group name="review_settings">
<group
string="Reviewer permissions"
name="reviewer_permissions"
>
<field name="allow_write_for_reviewer" />
</group>
<group string="When reviewing" name="review_options">
<field name="has_comment" />
<field
name="comment_approve_default"
invisible="not has_comment"
/>
</group>
</group>
</page>
<page name="options" string="Notifications">
<group name="more_option">
<group name="notify">
<group string="Notify reviewers when" name="notify">
<field name="notify_on_create" />
<field name="notify_on_pending" />
<label
for="notify_reminder_delay"
string="Remind every"
/>
<div>
<field
name="notify_reminder_delay"
class="oe_inline o_input_4ch"
/>
<span class="ms-1">days</span>
</div>
<field name="notify_on_accepted" />
<field name="notify_on_rejected" />
<field name="notify_on_restarted" />
<field name="has_comment" />
<field
name="comment_approve_default"
invisible="not has_comment"
/>
<field name="notify_reminder_delay" />
</group>
</group>
</page>
Expand All @@ -142,6 +162,11 @@
<field name="reviewer_group_id" />
<field name="active" />
<separator />
<filter
string="Archived"
name="inactive"
domain="[('active', '=', False)]"
/>
<filter
string="All"
name="all"
Expand All @@ -163,7 +188,25 @@
<field name="type">ir.actions.act_window</field>
<field name="res_model">tier.definition</field>
<field name="view_mode">list,form</field>
<field name="context">{'search_default_all': 1}</field>
<!-- Explicitly empty: an upgrade keeps the old show-all default
otherwise, and archived definitions would show up again. -->
<field name="context">{}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Define a new validation tier
</p>
<p>
A tier definition tells Odoo which records on a given model need
to be reviewed by which user (or group, or field-based reviewer)
before they can move forward in their workflow.
</p>
<p>
Tiers can be chained -- enable
<em>Approve by sequence</em>
on each definition for the model to require the lower-sequence
tier to be approved first.
</p>
</field>
</record>
<menuitem
id="menu_tier_confirmation"
Expand Down
14 changes: 11 additions & 3 deletions base_tier_validation/views/tier_review_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,22 @@
<list
decoration-danger="status=='rejected'"
decoration-success="status=='approved'"
decoration-warning="status=='pending'"
decoration-muted="status=='waiting'"
>
<field name="sequence" />
<field name="requested_by" />
<field name="requested_by" widget="many2one_avatar_user" />
<field name="review_type" />
<field name="name" />
<field name="status" />
<field
name="status"
widget="badge"
decoration-warning="status == 'pending'"
decoration-success="status == 'approved'"
decoration-danger="status == 'rejected'"
/>
<field name="todo_by" />
<field name="done_by" />
<field name="done_by" widget="many2one_avatar_user" />
<field name="reviewed_date" />
<field name="comment" />
</list>
Expand Down
Loading
Loading