From 877951debc3d9dd42d122ab6f970c3e813e87628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Mart=C3=ADnez?= Date: Thu, 22 May 2025 17:57:58 +0200 Subject: [PATCH 1/2] [IMP] base_tier_validation: Make the validation_status field store so that it can be used in searchs. Similar to https://github.com/kevinkhao/server-ux/commit/217de7cc08f5e0f39b80507c2fcfd734cfb1b1b5 TT56391 --- base_tier_validation/README.rst | 142 +++++++++--------- base_tier_validation/__manifest__.py | 2 +- .../migrations/18.0.2.1.0/pre-migration.py | 79 ++++++++++ base_tier_validation/models/res_users.py | 2 +- .../models/tier_validation.py | 107 ++++++------- .../static/description/index.html | 84 +++++------ .../templates/tier_validation_templates.xml | 8 +- base_tier_validation/tests/common.py | 30 ++-- .../tests/test_tier_validation.py | 84 ++++------- .../models/tier_validation.py | 17 +-- 10 files changed, 292 insertions(+), 263 deletions(-) create mode 100644 base_tier_validation/migrations/18.0.2.1.0/pre-migration.py diff --git a/base_tier_validation/README.rst b/base_tier_validation/README.rst index 7963cfbc01..d6c382555f 100644 --- a/base_tier_validation/README.rst +++ b/base_tier_validation/README.rst @@ -1,7 +1,3 @@ -.. image:: https://odoo-community.org/readme-banner-image - :target: https://odoo-community.org/get-involved?utm_source=readme - :alt: Odoo Community Association - ==================== Base Tier Validation ==================== @@ -17,7 +13,7 @@ Base Tier Validation .. |badge1| image:: https://img.shields.io/badge/maturity-Mature-brightgreen.png :target: https://odoo-community.org/page/development-status :alt: Mature -.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png +.. |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-OCA%2Fserver--ux-lightgray.png?logo=github @@ -70,16 +66,16 @@ To configure this module, you need to: **Note:** -- If check *Notify Reviewers on Creation*, all possible reviewers will - be notified by email when this definition is triggered. -- If check *Notify reviewers on reaching pending* if you want to send a - notification when pending status is reached. This is usefull in a - approve by sequence scenario to only notify reviewers when it is their - turn in the sequence. -- If check *Comment*, reviewers can comment after click Validate or - Reject. -- If check *Approve by sequence*, reviewers is forced to review by - specified sequence. +- If check *Notify Reviewers on Creation*, all possible reviewers will + be notified by email when this definition is triggered. +- If check *Notify reviewers on reaching pending* if you want to send a + notification when pending status is reached. This is usefull in a + approve by sequence scenario to only notify reviewers when it is + their turn in the sequence. +- If check *Comment*, reviewers can comment after click Validate or + Reject. +- If check *Approve by sequence*, reviewers is forced to review by + specified sequence. To configure Tier Validation Exceptions, you need to: @@ -94,14 +90,16 @@ To configure Tier Validation Exceptions, you need to: **Note:** -- If you don't create any exception, the Validated record will be - readonly and cannot be modified. -- If check *Write under Validation*, records will be able to be modified - only in the defined fields when the Validation process is ongoing. -- If check *Write after Validation*, records will be able to be modified - only in the defined fields when the Validation process is finished. -- If check *Write after Validation* and *Write under Validation*, - records will be able to be modified defined fields always. +- If you don't create any exception, the Validated record will be + readonly and cannot be modified. +- If check *Write under Validation*, records will be able to be + modified only in the defined fields when the Validation process is + ongoing. +- If check *Write after Validation*, records will be able to be + modified only in the defined fields when the Validation process is + finished. +- If check *Write after Validation* and *Write under Validation*, + records will be able to be modified defined fields always. Known issues / Roadmap ====================== @@ -109,25 +107,25 @@ Known issues / Roadmap This is the list of known issues for this module. Any proposal for improvement will be very valuable. -- **Issue:** +- **Issue:** - When using approve_sequence option in any tier.definition there can be - inconsistencies in the systray notifications. + When using approve_sequence option in any tier.definition there can + be inconsistencies in the systray notifications. - **Description:** + **Description:** - Field can_review in tier.review is used to filter out, in the systray - notifications, the reviews a user can approve. This can_review field - is updated **in the database** in method review_user_count, this can - make it very inconsistent for databases with a lot of users and - recurring updates that can change the expected behavior. + Field can_review in tier.review is used to filter out, in the systray + notifications, the reviews a user can approve. This can_review field + is updated **in the database** in method review_user_count, this can + make it very inconsistent for databases with a lot of users and + recurring updates that can change the expected behavior. -- **Migration to 15.0:** +- **Migration to 15.0:** - The parameter \_tier_validation_manual_config will become False, on - 14.0, the default value is True, as the change is applied after the - migration. In order to use the new behavior we need to modify the - value on our expected model. + The parameter \_tier_validation_manual_config will become False, on + 14.0, the default value is True, as the change is applied after the + migration. In order to use the new behavior we need to modify the + value on our expected model. Changelog ========= @@ -149,69 +147,69 @@ Migrated to Odoo 14. Fixes: -- When using approve_sequence option in any tier.definition there can be - inconsistencies in the systray notifications -- When using approve_sequence, still not approve only the needed - sequence, but also other sequence for the same approver +- When using approve_sequence option in any tier.definition there can + be inconsistencies in the systray notifications +- When using approve_sequence, still not approve only the needed + sequence, but also other sequence for the same approver 12.0.3.3.1 (2019-12-02) ----------------------- Fixes: -- Show comment on Reviews Table. -- Edit notification with approve_sequence. +- Show comment on Reviews Table. +- Edit notification with approve_sequence. 12.0.3.3.0 (2019-11-27) ----------------------- New features: -- Add comment on Reviews Table. -- Approve by sequence. +- Add comment on Reviews Table. +- Approve by sequence. 12.0.3.2.1 (2019-11-26) ----------------------- Fixes: -- Remove message_subscribe_users +- Remove message_subscribe_users 12.0.3.2.0 (2019-11-25) ----------------------- New features: -- Notify reviewers +- Notify reviewers 12.0.3.1.0 (2019-07-08) ----------------------- Fixes: -- Singleton error +- Singleton error 12.0.3.0.0 (2019-12-02) ----------------------- Fixes: -- Edit Reviews Table +- Edit Reviews Table 12.0.2.1.0 (2019-05-29) ----------------------- Fixes: -- Edit drop-down style width and position +- Edit drop-down style width and position 12.0.2.0.0 (2019-05-28) ----------------------- New features: -- Pass parameters as functions. -- Add Systray. +- Pass parameters as functions. +- Add Systray. 12.0.1.0.0 (2019-02-18) ----------------------- @@ -254,26 +252,26 @@ Authors Contributors ------------ -- Lois Rilo -- Naglis Jonaitis -- Adrià Gil Sorribes -- Pimolnat Suntian -- Pedro Gonzalez -- Kitti U. -- Saran Lim. -- Carlos Lopez -- Javier Colmeiro -- bosd -- Evan Soh -- Manuel Regidor -- Eduardo de Miguel -- `XCG Consulting `__: - - - Houzéfa Abbasbhay - -- Stefan Rijnhart -- Kevin Khao -- Do Anh Duy +- Lois Rilo +- Naglis Jonaitis +- Adrià Gil Sorribes +- Pimolnat Suntian +- Pedro Gonzalez +- Kitti U. +- Saran Lim. +- Carlos Lopez +- Javier Colmeiro +- bosd +- Evan Soh +- Manuel Regidor +- Eduardo de Miguel +- `XCG Consulting `__: + + - Houzéfa Abbasbhay + +- Stefan Rijnhart +- Kevin Khao +- Do Anh Duy Other credits ------------- diff --git a/base_tier_validation/__manifest__.py b/base_tier_validation/__manifest__.py index 7ed6d7e820..5e792728dd 100644 --- a/base_tier_validation/__manifest__.py +++ b/base_tier_validation/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Base Tier Validation", "summary": "Implement a validation process based on tiers.", - "version": "18.0.2.0.1", + "version": "18.0.2.1.0", "development_status": "Mature", "maintainers": ["LoisRForgeFlow"], "category": "Tools", diff --git a/base_tier_validation/migrations/18.0.2.1.0/pre-migration.py b/base_tier_validation/migrations/18.0.2.1.0/pre-migration.py new file mode 100644 index 0000000000..a19c940ebb --- /dev/null +++ b/base_tier_validation/migrations/18.0.2.1.0/pre-migration.py @@ -0,0 +1,79 @@ +# Copyright 2025 Tecnativa - Víctor Martínez +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openupgradelib import openupgrade + + +@openupgrade.migrate() +def migrate(env, version): + env.cr.execute( + """ + SELECT imf.model + FROM ir_model_fields AS imf + WHERE imf.name = 'review_ids' + AND imf.ttype = 'one2many' + AND imf.model != 'tier.validation' + """ + ) + for (model_name,) in env.cr.fetchall(): + table_name = model_name.replace(".", "_") + # validation_status column + if not openupgrade.column_exists(env.cr, table_name, "validation_status"): + openupgrade.logged_query( + env.cr, + f""" + ALTER TABLE {table_name} + ADD COLUMN IF NOT EXISTS validation_status VARCHAR + """, + ) + openupgrade.logged_query( + env.cr, + f""" + UPDATE {table_name} SET validation_status = 'no' + """, + ) + openupgrade.logged_query( + env.cr, + f""" + UPDATE {table_name} SET validation_status = 'rejected' + WHERE validation_status = 'no' AND id IN ( + SELECT DISTINCT(tr.res_id) + FROM tier_review AS tr + WHERE tr.model = '{model_name}' AND tr.status = 'rejected' + ) + """, + ) + openupgrade.logged_query( + env.cr, + f""" + UPDATE {table_name} SET validation_status = 'pending' + WHERE validation_status = 'no' AND id IN ( + SELECT DISTINCT(tr.res_id) + FROM tier_review AS tr + WHERE tr.model = '{model_name}' AND tr.status = 'pending' + ) + """, + ) + openupgrade.logged_query( + env.cr, + f""" + UPDATE {table_name} SET validation_status = 'waiting' + WHERE validation_status = 'no' AND id IN ( + SELECT DISTINCT(tr.res_id) + FROM tier_review AS tr + WHERE tr.model = '{model_name}' AND tr.status = 'waiting' + ) + """, + ) + openupgrade.logged_query( + env.cr, + f""" + UPDATE {table_name} SET validation_status = 'validated' + WHERE validation_status = 'no' AND id IN ( + SELECT DISTINCT(tr.res_id) + FROM tier_review AS tr + WHERE tr.model = '{model_name}' + AND tr.status IN ('approved', 'forwarded') + ) + """, + ) diff --git a/base_tier_validation/models/res_users.py b/base_tier_validation/models/res_users.py index 111ad76df7..8415a82e34 100644 --- a/base_tier_validation/models/res_users.py +++ b/base_tier_validation/models/res_users.py @@ -29,7 +29,7 @@ def review_user_count(self): if tier_review and hasattr(Model, "can_review"): records_domain = [ ("id", "in", tier_review.mapped("res_id")), - ("rejected", "=", False), + ("validation_status", "!=", "rejected"), ("can_review", "=", True), ] records = ( diff --git a/base_tier_validation/models/tier_validation.py b/base_tier_validation/models/tier_validation.py index 3ca8be13ea..6479bb5bff 100644 --- a/base_tier_validation/models/tier_validation.py +++ b/base_tier_validation/models/tier_validation.py @@ -40,19 +40,18 @@ class TierValidation(models.AbstractModel): domain=lambda self: [("model", "=", self._name)], auto_join=True, ) - to_validate_message = fields.Html(compute="_compute_validated_rejected") - # TODO: Delete in v17 in favor of validation_status field + # TODO: Delete in v19 in favor of validation_status field validated = fields.Boolean( compute="_compute_validated_rejected", search="_search_validated" ) - validated_message = fields.Html(compute="_compute_validated_rejected") + to_validate_message = fields.Html(compute="_compute_to_validate_message") + validated_message = fields.Html(compute="_compute_validated_message") need_validation = fields.Boolean(compute="_compute_need_validation") - # TODO: Delete in v17 in favor of validation_status field + # TODO: Delete in v19 in favor of validation_status field rejected = fields.Boolean( compute="_compute_validated_rejected", search="_search_rejected" ) - rejected_message = fields.Html(compute="_compute_validated_rejected") - # Informative field (used in purchase_tier_validation), will be reliable as of v17 + rejected_message = fields.Html(compute="_compute_rejected_message") validation_status = fields.Selection( selection=[ ("no", "Without validation"), @@ -63,6 +62,7 @@ class TierValidation(models.AbstractModel): ], default="no", compute="_compute_validation_status", + store=True, ) reviewer_ids = fields.Many2many( string="Reviewers", @@ -106,6 +106,8 @@ def _get_sequences_to_approve(self, user): sequences.append(my_sequence) return sequences + @api.depends_context("uid") + @api.depends("review_ids.status") def _compute_can_review(self): for rec in self: rec.can_review = rec._get_sequences_to_approve(self.env.user) @@ -116,7 +118,7 @@ def _search_can_review(self, operator, value): ("review_ids.reviewer_ids", "=", self.env.user.id), ("review_ids.status", "in", ["pending", "waiting"]), ("review_ids.can_review", "=", True), - ("rejected", "=", False), + ("validation_status", "!=", "rejected"), ] if "active" in self._fields: domain.append(("active", "in", [True, False])) @@ -130,29 +132,21 @@ def _compute_reviewer_ids(self): lambda r: r.status in ("waiting", "pending") ).mapped("reviewer_ids") + # TODO: delete in 19.0 migration in favor of validation_status field @api.model def _search_validated(self, operator, value): assert operator in ("=", "!="), "Invalid domain operator" assert value in (True, False), "Invalid domain value" - pos = self.search([(self._state_field, "in", self._state_from)]).filtered( - lambda r: r.validated - ) - if value: - return [("id", "in", pos.ids)] - else: - return [("id", "not in", pos.ids)] + operator_equal = (operator == "=" and value) or (operator == "!=" and not value) + return [("validation_status", operator_equal and "=" or "!=", "validated")] + # TODO: delete in 19.0 migration in favor of validation_status field @api.model def _search_rejected(self, operator, value): assert operator in ("=", "!="), "Invalid domain operator" assert value in (True, False), "Invalid domain value" - pos = self.search([(self._state_field, "in", self._state_from)]).filtered( - lambda r: r.rejected - ) - if value: - return [("id", "in", pos.ids)] - else: - return [("id", "not in", pos.ids)] + operator_equal = (operator == "=" and value) or (operator == "!=" and not value) + return [("validation_status", operator_equal and "=" or "!=", "rejected")] @api.model def _search_reviewer_ids(self, operator, value): @@ -184,39 +178,60 @@ def _get_validated_message(self): msg = f""" {self.env._( "Operation has been validated!" )}""" - return self.validated and msg or "" + return self.validation_status == "validated" and msg or "" def _get_rejected_message(self): msg = f""" {self.env._( "Operation has been rejected." )}""" - return self.rejected and msg or "" + return self.validation_status == "rejected" and msg or "" + # TODO: delete in 19.0 migration in favor of validation_status field + @api.depends("validation_status") def _compute_validated_rejected(self): for rec in self: - rec.validated = self._calc_reviews_validated(rec.review_ids) + for field in ("validated", "rejected"): + rec[field] = rec.validation_status == field + + @api.depends("validation_status") + def _compute_to_validate_message(self): + for rec in self: + rec.to_validate_message = rec._get_to_validate_message() + + def _validated_states(self): + """Override for different validation policy.""" + return ["approved"] + + @api.depends("validation_status") + def _compute_validated_message(self): + for rec in self: rec.validated_message = rec._get_validated_message() - rec.rejected = self._calc_reviews_rejected(rec.review_ids) + + def _rejected_states(self): + """Override for different rejected policy.""" + return ["rejected"] + + @api.depends("validation_status") + def _compute_rejected_message(self): + for rec in self: rec.rejected_message = rec._get_rejected_message() - rec.to_validate_message = rec._get_to_validate_message() + @api.depends("review_ids", "review_ids.status") def _compute_validation_status(self): + validated_states = self._validated_states() + rejected_states = self._rejected_states() for item in self: - if item.validated and not item.rejected: + reviews = item.review_ids + any_rejected = any(reviews.filtered(lambda x: x.status in rejected_states)) + any_pending = any(reviews.filtered(lambda x: x.status == "pending")) + any_waiting = any(item.review_ids.filtered(lambda x: x.status == "waiting")) + if reviews and all(x.status in validated_states for x in reviews): item.validation_status = "validated" - elif not item.validated and item.rejected: + elif any_rejected: item.validation_status = "rejected" - elif ( - not item.validated - and not item.rejected - and any(item.review_ids.filtered(lambda x: x.status == "pending")) - ): + elif any_pending: item.validation_status = "pending" - elif ( - not item.validated - and not item.rejected - and any(item.review_ids.filtered(lambda x: x.status == "waiting")) - ): + elif any_waiting: item.validation_status = "waiting" else: item.validation_status = "no" @@ -232,18 +247,6 @@ def _compute_hide_reviews(self): for rec in self: rec.hide_reviews = rec[self._state_field] not in self._state_from - @api.model - def _calc_reviews_validated(self, reviews): - """Override for different validation policy.""" - if not reviews: - return False - return not any([s != "approved" for s in reviews.mapped("status")]) - - @api.model - def _calc_reviews_rejected(self, reviews): - """Override for different rejection policy.""" - return any([s == "rejected" for s in reviews.mapped("status")]) - def _compute_need_validation(self): for rec in self: if isinstance(rec.id, models.NewId): @@ -408,7 +411,7 @@ def _tier_validation_check_state_on_write(self, vals): # try to validate operation reviews = rec.request_validation() rec._validate_tier(reviews) - if not self._calc_reviews_validated(reviews): + if rec.validation_status != "validated": pending_reviews = reviews.filtered( lambda r: r.status == "pending" ).mapped("name") @@ -420,7 +423,7 @@ def _tier_validation_check_state_on_write(self, vals): "\n - ".join(pending_reviews), ) ) - if rec.review_ids and not rec.validated: + if rec.review_ids and rec.validation_status != "validated": raise ValidationError( self.env._( "A validation process is still open for at least " diff --git a/base_tier_validation/static/description/index.html b/base_tier_validation/static/description/index.html index e01521e59c..866e505452 100644 --- a/base_tier_validation/static/description/index.html +++ b/base_tier_validation/static/description/index.html @@ -3,7 +3,7 @@ -README.rst +Base Tier Validation -
+
+

Base Tier Validation

- - -Odoo Community Association - -
-

Base Tier Validation

-

Mature License: AGPL-3 OCA/server-ux Translate me on Weblate Try me on Runboat

+

Mature License: AGPL-3 OCA/server-ux Translate me on Weblate Try me on Runboat

Validating some operations is a common need across different areas in a company and sometimes it also involves several people and stages in the process. With this module you will be able to define your custom @@ -426,7 +421,7 @@

Base Tier Validation

-

Configuration

+

Configuration

To configure this module, you need to:

  1. Go to Settings > Technical > Tier Validations > Tier Definition.
  2. @@ -439,8 +434,8 @@

    Configuration

    be notified by email when this definition is triggered.
  3. If check Notify reviewers on reaching pending if you want to send a notification when pending status is reached. This is usefull in a -approve by sequence scenario to only notify reviewers when it is their -turn in the sequence.
  4. +approve by sequence scenario to only notify reviewers when it is +their turn in the sequence.
  5. If check Comment, reviewers can comment after click Validate or Reject.
  6. If check Approve by sequence, reviewers is forced to review by @@ -461,22 +456,24 @@

    Configuration

    • If you don’t create any exception, the Validated record will be readonly and cannot be modified.
    • -
    • If check Write under Validation, records will be able to be modified -only in the defined fields when the Validation process is ongoing.
    • -
    • If check Write after Validation, records will be able to be modified -only in the defined fields when the Validation process is finished.
    • +
    • If check Write under Validation, records will be able to be +modified only in the defined fields when the Validation process is +ongoing.
    • +
    • If check Write after Validation, records will be able to be +modified only in the defined fields when the Validation process is +finished.
    • If check Write after Validation and Write under Validation, records will be able to be modified defined fields always.
-

Known issues / Roadmap

+

Known issues / Roadmap

This is the list of known issues for this module. Any proposal for improvement will be very valuable.

  • Issue:

    -

    When using approve_sequence option in any tier.definition there can be -inconsistencies in the systray notifications.

    +

    When using approve_sequence option in any tier.definition there can +be inconsistencies in the systray notifications.

    Description:

    Field can_review in tier.review is used to filter out, in the systray notifications, the reviews a user can approve. This can_review field @@ -493,29 +490,29 @@

    Known issues / Roadmap

-

Changelog

+

Changelog

-

17.0.1.0.0 (2024-01-10)

+

17.0.1.0.0 (2024-01-10)

Migrated to Odoo 17. Merged module with tier_validation_waiting. To support sending messages in a validation sequence when it is their turn to validate.

-

13.0.1.2.2 (2020-08-30)

+

13.0.1.2.2 (2020-08-30)

Fixes:

    -
  • When using approve_sequence option in any tier.definition there can be -inconsistencies in the systray notifications
  • +
  • When using approve_sequence option in any tier.definition there can +be inconsistencies in the systray notifications
  • When using approve_sequence, still not approve only the needed sequence, but also other sequence for the same approver
-

12.0.3.3.1 (2019-12-02)

+

12.0.3.3.1 (2019-12-02)

Fixes:

-

12.0.3.3.0 (2019-11-27)

+

12.0.3.3.0 (2019-11-27)

New features:

-

12.0.3.2.1 (2019-11-26)

+

12.0.3.2.1 (2019-11-26)

Fixes:

  • Remove message_subscribe_users
-

12.0.3.2.0 (2019-11-25)

+

12.0.3.2.0 (2019-11-25)

New features:

  • Notify reviewers
-

12.0.3.0.0 (2019-12-02)

+

12.0.3.0.0 (2019-12-02)

Fixes:

  • Edit Reviews Table
-

12.0.2.1.0 (2019-05-29)

+

12.0.2.1.0 (2019-05-29)

Fixes:

  • Edit drop-down style width and position
-

12.0.2.0.0 (2019-05-28)

+

12.0.2.0.0 (2019-05-28)

New features:

-

Bug Tracker

+

Bug Tracker

Bugs are tracked on GitHub 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 @@ -599,15 +596,15 @@

Bug Tracker

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

-

Credits

+

Credits

-

Authors

+

Authors

  • ForgeFlow
-

Contributors

+

Contributors

-

Other credits

+

Other credits

The migration of this module from 17.0 to 18.0 was financially supported by Camptocamp.

-

Maintainers

+

Maintainers

This module is maintained by the OCA.

Odoo Community Association @@ -652,6 +649,5 @@

Maintainers

-
diff --git a/base_tier_validation/templates/tier_validation_templates.xml b/base_tier_validation/templates/tier_validation_templates.xml index 92d4f8ff09..b63d94ddca 100644 --- a/base_tier_validation/templates/tier_validation_templates.xml +++ b/base_tier_validation/templates/tier_validation_templates.xml @@ -6,7 +6,7 @@