From 2ba469d5ff92f390e640b40e0147ad6e59000b2d Mon Sep 17 00:00:00 2001 From: Damien Crier Date: Mon, 12 May 2025 10:31:08 +0200 Subject: [PATCH 1/5] IMP from https://github.com/OCA/product-pack/pull/159 --- sale_product_pack/__manifest__.py | 2 +- sale_product_pack/models/__init__.py | 1 + sale_product_pack/models/models.py | 29 +++ sale_product_pack/models/sale_order.py | 20 -- sale_product_pack/models/sale_order_line.py | 27 ++- .../tests/test_sale_product_pack.py | 200 ++++++++++++++++++ 6 files changed, 257 insertions(+), 22 deletions(-) create mode 100644 sale_product_pack/models/models.py diff --git a/sale_product_pack/__manifest__.py b/sale_product_pack/__manifest__.py index 9f8482d3c..91260ab63 100644 --- a/sale_product_pack/__manifest__.py +++ b/sale_product_pack/__manifest__.py @@ -2,7 +2,7 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). { "name": "Sale Product Pack", - "version": "17.0.2.0.1", + "version": "17.0.2.0.2", "category": "Sales", "summary": "This module allows you to sell product packs", "website": "https://github.com/OCA/product-pack", diff --git a/sale_product_pack/models/__init__.py b/sale_product_pack/models/__init__.py index cd9997169..ce88d17aa 100644 --- a/sale_product_pack/models/__init__.py +++ b/sale_product_pack/models/__init__.py @@ -1,5 +1,6 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from . import models from . import product_pack_line from . import sale_order_line from . import sale_order diff --git a/sale_product_pack/models/models.py b/sale_product_pack/models/models.py new file mode 100644 index 000000000..5a4004eb7 --- /dev/null +++ b/sale_product_pack/models/models.py @@ -0,0 +1,29 @@ +# Copyright 2023 Foodles (http://www.foodles.co). +# @author Pierre Verkest +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from collections import defaultdict + +from odoo import models + + +class BaseModel(models.AbstractModel): + _inherit = "base" + + def group_recordset_by(self, key): + """Return a collection of pairs ``(key, recordset)`` from ``self``. The + ``key`` is a function computing a key value for each element. This + function is similar to ``itertools.groupby``, but aggregates all + elements under the same key, not only consecutive elements. + it's also similar to ``òdoo.tools.misc.groupby`` but return a recordset + of sale.order.line instead list + this let write some code likes this:: + my_recordset.filtered( + lambda record: record.to_use + ).group_recordset_by( + lambda record: record.group_key + ) + """ + groups = defaultdict(self.env[self._name].browse) + for elem in self: + groups[key(elem)] |= elem + return groups.items() diff --git a/sale_product_pack/models/sale_order.py b/sale_product_pack/models/sale_order.py index 62baddc9b..c8419990e 100644 --- a/sale_product_pack/models/sale_order.py +++ b/sale_product_pack/models/sale_order.py @@ -38,26 +38,6 @@ def check_pack_line_unlink(self): ) ) - def write(self, vals): - if "order_line" in vals: - to_delete_ids = [e[1] for e in vals["order_line"] if e[0] == 2] - subpacks_to_delete_ids = ( - self.env["sale.order.line"] - .search( - [("id", "child_of", to_delete_ids), ("id", "not in", to_delete_ids)] - ) - .ids - ) - if subpacks_to_delete_ids: - for cmd in vals["order_line"]: - if cmd[1] in subpacks_to_delete_ids: - if cmd[0] != 2: - cmd[0] = 2 - subpacks_to_delete_ids.remove(cmd[1]) - for to_delete_id in subpacks_to_delete_ids: - vals["order_line"].append([2, to_delete_id, False]) - return super().write(vals) - def _get_update_prices_lines(self): res = super()._get_update_prices_lines() return res.filtered( diff --git a/sale_product_pack/models/sale_order_line.py b/sale_product_pack/models/sale_order_line.py index ee65663c7..28d70c83a 100644 --- a/sale_product_pack/models/sale_order_line.py +++ b/sale_product_pack/models/sale_order_line.py @@ -1,5 +1,6 @@ # Copyright 2019 Tecnativa - Ernesto Tejeda # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + from odoo import _, api, fields, models from odoo.exceptions import UserError from odoo.fields import first @@ -93,9 +94,21 @@ def create(self, vals_list): else: return super().create(vals_list) + @api.model + def _pack_fields_trigger_expand_pack_line_on_write(self): + """A set of fields that will trigger expand pack line + To propagate information over sale order line add your + field in this list and overload the following method: + `ProductPack.get_sale_order_line_vals` + Be aware if pack line are "modifiable" user input can + be overwrite once save if one of this field as been + changed on the pack line... + """ + return {"product_id", "product_uom_qty"} + def write(self, vals): res = super().write(vals) - if "product_id" in vals or "product_uom_qty" in vals: + if self._pack_fields_trigger_expand_pack_line_on_write() & set(vals.keys()): for record in self: record.expand_pack_line(write=True) return res @@ -167,3 +180,15 @@ def _compute_discount(self): for pack_line in self.filtered("pack_parent_line_id"): pack_line.discount = pack_line._get_pack_line_discount() return res + + def unlink(self): + for order, lines in self.group_recordset_by(lambda sol: sol.order_id): + pack_component_to_delete = self.search( + [ + ("id", "child_of", lines.ids), + ("id", "not in", lines.ids), + ("order_id", "=", order.id), + ] + ) + pack_component_to_delete.unlink() + return super().unlink() diff --git a/sale_product_pack/tests/test_sale_product_pack.py b/sale_product_pack/tests/test_sale_product_pack.py index fc7fe6f8b..38f01a07c 100644 --- a/sale_product_pack/tests/test_sale_product_pack.py +++ b/sale_product_pack/tests/test_sale_product_pack.py @@ -1,6 +1,9 @@ # Copyright 2019 Tecnativa - Ernesto Tejeda # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from odoo.exceptions import UserError +from odoo.tests import Form + from odoo.addons.base.tests.common import BaseCommon @@ -221,6 +224,105 @@ def qty_in_order(): total_qty_confirmed = qty_in_order() self.assertAlmostEqual(total_qty_updated * 2, total_qty_confirmed) + def test_update_qty_do_not_expand(self): + product_cp = self.env.ref("product_pack.product_pack_cpu_detailed_components") + main_sol = self.env["sale.order.line"].create( + { + "order_id": self.sale_order.id, + "name": product_cp.name, + "product_id": product_cp.id, + "product_uom_qty": 1, + } + ) + main_sol.with_context(update_prices=True).product_uom_qty = 2 + self.assertTrue( + all( + self.sale_order.order_line.filtered( + lambda sol: sol.pack_parent_line_id == main_sol + ).mapped(lambda sol: sol.product_uom_qty == 1) + ), + ) + + def test_update_pack_qty_with_new_component(self): + product_cp = self.env.ref("product_pack.product_pack_cpu_detailed_components") + main_sol = self.env["sale.order.line"].create( + { + "order_id": self.sale_order.id, + "name": product_cp.name, + "product_id": product_cp.id, + "product_uom_qty": 1, + } + ) + + self.assertEqual( + sum( + self.sale_order.order_line.filtered( + lambda sol: sol.pack_parent_line_id == main_sol + ).mapped("product_uom_qty") + ), + 3, + "Expected 3 lines with quantity 1 while setup this test", + ) + + product_cp.pack_line_ids |= self.env["product.pack.line"].create( + { + "parent_product_id": product_cp.id, + "product_id": self.env.ref("product.product_product_12").id, + "quantity": 2, + } + ) + + main_sol.product_uom_qty = 2 + self.assertEqual( + sum( + self.sale_order.order_line.filtered( + lambda sol: sol.pack_parent_line_id == main_sol + ).mapped("product_uom_qty") + ), + 10, + "Expected 3 lines with quantity 2 and new component line with quantity 4", + ) + + def test_update_pack_qty_with_new_component_do_not_expand(self): + product_cp = self.env.ref("product_pack.product_pack_cpu_detailed_components") + main_sol = self.env["sale.order.line"].create( + { + "order_id": self.sale_order.id, + "name": product_cp.name, + "product_id": product_cp.id, + "product_uom_qty": 1, + } + ) + + self.assertEqual( + sum( + self.sale_order.order_line.filtered( + lambda sol: sol.pack_parent_line_id == main_sol + ).mapped("product_uom_qty") + ), + 3, + "Expected 3 lines with quantity 1 while setup this test", + ) + + product_cp.pack_line_ids |= self.env["product.pack.line"].create( + { + "parent_product_id": product_cp.id, + "product_id": self.env.ref("product.product_product_12").id, + "quantity": 2, + } + ) + + main_sol.with_context(update_prices=True).product_uom_qty = 2 + self.assertEqual( + sum( + self.sale_order.order_line.filtered( + lambda sol: sol.pack_parent_line_id == main_sol + ).mapped("product_uom_qty") + ), + 3, + "Expected 3 lines with quantity 2 and no new component line", + ) + def test_do_not_expand(self): product_cp = self.env.ref("product_pack.product_pack_cpu_detailed_components") pack_line = self.env["sale.order.line"].create( @@ -370,3 +472,101 @@ def test_compute_discount_for_detailed_packs(self): [19, 19, 10], "Discounts for the pack lines are not calculated correctly.", ) + + def test_copy_sale_order_with_detailed_product_pack(self): + product_cp = self.env.ref( + "product_pack.product_pack_cpu_detailed_components" + ) + self.env["sale.order.line"].create( + { + "order_id": self.sale_order.id, + "name": product_cp.name, + "product_id": product_cp.id, + "product_uom_qty": 1, + } + ) + copied_order = self.sale_order.copy() + copied_order_component_lines_pack_line = copied_order.order_line.filtered( + lambda line: line.product_id.pack_ok + ) + copied_order_component_lines = copied_order.order_line.filtered( + lambda line: line.pack_parent_line_id + ) + self.assertEqual( + copied_order_component_lines.pack_parent_line_id, + copied_order_component_lines_pack_line, + ) + + def test_check_pack_line_unlink(self): + product_cp = self.env.ref( + "product_pack.product_pack_cpu_detailed_components" + ) + self.env["sale.order.line"].create( + { + "order_id": self.sale_order.id, + "name": product_cp.name, + "product_id": product_cp.id, + "product_uom_qty": 1, + } + ) + with Form(self.sale_order) as so_form: + with self.assertRaisesRegex( + UserError, + "You cannot delete this line because is part of a pack in this " + "sale order. In order to delete this line you need to delete the " + "pack itself", + ): + so_form.order_line.remove(len(self.sale_order.order_line) - 1) + + def test_unlink_pack_form_proxy(self): + product_cp = self.env.ref( + "product_pack.product_pack_cpu_detailed_components" + ) + self.env["sale.order.line"].create( + { + "order_id": self.sale_order.id, + "name": product_cp.name, + "product_id": product_cp.id, + "product_uom_qty": 1, + } + ) + with Form(self.sale_order) as so_form: + so_form.order_line.remove(0) + so_form.save() + self.assertEqual(len(self.sale_order.order_line), 0) + + def test_unlink_pack_record_unlink(self): + product_cp = self.env.ref( + "product_pack.product_pack_cpu_detailed_components" + ) + self.env["sale.order.line"].create( + { + "order_id": self.sale_order.id, + "name": product_cp.name, + "product_id": product_cp.id, + "product_uom_qty": 1, + } + ) + pack_line = self.sale_order.order_line.filtered( + lambda line: line.product_id.pack_ok + ) + pack_line.unlink() + self.assertEqual(len(self.sale_order.order_line), 0) + + def test_unlink_pack_old_style_like_ui(self): + product_cp = self.env.ref( + "product_pack.product_pack_cpu_detailed_components" + ) + self.env["sale.order.line"].create( + { + "order_id": self.sale_order.id, + "name": product_cp.name, + "product_id": product_cp.id, + "product_uom_qty": 1, + } + ) + pack_line = self.sale_order.order_line.filtered( + lambda line: line.product_id.pack_ok + ) + self.sale_order.write({"order_line": [(2, pack_line.id)]}) + self.assertEqual(len(self.sale_order.order_line), 0) From 2f7459f5fea40160562979213a1e7230f56fea86 Mon Sep 17 00:00:00 2001 From: Pierre Verkest Date: Tue, 23 Jan 2024 18:29:05 +0100 Subject: [PATCH 2/5] [ADD] sale_product_pack_contract --- sale_product_pack_contract/README.rst | 100 ++++ sale_product_pack_contract/__init__.py | 1 + sale_product_pack_contract/__manifest__.py | 22 + .../demo/product_product_demo.xml | 21 + sale_product_pack_contract/i18n/fr.po | 67 +++ .../i18n/sale_product_pack_contract.pot | 63 +++ sale_product_pack_contract/models/__init__.py | 2 + .../models/product_pack_line.py | 38 ++ .../models/product_product.py | 11 + .../readme/CONTRIBUTORS.rst | 1 + .../readme/DESCRIPTION.rst | 10 + sale_product_pack_contract/readme/ROADMAP.rst | 2 + .../static/description/index.html | 439 ++++++++++++++++++ sale_product_pack_contract/tests/__init__.py | 1 + .../tests/test_sale_product_pack_contract.py | 133 ++++++ 15 files changed, 911 insertions(+) create mode 100644 sale_product_pack_contract/README.rst create mode 100644 sale_product_pack_contract/__init__.py create mode 100644 sale_product_pack_contract/__manifest__.py create mode 100644 sale_product_pack_contract/demo/product_product_demo.xml create mode 100644 sale_product_pack_contract/i18n/fr.po create mode 100644 sale_product_pack_contract/i18n/sale_product_pack_contract.pot create mode 100644 sale_product_pack_contract/models/__init__.py create mode 100644 sale_product_pack_contract/models/product_pack_line.py create mode 100644 sale_product_pack_contract/models/product_product.py create mode 100644 sale_product_pack_contract/readme/CONTRIBUTORS.rst create mode 100644 sale_product_pack_contract/readme/DESCRIPTION.rst create mode 100644 sale_product_pack_contract/readme/ROADMAP.rst create mode 100644 sale_product_pack_contract/static/description/index.html create mode 100644 sale_product_pack_contract/tests/__init__.py create mode 100644 sale_product_pack_contract/tests/test_sale_product_pack_contract.py diff --git a/sale_product_pack_contract/README.rst b/sale_product_pack_contract/README.rst new file mode 100644 index 000000000..2ece65c2d --- /dev/null +++ b/sale_product_pack_contract/README.rst @@ -0,0 +1,100 @@ +========================== +Sale Product Pack Contract +========================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:93384966ede1665e09b7a7d2da813b657e9a47953428b9d25248a47a98e6af1b + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Fproduct--pack-lightgray.png?logo=github + :target: https://github.com/OCA/product-pack/tree/14.0/sale_product_pack_contract + :alt: OCA/product-pack +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/product-pack-14-0/product-pack-14-0-sale_product_pack_contract + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/product-pack&target_branch=14.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module is glue module between *Sale Product Pack* and *Contract*'s modules. + +At the moment it mainly prevent to configure pack with weird +configuration that would let user in unexpected situations. + +Currently this module support following situation: + +* Pack is not a contract with at least one line of the pack is a + contract line, pack must be configured as `pack_type=detailed` and + `pack_component_price=detailed`. + +**Table of contents** + +.. contents:: + :local: + +Known issues / Roadmap +====================== + +* Add more support over different kind of product pack with + contract line + +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 `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Pierre Verkest +* + +Contributors +~~~~~~~~~~~~ + +* Pierre Verkest + +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. + +.. |maintainer-petrus-v| image:: https://github.com/petrus-v.png?size=40px + :target: https://github.com/petrus-v + :alt: petrus-v + +Current `maintainer `__: + +|maintainer-petrus-v| + +This module is part of the `OCA/product-pack `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/sale_product_pack_contract/__init__.py b/sale_product_pack_contract/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/sale_product_pack_contract/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/sale_product_pack_contract/__manifest__.py b/sale_product_pack_contract/__manifest__.py new file mode 100644 index 000000000..9072874f6 --- /dev/null +++ b/sale_product_pack_contract/__manifest__.py @@ -0,0 +1,22 @@ +# Copyright 2023 Foodles (http://www.foodles.co). +# @author Pierre Verkest +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +{ + "name": "Sale Product Pack Contract", + "version": "14.0.1.0.0", + "category": "Sales", + "summary": "Glue module between sale product pack and product.", + "website": "https://github.com/OCA/product-pack", + "author": ( + "Pierre Verkest , " + "Odoo Community Association (OCA)," + ), + "maintainers": ["petrus-v"], + "license": "AGPL-3", + "depends": ["sale_product_pack", "product_contract"], + "data": [], + "demo": [ + "demo/product_product_demo.xml", + ], + "installable": True, +} diff --git a/sale_product_pack_contract/demo/product_product_demo.xml b/sale_product_pack_contract/demo/product_product_demo.xml new file mode 100644 index 000000000..2ce5681c5 --- /dev/null +++ b/sale_product_pack_contract/demo/product_product_demo.xml @@ -0,0 +1,21 @@ + + + + + Maintenance CPU compenent contract template + + + Maintenance CPU compenent + + 22.5 + 32.75 + service + + + + diff --git a/sale_product_pack_contract/i18n/fr.po b/sale_product_pack_contract/i18n/fr.po new file mode 100644 index 000000000..3841672c4 --- /dev/null +++ b/sale_product_pack_contract/i18n/fr.po @@ -0,0 +1,67 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * mail_activity_board +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0+e\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: sale_product_pack_contract +#: model:ir.model.fields,field_description:sale_product_pack_contract.field_product_pack_line__display_name +#: model:ir.model.fields,field_description:sale_product_pack_contract.field_product_product__display_name +msgid "Display Name" +msgstr "Nom d'affichage" + +#. module: sale_product_pack_contract +#: model:ir.model.fields,field_description:sale_product_pack_contract.field_product_pack_line__id +#: model:ir.model.fields,field_description:sale_product_pack_contract.field_product_product__id +msgid "ID" +msgstr "ID" + +#. module: sale_product_pack_contract +#: model:ir.model.fields,field_description:sale_product_pack_contract.field_product_pack_line____last_update +#: model:ir.model.fields,field_description:sale_product_pack_contract.field_product_product____last_update +msgid "Last Modified on" +msgstr "Dernière modification le" + +#. module: sale_product_pack_contract +#: model:product.product,name:sale_product_pack_contract.product_pack_cpu_maintenance_component +#: model:product.template,name:sale_product_pack_contract.product_pack_cpu_maintenance_component_product_template +msgid "Maintenance CPU compenent" +msgstr "Composant: maintenance CPU" + +#. module: sale_product_pack_contract +#: model:ir.model,name:sale_product_pack_contract.model_product_product +msgid "Product" +msgstr "Produit" + +#. module: sale_product_pack_contract +#: model:ir.model,name:sale_product_pack_contract.model_product_pack_line +msgid "Product pack line" +msgstr "Ligne de composante de pack" + +#. module: sale_product_pack_contract +#: code:addons/sale_product_pack_contract/models/product_pack_line.py:0 +#, python-format +msgid "" +"This pack '%s' contains components %r that are marked to be contract " +"products. At the moment contract component support only on detailed pack " +"type and detailed component price pack." +msgstr "" +"Ce pack : '%s' contient des composants : %r qui sont marqué comme étant " +"des articles de contrat. Pour le moment les linge de composante de pack " +"sont supportées uniquement sur les pack de type détaillé dont le prix est " +"détaillé sur les lignes de composants." + +#. module: sale_product_pack_contract +#: model:product.product,uom_name:sale_product_pack_contract.product_pack_cpu_maintenance_component +#: model:product.template,uom_name:sale_product_pack_contract.product_pack_cpu_maintenance_component_product_template +msgid "Units" +msgstr "Unités" diff --git a/sale_product_pack_contract/i18n/sale_product_pack_contract.pot b/sale_product_pack_contract/i18n/sale_product_pack_contract.pot new file mode 100644 index 000000000..15a7e28a1 --- /dev/null +++ b/sale_product_pack_contract/i18n/sale_product_pack_contract.pot @@ -0,0 +1,63 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_product_pack_contract +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0+e\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: sale_product_pack_contract +#: model:ir.model.fields,field_description:sale_product_pack_contract.field_product_pack_line__display_name +#: model:ir.model.fields,field_description:sale_product_pack_contract.field_product_product__display_name +msgid "Display Name" +msgstr "" + +#. module: sale_product_pack_contract +#: model:ir.model.fields,field_description:sale_product_pack_contract.field_product_pack_line__id +#: model:ir.model.fields,field_description:sale_product_pack_contract.field_product_product__id +msgid "ID" +msgstr "" + +#. module: sale_product_pack_contract +#: model:ir.model.fields,field_description:sale_product_pack_contract.field_product_pack_line____last_update +#: model:ir.model.fields,field_description:sale_product_pack_contract.field_product_product____last_update +msgid "Last Modified on" +msgstr "" + +#. module: sale_product_pack_contract +#: model:product.product,name:sale_product_pack_contract.product_pack_cpu_maintenance_component +#: model:product.template,name:sale_product_pack_contract.product_pack_cpu_maintenance_component_product_template +msgid "Maintenance CPU compenent" +msgstr "" + +#. module: sale_product_pack_contract +#: model:ir.model,name:sale_product_pack_contract.model_product_product +msgid "Product" +msgstr "" + +#. module: sale_product_pack_contract +#: model:ir.model,name:sale_product_pack_contract.model_product_pack_line +msgid "Product pack line" +msgstr "" + +#. module: sale_product_pack_contract +#: code:addons/sale_product_pack_contract/models/product_pack_line.py:0 +#, python-format +msgid "" +"This pack '%s' contains components %r that are marked to be contract " +"products. At the moment contract component support only on detailed pack " +"type and detailed component price pack." +msgstr "" + +#. module: sale_product_pack_contract +#: model:product.product,uom_name:sale_product_pack_contract.product_pack_cpu_maintenance_component +#: model:product.template,uom_name:sale_product_pack_contract.product_pack_cpu_maintenance_component_product_template +msgid "Units" +msgstr "" diff --git a/sale_product_pack_contract/models/__init__.py b/sale_product_pack_contract/models/__init__.py new file mode 100644 index 000000000..6f0d63956 --- /dev/null +++ b/sale_product_pack_contract/models/__init__.py @@ -0,0 +1,2 @@ +from . import product_pack_line +from . import product_product diff --git a/sale_product_pack_contract/models/product_pack_line.py b/sale_product_pack_contract/models/product_pack_line.py new file mode 100644 index 000000000..8c1ae59b8 --- /dev/null +++ b/sale_product_pack_contract/models/product_pack_line.py @@ -0,0 +1,38 @@ +# Copyright 2023 Foodles (http://www.foodles.co). +# @author Pierre Verkest +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import _, api, models +from odoo.exceptions import UserError + + +class ProductPack(models.Model): + _inherit = "product.pack.line" + + @api.constrains( + "parent_product_id", + "product_id", + ) + def _check_contract_component(self): + for pack_product, pack_lines in self.group_recordset_by( + lambda pack_line: pack_line.parent_product_id + ): + if ( + pack_product.pack_type != "detailed" + or pack_product.pack_component_price != "detailed" + ): + component_contract_products = pack_lines.filtered( + lambda line: line.product_id.is_contract + ) + if component_contract_products.exists(): + raise UserError( + _( + "This pack '%s' contains components %r that are marked " + "to be contract products. At the moment contract component " + "support only on detailed pack type and detailed " + "component price pack." + ) + % ( + pack_product.name, + component_contract_products.mapped("product_id.name"), + ) + ) diff --git a/sale_product_pack_contract/models/product_product.py b/sale_product_pack_contract/models/product_product.py new file mode 100644 index 000000000..e1a507c06 --- /dev/null +++ b/sale_product_pack_contract/models/product_product.py @@ -0,0 +1,11 @@ +from odoo import api, models + + +class ProductProduct(models.Model): + _inherit = "product.product" + + @api.constrains("pack_type", "pack_component_price") + def _check_contract_component(self): + for product in self: + if product.pack_ok: + product.pack_line_ids._check_contract_component() diff --git a/sale_product_pack_contract/readme/CONTRIBUTORS.rst b/sale_product_pack_contract/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..638be8663 --- /dev/null +++ b/sale_product_pack_contract/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Pierre Verkest diff --git a/sale_product_pack_contract/readme/DESCRIPTION.rst b/sale_product_pack_contract/readme/DESCRIPTION.rst new file mode 100644 index 000000000..10463f62b --- /dev/null +++ b/sale_product_pack_contract/readme/DESCRIPTION.rst @@ -0,0 +1,10 @@ +This module is glue module between *Sale Product Pack* and *Contract*'s modules. + +At the moment it mainly prevent to configure pack with weird +configuration that would let user in unexpected situations. + +Currently this module support following situation: + +* Pack is not a contract with at least one line of the pack is a + contract line, pack must be configured as `pack_type=detailed` and + `pack_component_price=detailed`. diff --git a/sale_product_pack_contract/readme/ROADMAP.rst b/sale_product_pack_contract/readme/ROADMAP.rst new file mode 100644 index 000000000..407e09595 --- /dev/null +++ b/sale_product_pack_contract/readme/ROADMAP.rst @@ -0,0 +1,2 @@ +* Add more support over different kind of product pack with + contract line diff --git a/sale_product_pack_contract/static/description/index.html b/sale_product_pack_contract/static/description/index.html new file mode 100644 index 000000000..a29924537 --- /dev/null +++ b/sale_product_pack_contract/static/description/index.html @@ -0,0 +1,439 @@ + + + + + +Sale Product Pack Contract + + + +
+

Sale Product Pack Contract

+ + +

Beta License: AGPL-3 OCA/product-pack Translate me on Weblate Try me on Runboat

+

This module is glue module between Sale Product Pack and Contract’s modules.

+

At the moment it mainly prevent to configure pack with weird +configuration that would let user in unexpected situations.

+

Currently this module support following situation:

+
    +
  • Pack is not a contract with at least one line of the pack is a +contract line, pack must be configured as pack_type=detailed and +pack_component_price=detailed.
  • +
+

Table of contents

+ +
+

Known issues / Roadmap

+
    +
  • Add more support over different kind of product pack with +contract line
  • +
+
+
+

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.

+

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

+
+
+

Credits

+
+

Authors

+ +
+
+

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.

+

Current maintainer:

+

petrus-v

+

This module is part of the OCA/product-pack project on GitHub.

+

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

+
+
+
+ + diff --git a/sale_product_pack_contract/tests/__init__.py b/sale_product_pack_contract/tests/__init__.py new file mode 100644 index 000000000..f32f22c2b --- /dev/null +++ b/sale_product_pack_contract/tests/__init__.py @@ -0,0 +1 @@ +from . import test_sale_product_pack_contract diff --git a/sale_product_pack_contract/tests/test_sale_product_pack_contract.py b/sale_product_pack_contract/tests/test_sale_product_pack_contract.py new file mode 100644 index 000000000..e2bfb0901 --- /dev/null +++ b/sale_product_pack_contract/tests/test_sale_product_pack_contract.py @@ -0,0 +1,133 @@ +# Copyright 2023 Foodles (http://www.foodles.co). +# @author Pierre Verkest +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import fields +from odoo.exceptions import UserError +from odoo.tests import Form, SavepointCase + + +class TestSaleProductPackContract(SavepointCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.product_contract_component = cls.env.ref( + "sale_product_pack_contract.product_pack_cpu_maintenance_component" + ) + cls.env["product.pack.line"].create( + { + "parent_product_id": cls.env.ref( + "product_pack.product_pack_cpu_detailed_components" + ).id, + "product_id": cls.product_contract_component.id, + "quantity": 1, + } + ) + + cls.sale_order = cls.env["sale.order"].create( + { + "company_id": cls.env.company.id, + "partner_id": cls.env.ref("base.res_partner_12").id, + } + ) + + def test_confirm_sale_with_component_details_generate_contract(self): + product_cp = self.env.ref("product_pack.product_pack_cpu_detailed_components") + pack_sol = self.env["sale.order.line"].create( + { + "order_id": self.sale_order.id, + "name": product_cp.name, + "product_id": product_cp.id, + "date_start": "2024-01-05", + "product_uom_qty": 1, + } + ) + for position, contract_sol in enumerate(self.sale_order.order_line): + if ( + contract_sol.product_id.is_contract + and contract_sol.pack_parent_line_id == pack_sol + ): + with Form(self.sale_order) as order_form: + with order_form.order_line.edit(position) as sol_form: + sol_form.date_start = "2024-01-05" + order_form.save() + break + + self.sale_order.action_confirm() + contracts = self.sale_order.order_line.contract_id + contract_lines = self.sale_order.order_line.contract_id.contract_line_ids + self.assertEqual(len(contracts), 1) + self.assertEqual(len(contract_lines), 1) + self.assertEqual( + contract_lines.date_start, + fields.Date.to_date("2024-01-05"), + ) + + def test_update_pack_with_contract_component_type_non_detailed_forbidden(self): + product_cp = self.env.ref("product_pack.product_pack_cpu_detailed_components") + with self.assertRaisesRegex( + UserError, + ( + r"This pack '" + + product_cp.name.replace("(", r"\(").replace(")", r"\)") + + r"' contains components \[" + rf"'{self.product_contract_component.name}'\] " + r"that are marked to be contract products. " + r"At the moment contract component support only " + r"on detailed pack type and detailed component price pack." + ), + ): + product_cp.pack_type = "non_detailed" + + def test_update_pack_with_contract_component_price_totalized_forbidden(self): + product_cp = self.env.ref("product_pack.product_pack_cpu_detailed_components") + with self.assertRaisesRegex( + UserError, + ( + r"This pack '" + + product_cp.name.replace("(", r"\(").replace(")", r"\)") + + r"' contains components \[" + rf"'{self.product_contract_component.name}'\] " + r"that are marked to be contract products. " + r"At the moment contract component support only " + r"on detailed pack type and detailed component price pack." + ), + ): + product_cp.pack_component_price = "totalized" + + def test_update_pack_with_contract_component_price_ignored_forbidden(self): + product_cp = self.env.ref("product_pack.product_pack_cpu_detailed_components") + with self.assertRaisesRegex( + UserError, + ( + r"This pack '" + + product_cp.name.replace("(", r"\(").replace(")", r"\)") + + r"' contains components \[" + rf"'{self.product_contract_component.name}'\] " + r"that are marked to be contract products. " + r"At the moment contract component support only " + r"on detailed pack type and detailed component price pack." + ), + ): + product_cp.pack_component_price = "ignored" + + def test_add_contract_line_in_non_detail_pack_forbidden(self): + product_cp = self.env.ref("product_pack.product_pack_cpu_non_detailed") + with self.assertRaisesRegex( + UserError, + ( + r"This pack '" + + product_cp.name.replace("(", r"\(").replace(")", r"\)") + + r"' contains components \[" + rf"'{self.product_contract_component.name}'\] " + r"that are marked to be contract products. " + r"At the moment contract component support only " + r"on detailed pack type and detailed component price pack." + ), + ): + self.env["product.pack.line"].create( + { + "parent_product_id": product_cp.id, + "product_id": self.product_contract_component.id, + "quantity": 1, + } + ) From e2a4a9c7aa7fd6eb91253ba94c7fcd16a973ebfe Mon Sep 17 00:00:00 2001 From: Pierre Verkest Date: Wed, 24 Jan 2024 18:50:05 +0100 Subject: [PATCH 3/5] Apply suggestions from code review Co-authored-by: Damien Crier --- sale_product_pack_contract/__manifest__.py | 2 +- sale_product_pack_contract/demo/product_product_demo.xml | 2 +- sale_product_pack_contract/i18n/fr.po | 6 +++--- sale_product_pack_contract/models/product_pack_line.py | 2 +- .../tests/test_sale_product_pack_contract.py | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/sale_product_pack_contract/__manifest__.py b/sale_product_pack_contract/__manifest__.py index 9072874f6..41c71f100 100644 --- a/sale_product_pack_contract/__manifest__.py +++ b/sale_product_pack_contract/__manifest__.py @@ -1,4 +1,4 @@ -# Copyright 2023 Foodles (http://www.foodles.co). +# Copyright 2024 Foodles (http://www.foodles.co). # @author Pierre Verkest # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). { diff --git a/sale_product_pack_contract/demo/product_product_demo.xml b/sale_product_pack_contract/demo/product_product_demo.xml index 2ce5681c5..37102fba4 100644 --- a/sale_product_pack_contract/demo/product_product_demo.xml +++ b/sale_product_pack_contract/demo/product_product_demo.xml @@ -1,5 +1,5 @@ - diff --git a/sale_product_pack_contract/i18n/fr.po b/sale_product_pack_contract/i18n/fr.po index 3841672c4..d4490defd 100644 --- a/sale_product_pack_contract/i18n/fr.po +++ b/sale_product_pack_contract/i18n/fr.po @@ -1,6 +1,6 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: -# * mail_activity_board +# * sale_product_pack_contract # msgid "" msgstr "" @@ -55,8 +55,8 @@ msgid "" "products. At the moment contract component support only on detailed pack " "type and detailed component price pack." msgstr "" -"Ce pack : '%s' contient des composants : %r qui sont marqué comme étant " -"des articles de contrat. Pour le moment les linge de composante de pack " +"Ce pack : '%s' contient des composants : %r qui sont marqués comme étant " +"des articles de contrat. Pour le moment, les lignes de composante de pack " "sont supportées uniquement sur les pack de type détaillé dont le prix est " "détaillé sur les lignes de composants." diff --git a/sale_product_pack_contract/models/product_pack_line.py b/sale_product_pack_contract/models/product_pack_line.py index 8c1ae59b8..b567d65d7 100644 --- a/sale_product_pack_contract/models/product_pack_line.py +++ b/sale_product_pack_contract/models/product_pack_line.py @@ -1,4 +1,4 @@ -# Copyright 2023 Foodles (http://www.foodles.co). +# Copyright 2024 Foodles (http://www.foodles.co). # @author Pierre Verkest # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from odoo import _, api, models diff --git a/sale_product_pack_contract/tests/test_sale_product_pack_contract.py b/sale_product_pack_contract/tests/test_sale_product_pack_contract.py index e2bfb0901..198d3a878 100644 --- a/sale_product_pack_contract/tests/test_sale_product_pack_contract.py +++ b/sale_product_pack_contract/tests/test_sale_product_pack_contract.py @@ -1,4 +1,4 @@ -# Copyright 2023 Foodles (http://www.foodles.co). +# Copyright 2024 Foodles (http://www.foodles.co). # @author Pierre Verkest # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from odoo import fields From c5332b026133cc9c106851e4109681baf357c79e Mon Sep 17 00:00:00 2001 From: Damien Crier Date: Mon, 12 May 2025 15:00:20 +0200 Subject: [PATCH 4/5] [IMP] sale_product_pack_contract: pre-commit auto fixes --- sale_product_pack_contract/README.rst | 31 ++++++++++--------- .../models/product_pack_line.py | 12 ++++--- sale_product_pack_contract/pyproject.toml | 3 ++ .../readme/CONTRIBUTORS.md | 1 + .../readme/CONTRIBUTORS.rst | 1 - .../{DESCRIPTION.rst => DESCRIPTION.md} | 9 +++--- sale_product_pack_contract/readme/ROADMAP.md | 2 ++ sale_product_pack_contract/readme/ROADMAP.rst | 2 -- .../static/description/index.html | 17 +++++----- 9 files changed, 43 insertions(+), 35 deletions(-) create mode 100644 sale_product_pack_contract/pyproject.toml create mode 100644 sale_product_pack_contract/readme/CONTRIBUTORS.md delete mode 100644 sale_product_pack_contract/readme/CONTRIBUTORS.rst rename sale_product_pack_contract/readme/{DESCRIPTION.rst => DESCRIPTION.md} (56%) create mode 100644 sale_product_pack_contract/readme/ROADMAP.md delete mode 100644 sale_product_pack_contract/readme/ROADMAP.rst diff --git a/sale_product_pack_contract/README.rst b/sale_product_pack_contract/README.rst index 2ece65c2d..6f3bf14ec 100644 --- a/sale_product_pack_contract/README.rst +++ b/sale_product_pack_contract/README.rst @@ -17,27 +17,28 @@ Sale Product Pack Contract :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fproduct--pack-lightgray.png?logo=github - :target: https://github.com/OCA/product-pack/tree/14.0/sale_product_pack_contract + :target: https://github.com/OCA/product-pack/tree/17.0/sale_product_pack_contract :alt: OCA/product-pack .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/product-pack-14-0/product-pack-14-0-sale_product_pack_contract + :target: https://translation.odoo-community.org/projects/product-pack-17-0/product-pack-17-0-sale_product_pack_contract :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png - :target: https://runboat.odoo-community.org/builds?repo=OCA/product-pack&target_branch=14.0 + :target: https://runboat.odoo-community.org/builds?repo=OCA/product-pack&target_branch=17.0 :alt: Try me on Runboat |badge1| |badge2| |badge3| |badge4| |badge5| -This module is glue module between *Sale Product Pack* and *Contract*'s modules. +This module is glue module between *Sale Product Pack* and *Contract*'s +modules. At the moment it mainly prevent to configure pack with weird configuration that would let user in unexpected situations. Currently this module support following situation: -* Pack is not a contract with at least one line of the pack is a - contract line, pack must be configured as `pack_type=detailed` and - `pack_component_price=detailed`. +- Pack is not a contract with at least one line of the pack is a + contract line, pack must be configured as pack_type=detailed and + pack_component_price=detailed. **Table of contents** @@ -47,8 +48,8 @@ Currently this module support following situation: Known issues / Roadmap ====================== -* Add more support over different kind of product pack with - contract line +- Add more support over different kind of product pack with contract + line Bug Tracker =========== @@ -56,7 +57,7 @@ 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. @@ -64,18 +65,18 @@ Credits ======= Authors -~~~~~~~ +------- * Pierre Verkest * Contributors -~~~~~~~~~~~~ +------------ -* Pierre Verkest +- Pierre Verkest Maintainers -~~~~~~~~~~~ +----------- This module is maintained by the OCA. @@ -95,6 +96,6 @@ Current `maintainer `__: |maintainer-petrus-v| -This module is part of the `OCA/product-pack `_ project on GitHub. +This module is part of the `OCA/product-pack `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/sale_product_pack_contract/models/product_pack_line.py b/sale_product_pack_contract/models/product_pack_line.py index b567d65d7..779527b0d 100644 --- a/sale_product_pack_contract/models/product_pack_line.py +++ b/sale_product_pack_contract/models/product_pack_line.py @@ -26,13 +26,15 @@ def _check_contract_component(self): if component_contract_products.exists(): raise UserError( _( - "This pack '%s' contains components %r that are marked " + "This pack '%(product_name)s' contains components %(components)r that are marked " "to be contract products. At the moment contract component " "support only on detailed pack type and detailed " "component price pack." ) - % ( - pack_product.name, - component_contract_products.mapped("product_id.name"), - ) + % { + "product_name": pack_product.name, + "components": component_contract_products.mapped( + "product_id.name" + ), + } ) diff --git a/sale_product_pack_contract/pyproject.toml b/sale_product_pack_contract/pyproject.toml new file mode 100644 index 000000000..4231d0ccc --- /dev/null +++ b/sale_product_pack_contract/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/sale_product_pack_contract/readme/CONTRIBUTORS.md b/sale_product_pack_contract/readme/CONTRIBUTORS.md new file mode 100644 index 000000000..539619659 --- /dev/null +++ b/sale_product_pack_contract/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +- Pierre Verkest \ diff --git a/sale_product_pack_contract/readme/CONTRIBUTORS.rst b/sale_product_pack_contract/readme/CONTRIBUTORS.rst deleted file mode 100644 index 638be8663..000000000 --- a/sale_product_pack_contract/readme/CONTRIBUTORS.rst +++ /dev/null @@ -1 +0,0 @@ -* Pierre Verkest diff --git a/sale_product_pack_contract/readme/DESCRIPTION.rst b/sale_product_pack_contract/readme/DESCRIPTION.md similarity index 56% rename from sale_product_pack_contract/readme/DESCRIPTION.rst rename to sale_product_pack_contract/readme/DESCRIPTION.md index 10463f62b..211c43a5f 100644 --- a/sale_product_pack_contract/readme/DESCRIPTION.rst +++ b/sale_product_pack_contract/readme/DESCRIPTION.md @@ -1,10 +1,11 @@ -This module is glue module between *Sale Product Pack* and *Contract*'s modules. +This module is glue module between *Sale Product Pack* and *Contract*'s +modules. At the moment it mainly prevent to configure pack with weird configuration that would let user in unexpected situations. Currently this module support following situation: -* Pack is not a contract with at least one line of the pack is a - contract line, pack must be configured as `pack_type=detailed` and - `pack_component_price=detailed`. +- Pack is not a contract with at least one line of the pack is a + contract line, pack must be configured as pack_type=detailed and + pack_component_price=detailed. diff --git a/sale_product_pack_contract/readme/ROADMAP.md b/sale_product_pack_contract/readme/ROADMAP.md new file mode 100644 index 000000000..bb37ea9d9 --- /dev/null +++ b/sale_product_pack_contract/readme/ROADMAP.md @@ -0,0 +1,2 @@ +- Add more support over different kind of product pack with contract + line diff --git a/sale_product_pack_contract/readme/ROADMAP.rst b/sale_product_pack_contract/readme/ROADMAP.rst deleted file mode 100644 index 407e09595..000000000 --- a/sale_product_pack_contract/readme/ROADMAP.rst +++ /dev/null @@ -1,2 +0,0 @@ -* Add more support over different kind of product pack with - contract line diff --git a/sale_product_pack_contract/static/description/index.html b/sale_product_pack_contract/static/description/index.html index a29924537..31379e7c6 100644 --- a/sale_product_pack_contract/static/description/index.html +++ b/sale_product_pack_contract/static/description/index.html @@ -368,15 +368,16 @@

Sale Product Pack Contract

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! source digest: sha256:93384966ede1665e09b7a7d2da813b657e9a47953428b9d25248a47a98e6af1b !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

Beta License: AGPL-3 OCA/product-pack Translate me on Weblate Try me on Runboat

-

This module is glue module between Sale Product Pack and Contract’s modules.

+

Beta License: AGPL-3 OCA/product-pack Translate me on Weblate Try me on Runboat

+

This module is glue module between Sale Product Pack and Contract’s +modules.

At the moment it mainly prevent to configure pack with weird configuration that would let user in unexpected situations.

Currently this module support following situation:

  • Pack is not a contract with at least one line of the pack is a -contract line, pack must be configured as pack_type=detailed and -pack_component_price=detailed.
  • +contract line, pack must be configured as pack_type=detailed and +pack_component_price=detailed.

Table of contents

@@ -394,8 +395,8 @@

Sale Product Pack Contract

Known issues / Roadmap

    -
  • Add more support over different kind of product pack with -contract line
  • +
  • Add more support over different kind of product pack with contract +line
@@ -403,7 +404,7 @@

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.

@@ -430,7 +431,7 @@

Maintainers

promote its widespread use.

Current maintainer:

petrus-v

-

This module is part of the OCA/product-pack project on GitHub.

+

This module is part of the OCA/product-pack project on GitHub.

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

From ced4750d2a10d113340e36141ff25d2ba526d3af Mon Sep 17 00:00:00 2001 From: Damien Crier Date: Mon, 12 May 2025 15:01:43 +0200 Subject: [PATCH 5/5] [MIG] sale_product_pack_contract: Migration to 17.0 --- sale_product_pack_contract/__manifest__.py | 2 +- sale_product_pack_contract/models/product_pack_line.py | 6 +++--- .../tests/test_sale_product_pack_contract.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sale_product_pack_contract/__manifest__.py b/sale_product_pack_contract/__manifest__.py index 41c71f100..04f52a64c 100644 --- a/sale_product_pack_contract/__manifest__.py +++ b/sale_product_pack_contract/__manifest__.py @@ -3,7 +3,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). { "name": "Sale Product Pack Contract", - "version": "14.0.1.0.0", + "version": "17.0.1.0.0", "category": "Sales", "summary": "Glue module between sale product pack and product.", "website": "https://github.com/OCA/product-pack", diff --git a/sale_product_pack_contract/models/product_pack_line.py b/sale_product_pack_contract/models/product_pack_line.py index 779527b0d..9e4e3ab84 100644 --- a/sale_product_pack_contract/models/product_pack_line.py +++ b/sale_product_pack_contract/models/product_pack_line.py @@ -26,9 +26,9 @@ def _check_contract_component(self): if component_contract_products.exists(): raise UserError( _( - "This pack '%(product_name)s' contains components %(components)r that are marked " - "to be contract products. At the moment contract component " - "support only on detailed pack type and detailed " + "This pack '%(product_name)s' contains components %(components)r " + "that are marked to be contract products. At the moment contract " + "component support only on detailed pack type and detailed " "component price pack." ) % { diff --git a/sale_product_pack_contract/tests/test_sale_product_pack_contract.py b/sale_product_pack_contract/tests/test_sale_product_pack_contract.py index 198d3a878..384c8a233 100644 --- a/sale_product_pack_contract/tests/test_sale_product_pack_contract.py +++ b/sale_product_pack_contract/tests/test_sale_product_pack_contract.py @@ -3,10 +3,10 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from odoo import fields from odoo.exceptions import UserError -from odoo.tests import Form, SavepointCase +from odoo.tests import Form, TransactionCase -class TestSaleProductPackContract(SavepointCase): +class TestSaleProductPackContract(TransactionCase): @classmethod def setUpClass(cls): super().setUpClass()