From 036169ef36ff454f89d24213a369b45be7d70ef2 Mon Sep 17 00:00:00 2001 From: Arnau Date: Tue, 16 Sep 2025 15:34:59 +0200 Subject: [PATCH 1/2] [ADD] tier_validation_password_confirm --- tier_validation_password_confirm/README.rst | 14 +++++ tier_validation_password_confirm/__init__.py | 2 + .../__manifest__.py | 19 +++++++ .../models/__init__.py | 3 ++ .../models/tier_definition.py | 12 +++++ .../models/tier_review.py | 11 ++++ .../models/tier_validation.py | 53 +++++++++++++++++++ .../pyproject.toml | 3 ++ .../views/tier_definition_view.xml | 16 ++++++ .../wizards/__init__.py | 1 + .../wizards/tier_password_wizard.py | 37 +++++++++++++ .../wizards/tier_password_wizard_view.xml | 28 ++++++++++ 12 files changed, 199 insertions(+) create mode 100644 tier_validation_password_confirm/README.rst create mode 100644 tier_validation_password_confirm/__init__.py create mode 100644 tier_validation_password_confirm/__manifest__.py create mode 100644 tier_validation_password_confirm/models/__init__.py create mode 100644 tier_validation_password_confirm/models/tier_definition.py create mode 100644 tier_validation_password_confirm/models/tier_review.py create mode 100644 tier_validation_password_confirm/models/tier_validation.py create mode 100644 tier_validation_password_confirm/pyproject.toml create mode 100644 tier_validation_password_confirm/views/tier_definition_view.xml create mode 100644 tier_validation_password_confirm/wizards/__init__.py create mode 100644 tier_validation_password_confirm/wizards/tier_password_wizard.py create mode 100644 tier_validation_password_confirm/wizards/tier_password_wizard_view.xml diff --git a/tier_validation_password_confirm/README.rst b/tier_validation_password_confirm/README.rst new file mode 100644 index 0000000000..bd00a4ee5c --- /dev/null +++ b/tier_validation_password_confirm/README.rst @@ -0,0 +1,14 @@ +===================================== +Tier Validation Password Confirm +===================================== + +This module adds a new field to the tier definition that allows to set a password confirmation, +so later in the tier validation the system will ask the user to confirm the password. + +Credits +======= + +Contributors +------------ + +- ForgeFlow S.L. diff --git a/tier_validation_password_confirm/__init__.py b/tier_validation_password_confirm/__init__.py new file mode 100644 index 0000000000..aee8895e7a --- /dev/null +++ b/tier_validation_password_confirm/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizards diff --git a/tier_validation_password_confirm/__manifest__.py b/tier_validation_password_confirm/__manifest__.py new file mode 100644 index 0000000000..03d9fda3f2 --- /dev/null +++ b/tier_validation_password_confirm/__manifest__.py @@ -0,0 +1,19 @@ +# Copyright 2024 ForgeFlow S.L. (https://www.forgeflow.com) +{ + "name": "Tier Validation Password Confirm", + "version": "18.0.1.0.0", + "category": "Server UX", + "author": "ForgeFlow, Odoo Community Association (OCA)", + "license": "AGPL-3", + "website": "https://github.com/OCA/server-ux", + "depends": [ + "base_tier_validation", + ], + "data": [ + "views/tier_definition_view.xml", + "wizards/tier_password_wizard_view.xml", + ], + "installable": True, + "auto_install": False, + "application": False, +} diff --git a/tier_validation_password_confirm/models/__init__.py b/tier_validation_password_confirm/models/__init__.py new file mode 100644 index 0000000000..0ec59487ef --- /dev/null +++ b/tier_validation_password_confirm/models/__init__.py @@ -0,0 +1,3 @@ +from . import tier_review +from . import tier_validation +from . import tier_definition diff --git a/tier_validation_password_confirm/models/tier_definition.py b/tier_validation_password_confirm/models/tier_definition.py new file mode 100644 index 0000000000..83f0748dc8 --- /dev/null +++ b/tier_validation_password_confirm/models/tier_definition.py @@ -0,0 +1,12 @@ +# Copyright 2024 ForgeFlow S.L. + +from odoo import fields, models + + +class TierDefinition(models.Model): + _inherit = "tier.definition" + + require_password = fields.Boolean( + help="If checked, the user will be asked to enter " + "the password to validate the tier.", + ) diff --git a/tier_validation_password_confirm/models/tier_review.py b/tier_validation_password_confirm/models/tier_review.py new file mode 100644 index 0000000000..4441d3c006 --- /dev/null +++ b/tier_validation_password_confirm/models/tier_review.py @@ -0,0 +1,11 @@ +# Copyright 2024 ForgeFlow S.L. +from odoo import fields, models + + +class TierReview(models.Model): + _inherit = "tier.review" + + require_password = fields.Boolean( + related="definition_id.require_password", readonly=True + ) + password_confirmed = fields.Boolean() diff --git a/tier_validation_password_confirm/models/tier_validation.py b/tier_validation_password_confirm/models/tier_validation.py new file mode 100644 index 0000000000..0ac593854a --- /dev/null +++ b/tier_validation_password_confirm/models/tier_validation.py @@ -0,0 +1,53 @@ +# Copyright 2025 ForgeFlow S.L. + +from odoo import _, fields, models +from odoo.exceptions import ValidationError + + +class TierValidation(models.AbstractModel): + _inherit = "tier.validation" + + require_password = fields.Boolean(compute="_compute_require_password") + + def _compute_require_password(self): + for rec in self: + require_password = rec.review_ids.filtered( + lambda r: r.status == "pending" and (self.env.user in r.reviewer_ids) + ).mapped("require_password") + rec.require_password = True in require_password + + def validate_tier(self): + self.ensure_one() + sequences = self._get_sequences_to_approve(self.env.user) + reviews = self.review_ids.filtered( + lambda r: r.sequence in sequences or r.approve_sequence_bypass + ) + if self.has_comment or self.require_password: + user_reviews = reviews.filtered( + lambda r: r.status == "pending" and (self.env.user in r.reviewer_ids) + ) + return self._add_comment("validate", user_reviews) + self._validate_tier(reviews) + self._update_counter() + + def _add_comment(self, validate_reject, reviews): + res = super()._add_comment(validate_reject, reviews) + res["context"]["comment"] = self.has_comment + res["context"]["require_password"] = self.require_password + return res + + def _validate_tier(self, tiers=False): + self.ensure_one() + tier_reviews = tiers or self.review_ids + user_reviews = tier_reviews.filtered( + lambda r: r.status == "pending" and (self.env.user in r.reviewer_ids) + ) + for review in user_reviews: + if review.require_password and not review.password_confirmed: + raise ValidationError( + _( + "You need to request a validation, because " + "the reviewer requires a password to validate." + ) + ) + return super()._validate_tier(tiers) diff --git a/tier_validation_password_confirm/pyproject.toml b/tier_validation_password_confirm/pyproject.toml new file mode 100644 index 0000000000..4231d0cccb --- /dev/null +++ b/tier_validation_password_confirm/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/tier_validation_password_confirm/views/tier_definition_view.xml b/tier_validation_password_confirm/views/tier_definition_view.xml new file mode 100644 index 0000000000..a647082037 --- /dev/null +++ b/tier_validation_password_confirm/views/tier_definition_view.xml @@ -0,0 +1,16 @@ + + + + + complaint.tier.definition.form + tier.definition + + + + + + + + + + diff --git a/tier_validation_password_confirm/wizards/__init__.py b/tier_validation_password_confirm/wizards/__init__.py new file mode 100644 index 0000000000..4f01801c6d --- /dev/null +++ b/tier_validation_password_confirm/wizards/__init__.py @@ -0,0 +1 @@ +from . import tier_password_wizard diff --git a/tier_validation_password_confirm/wizards/tier_password_wizard.py b/tier_validation_password_confirm/wizards/tier_password_wizard.py new file mode 100644 index 0000000000..7bdb8078fd --- /dev/null +++ b/tier_validation_password_confirm/wizards/tier_password_wizard.py @@ -0,0 +1,37 @@ +from odoo import _, api, fields, models +from odoo.exceptions import AccessDenied, ValidationError + + +class CommentWizardInherit(models.TransientModel): + _inherit = "comment.wizard" + + password = fields.Char() + has_comment = fields.Boolean() + require_password = fields.Boolean() + comment = fields.Char(required=False) + + @api.model + def default_get(self, fields_list): + """Set default values based on context.""" + defaults = super().default_get(fields_list) + defaults["has_comment"] = self.env.context.get("comment", False) + defaults["require_password"] = self.env.context.get("require_password", False) + + return defaults + + def add_comment(self): + self.ensure_one() + if self.require_password: + user = self.env.user + try: + credentials = { + "login": user.login, + "password": self.password, + "type": "password", + } + user._check_credentials(credentials, {"interactive": True}) + self.review_ids.write({"password_confirmed": True}) + except AccessDenied as e: + raise ValidationError(_("Incorrect password. Please try again.")) from e + + return super().add_comment() diff --git a/tier_validation_password_confirm/wizards/tier_password_wizard_view.xml b/tier_validation_password_confirm/wizards/tier_password_wizard_view.xml new file mode 100644 index 0000000000..7186b26ef8 --- /dev/null +++ b/tier_validation_password_confirm/wizards/tier_password_wizard_view.xml @@ -0,0 +1,28 @@ + + + Comment Wizard Inherit + comment.wizard + + + + + + + + + + + + + + + not has_comment + has_comment + + + + + + + From 6ffd19c143ac1b4b070c40e0bfa949979bf9098d Mon Sep 17 00:00:00 2001 From: Jordi Ballester Alomar Date: Thu, 25 Sep 2025 16:58:42 +0200 Subject: [PATCH 2/2] [FIX] tier_validation_password_confirm: update counter needs argument --- tier_validation_password_confirm/models/tier_validation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tier_validation_password_confirm/models/tier_validation.py b/tier_validation_password_confirm/models/tier_validation.py index 0ac593854a..0cbe9a78a0 100644 --- a/tier_validation_password_confirm/models/tier_validation.py +++ b/tier_validation_password_confirm/models/tier_validation.py @@ -28,7 +28,7 @@ def validate_tier(self): ) return self._add_comment("validate", user_reviews) self._validate_tier(reviews) - self._update_counter() + self._update_counter({"review_deleted": True}) def _add_comment(self, validate_reject, reviews): res = super()._add_comment(validate_reject, reviews)