From 06387851a73fa7bf2901564111887d9d042c6c00 Mon Sep 17 00:00:00 2001 From: Damien Crier Date: Fri, 9 Jun 2023 15:57:48 +0200 Subject: [PATCH 1/5] [14.0][ADD] contract_split module --- contract_split/README.rst | 0 contract_split/__init__.py | 3 + contract_split/__manifest__.py | 20 ++ contract_split/models/__init__.py | 3 + contract_split/models/contract.py | 26 +++ contract_split/readme/CONTRIBUTORS.rst | 3 + contract_split/readme/DESCRIPTION.rst | 1 + contract_split/readme/USAGE.rst | 1 + contract_split/security/ir.model.access.csv | 3 + contract_split/tests/__init__.py | 0 contract_split/tests/test_contract_split.py | 123 +++++++++++++ contract_split/views/contract.xml | 19 ++ contract_split/wizard/__init__.py | 3 + .../wizard/wizard_split_contract.py | 171 ++++++++++++++++++ .../wizard/wizard_split_contract.xml | 46 +++++ .../contract_split/odoo/addons/contract_split | 1 + setup/contract_split/setup.py | 6 + 17 files changed, 429 insertions(+) create mode 100644 contract_split/README.rst create mode 100644 contract_split/__init__.py create mode 100644 contract_split/__manifest__.py create mode 100644 contract_split/models/__init__.py create mode 100644 contract_split/models/contract.py create mode 100644 contract_split/readme/CONTRIBUTORS.rst create mode 100644 contract_split/readme/DESCRIPTION.rst create mode 100644 contract_split/readme/USAGE.rst create mode 100644 contract_split/security/ir.model.access.csv create mode 100644 contract_split/tests/__init__.py create mode 100644 contract_split/tests/test_contract_split.py create mode 100644 contract_split/views/contract.xml create mode 100644 contract_split/wizard/__init__.py create mode 100644 contract_split/wizard/wizard_split_contract.py create mode 100644 contract_split/wizard/wizard_split_contract.xml create mode 120000 setup/contract_split/odoo/addons/contract_split create mode 100644 setup/contract_split/setup.py diff --git a/contract_split/README.rst b/contract_split/README.rst new file mode 100644 index 000000000..e69de29bb diff --git a/contract_split/__init__.py b/contract_split/__init__.py new file mode 100644 index 000000000..aed16e28f --- /dev/null +++ b/contract_split/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import models, wizard diff --git a/contract_split/__manifest__.py b/contract_split/__manifest__.py new file mode 100644 index 000000000..dede94c11 --- /dev/null +++ b/contract_split/__manifest__.py @@ -0,0 +1,20 @@ +# Copyright 2023 Damien Crier - Foodles +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + + +{ + "name": "Contract Split", + "version": "14.0.1.0.0", + "category": "Sales", + "license": "AGPL-3", + "summary": "Split contract", + "depends": ["contract"], + "author": "Foodles, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/contract", + "data": [ + "security/ir.model.access.csv", + "wizard/wizard_split_contract.xml", + "views/contract.xml", + ], + "installable": True, +} diff --git a/contract_split/models/__init__.py b/contract_split/models/__init__.py new file mode 100644 index 000000000..a51c4d1fb --- /dev/null +++ b/contract_split/models/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import contract diff --git a/contract_split/models/contract.py b/contract_split/models/contract.py new file mode 100644 index 000000000..26423f691 --- /dev/null +++ b/contract_split/models/contract.py @@ -0,0 +1,26 @@ +# Copyright 2023 Damien Crier - Foodles +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class Contract(models.Model): + _inherit = "contract.contract" + + original_contract_id = fields.Many2one( + comodel_name="contract.contract", + readonly=True, + ) + + +class ContractLine(models.Model): + _inherit = "contract.line" + + splitted_from_line_id = fields.Many2one( + comodel_name="contract.line", + readonly=True, + ) + splitted_from_contract_id = fields.Many2one( + comodel_name="contract.contract", + readonly=True, + ) diff --git a/contract_split/readme/CONTRIBUTORS.rst b/contract_split/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..8c134ee83 --- /dev/null +++ b/contract_split/readme/CONTRIBUTORS.rst @@ -0,0 +1,3 @@ +* `Foodles `_: + + * Damien Crier diff --git a/contract_split/readme/DESCRIPTION.rst b/contract_split/readme/DESCRIPTION.rst new file mode 100644 index 000000000..2e448fb1b --- /dev/null +++ b/contract_split/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +Adds a new button "Split" on a contract to be able to split lines across several contracts. diff --git a/contract_split/readme/USAGE.rst b/contract_split/readme/USAGE.rst new file mode 100644 index 000000000..c52036494 --- /dev/null +++ b/contract_split/readme/USAGE.rst @@ -0,0 +1 @@ +#. On a contract, hit the button "Split" and select which lines and/or quantities must be spitted to another contract. diff --git a/contract_split/security/ir.model.access.csv b/contract_split/security/ir.model.access.csv new file mode 100644 index 000000000..11ae43093 --- /dev/null +++ b/contract_split/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_split_contract,access_split_contract,model_split_contract,base.group_user,1,1,1,1 +access_split_contract_line,access_split_contract_line,model_split_contract_line,base.group_user,1,1,1,1 diff --git a/contract_split/tests/__init__.py b/contract_split/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/contract_split/tests/test_contract_split.py b/contract_split/tests/test_contract_split.py new file mode 100644 index 000000000..02f21820b --- /dev/null +++ b/contract_split/tests/test_contract_split.py @@ -0,0 +1,123 @@ +# Copyright 2023 Foodles (https://www.foodles.com/) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from odoo.exceptions import ValidationError +from odoo.tests import tagged + +from odoo.addons.contract.tests.test_contract import TestContractBase + + +@tagged("post_install", "-at_install") +class TestContractSplit(TestContractBase): + @classmethod + def setUpClass(cls): + super().setUpClass() + + def test_default_get(self): + wizard = ( + self.env["split.contract"] + .with_context(active_id=self.contract3.id) + .create({}) + ) + self.assertEqual(self.contract3.partner_id.id, wizard.partner_id.id) + self.assertEqual( + self.contract3.invoice_partner_id.id, wizard.invoice_partner_id.id + ) + self.assertEqual(self.contract3.id, wizard.main_contract_id.id) + self.assertEqual(3, len(wizard.split_line_ids.ids)) + + def test_no_split_because_no_qty_set(self): + wizard = ( + self.env["split.contract"] + .with_context(active_id=self.contract3.id) + .create({}) + ) + wizard.partner_id = self.partner_2.id + initial_contracts_length = self.env["contract.contract"].search_count([]) + # confirm wizard without setting to_split quantities + wizard.action_split_contract() + # nothing should have changed. No new contract created and original + # contract remains untouched + self.assertEqual(3, len(self.contract3.contract_line_ids.ids)) + self.assertEqual( + initial_contracts_length, self.env["contract.contract"].search_count([]) + ) + + def test_split_one_line_full_qty(self): + wizard = ( + self.env["split.contract"] + .with_context(active_id=self.contract3.id) + .create({}) + ) + wizard.partner_id = self.partner_2.id + initial_contracts_length = self.env["contract.contract"].search_count([]) + # set quantity to split in the wizard + wizard.split_line_ids[0].quantity_to_split = wizard.split_line_ids[ + 0 + ].original_qty + # confirm wizard with setting to_split quantities + new_contract = wizard.action_split_contract() + # A new contract must have been created. + self.assertEqual( + initial_contracts_length + 1, self.env["contract.contract"].search_count([]) + ) + # new contract has now the splitted line + self.assertEqual(self.partner2.id, new_contract.partner_id.id) + self.assertEqual(1, len(new_contract.contract_line_ids.ids)) + self.assertEqual( + self.contract3.id, + new_contract.contract_line_ids.mapped("splitted_from_contract_id"), + ) + # Original contract has now only 2 lines (3 at the beginning) + self.assertEqual(2, len(self.contract3.contract_line_ids.ids)) + + def test_split_one_line_one_qty(self): + # Set a qty = 2 in one line of a contract + self.contract3.contract_line_ids.filtered( + lambda line: line.name == "Line" + ).quantity = 2 + wizard = ( + self.env["split.contract"] + .with_context(active_id=self.contract3.id) + .create({}) + ) + wizard.partner_id = self.partner_2.id + initial_contracts_length = self.env["contract.contract"].search_count([]) + # set quantity to split in the wizard + wizard.split_line_ids.filtered(lambda l: l.name == "Line").quantity_to_split = 1 + # confirm wizard with setting to_split quantities + new_contract = wizard.action_split_contract() + # A new contract must have been created. + self.assertEqual( + initial_contracts_length + 1, self.env["contract.contract"].search_count([]) + ) + # new contract has partner2 as partner_id + self.assertEqual(self.partner2.id, new_contract.partner_id.id) + # new contract has now the splitted line with a qty of one + self.assertEqual(1, len(new_contract.contract_line_ids.ids)) + self.assertEqual(1, new_contract.contract_line_ids.quantity) + self.assertEqual( + self.contract3.id, + new_contract.contract_line_ids.mapped("splitted_from_contract_id"), + ) + # Original contract still has 3 lines but with a qty=1 in the last line named "Line" + self.assertEqual(3, len(self.contract3.contract_line_ids.ids)) + self.assertEqual( + 1, + self.contract3.contract_line_ids.filtered( + lambda l: l.name == "Line" + ).quantity, + ) + + def test_split_with_more_quantity_should_raise_error(self): + wizard = ( + self.env["split.contract"] + .with_context(active_id=self.contract3.id) + .create({}) + ) + # set quantity to split in the wizard + wizard.split_line_ids[0].quantity_to_split = ( + wizard.split_line_ids[0].original_qty + 2 + ) + # confirm wizard with setting to_split quantities that should raise an error + with self.assertRaises(ValidationError): + wizard.action_split_contract() diff --git a/contract_split/views/contract.xml b/contract_split/views/contract.xml new file mode 100644 index 000000000..27a252444 --- /dev/null +++ b/contract_split/views/contract.xml @@ -0,0 +1,19 @@ + + + + contract.contract.form + contract.contract + + +
+
+
+
+
diff --git a/contract_split/wizard/__init__.py b/contract_split/wizard/__init__.py new file mode 100644 index 000000000..b6b9c16b1 --- /dev/null +++ b/contract_split/wizard/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import wizard_split_contract diff --git a/contract_split/wizard/wizard_split_contract.py b/contract_split/wizard/wizard_split_contract.py new file mode 100644 index 000000000..d74da61fd --- /dev/null +++ b/contract_split/wizard/wizard_split_contract.py @@ -0,0 +1,171 @@ +# Copyright 2023 Damien Crier - Foodles +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError +from odoo.tools import float_compare + + +class SplitContract(models.TransientModel): + _name = "split.contract" + + split_line_ids = fields.One2many( + comodel_name="split.contract.line", inverse_name="split_contract_id" + ) + main_contract_id = fields.Many2one(comodel_name="contract.contract") + partner_id = fields.Many2one(comodel_name="res.partner") + invoice_partner_id = fields.Many2one(comodel_name="res.partner") + + @api.model + def default_get(self, fields) -> dict: + vals = super().default_get(fields) + contract_id = self.env.context.get("active_id") + contract = self.env["contract.contract"].browse(contract_id) + vals.update(self._get_default_values_from_contract(contract)) + return vals + + def _get_default_values_from_contract(self, contract) -> dict: + return { + "main_contract_id": contract.id, + "partner_id": contract.partner_id.id, + "invoice_partner_id": contract.invoice_partner_id.id, + "split_line_ids": [ + (0, 0, self._get_default_split_line_values(line)) + for line in contract.contract_line_ids + ], + } + + def _get_default_split_line_values(self, line) -> list: + return { + "original_contract_line_id": line.id, + } + + def _get_contract_name(self): + return self.main_contract_id.name + + def _get_values_create_contract(self): + self.ensure_one() + return { + "name": self._get_contract_name(), + "partner_id": self.partner_id.id, + "invoice_partner_id": self.invoice_partner_id.id, + "original_contract_id": self.main_contract_id.id, + "line_recurrence": True, + } + + def action_split_contract(self): + """ + If lines exists in the wizard, create a new contract + For all lines that are kept in the wizard lines : + - check if it needs to be split or only moved to the new contract + - if original_qty == qty_to_split: just move the contract_id + - if original_qty < qty_to_split: split the line + (eg: duplicate and change qties) + - if qty_to_split == 0: do nothing + """ + self.ensure_one() + if self.split_line_ids and any( + line.quantity_to_split for line in self.split_line_ids + ): + new_contract = self.env["contract.contract"].create( + self._get_values_create_contract() + ) + # TODO: play onchange on partner_id. use onchange_helper from OCA ? + for line in self.split_line_ids: + original_line = line.original_contract_line_id + if not line.quantity_to_split: + continue + if ( + float_compare( + line.quantity_to_split, line.original_qty, precision_digits=2 + ) + == 0 + ): + # only move because new_qty = original_qty + original_line.write( + line._get_write_values_when_moving_line(new_contract) + ) + elif ( + float_compare( + line.quantity_to_split, line.original_qty, precision_digits=2 + ) + < 0 + ): + # need to split and move + new_line = original_line.copy() + new_line.write( + line._get_write_values_when_splitting_and_moving_line( + new_contract, line + ) + ) + + original_line.quantity -= new_line.quantity + return new_contract + return True + + +class SplitContractLine(models.TransientModel): + _name = "split.contract.line" + + split_contract_id = fields.Many2one(comodel_name="split.contract") + original_contract_line_id = fields.Many2one(comodel_name="contract.line") + original_qty = fields.Float( + related="original_contract_line_id.quantity", + readonly=True, + store=False, + ) + original_contract_id = fields.Many2one( + comodel_name="contract.contract", + related="original_contract_line_id.contract_id", + readonly=True, + store=False, + ) + product_id = fields.Many2one( + comodel_name="product.product", + related="original_contract_line_id.product_id", + readonly=True, + store=False, + ) + uom_id = fields.Many2one( + comodel_name="uom.uom", + related="original_contract_line_id.uom_id", + readonly=True, + store=False, + ) + name = fields.Text( + comodel_name="product.product", + related="original_contract_line_id.name", + readonly=True, + ) + quantity_to_split = fields.Float(string="Quantity to move", default=0) + + @api.constrains("quantity_to_split") + def _check_quantity_to_move(self): + for rec in self: + if ( + float_compare( + rec.quantity_to_split, rec.original_qty, precision_digits=2 + ) + > 0 + ): + # we try to move more qty than present in the initial contract line + raise ValidationError( + _( + "You cannot split more quantities than the " + "original quantity of the initial contract line." + ) + ) + + def _get_write_values_when_moving_line(self, new_contract): + return { + "contract_id": new_contract.id, + "splitted_from_contract_id": self.original_contract_id.id, + } + + def _get_write_values_when_splitting_and_moving_line(self, new_contract, line): + return { + "contract_id": new_contract.id, + "splitted_from_contract_id": self.original_contract_id.id, + "splitted_from_line_id": self.original_contract_line_id.id, + "quantity": line.quantity_to_split, + } diff --git a/contract_split/wizard/wizard_split_contract.xml b/contract_split/wizard/wizard_split_contract.xml new file mode 100644 index 000000000..175a75685 --- /dev/null +++ b/contract_split/wizard/wizard_split_contract.xml @@ -0,0 +1,46 @@ + + + + split.contract.form + split.contract + +
+ + + + + + + + + + + + + + + + + + + +
+
+
+
+
+ + + Split Contract + split.contract + form + new + +
diff --git a/setup/contract_split/odoo/addons/contract_split b/setup/contract_split/odoo/addons/contract_split new file mode 120000 index 000000000..8ccc6624e --- /dev/null +++ b/setup/contract_split/odoo/addons/contract_split @@ -0,0 +1 @@ +../../../../contract_split \ No newline at end of file diff --git a/setup/contract_split/setup.py b/setup/contract_split/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/contract_split/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) From 920001c2cca1a66a47956d742a33e6a2e53e05cf Mon Sep 17 00:00:00 2001 From: Damien Crier Date: Wed, 14 Jun 2023 10:28:05 +0200 Subject: [PATCH 2/5] set default value of split quantity to initial contract line quantity --- contract_split/wizard/wizard_split_contract.py | 1 + 1 file changed, 1 insertion(+) diff --git a/contract_split/wizard/wizard_split_contract.py b/contract_split/wizard/wizard_split_contract.py index d74da61fd..cb01efb5b 100644 --- a/contract_split/wizard/wizard_split_contract.py +++ b/contract_split/wizard/wizard_split_contract.py @@ -38,6 +38,7 @@ class SplitContract(models.TransientModel): def _get_default_split_line_values(self, line) -> list: return { "original_contract_line_id": line.id, + "quantity_to_split": line.quantity, } def _get_contract_name(self): From 2736528785d4f1aa2e4e31b7296f93325007cbbb Mon Sep 17 00:00:00 2001 From: Damien Crier Date: Wed, 14 Jun 2023 10:30:42 +0200 Subject: [PATCH 3/5] IMP: add roadmap --- contract_split/readme/ROADMAP.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 contract_split/readme/ROADMAP.rst diff --git a/contract_split/readme/ROADMAP.rst b/contract_split/readme/ROADMAP.rst new file mode 100644 index 000000000..ac1aef37f --- /dev/null +++ b/contract_split/readme/ROADMAP.rst @@ -0,0 +1 @@ +* Allow to choose between move and plan successor (+ date) From 3a4480685930417d58d7890e55ce04b6993f405b Mon Sep 17 00:00:00 2001 From: Damien Crier Date: Fri, 16 Jun 2023 15:38:55 +0200 Subject: [PATCH 4/5] refactor module --- contract_split/models/contract.py | 56 +++++++++++++++- contract_split/tests/test_contract_split.py | 26 ++++++++ .../wizard/wizard_split_contract.py | 66 +++---------------- 3 files changed, 89 insertions(+), 59 deletions(-) diff --git a/contract_split/models/contract.py b/contract_split/models/contract.py index 26423f691..a8f610e3b 100644 --- a/contract_split/models/contract.py +++ b/contract_split/models/contract.py @@ -1,17 +1,46 @@ # Copyright 2023 Damien Crier - Foodles # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import fields, models +from odoo import api, fields, models class Contract(models.Model): _inherit = "contract.contract" - original_contract_id = fields.Many2one( + original_contract_ids = fields.Many2many( comodel_name="contract.contract", + relation="contract_split_contract_rel", + column1="contract_id", + column2="split_contract_id", readonly=True, ) + @api.model + def _get_contract_split_name(self, split_wizard): + return split_wizard.main_contract_id.name + + @api.model + def _get_values_create_split_contract(self, split_wizard): + return { + "name": self._get_contract_split_name(split_wizard), + "partner_id": split_wizard.partner_id.id, + "invoice_partner_id": split_wizard.invoice_partner_id.id, + "original_contract_ids": [split_wizard.main_contract_id.id], + "line_recurrence": True, + } + + def _get_default_split_values(self) -> dict: + self.ensure_one() + return { + "main_contract_id": self.id, + "partner_id": self.partner_id.id, + "invoice_partner_id": self.invoice_partner_id.id, + "split_line_ids": [ + (0, 0, line._get_default_split_line_values()) + for line in self.contract_line_ids + ], + } + class ContractLine(models.Model): _inherit = "contract.line" @@ -24,3 +53,26 @@ class ContractLine(models.Model): comodel_name="contract.contract", readonly=True, ) + + def _get_write_values_when_moving_line(self, new_contract): + self.ensure_one() + return { + "contract_id": new_contract.id, + "splitted_from_contract_id": self.contract_id.id, + } + + def _get_write_values_when_splitting_and_moving_line(self, new_contract, qty): + self.ensure_one() + return { + "contract_id": new_contract.id, + "splitted_from_contract_id": self.contract_id.id, + "splitted_from_line_id": self.id, + "quantity": qty, + } + + def _get_default_split_line_values(self) -> dict: + self.ensure_one() + return { + "original_contract_line_id": self.id, + "quantity_to_split": self.quantity, + } diff --git a/contract_split/tests/test_contract_split.py b/contract_split/tests/test_contract_split.py index 02f21820b..ac636a496 100644 --- a/contract_split/tests/test_contract_split.py +++ b/contract_split/tests/test_contract_split.py @@ -25,6 +25,32 @@ class TestContractSplit(TestContractBase): self.assertEqual(self.contract3.id, wizard.main_contract_id.id) self.assertEqual(3, len(wizard.split_line_ids.ids)) + def test_contract_default_get_method_1(self): + wizard = ( + self.env["split.contract"] + .with_context(active_id=self.contract3.id) + .create({}) + ) + contract_split_name = self.contract3._get_contract_split_name(wizard) + self.assertEqual(contract_split_name, self.contract3.name) + expected_result = { + "main_contract_id": self.contract3.id, + "partner_id": self.contract3.partner_id.id, + "invoice_partner_id": self.contract3.invoice_partner_id.id, + "split_line_ids": [ + ( + 0, + 0, + { + "original_contract_line_id": line.id, + "quantity_to_split": line.quantity, + }, + ) + for line in self.contract_line_ids + ], + } + self.assertEqual(self.contract3._get_default_split_values(), expected_result) + def test_no_split_because_no_qty_set(self): wizard = ( self.env["split.contract"] diff --git a/contract_split/wizard/wizard_split_contract.py b/contract_split/wizard/wizard_split_contract.py index cb01efb5b..999fa823c 100644 --- a/contract_split/wizard/wizard_split_contract.py +++ b/contract_split/wizard/wizard_split_contract.py @@ -8,6 +8,7 @@ from odoo.tools import float_compare class SplitContract(models.TransientModel): _name = "split.contract" + _description = "Contract split transient model" split_line_ids = fields.One2many( comodel_name="split.contract.line", inverse_name="split_contract_id" @@ -21,39 +22,9 @@ class SplitContract(models.TransientModel): vals = super().default_get(fields) contract_id = self.env.context.get("active_id") contract = self.env["contract.contract"].browse(contract_id) - vals.update(self._get_default_values_from_contract(contract)) + vals.update(contract._get_default_split_values()) return vals - def _get_default_values_from_contract(self, contract) -> dict: - return { - "main_contract_id": contract.id, - "partner_id": contract.partner_id.id, - "invoice_partner_id": contract.invoice_partner_id.id, - "split_line_ids": [ - (0, 0, self._get_default_split_line_values(line)) - for line in contract.contract_line_ids - ], - } - - def _get_default_split_line_values(self, line) -> list: - return { - "original_contract_line_id": line.id, - "quantity_to_split": line.quantity, - } - - def _get_contract_name(self): - return self.main_contract_id.name - - def _get_values_create_contract(self): - self.ensure_one() - return { - "name": self._get_contract_name(), - "partner_id": self.partner_id.id, - "invoice_partner_id": self.invoice_partner_id.id, - "original_contract_id": self.main_contract_id.id, - "line_recurrence": True, - } - def action_split_contract(self): """ If lines exists in the wizard, create a new contract @@ -68,8 +39,9 @@ class SplitContract(models.TransientModel): if self.split_line_ids and any( line.quantity_to_split for line in self.split_line_ids ): - new_contract = self.env["contract.contract"].create( - self._get_values_create_contract() + contract_obj = self.env["contract.contract"] + new_contract = contract_obj.create( + contract_obj._get_values_create_split_contract(self) ) # TODO: play onchange on partner_id. use onchange_helper from OCA ? for line in self.split_line_ids: @@ -84,7 +56,7 @@ class SplitContract(models.TransientModel): ): # only move because new_qty = original_qty original_line.write( - line._get_write_values_when_moving_line(new_contract) + original_line._get_write_values_when_moving_line(new_contract) ) elif ( float_compare( @@ -95,11 +67,10 @@ class SplitContract(models.TransientModel): # need to split and move new_line = original_line.copy() new_line.write( - line._get_write_values_when_splitting_and_moving_line( - new_contract, line + original_line._get_write_values_when_splitting_and_moving_line( + new_contract, line.quantity_to_split ) ) - original_line.quantity -= new_line.quantity return new_contract return True @@ -107,6 +78,7 @@ class SplitContract(models.TransientModel): class SplitContractLine(models.TransientModel): _name = "split.contract.line" + _description = "Contract split line transient model" split_contract_id = fields.Many2one(comodel_name="split.contract") original_contract_line_id = fields.Many2one(comodel_name="contract.line") @@ -115,12 +87,6 @@ class SplitContractLine(models.TransientModel): readonly=True, store=False, ) - original_contract_id = fields.Many2one( - comodel_name="contract.contract", - related="original_contract_line_id.contract_id", - readonly=True, - store=False, - ) product_id = fields.Many2one( comodel_name="product.product", related="original_contract_line_id.product_id", @@ -156,17 +122,3 @@ class SplitContractLine(models.TransientModel): "original quantity of the initial contract line." ) ) - - def _get_write_values_when_moving_line(self, new_contract): - return { - "contract_id": new_contract.id, - "splitted_from_contract_id": self.original_contract_id.id, - } - - def _get_write_values_when_splitting_and_moving_line(self, new_contract, line): - return { - "contract_id": new_contract.id, - "splitted_from_contract_id": self.original_contract_id.id, - "splitted_from_line_id": self.original_contract_line_id.id, - "quantity": line.quantity_to_split, - } From 20bf0ca88bfc067bc8391b5ccc1e2e07ee275992 Mon Sep 17 00:00:00 2001 From: Damien Crier Date: Tue, 27 Jun 2023 15:12:24 +0200 Subject: [PATCH 5/5] fix test import and use uom rouding for float_compare --- contract_split/tests/__init__.py | 1 + contract_split/tests/test_contract_split.py | 23 ++++++++++--------- .../wizard/wizard_split_contract.py | 21 ++++++++++++++--- 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/contract_split/tests/__init__.py b/contract_split/tests/__init__.py index e69de29bb..595abd956 100644 --- a/contract_split/tests/__init__.py +++ b/contract_split/tests/__init__.py @@ -0,0 +1 @@ +from . import test_contract_split diff --git a/contract_split/tests/test_contract_split.py b/contract_split/tests/test_contract_split.py index ac636a496..49eefe090 100644 --- a/contract_split/tests/test_contract_split.py +++ b/contract_split/tests/test_contract_split.py @@ -46,7 +46,7 @@ class TestContractSplit(TestContractBase): "quantity_to_split": line.quantity, }, ) - for line in self.contract_line_ids + for line in self.contract3.contract_line_ids ], } self.assertEqual(self.contract3._get_default_split_values(), expected_result) @@ -58,6 +58,7 @@ class TestContractSplit(TestContractBase): .create({}) ) wizard.partner_id = self.partner_2.id + wizard.split_line_ids.quantity_to_split = 0 initial_contracts_length = self.env["contract.contract"].search_count([]) # confirm wizard without setting to_split quantities wizard.action_split_contract() @@ -75,6 +76,7 @@ class TestContractSplit(TestContractBase): .create({}) ) wizard.partner_id = self.partner_2.id + wizard.split_line_ids.quantity_to_split = 0 initial_contracts_length = self.env["contract.contract"].search_count([]) # set quantity to split in the wizard wizard.split_line_ids[0].quantity_to_split = wizard.split_line_ids[ @@ -87,10 +89,10 @@ class TestContractSplit(TestContractBase): initial_contracts_length + 1, self.env["contract.contract"].search_count([]) ) # new contract has now the splitted line - self.assertEqual(self.partner2.id, new_contract.partner_id.id) + self.assertEqual(self.partner_2.id, new_contract.partner_id.id) self.assertEqual(1, len(new_contract.contract_line_ids.ids)) self.assertEqual( - self.contract3.id, + self.contract3, new_contract.contract_line_ids.mapped("splitted_from_contract_id"), ) # Original contract has now only 2 lines (3 at the beginning) @@ -107,6 +109,7 @@ class TestContractSplit(TestContractBase): .create({}) ) wizard.partner_id = self.partner_2.id + wizard.split_line_ids.quantity_to_split = 0 initial_contracts_length = self.env["contract.contract"].search_count([]) # set quantity to split in the wizard wizard.split_line_ids.filtered(lambda l: l.name == "Line").quantity_to_split = 1 @@ -116,13 +119,13 @@ class TestContractSplit(TestContractBase): self.assertEqual( initial_contracts_length + 1, self.env["contract.contract"].search_count([]) ) - # new contract has partner2 as partner_id - self.assertEqual(self.partner2.id, new_contract.partner_id.id) + # new contract has partner_2 as partner_id + self.assertEqual(self.partner_2.id, new_contract.partner_id.id) # new contract has now the splitted line with a qty of one self.assertEqual(1, len(new_contract.contract_line_ids.ids)) self.assertEqual(1, new_contract.contract_line_ids.quantity) self.assertEqual( - self.contract3.id, + self.contract3, new_contract.contract_line_ids.mapped("splitted_from_contract_id"), ) # Original contract still has 3 lines but with a qty=1 in the last line named "Line" @@ -141,9 +144,7 @@ class TestContractSplit(TestContractBase): .create({}) ) # set quantity to split in the wizard - wizard.split_line_ids[0].quantity_to_split = ( - wizard.split_line_ids[0].original_qty + 2 - ) - # confirm wizard with setting to_split quantities that should raise an error with self.assertRaises(ValidationError): - wizard.action_split_contract() + wizard.split_line_ids[0].quantity_to_split = ( + wizard.split_line_ids[0].original_qty + 2 + ) diff --git a/contract_split/wizard/wizard_split_contract.py b/contract_split/wizard/wizard_split_contract.py index 999fa823c..f45c3c7cc 100644 --- a/contract_split/wizard/wizard_split_contract.py +++ b/contract_split/wizard/wizard_split_contract.py @@ -44,13 +44,19 @@ class SplitContract(models.TransientModel): contract_obj._get_values_create_split_contract(self) ) # TODO: play onchange on partner_id. use onchange_helper from OCA ? + precision = self.env["decimal.precision"].precision_get( + "Product Unit of Measure" + ) for line in self.split_line_ids: original_line = line.original_contract_line_id if not line.quantity_to_split: continue if ( float_compare( - line.quantity_to_split, line.original_qty, precision_digits=2 + line.quantity_to_split, + line.original_qty, + precision_digits=line.uom_id and None or precision, + precision_rounding=line.uom_id.rounding or None, ) == 0 ): @@ -60,7 +66,10 @@ class SplitContract(models.TransientModel): ) elif ( float_compare( - line.quantity_to_split, line.original_qty, precision_digits=2 + line.quantity_to_split, + line.original_qty, + precision_digits=line.uom_id and None or precision, + precision_rounding=line.uom_id.rounding or None, ) < 0 ): @@ -108,10 +117,16 @@ class SplitContractLine(models.TransientModel): @api.constrains("quantity_to_split") def _check_quantity_to_move(self): + precision = self.env["decimal.precision"].precision_get( + "Product Unit of Measure" + ) for rec in self: if ( float_compare( - rec.quantity_to_split, rec.original_qty, precision_digits=2 + rec.quantity_to_split, + rec.original_qty, + precision_digits=rec.uom_id and None or precision, + precision_rounding=rec.uom_id.rounding or None, ) > 0 ):