From e9f8a064cfee535f03e8d542a2c697e4fb577a21 Mon Sep 17 00:00:00 2001 From: Matthias Barkat Date: Thu, 12 Dec 2024 17:37:42 +0100 Subject: [PATCH] [ADD] add new module contract_exception --- contract_exception/__init__.py | 1 + contract_exception/__manifest__.py | 22 +++++ .../data/contract_exception_data.xml | 16 ++++ contract_exception/models/__init__.py | 5 ++ contract_exception/models/contract.py | 50 +++++++++++ contract_exception/models/contract_line.py | 25 ++++++ contract_exception/models/exception_rule.py | 22 +++++ .../security/ir.model.access.csv | 2 + contract_exception/tests/__init__.py | 1 + contract_exception/tests/test_contract.py | 67 +++++++++++++++ contract_exception/views/contract_views.xml | 84 +++++++++++++++++++ contract_exception/wizard/__init__.py | 1 + .../wizard/contract_exception_confirm.py | 23 +++++ .../contract_exception_confirm_view.xml | 43 ++++++++++ .../contract_manually_single_invoice.py | 18 ++++ oca_dependencies.txt | 1 + .../odoo/addons/contract_exception | 1 + setup/contract_exception/setup.py | 6 ++ test-requirements.txt | 2 + 19 files changed, 390 insertions(+) create mode 100644 contract_exception/__init__.py create mode 100644 contract_exception/__manifest__.py create mode 100644 contract_exception/data/contract_exception_data.xml create mode 100644 contract_exception/models/__init__.py create mode 100644 contract_exception/models/contract.py create mode 100644 contract_exception/models/contract_line.py create mode 100644 contract_exception/models/exception_rule.py create mode 100644 contract_exception/security/ir.model.access.csv create mode 100644 contract_exception/tests/__init__.py create mode 100644 contract_exception/tests/test_contract.py create mode 100644 contract_exception/views/contract_views.xml create mode 100644 contract_exception/wizard/__init__.py create mode 100644 contract_exception/wizard/contract_exception_confirm.py create mode 100644 contract_exception/wizard/contract_exception_confirm_view.xml create mode 100644 contract_exception/wizard/contract_manually_single_invoice.py create mode 120000 setup/contract_exception/odoo/addons/contract_exception create mode 100644 setup/contract_exception/setup.py diff --git a/contract_exception/__init__.py b/contract_exception/__init__.py new file mode 100644 index 0000000000..b046ff82fc --- /dev/null +++ b/contract_exception/__init__.py @@ -0,0 +1 @@ +from . import models, wizard diff --git a/contract_exception/__manifest__.py b/contract_exception/__manifest__.py new file mode 100644 index 0000000000..4a856a513e --- /dev/null +++ b/contract_exception/__manifest__.py @@ -0,0 +1,22 @@ +# Copyright 2024 Foodles (http://www.foodles.co/) +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html +{ + "name": "Contract Exception", + "version": "14.0.1.0.0", + "category": "Contract Management", + "author": "Odoo Community Association (OCA), Foodles", + "maintainers": [""], + "website": "https://github.com/OCA/contract", + "depends": [ + "base_exception", + "contract", + ], + "data": [ + "security/ir.model.access.csv", + "data/contract_exception_data.xml", + "views/contract_views.xml", + "wizard/contract_exception_confirm_view.xml", + ], + "license": "AGPL-3", + "installable": True, +} diff --git a/contract_exception/data/contract_exception_data.xml b/contract_exception/data/contract_exception_data.xml new file mode 100644 index 0000000000..1031ca2d41 --- /dev/null +++ b/contract_exception/data/contract_exception_data.xml @@ -0,0 +1,16 @@ + + + + + Test Contracts + + + 20 + minutes + -1 + + + code + model.test_all_contracts() + + diff --git a/contract_exception/models/__init__.py b/contract_exception/models/__init__.py new file mode 100644 index 0000000000..0c523d8527 --- /dev/null +++ b/contract_exception/models/__init__.py @@ -0,0 +1,5 @@ +from . import ( + contract, + contract_line, + exception_rule, +) diff --git a/contract_exception/models/contract.py b/contract_exception/models/contract.py new file mode 100644 index 0000000000..50eadae7a4 --- /dev/null +++ b/contract_exception/models/contract.py @@ -0,0 +1,50 @@ +# Copyright 2024 Foodles (https://www.foodles.co/). +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import api, models + + +class Contract(models.Model): + _inherit = ["contract.contract", "base.exception"] + _name = "contract.contract" + + @api.model + def create(self, vals): + record = super().create(vals) + record._contract_check_exception(vals) + return record + + def write(self, vals): + result = super().write(vals) + self._contract_check_exception(vals) + return result + + @api.model + def _reverse_field(self): + return "contract_ids" + + def _fields_trigger_check_exception(self): + return ["ignore_exception", "contract_line_ids"] + + def detect_exceptions(self): + all_exceptions = super().detect_exceptions() + lines = self.mapped("contract_line_ids") + all_exceptions += lines.detect_exceptions() + return all_exceptions + + def _contract_check_exception(self, vals): + check_exceptions = any( + field in vals for field in self._fields_trigger_check_exception() + ) + if check_exceptions: + self.detect_exceptions() + + @api.model + def test_all_contracts(self): + contract_set = self.search([]) + contract_set.detect_exceptions() + return True + + @api.model + def _get_popup_action(self): + return self.env.ref("contract_exception.action_contract_exception_confirm") diff --git a/contract_exception/models/contract_line.py b/contract_exception/models/contract_line.py new file mode 100644 index 0000000000..e0e65f7ccc --- /dev/null +++ b/contract_exception/models/contract_line.py @@ -0,0 +1,25 @@ +# Copyright 2024 Foodles (https://www.foodles.co/). +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + + +from odoo import api, fields, models + + +class ContractLine(models.Model): + _inherit = ["contract.line", "base.exception.method"] + _name = "contract.line" + + ignore_exception = fields.Boolean( + related="contract_id.ignore_exception", store=True, string="Ignore Exceptions" + ) + + def _get_main_records(self): + return self.mapped("contract_id") + + @api.model + def _reverse_field(self): + return "contract_ids" + + def _detect_exceptions(self, rule): + records = super()._detect_exceptions(rule) + return records.mapped("contract_id") diff --git a/contract_exception/models/exception_rule.py b/contract_exception/models/exception_rule.py new file mode 100644 index 0000000000..4509a4e31b --- /dev/null +++ b/contract_exception/models/exception_rule.py @@ -0,0 +1,22 @@ +# Copyright 2024 Foodles (https://www.foodles.co/). +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class ExceptionRule(models.Model): + _inherit = "exception.rule" + + contract_ids = fields.Many2many( + comodel_name="contract.contract", string="Contracts" + ) + model = fields.Selection( + selection_add=[ + ("contract.contract", "Contract"), + ("contract.line", "Contract line"), + ], + ondelete={ + "contract.contract": "cascade", + "contract.line": "cascade", + }, + ) diff --git a/contract_exception/security/ir.model.access.csv b/contract_exception/security/ir.model.access.csv new file mode 100644 index 0000000000..e2598f5721 --- /dev/null +++ b/contract_exception/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_contract_exception_confirm,contract.exception.confirm,model_contract_exception_confirm,base_exception.group_exception_rule_manager,1,1,1,1 diff --git a/contract_exception/tests/__init__.py b/contract_exception/tests/__init__.py new file mode 100644 index 0000000000..3c02082d69 --- /dev/null +++ b/contract_exception/tests/__init__.py @@ -0,0 +1 @@ +from . import test_contract diff --git a/contract_exception/tests/test_contract.py b/contract_exception/tests/test_contract.py new file mode 100644 index 0000000000..e221e45a7b --- /dev/null +++ b/contract_exception/tests/test_contract.py @@ -0,0 +1,67 @@ +# Copyright 2024 Foodles (https://www.foodles.co/). +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from unittest.mock import patch + +from odoo.tests import SavepointCase + + +class TestContract(SavepointCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.customer = cls.env["res.partner"].create({"name": "Test Customer"}) + cls.contract = cls.env["contract.contract"].create( + {"name": "Test Contract", "partner_id": cls.customer.id} + ) + + @patch("odoo.addons.contract_exception.models.contract.Contract.detect_exceptions") + @patch( + "odoo.addons.contract_exception." + "models.contract.Contract._fields_trigger_check_exception" + ) + def test_contract_check_exception_with_field_that_trigger_exception( + self, mock_fields_trigger_check_exception, mock_detect_exceptions + ): + mock_fields_trigger_check_exception.return_value = ["partner_id"] + self.contract._contract_check_exception({"partner_id": 1}) + mock_detect_exceptions.assert_called_once() + + @patch("odoo.addons.contract_exception.models.contract.Contract.detect_exceptions") + @patch( + "odoo.addons.contract_exception." + "models.contract.Contract._fields_trigger_check_exception" + ) + def test_contract_check_exception_without_field_that_trigger_exception( + self, mock_fields_trigger_check_exception, mock_detect_exceptions + ): + mock_fields_trigger_check_exception.return_value = [] + self.contract._contract_check_exception({"partner_id": 1}) + self.assertEqual(mock_detect_exceptions.call_count, 0) + + def test_detect_exceptions(self): + rules = self.env["exception.rule"].create( + [ + { + "name": "Test Rule", + "model": "contract.contract", + "code": "failed=True", + }, + { + "name": "Test contract line Rule", + "model": "contract.line", + "code": "failed=True", + }, + ] + ) + self.contract.contract_line_ids = [(0, 0, {"name": "Test Line"})] + exceptions = self.contract.detect_exceptions() + self.assertEqual(exceptions, rules.ids) + + @patch("odoo.addons.contract_exception.models.contract.Contract.detect_exceptions") + def test_all_contracts(self, mock_detect_exceptions): + self.env["contract.contract"].test_all_contracts() + self.assertEqual( + self.env["contract.contract"].search_count([]), + mock_detect_exceptions.call_count, + ) diff --git a/contract_exception/views/contract_views.xml b/contract_exception/views/contract_views.xml new file mode 100644 index 0000000000..8037b3d664 --- /dev/null +++ b/contract_exception/views/contract_views.xml @@ -0,0 +1,84 @@ + + + + + Contract Exception Rules + exception.rule + tree,form + + [('model', 'in', ['contract.contract', 'contract.line'])] + {'active_test': False, 'default_model' : 'contract.contract'} + + + + contract_exception.view_contract_form + contract.contract + + + + + + + + + + + + + + + contract_exception.view_order_tree + contract.contract + + + + + + + + + + contract_exception.view_contracts_filter + contract.contract + + + + + + + + + diff --git a/contract_exception/wizard/__init__.py b/contract_exception/wizard/__init__.py new file mode 100644 index 0000000000..f12ac31969 --- /dev/null +++ b/contract_exception/wizard/__init__.py @@ -0,0 +1 @@ +from . import contract_exception_confirm, contract_manually_single_invoice diff --git a/contract_exception/wizard/contract_exception_confirm.py b/contract_exception/wizard/contract_exception_confirm.py new file mode 100644 index 0000000000..9048406828 --- /dev/null +++ b/contract_exception/wizard/contract_exception_confirm.py @@ -0,0 +1,23 @@ +# Copyright 2024 Foodles (https://www.foodles.co/). +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class ContractExceptionConfirm(models.TransientModel): + _name = "contract.exception.confirm" + _description = "Contract exception wizard" + _inherit = ["exception.rule.confirm"] + + related_model_id = fields.Many2one( + comodel_name="contract.contract", string="Contract" + ) + + date = fields.Date(required=True) + + def action_confirm(self): + self.ensure_one() + if self.ignore: + self.related_model_id.ignore_exception = True + self.related_model_id.generate_invoices_manually(self.date) + return super().action_confirm() diff --git a/contract_exception/wizard/contract_exception_confirm_view.xml b/contract_exception/wizard/contract_exception_confirm_view.xml new file mode 100644 index 0000000000..43c700b00d --- /dev/null +++ b/contract_exception/wizard/contract_exception_confirm_view.xml @@ -0,0 +1,43 @@ + + + + Contract Exceptions + contract.exception.confirm + +
+ + + + + + + + + + + + +
+
+
+
+
+ + Outstanding exceptions to manage + ir.actions.act_window + contract.exception.confirm + form + + new + +
diff --git a/contract_exception/wizard/contract_manually_single_invoice.py b/contract_exception/wizard/contract_manually_single_invoice.py new file mode 100644 index 0000000000..a2b72b4194 --- /dev/null +++ b/contract_exception/wizard/contract_manually_single_invoice.py @@ -0,0 +1,18 @@ +# Copyright 2024 Foodles (https://www.foodles.co/). +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import models + + +class ContractManuallySingleInvoice(models.TransientModel): + _inherit = "contract.manually.single.invoice" + + def create_invoice(self): + if ( + self.contract_id.detect_exceptions() + and not self.contract_id.ignore_exception + ): + action = self.contract_id._popup_exceptions() + action.get("context").update({"default_date": self.date}) + return action + return self.contract_id.generate_invoices_manually(date=self.date) diff --git a/oca_dependencies.txt b/oca_dependencies.txt index ca3c726ba2..f44fdbd642 100644 --- a/oca_dependencies.txt +++ b/oca_dependencies.txt @@ -1 +1,2 @@ # See https://github.com/OCA/odoo-community.org/blob/master/website/Contribution/CONTRIBUTING.rst#oca_dependencies-txt +contract https://github.com/OCA/contract dixmit:14.0-contract-improve diff --git a/setup/contract_exception/odoo/addons/contract_exception b/setup/contract_exception/odoo/addons/contract_exception new file mode 120000 index 0000000000..bdc4a2808f --- /dev/null +++ b/setup/contract_exception/odoo/addons/contract_exception @@ -0,0 +1 @@ +../../../../contract_exception \ No newline at end of file diff --git a/setup/contract_exception/setup.py b/setup/contract_exception/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/contract_exception/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/test-requirements.txt b/test-requirements.txt index 66bc2cbae3..282127950d 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1 +1,3 @@ odoo_test_helper +odoo14-addon-contract @ git+https://github.com/OCA/contract.git@refs/pull/1005/head#subdirectory=setup/contract +odoo14-addon-contract-sale-generation @ git+https://github.com/OCA/contract.git@refs/pull/1005/head#subdirectory=setup/contract_sale_generation