From 09dcf000ed8a428354201479b67c9101cbec4642 Mon Sep 17 00:00:00 2001 From: Carmen Bianca BAKKER Date: Wed, 3 May 2023 11:26:37 +0200 Subject: [PATCH 1/3] Revert "[REM] remove obsolete module purchase_package_qty, replace by product_supplierinfo_qty_multiplier" This reverts commit 7126c71898c72ba91bd3df6b4087038b39a4758d. --- purchase_package_qty/README.rst | 75 +++++++++ purchase_package_qty/__init__.py | 1 + purchase_package_qty/__manifest__.py | 26 +++ .../demo/product_supplierinfo.xml | 25 +++ .../demo/product_template.xml | 18 +++ purchase_package_qty/demo/purchase_order.xml | 25 +++ purchase_package_qty/demo/res_groups.xml | 13 ++ purchase_package_qty/i18n/fr.po | 54 +++++++ .../migrations/12.0.2.0.1/pre-migration.py | 22 +++ purchase_package_qty/models/__init__.py | 2 + .../models/product_supplierinfo.py | 50 ++++++ .../models/purchase_order_line.py | 27 ++++ purchase_package_qty/readme/CONTRIBUTORS.rst | 2 + purchase_package_qty/readme/DESCRIPTION.rst | 18 +++ .../description/product_supplierinfo_form.png | Bin 0 -> 34323 bytes purchase_package_qty/tests/__init__.py | 1 + purchase_package_qty/tests/test_module.py | 152 ++++++++++++++++++ .../views/view_product_supplierinfo.xml | 34 ++++ .../odoo/addons/purchase_package_qty | 1 + setup/purchase_package_qty/setup.py | 6 + 20 files changed, 552 insertions(+) create mode 100644 purchase_package_qty/README.rst create mode 100644 purchase_package_qty/__init__.py create mode 100644 purchase_package_qty/__manifest__.py create mode 100644 purchase_package_qty/demo/product_supplierinfo.xml create mode 100644 purchase_package_qty/demo/product_template.xml create mode 100644 purchase_package_qty/demo/purchase_order.xml create mode 100644 purchase_package_qty/demo/res_groups.xml create mode 100644 purchase_package_qty/i18n/fr.po create mode 100644 purchase_package_qty/migrations/12.0.2.0.1/pre-migration.py create mode 100644 purchase_package_qty/models/__init__.py create mode 100644 purchase_package_qty/models/product_supplierinfo.py create mode 100644 purchase_package_qty/models/purchase_order_line.py create mode 100644 purchase_package_qty/readme/CONTRIBUTORS.rst create mode 100644 purchase_package_qty/readme/DESCRIPTION.rst create mode 100644 purchase_package_qty/static/description/product_supplierinfo_form.png create mode 100644 purchase_package_qty/tests/__init__.py create mode 100644 purchase_package_qty/tests/test_module.py create mode 100644 purchase_package_qty/views/view_product_supplierinfo.xml create mode 120000 setup/purchase_package_qty/odoo/addons/purchase_package_qty create mode 100644 setup/purchase_package_qty/setup.py diff --git a/purchase_package_qty/README.rst b/purchase_package_qty/README.rst new file mode 100644 index 00000000..77e236cb --- /dev/null +++ b/purchase_package_qty/README.rst @@ -0,0 +1,75 @@ +=========================== +Purchase - Package Quantity +=========================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-grap%2Fgrap--odoo--incubator-lightgray.png?logo=github + :target: https://github.com/grap/grap-odoo-incubator/tree/12.0/purchase_package_qty + :alt: grap/grap-odoo-incubator + +|badge1| |badge2| |badge3| + +This module extends the functionality of purchase module to support package +quantity. + +In the product supplierinfo, add a "Package Qty" field to register how many +purchase UoM of the product there are in the package the supplier uses. +All purchase lines for this product+supplier must have a quantity that is a +multiple of that package quantity. + +For example: + +* A supplier sells beers with a price per unit, thus the purchase UoM is PCE. +* The supplier put them in 6pcs boxes, and the purchaser have to buy a multiple + of 6. +* The supplier has a minimum quantity of 60. + +So the settings of the product will be the following : + +.. image:: https://raw.githubusercontent.com/grap/grap-odoo-incubator/12.0/purchase_package_qty/static/description/product_supplierinfo_form.png + +**Table of contents** + +.. contents:: + :local: + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* GRAP + +Contributors +~~~~~~~~~~~~ + +* Julien WESTE +* Sylvain LE GAL (https://www.twitter.com/legalsylvain) + +Maintainers +~~~~~~~~~~~ + +This module is part of the `grap/grap-odoo-incubator `_ project on GitHub. + +You are welcome to contribute. diff --git a/purchase_package_qty/__init__.py b/purchase_package_qty/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/purchase_package_qty/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/purchase_package_qty/__manifest__.py b/purchase_package_qty/__manifest__.py new file mode 100644 index 00000000..198f7c06 --- /dev/null +++ b/purchase_package_qty/__manifest__.py @@ -0,0 +1,26 @@ +# Copyright (C) 2013 - Today: GRAP (http://www.grap.coop) +# @author Julien WESTE +# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +{ + "name": "Purchase - Package Quantity", + "version": "12.0.1.1.3", + "category": "Purchase", + "author": "GRAP", + "website": "https://github.com/grap/grap-odoo-incubator", + "license": "AGPL-3", + "depends": [ + "purchase", + ], + "data": [ + "views/view_product_supplierinfo.xml", + ], + "demo": [ + "demo/res_groups.xml", + "demo/product_template.xml", + "demo/product_supplierinfo.xml", + "demo/purchase_order.xml", + ], + "installable": True, +} diff --git a/purchase_package_qty/demo/product_supplierinfo.xml b/purchase_package_qty/demo/product_supplierinfo.xml new file mode 100644 index 00000000..8b87c218 --- /dev/null +++ b/purchase_package_qty/demo/product_supplierinfo.xml @@ -0,0 +1,25 @@ + + + + + + + 6 + 60 + 12.0 + + + + + + 0 + 0 + 2.25 + + + + diff --git a/purchase_package_qty/demo/product_template.xml b/purchase_package_qty/demo/product_template.xml new file mode 100644 index 00000000..2ce65e9d --- /dev/null +++ b/purchase_package_qty/demo/product_template.xml @@ -0,0 +1,18 @@ + + + + + + Beers (package x6) + + + + + + + + diff --git a/purchase_package_qty/demo/purchase_order.xml b/purchase_package_qty/demo/purchase_order.xml new file mode 100644 index 00000000..6d847363 --- /dev/null +++ b/purchase_package_qty/demo/purchase_order.xml @@ -0,0 +1,25 @@ + + + + + + + Purchase Order (Package) + + + + + + Beers (package x6) + + + 12 + 20 + 1970-01-01 + + + diff --git a/purchase_package_qty/demo/res_groups.xml b/purchase_package_qty/demo/res_groups.xml new file mode 100644 index 00000000..81f676dc --- /dev/null +++ b/purchase_package_qty/demo/res_groups.xml @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/purchase_package_qty/i18n/fr.po b/purchase_package_qty/i18n/fr.po new file mode 100644 index 00000000..639b152a --- /dev/null +++ b/purchase_package_qty/i18n/fr.po @@ -0,0 +1,54 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * purchase_package_qty +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-11-22 13:18+0000\n" +"PO-Revision-Date: 2019-11-22 13:18+0000\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: purchase_package_qty +#: model:product.product,name:purchase_package_qty.product_product_package_6 +#: model:product.template,name:purchase_package_qty.product_product_package_6_product_template +msgid "Beers (package x6)" +msgstr "Bières (x 6)" + +#. module: purchase_package_qty +#: model:ir.model.fields,field_description:purchase_package_qty.field_product_supplierinfo__package_qty +msgid "Package Quantity" +msgstr "Quantité de Conditionnement" + +#. module: purchase_package_qty +#: model:ir.model,name:purchase_package_qty.model_purchase_order_line +msgid "Purchase Order Line" +msgstr "Ligne de commande d'achat" + +#. module: purchase_package_qty +#: model:ir.model,name:purchase_package_qty.model_product_supplierinfo +msgid "Supplier Pricelist" +msgstr "Liste de prix du fournisseur" + +#. module: purchase_package_qty +#: model:ir.model.fields,help:purchase_package_qty.field_product_supplierinfo__package_qty +msgid "The quantity of products in the supplier package. You will always have to buy a multiple of this quantity." +msgstr "La quantité de produit conditionnée par le fournisseur. Vous devez toujours acheter un multiple de cette quantité." + +#. module: purchase_package_qty +#: model:product.product,uom_name:purchase_package_qty.product_product_package_6 +#: model:product.template,uom_name:purchase_package_qty.product_product_package_6_product_template +msgid "Unit(s)" +msgstr "Unité(s)" + +#. module: purchase_package_qty +#: model:product.product,weight_uom_name:purchase_package_qty.product_product_package_6 +#: model:product.template,weight_uom_name:purchase_package_qty.product_product_package_6_product_template +msgid "kg" +msgstr "kg" diff --git a/purchase_package_qty/migrations/12.0.2.0.1/pre-migration.py b/purchase_package_qty/migrations/12.0.2.0.1/pre-migration.py new file mode 100644 index 00000000..59b439f7 --- /dev/null +++ b/purchase_package_qty/migrations/12.0.2.0.1/pre-migration.py @@ -0,0 +1,22 @@ +# Copyright (C) 2023 - Today: GRAP (http://www.grap.coop) +# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openupgradelib import openupgrade + + +def migrate(cr, version): + if not openupgrade.column_exists(cr, "product_supplierinfo", "multiplier_qty"): + cr.execute( + """ + ALTER TABLE product_supplierinfo + ADD COLUMN multiplier_qty double precision; + """ + ) + + cr.execute( + """ + UPDATE product_supplierinfo + SET multiplier_qty = package_qty; + """ + ) diff --git a/purchase_package_qty/models/__init__.py b/purchase_package_qty/models/__init__.py new file mode 100644 index 00000000..78f8d69f --- /dev/null +++ b/purchase_package_qty/models/__init__.py @@ -0,0 +1,2 @@ +from . import product_supplierinfo +from . import purchase_order_line diff --git a/purchase_package_qty/models/product_supplierinfo.py b/purchase_package_qty/models/product_supplierinfo.py new file mode 100644 index 00000000..b218c4c2 --- /dev/null +++ b/purchase_package_qty/models/product_supplierinfo.py @@ -0,0 +1,50 @@ +# Copyright (C) 2013 - Today: GRAP (http://www.grap.coop) +# @author Julien WESTE +# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from math import ceil + +from odoo import api, fields, models + + +class ProductSupplierinfo(models.Model): + _inherit = "product.supplierinfo" + + package_qty = fields.Float( + string="Package Quantity", + default=0, + help="The quantity of products in the supplier package." + " You will always have to buy a multiple of this quantity.", + ) + + @api.multi + def _get_quantity_according_package(self, product_qty, uom): + self.ensure_one() + # For the time being, Odoo is limited and doesn't + # recover seller if the unit of the purchase order line is different + # from the unit of the supplierinfo. + # this function should be improved using uom arg, + # when Odoo purchase module will be improved. (>12.0) + if self.package_qty and product_qty % self.package_qty: + return ceil(product_qty / self.package_qty) * self.package_qty + else: + return product_qty + + @api.onchange("package_qty") + def onchange_package_qty(self): + if self.package_qty: + if self.package_qty > self.min_qty: + self.min_qty = self.package_qty + elif self.min_qty % self.package_qty: + # check if min_qty is a multiple of package_qty + self.min_qty = ceil(self.min_qty // self.package_qty) * self.package_qty + + @api.onchange("min_qty") + def onchange_min_qty(self): + if self.package_qty: + if self.package_qty > self.min_qty: + self.package_qty = self.min_qty + elif self.min_qty % self.package_qty: + # check if min_qty is a multiple of package_qty + self.min_qty = ceil(self.min_qty / self.package_qty) * self.package_qty diff --git a/purchase_package_qty/models/purchase_order_line.py b/purchase_package_qty/models/purchase_order_line.py new file mode 100644 index 00000000..869d4be1 --- /dev/null +++ b/purchase_package_qty/models/purchase_order_line.py @@ -0,0 +1,27 @@ +# Copyright (C) 2013 - Today: GRAP (http://www.grap.coop) +# @author Julien WESTE +# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import api, models + + +class PurchaseOrderLine(models.Model): + _inherit = "purchase.order.line" + + @api.onchange("product_qty", "product_uom") + def _onchange_quantity(self): + if not self.product_id: + return + seller = self.product_id._select_seller( + partner_id=self.partner_id, + quantity=self.product_qty, + date=self.order_id.date_order and self.order_id.date_order.date(), + uom_id=self.product_uom, + params={"order_id": self.order_id}, + ) + if seller: + self.product_qty = seller._get_quantity_according_package( + self.product_qty, self.product_uom + ) + return super()._onchange_quantity() diff --git a/purchase_package_qty/readme/CONTRIBUTORS.rst b/purchase_package_qty/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000..90d26307 --- /dev/null +++ b/purchase_package_qty/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +* Julien WESTE +* Sylvain LE GAL (https://www.twitter.com/legalsylvain) diff --git a/purchase_package_qty/readme/DESCRIPTION.rst b/purchase_package_qty/readme/DESCRIPTION.rst new file mode 100644 index 00000000..b83e683c --- /dev/null +++ b/purchase_package_qty/readme/DESCRIPTION.rst @@ -0,0 +1,18 @@ +This module extends the functionality of purchase module to support package +quantity. + +In the product supplierinfo, add a "Package Qty" field to register how many +purchase UoM of the product there are in the package the supplier uses. +All purchase lines for this product+supplier must have a quantity that is a +multiple of that package quantity. + +For example: + +* A supplier sells beers with a price per unit, thus the purchase UoM is PCE. +* The supplier put them in 6pcs boxes, and the purchaser have to buy a multiple + of 6. +* The supplier has a minimum quantity of 60. + +So the settings of the product will be the following : + +.. image:: ../static/description/product_supplierinfo_form.png diff --git a/purchase_package_qty/static/description/product_supplierinfo_form.png b/purchase_package_qty/static/description/product_supplierinfo_form.png new file mode 100644 index 0000000000000000000000000000000000000000..03714e55bc89c0c488bf9d6085236109a5920347 GIT binary patch literal 34323 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 literal 0 HcmV?d00001 diff --git a/purchase_package_qty/tests/__init__.py b/purchase_package_qty/tests/__init__.py new file mode 100644 index 00000000..d9b96c4f --- /dev/null +++ b/purchase_package_qty/tests/__init__.py @@ -0,0 +1 @@ +from . import test_module diff --git a/purchase_package_qty/tests/test_module.py b/purchase_package_qty/tests/test_module.py new file mode 100644 index 00000000..58f85142 --- /dev/null +++ b/purchase_package_qty/tests/test_module.py @@ -0,0 +1,152 @@ +# Copyright (C) 2018 - Today: GRAP (http://www.grap.coop) +# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo.tests.common import TransactionCase + + +class TestModule(TransactionCase): + def setUp(self): + super().setUp() + + self.unit_uom = self.env.ref("uom.product_uom_unit") + self.supplier_with_package = self.env.ref("base.res_partner_1") + self.supplier_without_package = self.env.ref("base.res_partner_3") + self.product = self.env.ref("purchase_package_qty.product_product_package_6") + self.purchase_order = self.env.ref("purchase_package_qty.purchase_order") + self.purchase_order_line = self.env.ref( + "purchase_package_qty.purchase_order_line" + ) + self.supplierinfo = self.env.ref("purchase_package_qty.supplierinfo_supplier_1") + + def test_01_supplierinfo_change_min_qty_lower(self): + # If we set min_qty to 3, the package_qty should be down to + # 3 also + self.supplierinfo.write( + { + "min_qty": 3, + } + ) + + self.supplierinfo.onchange_min_qty() + self.assertEqual( + self.supplierinfo.min_qty, + self.supplierinfo.package_qty, + "set a min_qty lower than package_qty should decrease package_qty", + ) + + self.supplierinfo.write( + { + "min_qty": 0, + } + ) + + self.supplierinfo.onchange_min_qty() + self.assertEqual( + self.supplierinfo.min_qty, + self.supplierinfo.package_qty, + "Set no min_qty should set no package_qty", + ) + + def test_01_supplierinfo_change_min_qty_not_rounded(self): + # If we set min_qty to 601, with a package of 6, the min_qty should + # be rounded to 606 + self.supplierinfo.write( + { + "min_qty": 601, + } + ) + + self.supplierinfo.onchange_min_qty() + self.assertEqual( + self.supplierinfo.min_qty, + 606, + "set a min_qty not a multiple of package_qty should round" + " the min quantity.", + ) + + def test_03_supplierinfo_change_package_qty_upper(self): + # If we set package_qty to 70, the min_qty should be up to + # 70 also. + self.supplierinfo.write( + { + "package_qty": 70, + } + ) + + self.supplierinfo.onchange_package_qty() + self.assertEqual( + self.supplierinfo.min_qty, + self.supplierinfo.package_qty, + "set a package_qty greather than min_qty should increase min_qty", + ) + + def test_04_supplierinfo_change_package_qty_lower_not_rounded(self): + # If we set package_qty to 25, the min_qty should be down to 50. + self.supplierinfo.write( + { + "package_qty": 25, + } + ) + + self.supplierinfo.onchange_package_qty() + self.assertEqual( + self.supplierinfo.min_qty, + 50, + "set a package_qty lower than the min_qty should round" " the min_qty", + ) + + def test_05_supplierinfo_change_package_qty_lower_rounded(self): + # If we set package_qty to 30, the min_qty should not change. + self.supplierinfo.write( + { + "package_qty": 30, + } + ) + + self.supplierinfo.onchange_package_qty() + self.assertEqual( + self.supplierinfo.min_qty, + 60, + "set a package_qty lower than the min_qty should not change" + " the min_qty if the packaging is ok.", + ) + + def test_06_supplierinfo_change_package_qty_null(self): + # If we unset package_qty, min_qty should not change. + self.supplierinfo.write( + { + "package_qty": 0, + } + ) + + self.supplierinfo.onchange_package_qty() + self.assertEqual( + self.supplierinfo.min_qty, + 60, + "Unset package_qty should not change min_qty.", + ) + + def test_10_purchase_order_line_onchange_package_strict(self): + # [Functional Test] Check if onchange function changes + # quantity (strict context) + self.purchase_order.partner_id = self.supplier_with_package + self.purchase_order_line.product_qty = 119 + self.purchase_order_line._onchange_quantity() + self.assertEqual( + self.purchase_order_line.product_qty, + 120, + "On change with package defined should round quantity", + ) + + def test_11_purchase_order_line_onchange_no_package(self): + # [Functional Test] Check if onchange function doesn't change + # quantity (no package context) + self.purchase_order.partner_id = self.supplier_without_package + self.purchase_order_line.product_qty = 5 + self.purchase_order_line._onchange_quantity() + self.assertEqual( + self.purchase_order_line.product_qty, + 5, + "On change without package defined should not change quantity", + ) diff --git a/purchase_package_qty/views/view_product_supplierinfo.xml b/purchase_package_qty/views/view_product_supplierinfo.xml new file mode 100644 index 00000000..4b9e2e1d --- /dev/null +++ b/purchase_package_qty/views/view_product_supplierinfo.xml @@ -0,0 +1,34 @@ + + + + + + product.supplierinfo + + + + + + + + + product.supplierinfo + + + + + + + + + diff --git a/setup/purchase_package_qty/odoo/addons/purchase_package_qty b/setup/purchase_package_qty/odoo/addons/purchase_package_qty new file mode 120000 index 00000000..fa97bdbf --- /dev/null +++ b/setup/purchase_package_qty/odoo/addons/purchase_package_qty @@ -0,0 +1 @@ +../../../../purchase_package_qty \ No newline at end of file diff --git a/setup/purchase_package_qty/setup.py b/setup/purchase_package_qty/setup.py new file mode 100644 index 00000000..28c57bb6 --- /dev/null +++ b/setup/purchase_package_qty/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) From a40c95f1603e4670f894c75287846269164de75f Mon Sep 17 00:00:00 2001 From: Carmen Bianca BAKKER Date: Wed, 3 May 2023 11:30:55 +0200 Subject: [PATCH 2/3] Clear out module Signed-off-by: Carmen Bianca BAKKER --- purchase_package_qty/__init__.py | 1 - purchase_package_qty/__manifest__.py | 9 +- .../demo/product_supplierinfo.xml | 25 --- .../demo/product_template.xml | 18 --- purchase_package_qty/demo/purchase_order.xml | 25 --- purchase_package_qty/demo/res_groups.xml | 13 -- purchase_package_qty/i18n/fr.po | 54 ------- purchase_package_qty/models/__init__.py | 2 - .../models/product_supplierinfo.py | 50 ------ .../models/purchase_order_line.py | 27 ---- purchase_package_qty/tests/__init__.py | 1 - purchase_package_qty/tests/test_module.py | 152 ------------------ .../views/view_product_supplierinfo.xml | 34 ---- 13 files changed, 3 insertions(+), 408 deletions(-) delete mode 100644 purchase_package_qty/demo/product_supplierinfo.xml delete mode 100644 purchase_package_qty/demo/product_template.xml delete mode 100644 purchase_package_qty/demo/purchase_order.xml delete mode 100644 purchase_package_qty/demo/res_groups.xml delete mode 100644 purchase_package_qty/i18n/fr.po delete mode 100644 purchase_package_qty/models/__init__.py delete mode 100644 purchase_package_qty/models/product_supplierinfo.py delete mode 100644 purchase_package_qty/models/purchase_order_line.py delete mode 100644 purchase_package_qty/tests/__init__.py delete mode 100644 purchase_package_qty/tests/test_module.py delete mode 100644 purchase_package_qty/views/view_product_supplierinfo.xml diff --git a/purchase_package_qty/__init__.py b/purchase_package_qty/__init__.py index 0650744f..e69de29b 100644 --- a/purchase_package_qty/__init__.py +++ b/purchase_package_qty/__init__.py @@ -1 +0,0 @@ -from . import models diff --git a/purchase_package_qty/__manifest__.py b/purchase_package_qty/__manifest__.py index 198f7c06..6e12534b 100644 --- a/purchase_package_qty/__manifest__.py +++ b/purchase_package_qty/__manifest__.py @@ -5,22 +5,19 @@ { "name": "Purchase - Package Quantity", - "version": "12.0.1.1.3", + "version": "12.0.2.0.1", "category": "Purchase", "author": "GRAP", "website": "https://github.com/grap/grap-odoo-incubator", "license": "AGPL-3", "depends": [ "purchase", + # Depend on our replacement module. + "product_supplierinfo_qty_multiplier", ], "data": [ - "views/view_product_supplierinfo.xml", ], "demo": [ - "demo/res_groups.xml", - "demo/product_template.xml", - "demo/product_supplierinfo.xml", - "demo/purchase_order.xml", ], "installable": True, } diff --git a/purchase_package_qty/demo/product_supplierinfo.xml b/purchase_package_qty/demo/product_supplierinfo.xml deleted file mode 100644 index 8b87c218..00000000 --- a/purchase_package_qty/demo/product_supplierinfo.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - 6 - 60 - 12.0 - - - - - - 0 - 0 - 2.25 - - - - diff --git a/purchase_package_qty/demo/product_template.xml b/purchase_package_qty/demo/product_template.xml deleted file mode 100644 index 2ce65e9d..00000000 --- a/purchase_package_qty/demo/product_template.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - Beers (package x6) - - - - - - - - diff --git a/purchase_package_qty/demo/purchase_order.xml b/purchase_package_qty/demo/purchase_order.xml deleted file mode 100644 index 6d847363..00000000 --- a/purchase_package_qty/demo/purchase_order.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - Purchase Order (Package) - - - - - - Beers (package x6) - - - 12 - 20 - 1970-01-01 - - - diff --git a/purchase_package_qty/demo/res_groups.xml b/purchase_package_qty/demo/res_groups.xml deleted file mode 100644 index 81f676dc..00000000 --- a/purchase_package_qty/demo/res_groups.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - diff --git a/purchase_package_qty/i18n/fr.po b/purchase_package_qty/i18n/fr.po deleted file mode 100644 index 639b152a..00000000 --- a/purchase_package_qty/i18n/fr.po +++ /dev/null @@ -1,54 +0,0 @@ -# Translation of Odoo Server. -# This file contains the translation of the following modules: -# * purchase_package_qty -# -msgid "" -msgstr "" -"Project-Id-Version: Odoo Server 12.0\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-11-22 13:18+0000\n" -"PO-Revision-Date: 2019-11-22 13:18+0000\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: purchase_package_qty -#: model:product.product,name:purchase_package_qty.product_product_package_6 -#: model:product.template,name:purchase_package_qty.product_product_package_6_product_template -msgid "Beers (package x6)" -msgstr "Bières (x 6)" - -#. module: purchase_package_qty -#: model:ir.model.fields,field_description:purchase_package_qty.field_product_supplierinfo__package_qty -msgid "Package Quantity" -msgstr "Quantité de Conditionnement" - -#. module: purchase_package_qty -#: model:ir.model,name:purchase_package_qty.model_purchase_order_line -msgid "Purchase Order Line" -msgstr "Ligne de commande d'achat" - -#. module: purchase_package_qty -#: model:ir.model,name:purchase_package_qty.model_product_supplierinfo -msgid "Supplier Pricelist" -msgstr "Liste de prix du fournisseur" - -#. module: purchase_package_qty -#: model:ir.model.fields,help:purchase_package_qty.field_product_supplierinfo__package_qty -msgid "The quantity of products in the supplier package. You will always have to buy a multiple of this quantity." -msgstr "La quantité de produit conditionnée par le fournisseur. Vous devez toujours acheter un multiple de cette quantité." - -#. module: purchase_package_qty -#: model:product.product,uom_name:purchase_package_qty.product_product_package_6 -#: model:product.template,uom_name:purchase_package_qty.product_product_package_6_product_template -msgid "Unit(s)" -msgstr "Unité(s)" - -#. module: purchase_package_qty -#: model:product.product,weight_uom_name:purchase_package_qty.product_product_package_6 -#: model:product.template,weight_uom_name:purchase_package_qty.product_product_package_6_product_template -msgid "kg" -msgstr "kg" diff --git a/purchase_package_qty/models/__init__.py b/purchase_package_qty/models/__init__.py deleted file mode 100644 index 78f8d69f..00000000 --- a/purchase_package_qty/models/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from . import product_supplierinfo -from . import purchase_order_line diff --git a/purchase_package_qty/models/product_supplierinfo.py b/purchase_package_qty/models/product_supplierinfo.py deleted file mode 100644 index b218c4c2..00000000 --- a/purchase_package_qty/models/product_supplierinfo.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright (C) 2013 - Today: GRAP (http://www.grap.coop) -# @author Julien WESTE -# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). - -from math import ceil - -from odoo import api, fields, models - - -class ProductSupplierinfo(models.Model): - _inherit = "product.supplierinfo" - - package_qty = fields.Float( - string="Package Quantity", - default=0, - help="The quantity of products in the supplier package." - " You will always have to buy a multiple of this quantity.", - ) - - @api.multi - def _get_quantity_according_package(self, product_qty, uom): - self.ensure_one() - # For the time being, Odoo is limited and doesn't - # recover seller if the unit of the purchase order line is different - # from the unit of the supplierinfo. - # this function should be improved using uom arg, - # when Odoo purchase module will be improved. (>12.0) - if self.package_qty and product_qty % self.package_qty: - return ceil(product_qty / self.package_qty) * self.package_qty - else: - return product_qty - - @api.onchange("package_qty") - def onchange_package_qty(self): - if self.package_qty: - if self.package_qty > self.min_qty: - self.min_qty = self.package_qty - elif self.min_qty % self.package_qty: - # check if min_qty is a multiple of package_qty - self.min_qty = ceil(self.min_qty // self.package_qty) * self.package_qty - - @api.onchange("min_qty") - def onchange_min_qty(self): - if self.package_qty: - if self.package_qty > self.min_qty: - self.package_qty = self.min_qty - elif self.min_qty % self.package_qty: - # check if min_qty is a multiple of package_qty - self.min_qty = ceil(self.min_qty / self.package_qty) * self.package_qty diff --git a/purchase_package_qty/models/purchase_order_line.py b/purchase_package_qty/models/purchase_order_line.py deleted file mode 100644 index 869d4be1..00000000 --- a/purchase_package_qty/models/purchase_order_line.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright (C) 2013 - Today: GRAP (http://www.grap.coop) -# @author Julien WESTE -# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). - -from odoo import api, models - - -class PurchaseOrderLine(models.Model): - _inherit = "purchase.order.line" - - @api.onchange("product_qty", "product_uom") - def _onchange_quantity(self): - if not self.product_id: - return - seller = self.product_id._select_seller( - partner_id=self.partner_id, - quantity=self.product_qty, - date=self.order_id.date_order and self.order_id.date_order.date(), - uom_id=self.product_uom, - params={"order_id": self.order_id}, - ) - if seller: - self.product_qty = seller._get_quantity_according_package( - self.product_qty, self.product_uom - ) - return super()._onchange_quantity() diff --git a/purchase_package_qty/tests/__init__.py b/purchase_package_qty/tests/__init__.py deleted file mode 100644 index d9b96c4f..00000000 --- a/purchase_package_qty/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import test_module diff --git a/purchase_package_qty/tests/test_module.py b/purchase_package_qty/tests/test_module.py deleted file mode 100644 index 58f85142..00000000 --- a/purchase_package_qty/tests/test_module.py +++ /dev/null @@ -1,152 +0,0 @@ -# Copyright (C) 2018 - Today: GRAP (http://www.grap.coop) -# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). - -from odoo.tests.common import TransactionCase - - -class TestModule(TransactionCase): - def setUp(self): - super().setUp() - - self.unit_uom = self.env.ref("uom.product_uom_unit") - self.supplier_with_package = self.env.ref("base.res_partner_1") - self.supplier_without_package = self.env.ref("base.res_partner_3") - self.product = self.env.ref("purchase_package_qty.product_product_package_6") - self.purchase_order = self.env.ref("purchase_package_qty.purchase_order") - self.purchase_order_line = self.env.ref( - "purchase_package_qty.purchase_order_line" - ) - self.supplierinfo = self.env.ref("purchase_package_qty.supplierinfo_supplier_1") - - def test_01_supplierinfo_change_min_qty_lower(self): - # If we set min_qty to 3, the package_qty should be down to - # 3 also - self.supplierinfo.write( - { - "min_qty": 3, - } - ) - - self.supplierinfo.onchange_min_qty() - self.assertEqual( - self.supplierinfo.min_qty, - self.supplierinfo.package_qty, - "set a min_qty lower than package_qty should decrease package_qty", - ) - - self.supplierinfo.write( - { - "min_qty": 0, - } - ) - - self.supplierinfo.onchange_min_qty() - self.assertEqual( - self.supplierinfo.min_qty, - self.supplierinfo.package_qty, - "Set no min_qty should set no package_qty", - ) - - def test_01_supplierinfo_change_min_qty_not_rounded(self): - # If we set min_qty to 601, with a package of 6, the min_qty should - # be rounded to 606 - self.supplierinfo.write( - { - "min_qty": 601, - } - ) - - self.supplierinfo.onchange_min_qty() - self.assertEqual( - self.supplierinfo.min_qty, - 606, - "set a min_qty not a multiple of package_qty should round" - " the min quantity.", - ) - - def test_03_supplierinfo_change_package_qty_upper(self): - # If we set package_qty to 70, the min_qty should be up to - # 70 also. - self.supplierinfo.write( - { - "package_qty": 70, - } - ) - - self.supplierinfo.onchange_package_qty() - self.assertEqual( - self.supplierinfo.min_qty, - self.supplierinfo.package_qty, - "set a package_qty greather than min_qty should increase min_qty", - ) - - def test_04_supplierinfo_change_package_qty_lower_not_rounded(self): - # If we set package_qty to 25, the min_qty should be down to 50. - self.supplierinfo.write( - { - "package_qty": 25, - } - ) - - self.supplierinfo.onchange_package_qty() - self.assertEqual( - self.supplierinfo.min_qty, - 50, - "set a package_qty lower than the min_qty should round" " the min_qty", - ) - - def test_05_supplierinfo_change_package_qty_lower_rounded(self): - # If we set package_qty to 30, the min_qty should not change. - self.supplierinfo.write( - { - "package_qty": 30, - } - ) - - self.supplierinfo.onchange_package_qty() - self.assertEqual( - self.supplierinfo.min_qty, - 60, - "set a package_qty lower than the min_qty should not change" - " the min_qty if the packaging is ok.", - ) - - def test_06_supplierinfo_change_package_qty_null(self): - # If we unset package_qty, min_qty should not change. - self.supplierinfo.write( - { - "package_qty": 0, - } - ) - - self.supplierinfo.onchange_package_qty() - self.assertEqual( - self.supplierinfo.min_qty, - 60, - "Unset package_qty should not change min_qty.", - ) - - def test_10_purchase_order_line_onchange_package_strict(self): - # [Functional Test] Check if onchange function changes - # quantity (strict context) - self.purchase_order.partner_id = self.supplier_with_package - self.purchase_order_line.product_qty = 119 - self.purchase_order_line._onchange_quantity() - self.assertEqual( - self.purchase_order_line.product_qty, - 120, - "On change with package defined should round quantity", - ) - - def test_11_purchase_order_line_onchange_no_package(self): - # [Functional Test] Check if onchange function doesn't change - # quantity (no package context) - self.purchase_order.partner_id = self.supplier_without_package - self.purchase_order_line.product_qty = 5 - self.purchase_order_line._onchange_quantity() - self.assertEqual( - self.purchase_order_line.product_qty, - 5, - "On change without package defined should not change quantity", - ) diff --git a/purchase_package_qty/views/view_product_supplierinfo.xml b/purchase_package_qty/views/view_product_supplierinfo.xml deleted file mode 100644 index 4b9e2e1d..00000000 --- a/purchase_package_qty/views/view_product_supplierinfo.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - product.supplierinfo - - - - - - - - - product.supplierinfo - - - - - - - - - From 3204af91bd83339fc8136da07f149a78432293c2 Mon Sep 17 00:00:00 2001 From: hugues de keyzer Date: Fri, 23 Jun 2023 14:43:17 +0200 Subject: [PATCH 3/3] [IMP] remove useless keys in manifest --- purchase_package_qty/__manifest__.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/purchase_package_qty/__manifest__.py b/purchase_package_qty/__manifest__.py index 6e12534b..120540ca 100644 --- a/purchase_package_qty/__manifest__.py +++ b/purchase_package_qty/__manifest__.py @@ -15,9 +15,4 @@ # Depend on our replacement module. "product_supplierinfo_qty_multiplier", ], - "data": [ - ], - "demo": [ - ], - "installable": True, }