Skip to content
Merged
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
1 change: 1 addition & 0 deletions test-requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
odoo_test_helper
84 changes: 84 additions & 0 deletions tier_validation_dynamic_state/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
=============================
Tier Validation Dynamic State
=============================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:bcafd8e79751d2060b7bfa49c3b3c19b07407706bf2d6599b39e58ffaf059810
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-Escodoo%2Fserver--addons-lightgray.png?logo=github
:target: https://github.com/Escodoo/server-addons/tree/18.0/tier_validation_dynamic_state
:alt: Escodoo/server-addons

|badge1| |badge2| |badge3|

This module extends the base tier validation to configure state from and
state to in the tier definition.

**Table of contents**

.. contents::
:local:

Usage
=====

To use this module, you need to:

- Open Settings > Tier Definition and create a new definition.
- Select the model, the From State, and the To State for the
validation.
- When a user tries to change the state of a record, if there is a
definition for the current state and the target state, a validation
request will be created automatically.
- The Request Validation button will be hidden.

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/Escodoo/server-addons/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/Escodoo/server-addons/issues/new?body=module:%20tier_validation_dynamic_state%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

Do not contact contributors directly about support or help with technical issues.

Credits
=======

Authors
-------

* Escodoo

Contributors
------------

- ``Escodoo <https://www.escodoo.com.br>``\ \_:

- Wesley Oliveira wesley.oliveira@escodoo.com.br

Maintainers
-----------

.. |maintainer-WesleyOliveira98| image:: https://github.com/WesleyOliveira98.png?size=40px
:target: https://github.com/WesleyOliveira98
:alt: WesleyOliveira98

Current maintainer:

|maintainer-WesleyOliveira98|

This module is part of the `Escodoo/server-addons <https://github.com/Escodoo/server-addons/tree/18.0/tier_validation_dynamic_state>`_ project on GitHub.

You are welcome to contribute.
1 change: 1 addition & 0 deletions tier_validation_dynamic_state/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
18 changes: 18 additions & 0 deletions tier_validation_dynamic_state/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Copyright 2026 - TODAY, Escodoo
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

{
"name": "Tier Validation Dynamic State",
"summary": "Implement a validation process based on states.",
"version": "18.0.1.0.0",
"license": "AGPL-3",
"author": "Escodoo",
"website": "https://github.com/Escodoo/server-addons",
"depends": ["base_tier_validation"],
"data": [
"views/tier_definition_view.xml",
"templates/tier_validation_templates.xml",
],
"installable": True,
"maintainers": ["WesleyOliveira98"],
}
3 changes: 3 additions & 0 deletions tier_validation_dynamic_state/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from . import tier_definition
from . import tier_review
from . import tier_validation
17 changes: 17 additions & 0 deletions tier_validation_dynamic_state/models/tier_definition.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Copyright 2026 - TODAY, Wesley Oliveira <wesley.oliveira@escodoo.com.br>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import fields, models


class TierDefinition(models.Model):
_inherit = "tier.definition"

state_from = fields.Many2one(
comodel_name="ir.model.fields.selection",
ondelete="cascade",
)
state_to = fields.Many2one(
comodel_name="ir.model.fields.selection",
ondelete="cascade",
)
11 changes: 11 additions & 0 deletions tier_validation_dynamic_state/models/tier_review.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Copyright 2026 - TODAY, Wesley Oliveira <wesley.oliveira@escodoo.com.br>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import fields, models


class TierReview(models.Model):
_inherit = "tier.review"

state_from = fields.Char(readonly=True)
state_to = fields.Char(readonly=True)
188 changes: 188 additions & 0 deletions tier_validation_dynamic_state/models/tier_validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
# Copyright 2026 - TODAY, Wesley Oliveira <wesley.oliveira@escodoo.com.br>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from lxml import etree

from odoo import api, models


class TierValidation(models.AbstractModel):
_inherit = "tier.validation"

@api.depends("review_ids", "review_ids.status", "review_ids.state_from")
def _compute_validation_status(self):
for item in self:
current_state = item._tier_validation_get_current_state_value()
reviews = item.review_ids.filtered(
lambda r, cs=current_state: r.state_from == cs
)

if not reviews:
item.validation_status = "no"
continue

validated_states = item._validated_states()
rejected_states = item._rejected_states()

if any(reviews.filtered(lambda x, rs=rejected_states: x.status in rs)):
item.validation_status = "rejected"
elif all(x.status in validated_states for x in reviews):
item.validation_status = "validated"
elif any(reviews.filtered(lambda x: x.status == "pending")):
item.validation_status = "pending"
else:
item.validation_status = "waiting"

def _compute_need_validation(self):
for rec in self:
if not rec.id:
rec.need_validation = False
continue

current_state = rec._tier_validation_get_current_state_value()
tier_definitions = (
self.env["tier.definition"]
.sudo()
.search(
[
("model", "=", self._name),
("company_id", "in", [False] + rec._get_company().ids),
("state_from.value", "=", current_state),
]
)
)

valid_tiers = any([rec.evaluate_tier(tier) for tier in tier_definitions])
current_reviews = rec.review_ids.filtered(
lambda r, cs=current_state: r.state_from == cs
)

rec.need_validation = not current_reviews and valid_tiers

def _prepare_tier_review_vals(self, definition, sequence):
vals = super()._prepare_tier_review_vals(definition, sequence)
vals.update(
{
"state_from": definition.state_from.value,
"state_to": definition.state_to.value,
}
)
return vals

def _check_state_conditions(self, vals):
self.ensure_one()
state_field = self._state_field
if state_field not in vals:
return False

current_state = self._tier_validation_get_current_state_value()
target_state = vals[state_field]

matching_tiers = (
self.env["tier.definition"]
.sudo()
.search(
[
("model", "=", self._name),
("company_id", "in", [False] + self._get_company().ids),
("state_from.value", "=", current_state),
("state_to.value", "=", target_state),
]
)
)
return any(self.evaluate_tier(tier) for tier in matching_tiers)

def request_validation(self, target_state=None):
if not target_state:
return super().request_validation()

td_obj = self.env["tier.definition"]
tr_obj = self.env["tier.review"]
vals_list = []
for rec in self:
current_state = rec._tier_validation_get_current_state_value()
domain = [
("model", "=", self._name),
("company_id", "in", [False] + rec._get_company().ids),
("state_from.value", "=", current_state),
]
if target_state:
domain.append(("state_to.value", "=", target_state))

tier_definitions = td_obj.sudo().search(domain, order="sequence desc")

sequence = len(rec.review_ids)
for td in tier_definitions:
if rec.evaluate_tier(td):
sequence += 1
vals_list.append(rec._prepare_tier_review_vals(td, sequence))

created_trs = tr_obj.create(vals_list)
self._notify_review_requested(created_trs)
return created_trs

def _check_and_request_tier(self, target_state):
self.ensure_one()
tier_definitions = (
self.env["tier.definition"]
.sudo()
.search(
[
("model", "=", self._name),
("state_from.value", "=", self.state),
("state_to.value", "=", target_state),
],
)
)

if not tier_definitions:
return False

current_reviews = self.review_ids.filtered(
lambda r: r.state_from == self.state and r.state_to == target_state
)

if not current_reviews:
self.request_validation(target_state=target_state)
return True

if any(review.status != "approved" for review in current_reviews):
return True

return False

def write(self, vals):
if self._state_field in vals:
target_state = vals[self._state_field]
needs_validation = False
for rec in self:
if rec._check_and_request_tier(target_state):
needs_validation = True

if needs_validation:
return True
return super().write(vals)

def _add_tier_validation_buttons(self, node, params):
str_element = self.env["ir.qweb"]._render(
"tier_validation_dynamic_state.tier_validation_buttons_hide_request", params
)
new_node = etree.fromstring(str_element)
return new_node

def _compute_hide_reviews(self):
for rec in self:
hide_reviews = True
tier_definitions = (
rec.env["tier.definition"]
.sudo()
.search(
[
("model", "=", rec._name),
("state_from.value", "=", rec.state),
],
)
)
if tier_definitions:
hide_reviews = False
rec.hide_reviews = hide_reviews
3 changes: 3 additions & 0 deletions tier_validation_dynamic_state/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"
3 changes: 3 additions & 0 deletions tier_validation_dynamic_state/readme/CONTRIBUTORS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
* `Escodoo <https://www.escodoo.com.br>`_:

* Wesley Oliveira <wesley.oliveira@escodoo.com.br>
1 change: 1 addition & 0 deletions tier_validation_dynamic_state/readme/DESCRIPTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This module extends the base tier validation to configure state from and state to in the tier definition.
6 changes: 6 additions & 0 deletions tier_validation_dynamic_state/readme/USAGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
To use this module, you need to:

* Open Settings > Tier Definition and create a new definition.
* Select the model, the From State, and the To State for the validation.
* When a user tries to change the state of a record, if there is a definition for the current state and the target state, a validation request will be created automatically.
* The Request Validation button will be hidden.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading