From ac4b565b8a87771995a82a9b10ba128f57a02428 Mon Sep 17 00:00:00 2001 From: ps-tubtim Date: Tue, 8 Oct 2019 15:02:11 +0700 Subject: [PATCH 01/47] [12.0][ADD] hr_expense_tier_validation --- hr_expense_tier_validation/README.rst | 106 ++++ hr_expense_tier_validation/__init__.py | 4 + hr_expense_tier_validation/__manifest__.py | 21 + hr_expense_tier_validation/models/__init__.py | 5 + .../models/hr_expense_sheet.py | 11 + .../models/tier_definition.py | 14 + .../readme/CONFIGURE.rst | 4 + .../readme/CONTRIBUTORS.rst | 1 + .../readme/DESCRIPTION.rst | 1 + hr_expense_tier_validation/readme/INSTALL.rst | 2 + hr_expense_tier_validation/readme/USAGE.rst | 14 + .../static/description/icon.png | Bin 0 -> 9455 bytes .../static/description/index.html | 454 ++++++++++++++++++ hr_expense_tier_validation/tests/__init__.py | 4 + .../tests/test_hr_expense_tier_validation.py | 14 + .../views/hr_expense_sheet_view.xml | 83 ++++ 16 files changed, 738 insertions(+) create mode 100644 hr_expense_tier_validation/README.rst create mode 100644 hr_expense_tier_validation/__init__.py create mode 100644 hr_expense_tier_validation/__manifest__.py create mode 100644 hr_expense_tier_validation/models/__init__.py create mode 100644 hr_expense_tier_validation/models/hr_expense_sheet.py create mode 100644 hr_expense_tier_validation/models/tier_definition.py create mode 100644 hr_expense_tier_validation/readme/CONFIGURE.rst create mode 100644 hr_expense_tier_validation/readme/CONTRIBUTORS.rst create mode 100644 hr_expense_tier_validation/readme/DESCRIPTION.rst create mode 100644 hr_expense_tier_validation/readme/INSTALL.rst create mode 100644 hr_expense_tier_validation/readme/USAGE.rst create mode 100644 hr_expense_tier_validation/static/description/icon.png create mode 100644 hr_expense_tier_validation/static/description/index.html create mode 100644 hr_expense_tier_validation/tests/__init__.py create mode 100644 hr_expense_tier_validation/tests/test_hr_expense_tier_validation.py create mode 100644 hr_expense_tier_validation/views/hr_expense_sheet_view.xml diff --git a/hr_expense_tier_validation/README.rst b/hr_expense_tier_validation/README.rst new file mode 100644 index 000000000..93272bc9f --- /dev/null +++ b/hr_expense_tier_validation/README.rst @@ -0,0 +1,106 @@ +======================= +Expense Tier Validation +======================= + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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-OCA%2Fhr-lightgray.png?logo=github + :target: https://github.com/OCA/hr/tree/12.0/hr_expense_tier_validation + :alt: OCA/hr +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/hr-12-0/hr-12-0-hr_expense_tier_validation + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/116/12.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module extends the functionality of Expense Reports to support a tier +validation process. + +**Table of contents** + +.. contents:: + :local: + +Installation +============ + +This module depends on ``base_tier_validation``. +You can find it at `OCA/server-ux ` + +Configuration +============= + +To configure this module, you need to: + +#. Go to *Settings > Technical > Tier Validations > Tier Definition*. +#. Create as many tiers as you want for hr_expense model. + +Usage +===== + +To use this module, you need to: + +#. Create a Expense Report triggering at least one "Tier Definition". +#. Click on *Request Validation* button. +#. Under the tab *Reviews* have a look to pending reviews and their statuses. +#. Once all reviews are validated click on *Confirm Order*. + +Additional features: + +* You can filter the Expense Reports requesting your review through the + filter *Needs my Review*. +* User with rights to confirm the Expense Report (validate all tiers that would + be generated) can directly do the operation, this is, there is no need for + her/him to request a validation. + +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 smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Ecosoft + +Contributors +~~~~~~~~~~~~ + +* Pimolnat Suntian + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/hr `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/hr_expense_tier_validation/__init__.py b/hr_expense_tier_validation/__init__.py new file mode 100644 index 000000000..4fb530fd1 --- /dev/null +++ b/hr_expense_tier_validation/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2019 Ecosoft Co., Ltd. (http://ecosoft.co.th) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import models diff --git a/hr_expense_tier_validation/__manifest__.py b/hr_expense_tier_validation/__manifest__.py new file mode 100644 index 000000000..9b815d1bf --- /dev/null +++ b/hr_expense_tier_validation/__manifest__.py @@ -0,0 +1,21 @@ +# Copyright 2019 Ecosoft Co., Ltd. (http://ecosoft.co.th) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + 'name': 'Expense Tier Validation', + 'version': '12.0.1.0.0', + 'category': 'Human Resources', + 'author': 'Ecosoft, ' + 'Odoo Community Association (OCA)', + 'license': 'AGPL-3', + 'website': 'https://github.com/OCA/hr', + 'depends': [ + 'hr_expense', + 'base_tier_validation', + ], + 'data': [ + 'views/hr_expense_sheet_view.xml', + ], + 'installable': True, + 'maintainers': ['ps-tubtim'], +} diff --git a/hr_expense_tier_validation/models/__init__.py b/hr_expense_tier_validation/models/__init__.py new file mode 100644 index 000000000..89b01d5da --- /dev/null +++ b/hr_expense_tier_validation/models/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2019 Ecosoft Co., Ltd. (http://ecosoft.co.th) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import hr_expense_sheet +from . import tier_definition diff --git a/hr_expense_tier_validation/models/hr_expense_sheet.py b/hr_expense_tier_validation/models/hr_expense_sheet.py new file mode 100644 index 000000000..bfdfce545 --- /dev/null +++ b/hr_expense_tier_validation/models/hr_expense_sheet.py @@ -0,0 +1,11 @@ +# Copyright 2019 Ecosoft Co., Ltd. (http://ecosoft.co.th) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import models + + +class HrExpenseSheet(models.Model): + _name = 'hr.expense.sheet' + _inherit = ['hr.expense.sheet', 'tier.validation'] + _state_from = ['draft'] + _state_to = ['submit', 'approve', 'post', 'done'] diff --git a/hr_expense_tier_validation/models/tier_definition.py b/hr_expense_tier_validation/models/tier_definition.py new file mode 100644 index 000000000..31113cd34 --- /dev/null +++ b/hr_expense_tier_validation/models/tier_definition.py @@ -0,0 +1,14 @@ +# Copyright 2019 Ecosoft Co., Ltd. (http://ecosoft.co.th) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, models + + +class TierDefinition(models.Model): + _inherit = 'tier.definition' + + @api.model + def _get_tier_validation_model_names(self): + res = super(TierDefinition, self)._get_tier_validation_model_names() + res.append('hr.expense.sheet') + return res diff --git a/hr_expense_tier_validation/readme/CONFIGURE.rst b/hr_expense_tier_validation/readme/CONFIGURE.rst new file mode 100644 index 000000000..d09a3e0bb --- /dev/null +++ b/hr_expense_tier_validation/readme/CONFIGURE.rst @@ -0,0 +1,4 @@ +To configure this module, you need to: + +#. Go to *Settings > Technical > Tier Validations > Tier Definition*. +#. Create as many tiers as you want for hr_expense model. diff --git a/hr_expense_tier_validation/readme/CONTRIBUTORS.rst b/hr_expense_tier_validation/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..0ef1f84c3 --- /dev/null +++ b/hr_expense_tier_validation/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Pimolnat Suntian diff --git a/hr_expense_tier_validation/readme/DESCRIPTION.rst b/hr_expense_tier_validation/readme/DESCRIPTION.rst new file mode 100644 index 000000000..59623d7bb --- /dev/null +++ b/hr_expense_tier_validation/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +This module extends the functionality of Expense Reports to support a tier validation process. diff --git a/hr_expense_tier_validation/readme/INSTALL.rst b/hr_expense_tier_validation/readme/INSTALL.rst new file mode 100644 index 000000000..1a40eb744 --- /dev/null +++ b/hr_expense_tier_validation/readme/INSTALL.rst @@ -0,0 +1,2 @@ +This module depends on ``base_tier_validation``. +You can find it at `OCA/server-ux ` diff --git a/hr_expense_tier_validation/readme/USAGE.rst b/hr_expense_tier_validation/readme/USAGE.rst new file mode 100644 index 000000000..fc812f6e9 --- /dev/null +++ b/hr_expense_tier_validation/readme/USAGE.rst @@ -0,0 +1,14 @@ +To use this module, you need to: + +#. Create a Expense Report triggering at least one "Tier Definition". +#. Click on *Request Validation* button. +#. Under the tab *Reviews* have a look to pending reviews and their statuses. +#. Once all reviews are validated click on *Confirm Order*. + +Additional features: + +* You can filter the Expense Reports requesting your review through the + filter *Needs my Review*. +* User with rights to confirm the Expense Report (validate all tiers that would + be generated) can directly do the operation, this is, there is no need for + her/him to request a validation. diff --git a/hr_expense_tier_validation/static/description/icon.png b/hr_expense_tier_validation/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 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 z 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 Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 diff --git a/hr_expense_tier_validation/static/description/index.html b/hr_expense_tier_validation/static/description/index.html new file mode 100644 index 000000000..80e8302a3 --- /dev/null +++ b/hr_expense_tier_validation/static/description/index.html @@ -0,0 +1,454 @@ + + + + + + +Expense Tier Validation + + + +
+

Expense Tier Validation

+ + +

Beta License: AGPL-3 OCA/hr Translate me on Weblate Try me on Runbot

+

This module extends the functionality of Expense Reports to support a tier +validation process.

+

Table of contents

+ +
+

Installation

+

This module depends on base_tier_validation. +You can find it at OCA/server-ux <https://github.com/OCA/server-ux>

+
+
+

Configuration

+

To configure this module, you need to:

+
    +
  1. Go to Settings > Technical > Tier Validations > Tier Definition.
  2. +
  3. Create as many tiers as you want for hr_expense model.
  4. +
+
+
+

Usage

+

To use this module, you need to:

+
    +
  1. Create a Expense Report triggering at least one “Tier Definition”.
  2. +
  3. Click on Request Validation button.
  4. +
  5. Under the tab Reviews have a look to pending reviews and their statuses.
  6. +
  7. Once all reviews are validated click on Confirm Order.
  8. +
+

Additional features:

+
    +
  • You can filter the Expense Reports requesting your review through the +filter Needs my Review.
  • +
  • User with rights to confirm the Expense Report (validate all tiers that would +be generated) can directly do the operation, this is, there is no need for +her/him to request a validation.
  • +
+
+
+

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 smashing it by providing a detailed and welcomed +feedback.

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • Ecosoft
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/hr project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/hr_expense_tier_validation/tests/__init__.py b/hr_expense_tier_validation/tests/__init__.py new file mode 100644 index 000000000..85b34ee31 --- /dev/null +++ b/hr_expense_tier_validation/tests/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2019 Ecosoft Co., Ltd. (http://ecosoft.co.th) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import test_hr_expense_tier_validation diff --git a/hr_expense_tier_validation/tests/test_hr_expense_tier_validation.py b/hr_expense_tier_validation/tests/test_hr_expense_tier_validation.py new file mode 100644 index 000000000..13a469c2c --- /dev/null +++ b/hr_expense_tier_validation/tests/test_hr_expense_tier_validation.py @@ -0,0 +1,14 @@ +# Copyright 2019 Ecosoft Co., Ltd. (http://ecosoft.co.th) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.tests import common + + +class TestHrExpenseTierValidation(common.TransactionCase): + def setUp(self): + super(TestHrExpenseTierValidation, self).setUp() + self.tier_definition = self.env['tier.definition'] + + def test_get_tier_validation_model_names(self): + self.assertIn('hr.expense.sheet', + self.tier_definition._get_tier_validation_model_names()) diff --git a/hr_expense_tier_validation/views/hr_expense_sheet_view.xml b/hr_expense_tier_validation/views/hr_expense_sheet_view.xml new file mode 100644 index 000000000..86f257368 --- /dev/null +++ b/hr_expense_tier_validation/views/hr_expense_sheet_view.xml @@ -0,0 +1,83 @@ + + + + + hr.expense.sheet.form + hr.expense.sheet + + + +
+ + + + + + +
+ + + +
+
+ + + hr.expense.sheet.filter - hr_expense_tier_validation + hr.expense.sheet + + + + + + + + + + +
From 850efac9010825eb95f84e0201a4a925b34a6958 Mon Sep 17 00:00:00 2001 From: oca-travis Date: Fri, 25 Oct 2019 14:21:49 +0000 Subject: [PATCH 02/47] [UPD] Update hr_expense_tier_validation.pot --- .../i18n/hr_expense_tier_validation.pot | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 hr_expense_tier_validation/i18n/hr_expense_tier_validation.pot diff --git a/hr_expense_tier_validation/i18n/hr_expense_tier_validation.pot b/hr_expense_tier_validation/i18n/hr_expense_tier_validation.pot new file mode 100644 index 000000000..35ce4438c --- /dev/null +++ b/hr_expense_tier_validation/i18n/hr_expense_tier_validation.pot @@ -0,0 +1,81 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * hr_expense_tier_validation +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: hr_expense_tier_validation +#: model_terms:ir.ui.view,arch_db:hr_expense_tier_validation.view_hr_expense_sheet_form +msgid "\n" +" This expense report needs to be validated." +msgstr "" + +#. module: hr_expense_tier_validation +#: model_terms:ir.ui.view,arch_db:hr_expense_tier_validation.view_hr_expense_sheet_form +msgid " Operation has been rejected." +msgstr "" + +#. module: hr_expense_tier_validation +#: model_terms:ir.ui.view,arch_db:hr_expense_tier_validation.view_hr_expense_sheet_form +msgid " Operation has been validated!" +msgstr "" + +#. module: hr_expense_tier_validation +#: model:ir.model,name:hr_expense_tier_validation.model_hr_expense_sheet +msgid "Expense Report" +msgstr "" + +#. module: hr_expense_tier_validation +#: model_terms:ir.ui.view,arch_db:hr_expense_tier_validation.view_hr_expense_sheet_filter +msgid "Expense report validated and ready to be approved" +msgstr "" + +#. module: hr_expense_tier_validation +#: model_terms:ir.ui.view,arch_db:hr_expense_tier_validation.view_hr_expense_sheet_filter +msgid "My Expense report to review" +msgstr "" + +#. module: hr_expense_tier_validation +#: model_terms:ir.ui.view,arch_db:hr_expense_tier_validation.view_hr_expense_sheet_filter +msgid "Needs my Review" +msgstr "" + +#. module: hr_expense_tier_validation +#: model_terms:ir.ui.view,arch_db:hr_expense_tier_validation.view_hr_expense_sheet_form +msgid "Reject" +msgstr "" + +#. module: hr_expense_tier_validation +#: model_terms:ir.ui.view,arch_db:hr_expense_tier_validation.view_hr_expense_sheet_form +msgid "Request Validation" +msgstr "" + +#. module: hr_expense_tier_validation +#: model_terms:ir.ui.view,arch_db:hr_expense_tier_validation.view_hr_expense_sheet_form +msgid "Restart Validation" +msgstr "" + +#. module: hr_expense_tier_validation +#: model:ir.model,name:hr_expense_tier_validation.model_tier_definition +msgid "Tier Definition" +msgstr "" + +#. module: hr_expense_tier_validation +#: model_terms:ir.ui.view,arch_db:hr_expense_tier_validation.view_hr_expense_sheet_form +msgid "Validate" +msgstr "" + +#. module: hr_expense_tier_validation +#: model_terms:ir.ui.view,arch_db:hr_expense_tier_validation.view_hr_expense_sheet_filter +msgid "Validated" +msgstr "" + From 810ba077bfd721572a4df4957e9b21211f0c499a Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Tue, 29 Oct 2019 08:47:17 +0000 Subject: [PATCH 03/47] [UPD] README.rst --- hr_expense_tier_validation/README.rst | 11 +++++++++-- .../static/description/index.html | 7 ++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/hr_expense_tier_validation/README.rst b/hr_expense_tier_validation/README.rst index 93272bc9f..d4d5b56ec 100644 --- a/hr_expense_tier_validation/README.rst +++ b/hr_expense_tier_validation/README.rst @@ -25,8 +25,7 @@ Expense Tier Validation |badge1| |badge2| |badge3| |badge4| |badge5| -This module extends the functionality of Expense Reports to support a tier -validation process. +This module extends the functionality of Expense Reports to support a tier validation process. **Table of contents** @@ -101,6 +100,14 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use. +.. |maintainer-ps-tubtim| image:: https://github.com/ps-tubtim.png?size=40px + :target: https://github.com/ps-tubtim + :alt: ps-tubtim + +Current `maintainer `__: + +|maintainer-ps-tubtim| + This module is part of the `OCA/hr `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/hr_expense_tier_validation/static/description/index.html b/hr_expense_tier_validation/static/description/index.html index 80e8302a3..432a6895f 100644 --- a/hr_expense_tier_validation/static/description/index.html +++ b/hr_expense_tier_validation/static/description/index.html @@ -3,7 +3,7 @@ - + Expense Tier Validation -
-

Expense Tier Validation

+
+ + +Odoo Community Association + +
+

Expense Tier Validation

-

Beta License: AGPL-3 OCA/hr-expense Translate me on Weblate Try me on Runboat

+

Beta License: AGPL-3 OCA/hr-expense Translate me on Weblate Try me on Runboat

This module extends the functionality of Expense Reports to support a tier validation process.

Table of contents

@@ -387,7 +392,7 @@

Expense Tier Validation

-

Configuration

+

Configuration

To configure tier validation for HR expenses:

  1. Go to Settings > Technical > Tier Validations > Tier Definition
  2. @@ -402,7 +407,7 @@

    Configuration

-

Usage

+

Usage

To use this module, you need to:

  1. Create a Expense Report triggering at least one “Tier Definition”.
  2. @@ -421,30 +426,30 @@

    Usage

-

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 -feedback.

+feedback.

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

-

Credits

+

Credits

-

Authors

+

Authors

  • Ecosoft
-

Maintainers

+

Maintainers

This module is maintained by the OCA.

Odoo Community Association @@ -454,10 +459,11 @@

Maintainers

promote its widespread use.

Current maintainer:

ps-tubtim

-

This module is part of the OCA/hr-expense project on GitHub.

+

This module is part of the OCA/hr-expense project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
From e65461993fcd69a11289bd200c63d43d152cbf67 Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Sat, 30 May 2026 10:57:05 -0400 Subject: [PATCH 46/47] [IMP] hr_expense_tier_validation: pre-commit auto fixes --- hr_expense_tier_validation/__manifest__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hr_expense_tier_validation/__manifest__.py b/hr_expense_tier_validation/__manifest__.py index 3b1a81c8b..d076a0090 100644 --- a/hr_expense_tier_validation/__manifest__.py +++ b/hr_expense_tier_validation/__manifest__.py @@ -3,13 +3,13 @@ { "name": "Expense Tier Validation", - "version": "18.0.1.0.0", + "version": "19.0.1.0.0", "category": "Human Resources", "author": "Ecosoft, Odoo Community Association (OCA)", "license": "AGPL-3", "website": "https://github.com/OCA/hr-expense", "depends": ["hr_expense", "base_tier_validation"], - "data": ["data/ir_config_parameter.xml", "views/hr_expense_sheet_view.xml"], + "data": ["data/ir_config_parameter.xml", "views/hr_expense_views.xml"], "installable": True, "maintainers": ["ps-tubtim"], } From bfa850e7a4df4f1f3dba13693df5220ab73ac62c Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Sat, 30 May 2026 10:57:05 -0400 Subject: [PATCH 47/47] [MIG] hr_expense_tier_validation: Migration to 19.0 hr.expense.sheet was removed in 19.0; retarget tier validation to hr.expense. --- hr_expense_tier_validation/models/__init__.py | 1 - .../models/hr_expense.py | 21 +++++---- .../models/hr_expense_sheet.py | 15 ------ .../models/tier_definition.py | 2 +- .../tests/test_hr_expense_tier_validation.py | 46 ++++++++----------- .../views/hr_expense_sheet_view.xml | 27 ----------- .../views/hr_expense_views.xml | 29 ++++++++++++ 7 files changed, 63 insertions(+), 78 deletions(-) delete mode 100644 hr_expense_tier_validation/models/hr_expense_sheet.py delete mode 100644 hr_expense_tier_validation/views/hr_expense_sheet_view.xml create mode 100644 hr_expense_tier_validation/views/hr_expense_views.xml diff --git a/hr_expense_tier_validation/models/__init__.py b/hr_expense_tier_validation/models/__init__.py index fb65dd373..62536aa30 100644 --- a/hr_expense_tier_validation/models/__init__.py +++ b/hr_expense_tier_validation/models/__init__.py @@ -1,5 +1,4 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from . import hr_expense_sheet from . import hr_expense from . import tier_definition diff --git a/hr_expense_tier_validation/models/hr_expense.py b/hr_expense_tier_validation/models/hr_expense.py index 3bd97d38e..9d9a063ef 100644 --- a/hr_expense_tier_validation/models/hr_expense.py +++ b/hr_expense_tier_validation/models/hr_expense.py @@ -3,12 +3,19 @@ import ast -from odoo import _, api, models +from odoo import api, models from odoo.exceptions import ValidationError class HrExpense(models.Model): - _inherit = "hr.expense" + _name = "hr.expense" + _inherit = ["hr.expense", "tier.validation"] + + _state_field = "approval_state" + _state_from = ["submitted"] + _state_to = ["approved"] + + _tier_validation_manual_config = False @api.model def _get_under_validation_exceptions(self): @@ -36,13 +43,11 @@ def _check_allow_write_under_validation(self, vals): def write(self, vals): for rec in self: - sheet = rec.sheet_id if ( - sheet.state == "submit" - and sheet.review_ids - and not sheet.validated - and not sheet.rejected + rec.approval_state == "submitted" + and rec.review_ids + and rec.validation_status not in ("validated", "rejected") and not rec._check_allow_write_under_validation(vals) ): - raise ValidationError(_("The expense report is under validation.")) + raise ValidationError(self.env._("The expense is under validation.")) return super().write(vals) diff --git a/hr_expense_tier_validation/models/hr_expense_sheet.py b/hr_expense_tier_validation/models/hr_expense_sheet.py deleted file mode 100644 index e801a5f6a..000000000 --- a/hr_expense_tier_validation/models/hr_expense_sheet.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright 2019 Ecosoft Co., Ltd. (http://ecosoft.co.th) -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from odoo import models - - -class HrExpenseSheet(models.Model): - _name = "hr.expense.sheet" - _inherit = ["hr.expense.sheet", "tier.validation"] - - _state_field = "approval_state" - _state_from = ["submit"] - _state_to = ["approve", "post", "done"] - - _tier_validation_manual_config = False diff --git a/hr_expense_tier_validation/models/tier_definition.py b/hr_expense_tier_validation/models/tier_definition.py index 6bdb77749..85145216e 100644 --- a/hr_expense_tier_validation/models/tier_definition.py +++ b/hr_expense_tier_validation/models/tier_definition.py @@ -10,5 +10,5 @@ class TierDefinition(models.Model): @api.model def _get_tier_validation_model_names(self): res = super()._get_tier_validation_model_names() - res.append("hr.expense.sheet") + res.append("hr.expense") return res diff --git a/hr_expense_tier_validation/tests/test_hr_expense_tier_validation.py b/hr_expense_tier_validation/tests/test_hr_expense_tier_validation.py index 63fa76ab5..218ed122c 100644 --- a/hr_expense_tier_validation/tests/test_hr_expense_tier_validation.py +++ b/hr_expense_tier_validation/tests/test_hr_expense_tier_validation.py @@ -17,7 +17,7 @@ def setUpClass(cls): # Create tier validation cls.tier_def_obj.create( { - "model_id": cls.env.ref("hr_expense.model_hr_expense_sheet").id, + "model_id": cls.env.ref("hr_expense.model_hr_expense").id, "review_type": "individual", "reviewer_id": cls.expense_user_manager.id, } @@ -39,7 +39,7 @@ def _create_expense( def test_get_tier_validation_model_names(self): self.assertIn( - "hr.expense.sheet", self.tier_def_obj._get_tier_validation_model_names() + "hr.expense", self.tier_def_obj._get_tier_validation_model_names() ) def test_edit_value_expense(self): @@ -48,36 +48,30 @@ def test_edit_value_expense(self): self.expense_employee, self.product_a, ) - sheet_dict = expense.action_submit_expenses() + self.assertEqual(expense.state, "draft") + expense.action_submit() + self.assertEqual(expense.state, "submitted") - with Form(self.env["hr.expense.sheet"]) as sheet: - sheet.name = (sheet_dict["name"],) - sheet.employee_id = self.expense_employee - sheet = sheet.save() - sheet.expense_line_ids = [(6, 0, expense.id)] - self.assertEqual(sheet.state, "draft") - sheet.action_submit_sheet() - self.assertEqual(sheet.state, "submit") # Must request validation before approve with self.assertRaises(ValidationError): - sheet.action_approve_expense_sheets() - sheet.request_validation() - self.assertTrue(sheet) - sheet.invalidate_model() - # tier validation but state still submit - self.assertEqual(sheet.state, "submit") + expense.action_approve() + + expense.request_validation() + self.assertTrue(expense) + expense.invalidate_model() + + # tier validation but state still submitted + self.assertEqual(expense.state, "submitted") + # not allow edit expense when under validation + # Use ORM write directly: in 19.0 `name` is view-readonly on submitted + # expenses, so a Form()-based test fails at the view layer before + # reaching the model-level guard. with self.assertRaises(ValidationError): - sheet.review_ids = [(6, 0, self.expense_user_manager.ids)] - with self.assertRaises(ValidationError): - with Form(expense) as exp: - exp.name = "Change name" + expense.write({"name": "Change name"}) + # test change field exception in tier, it should allow edit self.env["ir.config_parameter"].sudo().set_param( "hr_expense.tier_exceptions", "['name']" ) - with Form(expense) as exp: - exp.name = "Change name" - - message = expense._message_subscribe(self.partner_a.ids) - self.assertTrue(message, True) + expense.write({"name": "Change name"}) diff --git a/hr_expense_tier_validation/views/hr_expense_sheet_view.xml b/hr_expense_tier_validation/views/hr_expense_sheet_view.xml deleted file mode 100644 index a1a773d2e..000000000 --- a/hr_expense_tier_validation/views/hr_expense_sheet_view.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - hr.expense.sheet.view.search - hr_expense_tier_validation - hr.expense.sheet - - - - - - - - - - diff --git a/hr_expense_tier_validation/views/hr_expense_views.xml b/hr_expense_tier_validation/views/hr_expense_views.xml new file mode 100644 index 000000000..2b84d606e --- /dev/null +++ b/hr_expense_tier_validation/views/hr_expense_views.xml @@ -0,0 +1,29 @@ + + + + + hr.expense.view.search - hr_expense_tier_validation + hr.expense + + + + + + + + + +