From 66bd81234529a0f2fd557f922edef2d0856610f7 Mon Sep 17 00:00:00 2001 From: Mourad Date: Thu, 18 Jun 2020 15:55:44 +0200 Subject: [PATCH 01/27] [IMP] rename module sale_order_min_qty to sale_restricted_qty --- sale_restricted_qty/README.rst | 89 ++++ sale_restricted_qty/__init__.py | 2 + sale_restricted_qty/__manifest__.py | 20 + sale_restricted_qty/hooks.py | 19 + .../i18n/sale_order_min_qty.pot | 82 ++++ sale_restricted_qty/models/__init__.py | 5 + .../models/product_category.py | 48 ++ sale_restricted_qty/models/product_product.py | 51 ++ .../models/product_restricted_qty_mixin.py | 105 +++++ .../models/product_template.py | 49 ++ sale_restricted_qty/models/sale.py | 147 ++++++ sale_restricted_qty/readme/CONTRIBUTORS.rst | 1 + sale_restricted_qty/readme/DESCRIPTION.rst | 11 + sale_restricted_qty/readme/USAGE.rst | 7 + .../static/description/icon.png | Bin 0 -> 9455 bytes .../static/description/index.html | 443 ++++++++++++++++++ sale_restricted_qty/tests/__init__.py | 1 + sale_restricted_qty/tests/test_sale.py | 172 +++++++ .../views/product_category_views.xml | 23 + .../views/product_product_views.xml | 25 + .../views/product_template_views.xml | 24 + sale_restricted_qty/views/sale_views.xml | 41 ++ 22 files changed, 1365 insertions(+) create mode 100644 sale_restricted_qty/README.rst create mode 100644 sale_restricted_qty/__init__.py create mode 100644 sale_restricted_qty/__manifest__.py create mode 100644 sale_restricted_qty/hooks.py create mode 100644 sale_restricted_qty/i18n/sale_order_min_qty.pot create mode 100644 sale_restricted_qty/models/__init__.py create mode 100644 sale_restricted_qty/models/product_category.py create mode 100644 sale_restricted_qty/models/product_product.py create mode 100644 sale_restricted_qty/models/product_restricted_qty_mixin.py create mode 100644 sale_restricted_qty/models/product_template.py create mode 100644 sale_restricted_qty/models/sale.py create mode 100644 sale_restricted_qty/readme/CONTRIBUTORS.rst create mode 100644 sale_restricted_qty/readme/DESCRIPTION.rst create mode 100644 sale_restricted_qty/readme/USAGE.rst create mode 100644 sale_restricted_qty/static/description/icon.png create mode 100644 sale_restricted_qty/static/description/index.html create mode 100644 sale_restricted_qty/tests/__init__.py create mode 100644 sale_restricted_qty/tests/test_sale.py create mode 100644 sale_restricted_qty/views/product_category_views.xml create mode 100644 sale_restricted_qty/views/product_product_views.xml create mode 100644 sale_restricted_qty/views/product_template_views.xml create mode 100644 sale_restricted_qty/views/sale_views.xml diff --git a/sale_restricted_qty/README.rst b/sale_restricted_qty/README.rst new file mode 100644 index 00000000000..7949177ad2d --- /dev/null +++ b/sale_restricted_qty/README.rst @@ -0,0 +1,89 @@ +======================= +Sale order min 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-OCA%2Fsale--workflow-lightgray.png?logo=github + :target: https://github.com/OCA/sale-workflow/tree/12.0/sale_order_min_qty + :alt: OCA/sale-workflow +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/sale-workflow-12-0/sale-workflow-12-0-sale_order_min_qty + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/167/12.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows to define minimum order quantity on product. +Minimum order quantity is either required or recommanded. +If you check "Force min Qty" on product the min qty do not block sale +order. +This can be used if the available stock is less then the min qty +or if you want to sell sample products. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +#. Go to **Sales > Products > Product Variants** and Set "Min Sale Qty". + If you dont want block product sale if qty is less the "Min Sale Qty", + chek "Force Min Qty" +#. Create a sale order and add a sale order line with this product and check + if Qty is less then "Min Qty" A message in displayed "Min Qty Required" + Or "Min Qty Recommanded". Check if Min qty is required you can't + validate order. + +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 +~~~~~~~ + +* Akretion + +Contributors +~~~~~~~~~~~~ + +* Mourad EL HADJ MIMOUN + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/sale-workflow `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/sale_restricted_qty/__init__.py b/sale_restricted_qty/__init__.py new file mode 100644 index 00000000000..8e0862219aa --- /dev/null +++ b/sale_restricted_qty/__init__.py @@ -0,0 +1,2 @@ +from . import models +from .hooks import rename_module diff --git a/sale_restricted_qty/__manifest__.py b/sale_restricted_qty/__manifest__.py new file mode 100644 index 00000000000..a686c872f4d --- /dev/null +++ b/sale_restricted_qty/__manifest__.py @@ -0,0 +1,20 @@ +# Copyright 2019 Akretion () +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Sale order min quantity", + "version": "12.0.1.0.0", + "category": "Sales Management", + "author": "Akretion, " "Odoo Community Association (OCA)", + "website": "https://github.com/OCA/sale-workflow/", + "license": "AGPL-3", + "depends": ["sale_management"], + "data": [ + "views/product_category_views.xml", + "views/product_template_views.xml", + "views/product_product_views.xml", + "views/sale_views.xml", + ], + "pre_init_hook": "rename_module", + "installable": True, +} diff --git a/sale_restricted_qty/hooks.py b/sale_restricted_qty/hooks.py new file mode 100644 index 00000000000..2a0f812af7d --- /dev/null +++ b/sale_restricted_qty/hooks.py @@ -0,0 +1,19 @@ +# Copyright 2019 Akretion +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import logging + +_logger = logging.getLogger(__name__) + +try: + from openupgradelib import openupgrade +except (ImportError, IOError) as err: + _logger.debug(err) + + +def rename_module(cr): + openupgrade.update_module_names( + cr, + [("sale_order_min_qty", "sale_restricted_qty")], + merge_modules=True, + ) diff --git a/sale_restricted_qty/i18n/sale_order_min_qty.pot b/sale_restricted_qty/i18n/sale_order_min_qty.pot new file mode 100644 index 00000000000..1400edffa5d --- /dev/null +++ b/sale_restricted_qty/i18n/sale_order_min_qty.pot @@ -0,0 +1,82 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_order_min_qty +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: sale_order_min_qty +#: code:addons/sale_order_min_qty/models/sale.py:51 +#, python-format +msgid "\n" +"* If you want sell quantity less than Min Quantity,Check \"force min quatity\" on product" +msgstr "" + +#. module: sale_order_min_qty +#: code:addons/sale_order_min_qty/models/sale.py:48 +#, python-format +msgid "Check minimum order quantity for this products: * \n" +"" +msgstr "" + +#. module: sale_order_min_qty +#: model:ir.model.fields,field_description:sale_order_min_qty.field_product_product__force_sale_min_qty +#: model:ir.model.fields,field_description:sale_order_min_qty.field_sale_order_line__force_sale_min_qty +msgid "Force Min Qty" +msgstr "" + +#. module: sale_order_min_qty +#: model:ir.model.fields,help:sale_order_min_qty.field_product_product__force_sale_min_qty +#: model:ir.model.fields,help:sale_order_min_qty.field_sale_order_line__force_sale_min_qty +msgid "If force min qty is checked, the min quantity is only indicative value." +msgstr "" + +#. module: sale_order_min_qty +#: model:ir.model.fields,field_description:sale_order_min_qty.field_sale_order_line__sale_min_qty +msgid "Min Qty" +msgstr "" + +#. module: sale_order_min_qty +#: model_terms:ir.ui.view,arch_db:sale_order_min_qty.view_order_form +msgid "Min Quantity Recommended" +msgstr "" + +#. module: sale_order_min_qty +#: model_terms:ir.ui.view,arch_db:sale_order_min_qty.view_order_form +msgid "Min Quantity Required" +msgstr "" + +#. module: sale_order_min_qty +#: model:ir.model.fields,field_description:sale_order_min_qty.field_product_product__sale_min_qty +msgid "Min Sale Qty" +msgstr "" + +#. module: sale_order_min_qty +#: model:ir.model,name:sale_order_min_qty.model_product_product +msgid "Product" +msgstr "" + +#. module: sale_order_min_qty +#: code:addons/sale_order_min_qty/models/sale.py:43 +#, python-format +msgid "Product \"%s\": Min Quantity %s." +msgstr "" + +#. module: sale_order_min_qty +#: model:ir.model.fields,field_description:sale_order_min_qty.field_sale_order_line__is_qty_less_min_qty +msgid "Qty < Min Qty" +msgstr "" + +#. module: sale_order_min_qty +#: model:ir.model,name:sale_order_min_qty.model_sale_order_line +msgid "Sales Order Line" +msgstr "" + diff --git a/sale_restricted_qty/models/__init__.py b/sale_restricted_qty/models/__init__.py new file mode 100644 index 00000000000..a1cc9138a71 --- /dev/null +++ b/sale_restricted_qty/models/__init__.py @@ -0,0 +1,5 @@ +from . import product_restricted_qty_mixin +from . import product_category +from . import product_template +from . import product_product +from . import sale diff --git a/sale_restricted_qty/models/product_category.py b/sale_restricted_qty/models/product_category.py new file mode 100644 index 00000000000..bb98cc5d44a --- /dev/null +++ b/sale_restricted_qty/models/product_category.py @@ -0,0 +1,48 @@ +# Copyright 2019 Akretion +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, models + + +class ProductCategory(models.Model): + _name = "product.category" + _inherit = ["product.category", "product.restricted.qty.mixin"] + + def _get_sale_restricted_qty(self): + res = super()._get_sale_restricted_qty() + force_sale_min_qty = False + if self.manual_force_sale_min_qty == 'force': + force_sale_min_qty = True + if self.manual_force_sale_min_qty == 'not_force': + force_sale_min_qty = False + if not self.manual_force_sale_min_qty: + force_sale_min_qty = self.parent_id.force_sale_min_qty + force_sale_max_qty = False + if self.manual_force_sale_max_qty == 'force': + force_sale_max_qty = True + if self.manual_force_sale_max_qty == 'not_force': + force_sale_max_qty = False + if not self.manual_force_sale_max_qty: + force_sale_max_qty = self.parent_id.force_sale_max_qty + res.update( + { + "sale_min_qty": self.manual_sale_min_qty or self.parent_id.sale_min_qty, + "force_sale_min_qty": force_sale_min_qty, + "sale_max_qty": self.manual_sale_max_qty or self.parent_id.sale_max_qty, + "force_sale_max_qty": force_sale_max_qty, + "sale_multiple_qty": self.manual_sale_multiple_qty + or self.parent_id.sale_multiple_qty, + } + ) + return res + + @api.depends( + "parent_id.force_sale_min_qty", + "parent_id.sale_min_qty", + "parent_id.force_sale_max_qty", + "parent_id.sale_max_qty", + "parent_id.sale_multiple_qty", + ) + def _compute_sale_restricted_qty(self): + for rec in self: + rec.update(rec._get_sale_restricted_qty()) diff --git a/sale_restricted_qty/models/product_product.py b/sale_restricted_qty/models/product_product.py new file mode 100644 index 00000000000..dc43340bddc --- /dev/null +++ b/sale_restricted_qty/models/product_product.py @@ -0,0 +1,51 @@ +# Copyright 2019 Akretion +# @author Mourad EL HADJ MIMOUNE +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, models + + +class ProductProduct(models.Model): + _name = "product.product" + _inherit = ["product.product", "product.restricted.qty.mixin"] + + def _get_sale_restricted_qty(self): + res = super()._get_sale_restricted_qty() + force_sale_min_qty = False + if self.manual_force_sale_min_qty == 'force': + force_sale_min_qty = True + if self.manual_force_sale_min_qty == 'not_force': + force_sale_min_qty = False + if not self.manual_force_sale_min_qty: + force_sale_min_qty = self.product_tmpl_id.force_sale_min_qty + force_sale_max_qty = False + if self.manual_force_sale_max_qty == 'force': + force_sale_max_qty = True + if self.manual_force_sale_max_qty == 'not_force': + force_sale_max_qty = False + if not self.manual_force_sale_max_qty: + force_sale_max_qty = self.product_tmpl_id.force_sale_max_qty + res.update( + { + "sale_min_qty": self.manual_sale_min_qty + or self.product_tmpl_id.sale_min_qty, + "force_sale_min_qty": force_sale_min_qty, + "sale_max_qty": self.manual_sale_max_qty + or self.product_tmpl_id.sale_max_qty, + "force_sale_max_qty": force_sale_max_qty, + "sale_multiple_qty": self.manual_sale_multiple_qty + or self.product_tmpl_id.sale_multiple_qty, + } + ) + return res + + @api.depends( + "product_tmpl_id.force_sale_min_qty", + "product_tmpl_id.sale_min_qty", + "product_tmpl_id.force_sale_max_qty", + "product_tmpl_id.sale_max_qty", + "product_tmpl_id.sale_multiple_qty", + ) + def _compute_sale_restricted_qty(self): + for rec in self: + rec.update(rec._get_sale_restricted_qty()) diff --git a/sale_restricted_qty/models/product_restricted_qty_mixin.py b/sale_restricted_qty/models/product_restricted_qty_mixin.py new file mode 100644 index 00000000000..105fc4c41eb --- /dev/null +++ b/sale_restricted_qty/models/product_restricted_qty_mixin.py @@ -0,0 +1,105 @@ +# Copyright 2019 Akretion +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models +from odoo.addons import decimal_precision as dp + + +class ProductMinMultipleMixin(models.AbstractModel): + _name = "product.restricted.qty.mixin" + + sale_multiple_qty = fields.Float( + compute="_compute_sale_restricted_qty", + store=True, + help="Define sale multiple qty" + " 'If not set', Odoo will" + " use the value defined in the parent object." + "Hierarchy is in this order :" + "Product/product Template/product category/parent categoroies ", + digits=dp.get_precision("Product Unit of Measure"), + ) + manual_sale_multiple_qty = fields.Float( + string="Multiple Sale Qty", digits=dp.get_precision("Product Unit of Measure") + ) + sale_min_qty = fields.Float( + compute="_compute_sale_restricted_qty", + store=True, + help="Define sale min qty" + " 'If not set, Odoo will" + " use the value defined in the parent object." + "Hierarchy is in this order :" + "Product/product Template/product category/parent categoroies ", + digits=dp.get_precision("Product Unit of Measure"), + ) + manual_sale_min_qty = fields.Float( + string="Min Sale Qty", digits=dp.get_precision("Product Unit of Measure") + ) + force_sale_min_qty = fields.Boolean( + compute="_compute_sale_restricted_qty", + string="Force Min Qty", + store=True, + help="Define if user can force sale min qty" + " 'If not set', Odoo will" + " use the value defined in the parent object." + "Hierarchy is in this order :" + "Product/product Template/product category/parent categoroies ", + ) + manual_force_sale_min_qty = fields.Selection([ + ('force', 'Yes'), + ('not_force', 'No'), + ], + string="Manual Force Min Qty", + help="If force min qty is checked, the min quantity " + "is only indicative value." + "If is not test we check parent value", + ) + sale_max_qty = fields.Float( + compute="_compute_sale_restricted_qty", + store=True, + help="Define sale max qty" + " 'If not set, Odoo will" + " use the value defined in the parent object." + "Hierarchy is in this order :" + "Product/product Template/product category/parent categoroies ", + digits=dp.get_precision("Product Unit of Measure"), + ) + manual_sale_max_qty = fields.Float( + string="Max Sale Qty", digits=dp.get_precision("Product Unit of Measure") + ) + force_sale_max_qty = fields.Boolean( + compute="_compute_sale_restricted_qty", + string="Force Max Qty", + store=True, + help="Define if user can force sale max qty" + " 'If not set', Odoo will" + " use the value defined in the parent object." + "Hierarchy is in this order :" + "Product/product Template/product category/parent categoroies ", + ) + manual_force_sale_max_qty = fields.Selection([ + ('force', 'Yes'), + ('not_force', 'No'), + ], + string="Manual Force Max Qty", + help="If force max qty is checked, the max quantity " + "is only indicative value." + "If is not test we check parent value", + ) + + def _get_sale_restricted_qty(self): + self.ensure_one() + res = { + "sale_min_qty": self.manual_sale_min_qty, + "force_sale_min_qty": self.manual_force_sale_min_qty == 'force', + "sale_max_qty": self.manual_sale_max_qty, + "force_sale_max_qty": self.manual_force_sale_max_qty == 'force', + "sale_multiple_qty": self.manual_sale_multiple_qty, + } + return res + + @api.depends("manual_force_sale_min_qty", "manual_sale_min_qty", + "manual_force_sale_max_qty", "manual_sale_max_qty", + "manual_sale_multiple_qty") + def _compute_sale_restricted_qty(self): + for rec in self: + rec.update(rec._get_sale_restricted_qty()) diff --git a/sale_restricted_qty/models/product_template.py b/sale_restricted_qty/models/product_template.py new file mode 100644 index 00000000000..6de94b4342b --- /dev/null +++ b/sale_restricted_qty/models/product_template.py @@ -0,0 +1,49 @@ +# Copyright 2019 Akretion +# @author Mourad EL HADJ MIMOUNE +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, models + + +class ProductTemplate(models.Model): + _name = "product.template" + _inherit = ["product.template", "product.restricted.qty.mixin"] + + def _get_sale_restricted_qty(self): + res = super()._get_sale_restricted_qty() + force_sale_min_qty = False + if self.manual_force_sale_min_qty == 'force': + force_sale_min_qty = True + if self.manual_force_sale_min_qty == 'not_force': + force_sale_min_qty = False + if not self.manual_force_sale_min_qty: + force_sale_min_qty = self.categ_id.force_sale_min_qty + force_sale_max_qty = False + if self.manual_force_sale_max_qty == 'force': + force_sale_max_qty = True + if self.manual_force_sale_max_qty == 'not_force': + force_sale_max_qty = False + if not self.manual_force_sale_max_qty: + force_sale_max_qty = self.categ_id.force_sale_max_qty + res.update( + { + "sale_min_qty": self.manual_sale_min_qty or self.categ_id.sale_min_qty, + "force_sale_min_qty": force_sale_min_qty, + "sale_max_qty": self.manual_sale_max_qty or self.categ_id.sale_max_qty, + "force_sale_max_qty": force_sale_max_qty, + "sale_multiple_qty": self.manual_sale_multiple_qty + or self.categ_id.sale_multiple_qty, + } + ) + return res + + @api.depends( + "categ_id.force_sale_min_qty", + "categ_id.sale_min_qty", + "categ_id.force_sale_max_qty", + "categ_id.sale_max_qty", + "categ_id.sale_multiple_qty", + ) + def _compute_sale_restricted_qty(self): + for rec in self: + rec.update(rec._get_sale_restricted_qty()) diff --git a/sale_restricted_qty/models/sale.py b/sale_restricted_qty/models/sale.py new file mode 100644 index 00000000000..86bc1f73595 --- /dev/null +++ b/sale_restricted_qty/models/sale.py @@ -0,0 +1,147 @@ +# Copyright 2019 Akretion +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import _, api, fields, models +from odoo.addons import decimal_precision as dp +from odoo.exceptions import ValidationError +from odoo.tools import float_compare + + +class SaleOrderLine(models.Model): + _inherit = "sale.order.line" + + sale_min_qty = fields.Float( + string="Min Qty", + related="product_id.sale_min_qty", + readonly=True, + store=True, + digits=dp.get_precision("Product Unit of Measure"), + ) + force_sale_min_qty = fields.Boolean( + related="product_id.force_sale_min_qty", readonly=True, store=True + ) + is_qty_less_min_qty = fields.Boolean( + string="Qty < Min Qty", compute="_compute_is_qty_less_min_qty" + ) + + sale_max_qty = fields.Float( + string="Max Qty", + related="product_id.sale_max_qty", + readonly=True, + store=True, + digits=dp.get_precision("Product Unit of Measure"), + ) + force_sale_max_qty = fields.Boolean( + related="product_id.force_sale_max_qty", readonly=True, store=True + ) + is_qty_bigger_max_qty = fields.Boolean( + string="Qty > max Qty", compute="_compute_is_qty_bigger_max_qty" + ) + sale_multiple_qty = fields.Float( + string="Multiple Qty", + related="product_id.sale_multiple_qty", + readonly=True, + store=True, + digits=dp.get_precision("Product Unit of Measure"), + ) + is_qty_not_multiple_qty = fields.Boolean( + string="Not Multiple Qty", compute="_compute_is_qty_not_multiple_qty" + ) + + @api.constrains("product_uom_qty") + def check_constraint_restricted_qty(self): + msg = "" + invaild_min_lines = [] + line_to_test = self.filtered( + lambda l: not l.product_id.force_sale_min_qty and l.is_qty_less_min_qty + ) + for line in line_to_test: + invaild_min_lines.append( + _('Product "%s": Min Quantity %s.') + % (line.product_id.name, line.product_id.sale_min_qty) + ) + + if invaild_min_lines: + msg += _( + "Check minimum order quantity for this products: * \n" + ) + "\n ".join(invaild_min_lines) + msg += _( + "\n* If you want sell quantity less than Min Quantity" + ',Check "force min quatity" on product' + ) + invaild_max_lines = [] + line_to_test = self.filtered( + lambda l: not l.product_id.force_sale_max_qty and l.is_qty_bigger_max_qty + ) + for line in line_to_test: + invaild_max_lines.append( + _('Product "%s": max Quantity %s.') + % (line.product_id.name, line.product_id.sale_max_qty) + ) + + if invaild_max_lines: + msg += _( + "Check maximum order quantity for this products: * \n" + ) + "\n ".join(invaild_max_lines) + msg += _( + "\n* If you want sell quantity bigger than max Quantity" + ',Check "force max quatity" on product' + ) + invaild_multiple_lines = [] + line_to_test = self.filtered(lambda l: l.is_qty_not_multiple_qty) + for line in line_to_test: + invaild_multiple_lines.append( + _('Product "%s": multiple Quantity %s.') + % (line.product_id.name, line.product_id.sale_multiple_qty) + ) + + if invaild_multiple_lines: + msg += _( + "Check multiple order quantity for this products: * \n" + ) + "\n ".join(invaild_multiple_lines) + msg += _( + "\n* If you want sell quantity not multiple Quantity" + ",Set multiple quantity to 0 on product or product template" + " or product category" + ) + + if msg: + raise ValidationError(msg) + + @api.depends("product_uom_qty", "sale_min_qty") + def _compute_is_qty_less_min_qty(self): + for line in self: + rounding = line.product_uom.rounding + product_qty = line.product_uom._compute_quantity( + line.product_uom_qty, line.product_id.uom_id + ) + line.is_qty_less_min_qty = ( + float_compare( + product_qty, line.sale_min_qty, precision_rounding=rounding + ) + < 0 + ) + + @api.depends("product_uom_qty", "sale_max_qty") + def _compute_is_qty_bigger_max_qty(self): + for line in self: + rounding = line.product_uom.rounding + product_qty = line.product_uom._compute_quantity( + line.product_uom_qty, line.product_id.uom_id + ) + line.is_qty_bigger_max_qty = ( + float_compare( + product_qty, line.sale_max_qty, precision_rounding=rounding + ) + < 0 + ) + + @api.depends("product_uom_qty", "sale_multiple_qty") + def _compute_is_qty_not_multiple_qty(self): + for line in self: + product_qty = line.product_uom._compute_quantity( + line.product_uom_qty, line.product_id.uom_id + ) + line.is_qty_not_multiple_qty = ( + line.sale_multiple_qty > 0 and product_qty % line.sale_multiple_qty != 0 + ) diff --git a/sale_restricted_qty/readme/CONTRIBUTORS.rst b/sale_restricted_qty/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000000..e803c0a3fba --- /dev/null +++ b/sale_restricted_qty/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Mourad EL HADJ MIMOUN diff --git a/sale_restricted_qty/readme/DESCRIPTION.rst b/sale_restricted_qty/readme/DESCRIPTION.rst new file mode 100644 index 00000000000..f1fd4fa94fa --- /dev/null +++ b/sale_restricted_qty/readme/DESCRIPTION.rst @@ -0,0 +1,11 @@ +This module allows to define minimum and multiple order quantity on product. +Minimum order quantity is either required or recommanded. +If you check "Force min Qty" on product the min qty do not block sale +order. +This can be used if the available stock is less then the min qty +or if you want to sell sample products. + +Setings are defined on product, if values are not set we use settings from +product template. +If settings are not set on product template we use settings from +product category otherwise parent category. diff --git a/sale_restricted_qty/readme/USAGE.rst b/sale_restricted_qty/readme/USAGE.rst new file mode 100644 index 00000000000..5623152596f --- /dev/null +++ b/sale_restricted_qty/readme/USAGE.rst @@ -0,0 +1,7 @@ +#. Go to **Sales > Products > Product Variants** and Set "Min Sale Qty". + If you dont want block product sale if qty is less the "Min Sale Qty", + chek "Force Min Qty" +#. Create a sale order and add a sale order line with this product and check + if Qty is less then "Min Qty" A message in displayed "Min Qty Required" + Or "Min Qty Recommanded". Check if Min qty is required you can't + validate order. diff --git a/sale_restricted_qty/static/description/icon.png b/sale_restricted_qty/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 zcmW++2RxMjAAjx~&dlBk9S+%}OXg)AGE&Cb*&}d0jUxM@u(PQx^-s)697TX`ehR4?GS^qbkof1cslKgkU)h65qZ9Oc=ml_0temigYLJfnz{IDzUf>bGs4N!v3=Z3jMq&A#7%rM5eQ#dc?k~! zVpnB`o+K7|Al`Q_U;eD$B zfJtP*jH`siUq~{KE)`jP2|#TUEFGRryE2`i0**z#*^6~AI|YzIWy$Cu#CSLW3q=GA z6`?GZymC;dCPk~rBS%eCb`5OLr;RUZ;D`}um=H)BfVIq%7VhiMr)_#G0N#zrNH|__ zc+blN2UAB0=617@>_u;MPHN;P;N#YoE=)R#i$k_`UAA>WWCcEVMh~L_ zj--gtp&|K1#58Yz*AHCTMziU1Jzt_jG0I@qAOHsk$2}yTmVkBp_eHuY$A9)>P6o~I z%aQ?!(GqeQ-Y+b0I(m9pwgi(IIZZzsbMv+9w{PFtd_<_(LA~0H(xz{=FhLB@(1&qHA5EJw1>>=%q2f&^X>IQ{!GJ4e9U z&KlB)z(84HmNgm2hg2C0>WM{E(DdPr+EeU_N@57;PC2&DmGFW_9kP&%?X4}+xWi)( z;)z%wI5>D4a*5XwD)P--sPkoY(a~WBw;E~AW`Yue4kFa^LM3X`8x|}ZUeMnqr}>kH zG%WWW>3ml$Yez?i%)2pbKPI7?5o?hydokgQyZsNEr{a|mLdt;X2TX(#B1j35xPnPW z*bMSSOauW>o;*=kO8ojw91VX!qoOQb)zHJ!odWB}d+*K?#sY_jqPdg{Sm2HdYzdEx zOGVPhVRTGPtv0o}RfVP;Nd(|CB)I;*t&QO8h zFfekr30S!-LHmV_Su-W+rEwYXJ^;6&3|L$mMC8*bQptyOo9;>Qb9Q9`ySe3%V$A*9 zeKEe+b0{#KWGp$F+tga)0RtI)nhMa-K@JS}2krK~n8vJ=Ngm?R!9G<~RyuU0d?nz# z-5EK$o(!F?hmX*2Yt6+coY`6jGbb7tF#6nHA zuKk=GGJ;ZwON1iAfG$E#Y7MnZVmrY|j0eVI(DN_MNFJmyZ|;w4tf@=CCDZ#5N_0K= z$;R~bbk?}TpfDjfB&aiQ$VA}s?P}xPERJG{kxk5~R`iRS(SK5d+Xs9swCozZISbnS zk!)I0>t=A<-^z(cmSFz3=jZ23u13X><0b)P)^1T_))Kr`e!-pb#q&J*Q`p+B6la%C zuVl&0duN<;uOsB3%T9Fp8t{ED108<+W(nOZd?gDnfNBC3>M8WE61$So|P zVvqH0SNtDTcsUdzaMDpT=Ty0pDHHNL@Z0w$Y`XO z2M-_r1S+GaH%pz#Uy0*w$Vdl=X=rQXEzO}d6J^R6zjM1u&c9vYLvLp?W7w(?np9x1 zE_0JSAJCPB%i7p*Wvg)pn5T`8k3-uR?*NT|J`eS#_#54p>!p(mLDvmc-3o0mX*mp_ zN*AeS<>#^-{S%W<*mz^!X$w_2dHWpcJ6^j64qFBft-o}o_Vx80o0>}Du;>kLts;$8 zC`7q$QI(dKYG`Wa8#wl@V4jVWBRGQ@1dr-hstpQL)Tl+aqVpGpbSfN>5i&QMXfiZ> zaA?T1VGe?rpQ@;+pkrVdd{klI&jVS@I5_iz!=UMpTsa~mBga?1r}aRBm1WS;TT*s0f0lY=JBl66Upy)-k4J}lh=P^8(SXk~0xW=T9v*B|gzIhN z>qsO7dFd~mgxAy4V?&)=5ieYq?zi?ZEoj)&2o)RLy=@hbCRcfT5jigwtQGE{L*8<@Yd{zg;CsL5mvzfDY}P-wos_6PfprFVaeqNE%h zKZhLtcQld;ZD+>=nqN~>GvROfueSzJD&BE*}XfU|H&(FssBqY=hPCt`d zH?@s2>I(|;fcW&YM6#V#!kUIP8$Nkdh0A(bEVj``-AAyYgwY~jB zT|I7Bf@%;7aL7Wf4dZ%VqF$eiaC38OV6oy3Z#TER2G+fOCd9Iaoy6aLYbPTN{XRPz z;U!V|vBf%H!}52L2gH_+j;`bTcQRXB+y9onc^wLm5wi3-Be}U>k_u>2Eg$=k!(l@I zcCg+flakT2Nej3i0yn+g+}%NYb?ta;R?(g5SnwsQ49U8Wng8d|{B+lyRcEDvR3+`O{zfmrmvFrL6acVP%yG98X zo&+VBg@px@i)%o?dG(`T;n*$S5*rnyiR#=wW}}GsAcfyQpE|>a{=$Hjg=-*_K;UtD z#z-)AXwSRY?OPefw^iI+ z)AXz#PfEjlwTes|_{sB?4(O@fg0AJ^g8gP}ex9Ucf*@_^J(s_5jJV}c)s$`Myn|Kd z$6>}#q^n{4vN@+Os$m7KV+`}c%4)4pv@06af4-x5#wj!KKb%caK{A&Y#Rfs z-po?Dcb1({W=6FKIUirH&(yg=*6aLCekcKwyfK^JN5{wcA3nhO(o}SK#!CINhI`-I z1)6&n7O&ZmyFMuNwvEic#IiOAwNkR=u5it{B9n2sAJV5pNhar=j5`*N!Na;c7g!l$ z3aYBqUkqqTJ=Re-;)s!EOeij=7SQZ3Hq}ZRds%IM*PtM$wV z@;rlc*NRK7i3y5BETSKuumEN`Xu_8GP1Ri=OKQ$@I^ko8>H6)4rjiG5{VBM>B|%`&&s^)jS|-_95&yc=GqjNo{zFkw%%HHhS~e=s zD#sfS+-?*t|J!+ozP6KvtOl!R)@@-z24}`9{QaVLD^9VCSR2b`b!KC#o;Ki<+wXB6 zx3&O0LOWcg4&rv4QG0)4yb}7BFSEg~=IR5#ZRj8kg}dS7_V&^%#Do==#`u zpy6{ox?jWuR(;pg+f@mT>#HGWHAJRRDDDv~@(IDw&R>9643kK#HN`!1vBJHnC+RM&yIh8{gG2q zA%e*U3|N0XSRa~oX-3EAneep)@{h2vvd3Xvy$7og(sayr@95+e6~Xvi1tUqnIxoIH zVWo*OwYElb#uyW{Imam6f2rGbjR!Y3`#gPqkv57dB6K^wRGxc9B(t|aYDGS=m$&S!NmCtrMMaUg(c zc2qC=2Z`EEFMW-me5B)24AqF*bV5Dr-M5ig(l-WPS%CgaPzs6p_gnCIvTJ=Y<6!gT zVt@AfYCzjjsMEGi=rDQHo0yc;HqoRNnNFeWZgcm?f;cp(6CNylj36DoL(?TS7eU#+ z7&mfr#y))+CJOXQKUMZ7QIdS9@#-}7y2K1{8)cCt0~-X0O!O?Qx#E4Og+;A2SjalQ zs7r?qn0H044=sDN$SRG$arw~n=+T_DNdSrarmu)V6@|?1-ZB#hRn`uilTGPJ@fqEy zGt(f0B+^JDP&f=r{#Y_wi#AVDf-y!RIXU^0jXsFpf>=Ji*TeqSY!H~AMbJdCGLhC) zn7Rx+sXw6uYj;WRYrLd^5IZq@6JI1C^YkgnedZEYy<&4(z%Q$5yv#Boo{AH8n$a zhb4Y3PWdr269&?V%uI$xMcUrMzl=;w<_nm*qr=c3Rl@i5wWB;e-`t7D&c-mcQl7x! zZWB`UGcw=Y2=}~wzrfLx=uet<;m3~=8I~ZRuzvMQUQdr+yTV|ATf1Uuomr__nDf=X zZ3WYJtHp_ri(}SQAPjv+Y+0=fH4krOP@S&=zZ-t1jW1o@}z;xk8 z(Nz1co&El^HK^NrhVHa-_;&88vTU>_J33=%{if;BEY*J#1n59=07jrGQ#IP>@u#3A z;!q+E1Rj3ZJ+!4bq9F8PXJ@yMgZL;>&gYA0%_Kbi8?S=XGM~dnQZQ!yBSgcZhY96H zrWnU;k)qy`rX&&xlDyA%(a1Hhi5CWkmg(`Gb%m(HKi-7Z!LKGRP_B8@`7&hdDy5n= z`OIxqxiVfX@OX1p(mQu>0Ai*v_cTMiw4qRt3~NBvr9oBy0)r>w3p~V0SCm=An6@3n)>@z!|o-$HvDK z|3D2ZMJkLE5loMKl6R^ez@Zz%S$&mbeoqH5`Bb){Ei21q&VP)hWS2tjShfFtGE+$z zzCR$P#uktu+#!w)cX!lWN1XU%K-r=s{|j?)Akf@q#3b#{6cZCuJ~gCxuMXRmI$nGtnH+-h z+GEi!*X=AP<|fG`1>MBdTb?28JYc=fGvAi2I<$B(rs$;eoJCyR6_bc~p!XR@O-+sD z=eH`-ye})I5ic1eL~TDmtfJ|8`0VJ*Yr=hNCd)G1p2MMz4C3^Mj?7;!w|Ly%JqmuW zlIEW^Ft%z?*|fpXda>Jr^1noFZEwFgVV%|*XhH@acv8rdGxeEX{M$(vG{Zw+x(ei@ zmfXb22}8-?Fi`vo-YVrTH*C?a8%M=Hv9MqVH7H^J$KsD?>!SFZ;ZsvnHr_gn=7acz z#W?0eCdVhVMWN12VV^$>WlQ?f;P^{(&pYTops|btm6aj>_Uz+hqpGwB)vWp0Cf5y< zft8-je~nn?W11plq}N)4A{l8I7$!ks_x$PXW-2XaRFswX_BnF{R#6YIwMhAgd5F9X zGmwdadS6(a^fjHtXg8=l?Rc0Sm%hk6E9!5cLVloEy4eh(=FwgP`)~I^5~pBEWo+F6 zSf2ncyMurJN91#cJTy_u8Y}@%!bq1RkGC~-bV@SXRd4F{R-*V`bS+6;W5vZ(&+I<9$;-V|eNfLa5n-6% z2(}&uGRF;p92eS*sE*oR$@pexaqr*meB)VhmIg@h{uzkk$9~qh#cHhw#>O%)b@+(| z^IQgqzuj~Sk(J;swEM-3TrJAPCq9k^^^`q{IItKBRXYe}e0Tdr=Huf7da3$l4PdpwWDop%^}n;dD#K4s#DYA8SHZ z&1!riV4W4R7R#C))JH1~axJ)RYnM$$lIR%6fIVA@zV{XVyx}C+a-Dt8Y9M)^KU0+H zR4IUb2CJ{Hg>CuaXtD50jB(_Tcx=Z$^WYu2u5kubqmwp%drJ6 z?Fo40g!Qd<-l=TQxqHEOuPX0;^z7iX?Ke^a%XT<13TA^5`4Xcw6D@Ur&VT&CUe0d} z1GjOVF1^L@>O)l@?bD~$wzgf(nxX1OGD8fEV?TdJcZc2KoUe|oP1#=$$7ee|xbY)A zDZq+cuTpc(fFdj^=!;{k03C69lMQ(|>uhRfRu%+!k&YOi-3|1QKB z z?n?eq1XP>p-IM$Z^C;2L3itnbJZAip*Zo0aw2bs8@(s^~*8T9go!%dHcAz2lM;`yp zD=7&xjFV$S&5uDaiScyD?B-i1ze`+CoRtz`Wn+Zl&#s4&}MO{@N!ufrzjG$B79)Y2d3tBk&)TxUTw@QS0TEL_?njX|@vq?Uz(nBFK5Pq7*xj#u*R&i|?7+6# z+|r_n#SW&LXhtheZdah{ZVoqwyT{D>MC3nkFF#N)xLi{p7J1jXlmVeb;cP5?e(=f# zuT7fvjSbjS781v?7{)-X3*?>tq?)Yd)~|1{BDS(pqC zC}~H#WXlkUW*H5CDOo<)#x7%RY)A;ShGhI5s*#cRDA8YgqG(HeKDx+#(ZQ?386dv! zlXCO)w91~Vw4AmOcATuV653fa9R$fyK8ul%rG z-wfS zihugoZyr38Im?Zuh6@RcF~t1anQu7>#lPpb#}4cOA!EM11`%f*07RqOVkmX{p~KJ9 z^zP;K#|)$`^Rb{rnHGH{~>1(fawV0*Z#)}M`m8-?ZJV<+e}s9wE# z)l&az?w^5{)`S(%MRzxdNqrs1n*-=jS^_jqE*5XDrA0+VE`5^*p3CuM<&dZEeCjoz zR;uu_H9ZPZV|fQq`Cyw4nscrVwi!fE6ciMmX$!_hN7uF;jjKG)d2@aC4ropY)8etW=xJvni)8eHi`H$%#zn^WJ5NLc-rqk|u&&4Z6fD_m&JfSI1Bvb?b<*n&sfl0^t z=HnmRl`XrFvMKB%9}>PaA`m-fK6a0(8=qPkWS5bb4=v?XcWi&hRY?O5HdulRi4?fN zlsJ*N-0Qw+Yic@s0(2uy%F@ib;GjXt01Fmx5XbRo6+n|pP(&nodMoap^z{~q ziEeaUT@Mxe3vJSfI6?uLND(CNr=#^W<1b}jzW58bIfyWTDle$mmS(|x-0|2UlX+9k zQ^EX7Nw}?EzVoBfT(-LT|=9N@^hcn-_p&sqG z&*oVs2JSU+N4ZD`FhCAWaS;>|wH2G*Id|?pa#@>tyxX`+4HyIArWDvVrX)2WAOQff z0qyHu&-S@i^MS-+j--!pr4fPBj~_8({~e1bfcl0wI1kaoN>mJL6KUPQm5N7lB(ui1 zE-o%kq)&djzWJ}ob<-GfDlkB;F31j-VHKvQUGQ3sp`CwyGJk_i!y^sD0fqC@$9|jO zOqN!r!8-p==F@ZVP=U$qSpY(gQ0)59P1&t@y?5rvg<}E+GB}26NYPp4f2YFQrQtot5mn3wu_qprZ=>Ig-$ zbW26Ws~IgY>}^5w`vTB(G`PTZaDiGBo5o(tp)qli|NeV( z@H_=R8V39rt5J5YB2Ky?4eJJ#b`_iBe2ot~6%7mLt5t8Vwi^Jy7|jWXqa3amOIoRb zOr}WVFP--DsS`1WpN%~)t3R!arKF^Q$e12KEqU36AWwnCBICpH4XCsfnyrHr>$I$4 z!DpKX$OKLWarN7nv@!uIA+~RNO)l$$w}p(;b>mx8pwYvu;dD_unryX_NhT8*Tj>BTrTTL&!?O+%Rv;b?B??gSzdp?6Uug9{ zd@V08Z$BdI?fpoCS$)t4mg4rT8Q_I}h`0d-vYZ^|dOB*Q^S|xqTV*vIg?@fVFSmMpaw0qtTRbx} z({Pg?#{2`sc9)M5N$*N|4;^t$+QP?#mov zGVC@I*lBVrOU-%2y!7%)fAKjpEFsgQc4{amtiHb95KQEwvf<(3T<9-Zm$xIew#P22 zc2Ix|App^>v6(3L_MCU0d3W##AB0M~3D00EWoKZqsJYT(#@w$Y_H7G22M~ApVFTRHMI_3be)Lkn#0F*V8Pq zc}`Cjy$bE;FJ6H7p=0y#R>`}-m4(0F>%@P|?7fx{=R^uFdISRnZ2W_xQhD{YuR3t< z{6yxu=4~JkeA;|(J6_nv#>Nvs&FuLA&PW^he@t(UwFFE8)|a!R{`E`K`i^ZnyE4$k z;(749Ix|oi$c3QbEJ3b~D_kQsPz~fIUKym($a_7dJ?o+40*OLl^{=&oq$<#Q(yyrp z{J-FAniyAw9tPbe&IhQ|a`DqFTVQGQ&Gq3!C2==4x{6EJwiPZ8zub-iXoUtkJiG{} zPaR&}_fn8_z~(=;5lD-aPWD3z8PZS@AaUiomF!G8I}Mf>e~0g#BelA-5#`cj;O5>N Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 diff --git a/sale_restricted_qty/static/description/index.html b/sale_restricted_qty/static/description/index.html new file mode 100644 index 00000000000..3bada92d5b5 --- /dev/null +++ b/sale_restricted_qty/static/description/index.html @@ -0,0 +1,443 @@ + + + + + + +Sale order min quantity + + + +
+

Sale order min quantity

+ + +

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

+

This module allows to define minimum order quantity on product. +Minimum order quantity is either required or recommanded. +If you check “Force min Qty” on product the min qty do not block sale +order. +This can be used if the available stock is less then the min qty +or if you want to sell sample products.

+

Table of contents

+ +
+

Usage

+
    +
  1. +
    Go to Sales > Products > Product Variants and Set “Min Sale Qty”.
    +
    If you dont want block product sale if qty is less the “Min Sale Qty”, +chek “Force Min Qty”
    +
    +
  2. +
  3. +
    Create a sale order and add a sale order line with this product and check
    +
    if Qty is less then “Min Qty” A message in displayed “Min Qty Required” +Or “Min Qty Recommanded”. Check if Min qty is required you can’t +validate order.
    +
    +
  4. +
+
+
+

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

+
    +
  • Akretion
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

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

+

This module is part of the OCA/sale-workflow project on GitHub.

+

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

+
+
+
+ + diff --git a/sale_restricted_qty/tests/__init__.py b/sale_restricted_qty/tests/__init__.py new file mode 100644 index 00000000000..67709762e39 --- /dev/null +++ b/sale_restricted_qty/tests/__init__.py @@ -0,0 +1 @@ +from . import test_sale diff --git a/sale_restricted_qty/tests/test_sale.py b/sale_restricted_qty/tests/test_sale.py new file mode 100644 index 00000000000..167a49c5258 --- /dev/null +++ b/sale_restricted_qty/tests/test_sale.py @@ -0,0 +1,172 @@ +# Copyright 2019 Akretion +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import odoo.tests.common as common +from odoo.exceptions import ValidationError + + +@common.at_install(False) +@common.post_install(True) +class TestSaleOrderLineMinQty(common.TransactionCase): + def setUp(self): + super(TestSaleOrderLineMinQty, self).setUp() + + # Create models + self.sale_order_model = self.env["sale.order"] + self.sale_order_line_model = self.env["sale.order.line"] + self.partner_model = self.env["res.partner"] + self.product_categ_model = self.env["product.category"] + self.product_model = self.env["product.product"] + self.sale_order = self.sale_order_model + # Create partner and product + self.partner = self.partner_model.create({"name": "Test partner"}) + self.categ_parent = self.product_categ_model.create( + { + "name": "Test Parent categ", + "manual_sale_min_qty": 10.0, + "manual_force_sale_min_qty": False, + "manual_sale_max_qty": 100.0, + "manual_force_sale_max_qty": False, + "manual_sale_multiple_qty": 5.0, + } + ) + + self.categ = self.product_categ_model.create( + { + "name": "Test categ", + "parent_id": self.categ_parent.id, + "manual_force_sale_min_qty": False, + "manual_force_sale_max_qty": False, + } + ) + + self.product = self.product_model.create({ + "name": "Test product", + "force_sale_min_qty": False, + "force_sale_max_qty": False, + }) + + def test_check_sale_order_min_qty_required(self): + line_values = {"product_id": self.product.id, "product_uom_qty": 5.0} + self.product.manual_sale_min_qty = 10 + # Create sale order line with Qty less than min Qty + with self.assertRaises(ValidationError): + self.sale_order = self.sale_order_model.create( + {"partner_id": self.partner.id, "order_line": [(0, 0, line_values)]} + ) + line_values["product_uom_qty"] = 12.0 + # Create sale order line with Qty great then min Qty + self.sale_order = self.sale_order_model.create( + {"partner_id": self.partner.id, "order_line": [(0, 0, line_values)]} + ) + self.assertFalse(self.sale_order.order_line.is_qty_less_min_qty) + + self.assertEqual(self.sale_order.order_line.product_uom_qty, 12.0) + + def test_check_sale_order_min_qty_recommended(self): + line_values = {"product_id": self.product.id, "product_uom_qty": 5.0} + self.product.manual_sale_min_qty = 10 + # Set Force min Qty to true + self.product.manual_force_sale_min_qty = 'force' + + # Create sale order line with Qty less than min Qty + self.sale_order = self.sale_order_model.create( + {"partner_id": self.partner.id, "order_line": [(0, 0, line_values)]} + ) + self.assertTrue(self.sale_order.order_line.is_qty_less_min_qty) + + self.assertEqual(self.sale_order.order_line.product_uom_qty, 5.0) + + def test_check_sale_order_max_qty_required(self): + line_values = {"product_id": self.product.id, "product_uom_qty": 5.0} + self.product.manual_sale_max_qty = 10 + # Create sale order line with Qty bigger than max Qty + with self.assertRaises(ValidationError): + self.sale_order = self.sale_order_model.create( + {"partner_id": self.partner.id, "order_line": [(0, 0, line_values)]} + ) + line_values["product_uom_qty"] = 12.0 + # Create sale order line with Qty great then max Qty + self.sale_order = self.sale_order_model.create( + {"partner_id": self.partner.id, "order_line": [(0, 0, line_values)]} + ) + self.assertFalse(self.sale_order.order_line.is_qty_bigger_max_qty) + + self.assertEqual(self.sale_order.order_line.product_uom_qty, 12.0) + + def test_check_sale_order_max_qty_recommended(self): + line_values = {"product_id": self.product.id, "product_uom_qty": 5.0} + self.product.manual_sale_max_qty = 10 + # Set Force max Qty to true + self.product.manual_force_sale_max_qty = 'force' + + # Create sale order line with Qty bigger than max Qty + self.sale_order = self.sale_order_model.create( + {"partner_id": self.partner.id, "order_line": [(0, 0, line_values)]} + ) + self.assertTrue(self.sale_order.order_line.is_qty_bigger_max_qty) + + self.assertEqual(self.sale_order.order_line.product_uom_qty, 5.0) + + def test_check_sale_order_multiple_qty_required(self): + line_values = {"product_id": self.product.id, "product_uom_qty": 15.0} + self.product.manual_sale_min_qty = 10 + self.product.manual_sale_multiple_qty = 10 + # Create sale order line with Qty not multiple Qty + with self.assertRaises(ValidationError): + self.sale_order = self.sale_order_model.create( + {"partner_id": self.partner.id, "order_line": [(0, 0, line_values)]} + ) + line_values["product_uom_qty"] = 20 + # Create sale order line with Qty multiple Qty + self.sale_order = self.sale_order_model.create( + {"partner_id": self.partner.id, "order_line": [(0, 0, line_values)]} + ) + self.assertFalse(self.sale_order.order_line.is_qty_not_multiple_qty) + + self.assertEqual(self.sale_order.order_line.product_uom_qty, 20) + + def test_check_restricted_qty_category_hierarchy(self): + # Check Restricted Qty from parent category + self.product.categ_id = self.categ + self.assertEqual(self.product.manual_sale_min_qty, 0) + self.assertEqual(self.product.manual_sale_multiple_qty, 0) + self.product.categ_id.parent_id._compute_sale_restricted_qty() + + self.assertEqual(self.product.sale_min_qty, 10) + self.assertEqual(self.product.sale_max_qty, 100) + self.assertEqual(self.product.sale_multiple_qty, 5) + self.categ_parent.manual_force_sale_min_qty = 'force' + self.assertEqual(self.product.manual_force_sale_min_qty, False) + self.assertEqual(self.product.force_sale_min_qty, True) + # Check Restricted Qty from category + self.categ.manual_sale_min_qty = 15 + self.categ.manual_sale_multiple_qty = 10 + self.categ.manual_force_sale_min_qty = 'not_force' + self.assertEqual(self.product.sale_min_qty, 15) + self.assertEqual(self.product.sale_multiple_qty, 10) + self.assertEqual(self.product.force_sale_min_qty, False) + self.categ.manual_sale_max_qty = 200 + self.categ.manual_force_sale_max_qty = 'not_force' + self.assertEqual(self.product.sale_max_qty, 200) + self.assertEqual(self.product.force_sale_max_qty, False) + + def test_check_restricted_qty_tmpl_hierarchy(self): + # Check Restricted from product template + self.product.product_tmpl_id.manual_sale_min_qty = 20 + self.product.product_tmpl_id.manual_sale_max_qty = 250 + self.product.product_tmpl_id.manual_sale_multiple_qty = 30 + self.product.product_tmpl_id._compute_sale_restricted_qty() + self.assertEqual(self.product.manual_sale_min_qty, 0) + self.assertEqual(self.product.sale_min_qty, 20) + self.assertEqual(self.product.manual_sale_max_qty, 0) + self.assertEqual(self.product.sale_max_qty, 250) + self.assertEqual(self.product.manual_sale_multiple_qty, 0) + self.assertEqual(self.product.sale_multiple_qty, 30) + # Check Restricted Qty from product + self.product.manual_sale_min_qty = 25 + self.product.manual_sale_max_qty = 150 + self.product.manual_sale_multiple_qty = 25 + self.assertEqual(self.product.sale_min_qty, 25) + self.assertEqual(self.product.sale_max_qty, 150) + self.assertEqual(self.product.sale_multiple_qty, 25) diff --git a/sale_restricted_qty/views/product_category_views.xml b/sale_restricted_qty/views/product_category_views.xml new file mode 100644 index 00000000000..0c578c04135 --- /dev/null +++ b/sale_restricted_qty/views/product_category_views.xml @@ -0,0 +1,23 @@ + + + + product.category + + + + + + + + + + + + + + + + + + + diff --git a/sale_restricted_qty/views/product_product_views.xml b/sale_restricted_qty/views/product_product_views.xml new file mode 100644 index 00000000000..2b33952ed04 --- /dev/null +++ b/sale_restricted_qty/views/product_product_views.xml @@ -0,0 +1,25 @@ + + + + + product.product.form + product.product + + + + + + + + + + + + + + + + + + diff --git a/sale_restricted_qty/views/product_template_views.xml b/sale_restricted_qty/views/product_template_views.xml new file mode 100644 index 00000000000..51b47cfa58a --- /dev/null +++ b/sale_restricted_qty/views/product_template_views.xml @@ -0,0 +1,24 @@ + + + + + product.template + + + + + + + + + + + + + + + + + + + diff --git a/sale_restricted_qty/views/sale_views.xml b/sale_restricted_qty/views/sale_views.xml new file mode 100644 index 00000000000..bc2beb4d2a2 --- /dev/null +++ b/sale_restricted_qty/views/sale_views.xml @@ -0,0 +1,41 @@ + + + + + sale.order + + + + + + + + + + + + + + + + + > + is_qty_less_min_qty == True and force_sale_min_qty == False + is_qty_less_min_qty == True and force_sale_min_qty == True + is_qty_not_multiple_qty == True + + + + From 4e8933388896bbea36c5f6617c7fb88d1a0985d7 Mon Sep 17 00:00:00 2001 From: Mourad Date: Thu, 18 Jun 2020 17:35:08 +0200 Subject: [PATCH 02/27] [FIX] Update Readme & "use_parent" value to manual_force_sale_min_qty and manual_force_sale_max_qty (imp user UI) --- sale_restricted_qty/hooks.py | 2 +- sale_restricted_qty/models/product_category.py | 8 ++++---- sale_restricted_qty/models/product_product.py | 8 ++++---- .../models/product_restricted_qty_mixin.py | 6 ++++++ sale_restricted_qty/models/product_template.py | 8 ++++---- sale_restricted_qty/readme/DESCRIPTION.rst | 8 +++++--- sale_restricted_qty/tests/test_sale.py | 14 +++++++------- 7 files changed, 31 insertions(+), 23 deletions(-) diff --git a/sale_restricted_qty/hooks.py b/sale_restricted_qty/hooks.py index 2a0f812af7d..810cf4053f5 100644 --- a/sale_restricted_qty/hooks.py +++ b/sale_restricted_qty/hooks.py @@ -16,4 +16,4 @@ def rename_module(cr): cr, [("sale_order_min_qty", "sale_restricted_qty")], merge_modules=True, - ) + ) diff --git a/sale_restricted_qty/models/product_category.py b/sale_restricted_qty/models/product_category.py index bb98cc5d44a..7ea01efffff 100644 --- a/sale_restricted_qty/models/product_category.py +++ b/sale_restricted_qty/models/product_category.py @@ -13,16 +13,16 @@ def _get_sale_restricted_qty(self): force_sale_min_qty = False if self.manual_force_sale_min_qty == 'force': force_sale_min_qty = True - if self.manual_force_sale_min_qty == 'not_force': + elif self.manual_force_sale_min_qty == 'not_force': force_sale_min_qty = False - if not self.manual_force_sale_min_qty: + else: force_sale_min_qty = self.parent_id.force_sale_min_qty force_sale_max_qty = False if self.manual_force_sale_max_qty == 'force': force_sale_max_qty = True - if self.manual_force_sale_max_qty == 'not_force': + elif self.manual_force_sale_max_qty == 'not_force': force_sale_max_qty = False - if not self.manual_force_sale_max_qty: + else: force_sale_max_qty = self.parent_id.force_sale_max_qty res.update( { diff --git a/sale_restricted_qty/models/product_product.py b/sale_restricted_qty/models/product_product.py index dc43340bddc..2ef51f58c64 100644 --- a/sale_restricted_qty/models/product_product.py +++ b/sale_restricted_qty/models/product_product.py @@ -14,16 +14,16 @@ def _get_sale_restricted_qty(self): force_sale_min_qty = False if self.manual_force_sale_min_qty == 'force': force_sale_min_qty = True - if self.manual_force_sale_min_qty == 'not_force': + elif self.manual_force_sale_min_qty == 'not_force': force_sale_min_qty = False - if not self.manual_force_sale_min_qty: + else: force_sale_min_qty = self.product_tmpl_id.force_sale_min_qty force_sale_max_qty = False if self.manual_force_sale_max_qty == 'force': force_sale_max_qty = True - if self.manual_force_sale_max_qty == 'not_force': + elif self.manual_force_sale_max_qty == 'not_force': force_sale_max_qty = False - if not self.manual_force_sale_max_qty: + else: force_sale_max_qty = self.product_tmpl_id.force_sale_max_qty res.update( { diff --git a/sale_restricted_qty/models/product_restricted_qty_mixin.py b/sale_restricted_qty/models/product_restricted_qty_mixin.py index 105fc4c41eb..e9a4e83ec9d 100644 --- a/sale_restricted_qty/models/product_restricted_qty_mixin.py +++ b/sale_restricted_qty/models/product_restricted_qty_mixin.py @@ -45,10 +45,13 @@ class ProductMinMultipleMixin(models.AbstractModel): "Product/product Template/product category/parent categoroies ", ) manual_force_sale_min_qty = fields.Selection([ + ('use_parent', 'Use Parent Setting'), ('force', 'Yes'), ('not_force', 'No'), ], string="Manual Force Min Qty", + required=True, + default="use_parent", help="If force min qty is checked, the min quantity " "is only indicative value." "If is not test we check parent value", @@ -77,9 +80,12 @@ class ProductMinMultipleMixin(models.AbstractModel): "Product/product Template/product category/parent categoroies ", ) manual_force_sale_max_qty = fields.Selection([ + ('use_parent', 'Use Parent Setting'), ('force', 'Yes'), ('not_force', 'No'), ], + required=True, + default="use_parent", string="Manual Force Max Qty", help="If force max qty is checked, the max quantity " "is only indicative value." diff --git a/sale_restricted_qty/models/product_template.py b/sale_restricted_qty/models/product_template.py index 6de94b4342b..3aa9bebcf49 100644 --- a/sale_restricted_qty/models/product_template.py +++ b/sale_restricted_qty/models/product_template.py @@ -14,16 +14,16 @@ def _get_sale_restricted_qty(self): force_sale_min_qty = False if self.manual_force_sale_min_qty == 'force': force_sale_min_qty = True - if self.manual_force_sale_min_qty == 'not_force': + elif self.manual_force_sale_min_qty == 'not_force': force_sale_min_qty = False - if not self.manual_force_sale_min_qty: + else: force_sale_min_qty = self.categ_id.force_sale_min_qty force_sale_max_qty = False if self.manual_force_sale_max_qty == 'force': force_sale_max_qty = True - if self.manual_force_sale_max_qty == 'not_force': + elif self.manual_force_sale_max_qty == 'not_force': force_sale_max_qty = False - if not self.manual_force_sale_max_qty: + else: force_sale_max_qty = self.categ_id.force_sale_max_qty res.update( { diff --git a/sale_restricted_qty/readme/DESCRIPTION.rst b/sale_restricted_qty/readme/DESCRIPTION.rst index f1fd4fa94fa..47ad576a40f 100644 --- a/sale_restricted_qty/readme/DESCRIPTION.rst +++ b/sale_restricted_qty/readme/DESCRIPTION.rst @@ -1,9 +1,11 @@ -This module allows to define minimum and multiple order quantity on product. -Minimum order quantity is either required or recommanded. -If you check "Force min Qty" on product the min qty do not block sale +This module allows to define min, max and multiple order quantity on product. +Min and Max order quantity is either required or recommanded. +If you check "Force mini/max Qty" on product the min/max qty do not block sale order. This can be used if the available stock is less then the min qty or if you want to sell sample products. +Max qty can be used if we dont want to sale more than available qty +(end of series product) Setings are defined on product, if values are not set we use settings from product template. diff --git a/sale_restricted_qty/tests/test_sale.py b/sale_restricted_qty/tests/test_sale.py index 167a49c5258..d634a034324 100644 --- a/sale_restricted_qty/tests/test_sale.py +++ b/sale_restricted_qty/tests/test_sale.py @@ -24,9 +24,9 @@ def setUp(self): { "name": "Test Parent categ", "manual_sale_min_qty": 10.0, - "manual_force_sale_min_qty": False, + "manual_force_sale_min_qty": "use_parent", "manual_sale_max_qty": 100.0, - "manual_force_sale_max_qty": False, + "manual_force_sale_max_qty": "use_parent", "manual_sale_multiple_qty": 5.0, } ) @@ -35,15 +35,15 @@ def setUp(self): { "name": "Test categ", "parent_id": self.categ_parent.id, - "manual_force_sale_min_qty": False, - "manual_force_sale_max_qty": False, + "manual_force_sale_min_qty": "use_parent", + "manual_force_sale_max_qty": "use_parent", } ) self.product = self.product_model.create({ "name": "Test product", - "force_sale_min_qty": False, - "force_sale_max_qty": False, + "force_sale_min_qty": False, + "force_sale_max_qty": False, }) def test_check_sale_order_min_qty_required(self): @@ -137,7 +137,7 @@ def test_check_restricted_qty_category_hierarchy(self): self.assertEqual(self.product.sale_max_qty, 100) self.assertEqual(self.product.sale_multiple_qty, 5) self.categ_parent.manual_force_sale_min_qty = 'force' - self.assertEqual(self.product.manual_force_sale_min_qty, False) + self.assertEqual(self.product.manual_force_sale_min_qty, "use_parent") self.assertEqual(self.product.force_sale_min_qty, True) # Check Restricted Qty from category self.categ.manual_sale_min_qty = 15 From e256c3dc8473428de9178308cb5348dba6991d35 Mon Sep 17 00:00:00 2001 From: Mourad Date: Fri, 19 Jun 2020 12:35:25 +0200 Subject: [PATCH 03/27] [IMP] views & fix is_qty_less_min_qty & is_qty_bigger_max_qty --- sale_restricted_qty/models/sale.py | 10 +++--- .../views/product_category_views.xml | 31 +++++++++++++------ .../views/product_product_views.xml | 30 ++++++++++++------ .../views/product_template_views.xml | 30 ++++++++++++------ sale_restricted_qty/views/sale_views.xml | 16 ++++++++++ 5 files changed, 84 insertions(+), 33 deletions(-) diff --git a/sale_restricted_qty/models/sale.py b/sale_restricted_qty/models/sale.py index 86bc1f73595..ae0d690b24d 100644 --- a/sale_restricted_qty/models/sale.py +++ b/sale_restricted_qty/models/sale.py @@ -115,12 +115,12 @@ def _compute_is_qty_less_min_qty(self): product_qty = line.product_uom._compute_quantity( line.product_uom_qty, line.product_id.uom_id ) - line.is_qty_less_min_qty = ( + line.is_qty_less_min_qty = line.sale_min_qty and ( float_compare( product_qty, line.sale_min_qty, precision_rounding=rounding ) < 0 - ) + ) or False @api.depends("product_uom_qty", "sale_max_qty") def _compute_is_qty_bigger_max_qty(self): @@ -129,12 +129,12 @@ def _compute_is_qty_bigger_max_qty(self): product_qty = line.product_uom._compute_quantity( line.product_uom_qty, line.product_id.uom_id ) - line.is_qty_bigger_max_qty = ( + line.is_qty_bigger_max_qty = line.sale_max_qty and ( float_compare( product_qty, line.sale_max_qty, precision_rounding=rounding ) - < 0 - ) + > 0 + ) or False @api.depends("product_uom_qty", "sale_multiple_qty") def _compute_is_qty_not_multiple_qty(self): diff --git a/sale_restricted_qty/views/product_category_views.xml b/sale_restricted_qty/views/product_category_views.xml index 0c578c04135..e1e92773169 100644 --- a/sale_restricted_qty/views/product_category_views.xml +++ b/sale_restricted_qty/views/product_category_views.xml @@ -6,16 +6,27 @@ - - - - - - - - - - + + + + + + + + + diff --git a/sale_restricted_qty/views/product_product_views.xml b/sale_restricted_qty/views/product_product_views.xml index 2b33952ed04..5389c4c2b7a 100644 --- a/sale_restricted_qty/views/product_product_views.xml +++ b/sale_restricted_qty/views/product_product_views.xml @@ -9,15 +9,27 @@ - - - - - - - - - + + + + + + + + + diff --git a/sale_restricted_qty/views/product_template_views.xml b/sale_restricted_qty/views/product_template_views.xml index 51b47cfa58a..18379214104 100644 --- a/sale_restricted_qty/views/product_template_views.xml +++ b/sale_restricted_qty/views/product_template_views.xml @@ -7,15 +7,27 @@ - - - - - - - - - + + + + + + + + + diff --git a/sale_restricted_qty/views/sale_views.xml b/sale_restricted_qty/views/sale_views.xml index bc2beb4d2a2..6d42932c483 100644 --- a/sale_restricted_qty/views/sale_views.xml +++ b/sale_restricted_qty/views/sale_views.xml @@ -11,6 +11,9 @@ + + + From 165840f269032bffce3cfacdb3e330f99e4ce4f8 Mon Sep 17 00:00:00 2001 From: Mourad Date: Fri, 19 Jun 2020 16:11:34 +0200 Subject: [PATCH 04/27] [IMP] add _get_sale_restricted_qty on sale to make qty overridable by other module & pep8 --- .../models/product_restricted_qty_mixin.py | 4 +- sale_restricted_qty/models/sale.py | 44 ++++++++++---- sale_restricted_qty/tests/test_sale.py | 59 ++++++++++++++----- 3 files changed, 79 insertions(+), 28 deletions(-) diff --git a/sale_restricted_qty/models/product_restricted_qty_mixin.py b/sale_restricted_qty/models/product_restricted_qty_mixin.py index e9a4e83ec9d..9fc9f569fa3 100644 --- a/sale_restricted_qty/models/product_restricted_qty_mixin.py +++ b/sale_restricted_qty/models/product_restricted_qty_mixin.py @@ -48,7 +48,7 @@ class ProductMinMultipleMixin(models.AbstractModel): ('use_parent', 'Use Parent Setting'), ('force', 'Yes'), ('not_force', 'No'), - ], + ], string="Manual Force Min Qty", required=True, default="use_parent", @@ -83,7 +83,7 @@ class ProductMinMultipleMixin(models.AbstractModel): ('use_parent', 'Use Parent Setting'), ('force', 'Yes'), ('not_force', 'No'), - ], + ], required=True, default="use_parent", string="Manual Force Max Qty", diff --git a/sale_restricted_qty/models/sale.py b/sale_restricted_qty/models/sale.py index ae0d690b24d..ca187dd4cdb 100644 --- a/sale_restricted_qty/models/sale.py +++ b/sale_restricted_qty/models/sale.py @@ -12,13 +12,13 @@ class SaleOrderLine(models.Model): sale_min_qty = fields.Float( string="Min Qty", - related="product_id.sale_min_qty", - readonly=True, + compute="_get_sale_restricted_qty", store=True, digits=dp.get_precision("Product Unit of Measure"), ) force_sale_min_qty = fields.Boolean( - related="product_id.force_sale_min_qty", readonly=True, store=True + compute="_get_sale_restricted_qty", + readonly=True, store=True ) is_qty_less_min_qty = fields.Boolean( string="Qty < Min Qty", compute="_compute_is_qty_less_min_qty" @@ -26,21 +26,20 @@ class SaleOrderLine(models.Model): sale_max_qty = fields.Float( string="Max Qty", - related="product_id.sale_max_qty", - readonly=True, + compute="_get_sale_restricted_qty", store=True, digits=dp.get_precision("Product Unit of Measure"), ) force_sale_max_qty = fields.Boolean( - related="product_id.force_sale_max_qty", readonly=True, store=True + compute="_get_sale_restricted_qty", + readonly=True, store=True ) is_qty_bigger_max_qty = fields.Boolean( string="Qty > max Qty", compute="_compute_is_qty_bigger_max_qty" ) sale_multiple_qty = fields.Float( string="Multiple Qty", - related="product_id.sale_multiple_qty", - readonly=True, + compute="_get_sale_restricted_qty", store=True, digits=dp.get_precision("Product Unit of Measure"), ) @@ -48,8 +47,10 @@ class SaleOrderLine(models.Model): string="Not Multiple Qty", compute="_compute_is_qty_not_multiple_qty" ) - @api.constrains("product_uom_qty") + @api.constrains( + "product_uom_qty", "sale_min_qty", "sale_max_qty", "sale_multiple_qty") def check_constraint_restricted_qty(self): + msg = "" invaild_min_lines = [] line_to_test = self.filtered( @@ -143,5 +144,28 @@ def _compute_is_qty_not_multiple_qty(self): line.product_uom_qty, line.product_id.uom_id ) line.is_qty_not_multiple_qty = ( - line.sale_multiple_qty > 0 and product_qty % line.sale_multiple_qty != 0 + line.sale_multiple_qty > 0 + and product_qty % line.sale_multiple_qty != 0 ) + + def _get_sale_restricted_qty(self): + """Overridable function to change qty values (ex: form stock)""" + self.ensure_one() + res = { + "sale_min_qty": (self.product_id and + self.product_id.sale_min_qty or 0), + "force_sale_min_qty": (self.product_id and + self.product_id.force_sale_min_qty or False), + "sale_max_qty": (self.product_id and + self.product_id.sale_max_qty or 0), + "force_sale_max_qty": (self.product_id and + self.product_id.force_sale_max_qty or False), + "sale_multiple_qty": (self.product_id and + self.product_id.sale_multiple_qty or 0), + } + return res + + @api.depends("product_id") + def _compute_sale_restricted_qty(self): + for rec in self: + rec.update(rec._get_sale_restricted_qty()) diff --git a/sale_restricted_qty/tests/test_sale.py b/sale_restricted_qty/tests/test_sale.py index d634a034324..b1bd517f1ab 100644 --- a/sale_restricted_qty/tests/test_sale.py +++ b/sale_restricted_qty/tests/test_sale.py @@ -46,19 +46,32 @@ def setUp(self): "force_sale_max_qty": False, }) + def refrech_sale_values(self, sale_order): + sale_order.order_line._compute_sale_restricted_qty() + sale_order.order_line.product_id_change() + sale_order.order_line._compute_is_qty_less_min_qty() + sale_order.order_line._compute_is_qty_bigger_max_qty() + sale_values = sale_order._convert_to_write(sale_order._cache) + return sale_values + def test_check_sale_order_min_qty_required(self): line_values = {"product_id": self.product.id, "product_uom_qty": 5.0} self.product.manual_sale_min_qty = 10 # Create sale order line with Qty less than min Qty with self.assertRaises(ValidationError): - self.sale_order = self.sale_order_model.create( - {"partner_id": self.partner.id, "order_line": [(0, 0, line_values)]} + sale_order = self.sale_order_model.new( + {"partner_id": self.partner.id, + "order_line": [(0, 0, line_values)]} ) + sale_values = self.refrech_sale_values(sale_order) + self.sale_order_model.create(sale_values) line_values["product_uom_qty"] = 12.0 # Create sale order line with Qty great then min Qty self.sale_order = self.sale_order_model.create( - {"partner_id": self.partner.id, "order_line": [(0, 0, line_values)]} + {"partner_id": self.partner.id, + "order_line": [(0, 0, line_values)]} ) + self.sale_order.order_line._compute_sale_restricted_qty() self.assertFalse(self.sale_order.order_line.is_qty_less_min_qty) self.assertEqual(self.sale_order.order_line.product_uom_qty, 12.0) @@ -71,42 +84,51 @@ def test_check_sale_order_min_qty_recommended(self): # Create sale order line with Qty less than min Qty self.sale_order = self.sale_order_model.create( - {"partner_id": self.partner.id, "order_line": [(0, 0, line_values)]} + {"partner_id": self.partner.id, + "order_line": [(0, 0, line_values)]} ) + self.sale_order.order_line._compute_sale_restricted_qty() self.assertTrue(self.sale_order.order_line.is_qty_less_min_qty) self.assertEqual(self.sale_order.order_line.product_uom_qty, 5.0) def test_check_sale_order_max_qty_required(self): - line_values = {"product_id": self.product.id, "product_uom_qty": 5.0} + line_values = {"product_id": self.product.id, "product_uom_qty": 15.0} self.product.manual_sale_max_qty = 10 # Create sale order line with Qty bigger than max Qty with self.assertRaises(ValidationError): - self.sale_order = self.sale_order_model.create( - {"partner_id": self.partner.id, "order_line": [(0, 0, line_values)]} + sale_order = self.sale_order_model.create( + {"partner_id": self.partner.id, + "order_line": [(0, 0, line_values)]} ) - line_values["product_uom_qty"] = 12.0 + sale_values = self.refrech_sale_values(sale_order) + self.sale_order_model.create(sale_values) + line_values["product_uom_qty"] = 2.0 # Create sale order line with Qty great then max Qty self.sale_order = self.sale_order_model.create( - {"partner_id": self.partner.id, "order_line": [(0, 0, line_values)]} + {"partner_id": self.partner.id, + "order_line": [(0, 0, line_values)]} ) + self.sale_order.order_line._compute_sale_restricted_qty() self.assertFalse(self.sale_order.order_line.is_qty_bigger_max_qty) - self.assertEqual(self.sale_order.order_line.product_uom_qty, 12.0) + self.assertEqual(self.sale_order.order_line.product_uom_qty, 2.0) def test_check_sale_order_max_qty_recommended(self): - line_values = {"product_id": self.product.id, "product_uom_qty": 5.0} + line_values = {"product_id": self.product.id, "product_uom_qty": 15.0} self.product.manual_sale_max_qty = 10 # Set Force max Qty to true self.product.manual_force_sale_max_qty = 'force' # Create sale order line with Qty bigger than max Qty self.sale_order = self.sale_order_model.create( - {"partner_id": self.partner.id, "order_line": [(0, 0, line_values)]} + {"partner_id": self.partner.id, + "order_line": [(0, 0, line_values)]} ) + self.sale_order.order_line._compute_sale_restricted_qty() self.assertTrue(self.sale_order.order_line.is_qty_bigger_max_qty) - self.assertEqual(self.sale_order.order_line.product_uom_qty, 5.0) + self.assertEqual(self.sale_order.order_line.product_uom_qty, 15.0) def test_check_sale_order_multiple_qty_required(self): line_values = {"product_id": self.product.id, "product_uom_qty": 15.0} @@ -114,14 +136,19 @@ def test_check_sale_order_multiple_qty_required(self): self.product.manual_sale_multiple_qty = 10 # Create sale order line with Qty not multiple Qty with self.assertRaises(ValidationError): - self.sale_order = self.sale_order_model.create( - {"partner_id": self.partner.id, "order_line": [(0, 0, line_values)]} + sale_order = self.sale_order_model.create( + {"partner_id": self.partner.id, + "order_line": [(0, 0, line_values)]} ) + sale_values = self.refrech_sale_values(sale_order) + self.sale_order_model.create(sale_values) line_values["product_uom_qty"] = 20 # Create sale order line with Qty multiple Qty self.sale_order = self.sale_order_model.create( - {"partner_id": self.partner.id, "order_line": [(0, 0, line_values)]} + {"partner_id": self.partner.id, + "order_line": [(0, 0, line_values)]} ) + self.sale_order.order_line._compute_sale_restricted_qty() self.assertFalse(self.sale_order.order_line.is_qty_not_multiple_qty) self.assertEqual(self.sale_order.order_line.product_uom_qty, 20) From 32bf0f9b181e59df0fb0e86f2332b853f9c1130d Mon Sep 17 00:00:00 2001 From: Mourad Date: Tue, 23 Jun 2020 17:37:51 +0200 Subject: [PATCH 05/27] [FIX] _compute_sale_restricted_qty --- sale_restricted_qty/models/sale.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/sale_restricted_qty/models/sale.py b/sale_restricted_qty/models/sale.py index ca187dd4cdb..46b6d3d471e 100644 --- a/sale_restricted_qty/models/sale.py +++ b/sale_restricted_qty/models/sale.py @@ -12,12 +12,12 @@ class SaleOrderLine(models.Model): sale_min_qty = fields.Float( string="Min Qty", - compute="_get_sale_restricted_qty", + compute="_compute_sale_restricted_qty", store=True, digits=dp.get_precision("Product Unit of Measure"), ) force_sale_min_qty = fields.Boolean( - compute="_get_sale_restricted_qty", + compute="_compute_sale_restricted_qty", readonly=True, store=True ) is_qty_less_min_qty = fields.Boolean( @@ -26,12 +26,12 @@ class SaleOrderLine(models.Model): sale_max_qty = fields.Float( string="Max Qty", - compute="_get_sale_restricted_qty", + compute="_compute_sale_restricted_qty", store=True, digits=dp.get_precision("Product Unit of Measure"), ) force_sale_max_qty = fields.Boolean( - compute="_get_sale_restricted_qty", + compute="_compute_sale_restricted_qty", readonly=True, store=True ) is_qty_bigger_max_qty = fields.Boolean( @@ -39,7 +39,7 @@ class SaleOrderLine(models.Model): ) sale_multiple_qty = fields.Float( string="Multiple Qty", - compute="_get_sale_restricted_qty", + compute="_compute_sale_restricted_qty", store=True, digits=dp.get_precision("Product Unit of Measure"), ) @@ -54,7 +54,8 @@ def check_constraint_restricted_qty(self): msg = "" invaild_min_lines = [] line_to_test = self.filtered( - lambda l: not l.product_id.force_sale_min_qty and l.is_qty_less_min_qty + lambda sl: not sl.product_id.force_sale_min_qty and + sl.is_qty_less_min_qty ) for line in line_to_test: invaild_min_lines.append( @@ -72,7 +73,8 @@ def check_constraint_restricted_qty(self): ) invaild_max_lines = [] line_to_test = self.filtered( - lambda l: not l.product_id.force_sale_max_qty and l.is_qty_bigger_max_qty + lambda sl: not sl.product_id.force_sale_max_qty and + sl.is_qty_bigger_max_qty ) for line in line_to_test: invaild_max_lines.append( @@ -89,7 +91,7 @@ def check_constraint_restricted_qty(self): ',Check "force max quatity" on product' ) invaild_multiple_lines = [] - line_to_test = self.filtered(lambda l: l.is_qty_not_multiple_qty) + line_to_test = self.filtered(lambda sl: sl.is_qty_not_multiple_qty) for line in line_to_test: invaild_multiple_lines.append( _('Product "%s": multiple Quantity %s.') From 4f098fb106d8a7090d53356a423f6889a98f1a58 Mon Sep 17 00:00:00 2001 From: Mourad Date: Fri, 26 Jun 2020 17:28:06 +0200 Subject: [PATCH 06/27] [FIX] restricted_qty UserError Message --- sale_restricted_qty/models/sale.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sale_restricted_qty/models/sale.py b/sale_restricted_qty/models/sale.py index 46b6d3d471e..cd72200404a 100644 --- a/sale_restricted_qty/models/sale.py +++ b/sale_restricted_qty/models/sale.py @@ -60,7 +60,7 @@ def check_constraint_restricted_qty(self): for line in line_to_test: invaild_min_lines.append( _('Product "%s": Min Quantity %s.') - % (line.product_id.name, line.product_id.sale_min_qty) + % (line.product_id.name, line.sale_min_qty) ) if invaild_min_lines: @@ -79,7 +79,7 @@ def check_constraint_restricted_qty(self): for line in line_to_test: invaild_max_lines.append( _('Product "%s": max Quantity %s.') - % (line.product_id.name, line.product_id.sale_max_qty) + % (line.product_id.name, line.sale_max_qty) ) if invaild_max_lines: @@ -95,7 +95,7 @@ def check_constraint_restricted_qty(self): for line in line_to_test: invaild_multiple_lines.append( _('Product "%s": multiple Quantity %s.') - % (line.product_id.name, line.product_id.sale_multiple_qty) + % (line.product_id.name, line.sale_multiple_qty) ) if invaild_multiple_lines: From a36caea15dd3fc7bb3d6299605e6dc6154feacba Mon Sep 17 00:00:00 2001 From: Mourad Date: Mon, 29 Jun 2020 14:43:39 +0200 Subject: [PATCH 07/27] [FIX] openupgradelib requirements.txt --- sale_restricted_qty/__manifest__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sale_restricted_qty/__manifest__.py b/sale_restricted_qty/__manifest__.py index a686c872f4d..aed05cb82af 100644 --- a/sale_restricted_qty/__manifest__.py +++ b/sale_restricted_qty/__manifest__.py @@ -8,6 +8,9 @@ "author": "Akretion, " "Odoo Community Association (OCA)", "website": "https://github.com/OCA/sale-workflow/", "license": "AGPL-3", + "external_dependencies": { + 'python' : ['openupgradelib'], + }, "depends": ["sale_management"], "data": [ "views/product_category_views.xml", From fa7396ff1a2af0055dcc253c16449a4fef5412ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BEAU?= Date: Tue, 29 Sep 2020 16:30:56 +0200 Subject: [PATCH 08/27] [REF] refactor code to make it simplier and inheritable --- .../models/product_restricted_qty_mixin.py | 1 + sale_restricted_qty/models/sale.py | 14 ++++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/sale_restricted_qty/models/product_restricted_qty_mixin.py b/sale_restricted_qty/models/product_restricted_qty_mixin.py index 9fc9f569fa3..3213b200748 100644 --- a/sale_restricted_qty/models/product_restricted_qty_mixin.py +++ b/sale_restricted_qty/models/product_restricted_qty_mixin.py @@ -7,6 +7,7 @@ class ProductMinMultipleMixin(models.AbstractModel): _name = "product.restricted.qty.mixin" + _description = "Product Restrict Qty Mixin" sale_multiple_qty = fields.Float( compute="_compute_sale_restricted_qty", diff --git a/sale_restricted_qty/models/sale.py b/sale_restricted_qty/models/sale.py index cd72200404a..9eda0bb043f 100644 --- a/sale_restricted_qty/models/sale.py +++ b/sale_restricted_qty/models/sale.py @@ -111,13 +111,17 @@ def check_constraint_restricted_qty(self): if msg: raise ValidationError(msg) + def _get_product_qty_in_product_unit(self): + self.ensure_one() + return self.product_uom._compute_quantity( + self.product_uom_qty, self.product_id.uom_id + ) + @api.depends("product_uom_qty", "sale_min_qty") def _compute_is_qty_less_min_qty(self): for line in self: rounding = line.product_uom.rounding - product_qty = line.product_uom._compute_quantity( - line.product_uom_qty, line.product_id.uom_id - ) + product_qty = line._get_product_qty_in_product_unit() line.is_qty_less_min_qty = line.sale_min_qty and ( float_compare( product_qty, line.sale_min_qty, precision_rounding=rounding @@ -129,9 +133,7 @@ def _compute_is_qty_less_min_qty(self): def _compute_is_qty_bigger_max_qty(self): for line in self: rounding = line.product_uom.rounding - product_qty = line.product_uom._compute_quantity( - line.product_uom_qty, line.product_id.uom_id - ) + product_qty = line._get_product_qty_in_product_unit() line.is_qty_bigger_max_qty = line.sale_max_qty and ( float_compare( product_qty, line.sale_max_qty, precision_rounding=rounding From d298b472fc466bada8f76df9469dc09de8fe4607 Mon Sep 17 00:00:00 2001 From: Mourad Date: Thu, 3 Dec 2020 15:50:51 +0100 Subject: [PATCH 09/27] [FIX] _compute_is_qty_less bigger multiple qty --- sale_restricted_qty/README.rst | 23 +- sale_restricted_qty/__manifest__.py | 2 +- .../i18n/sale_restricted_qty.pot | 380 ++++++++++++++++++ sale_restricted_qty/models/sale.py | 6 +- .../static/description/index.html | 20 +- 5 files changed, 412 insertions(+), 19 deletions(-) create mode 100644 sale_restricted_qty/i18n/sale_restricted_qty.pot diff --git a/sale_restricted_qty/README.rst b/sale_restricted_qty/README.rst index 7949177ad2d..265f0cb756c 100644 --- a/sale_restricted_qty/README.rst +++ b/sale_restricted_qty/README.rst @@ -14,10 +14,10 @@ Sale order min quantity :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fsale--workflow-lightgray.png?logo=github - :target: https://github.com/OCA/sale-workflow/tree/12.0/sale_order_min_qty + :target: https://github.com/OCA/sale-workflow/tree/12.0/sale_restricted_qty :alt: OCA/sale-workflow .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/sale-workflow-12-0/sale-workflow-12-0-sale_order_min_qty + :target: https://translation.odoo-community.org/projects/sale-workflow-12-0/sale-workflow-12-0-sale_restricted_qty :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png :target: https://runbot.odoo-community.org/runbot/167/12.0 @@ -25,12 +25,19 @@ Sale order min quantity |badge1| |badge2| |badge3| |badge4| |badge5| -This module allows to define minimum order quantity on product. -Minimum order quantity is either required or recommanded. -If you check "Force min Qty" on product the min qty do not block sale +This module allows to define min, max and multiple order quantity on product. +Min and Max order quantity is either required or recommanded. +If you check "Force mini/max Qty" on product the min/max qty do not block sale order. This can be used if the available stock is less then the min qty -or if you want to sell sample products. +or if you want to sell sample products. +Max qty can be used if we dont want to sale more than available qty +(end of series product) + +Setings are defined on product, if values are not set we use settings from +product template. +If settings are not set on product template we use settings from +product category otherwise parent category. **Table of contents** @@ -54,7 +61,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 smashing it by providing a detailed and welcomed -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -84,6 +91,6 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use. -This module is part of the `OCA/sale-workflow `_ project on GitHub. +This module is part of the `OCA/sale-workflow `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/sale_restricted_qty/__manifest__.py b/sale_restricted_qty/__manifest__.py index aed05cb82af..64d192f8ce0 100644 --- a/sale_restricted_qty/__manifest__.py +++ b/sale_restricted_qty/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Sale order min quantity", - "version": "12.0.1.0.0", + "version": "12.0.2.0.0", "category": "Sales Management", "author": "Akretion, " "Odoo Community Association (OCA)", "website": "https://github.com/OCA/sale-workflow/", diff --git a/sale_restricted_qty/i18n/sale_restricted_qty.pot b/sale_restricted_qty/i18n/sale_restricted_qty.pot new file mode 100644 index 00000000000..4578596920f --- /dev/null +++ b/sale_restricted_qty/i18n/sale_restricted_qty.pot @@ -0,0 +1,380 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_restricted_qty +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: sale_restricted_qty +#: code:addons/sale_restricted_qty/models/sale.py:89 +#, python-format +msgid "\n" +"* If you want sell quantity bigger than max Quantity,Check \"force max quatity\" on product" +msgstr "" + +#. module: sale_restricted_qty +#: code:addons/sale_restricted_qty/models/sale.py:70 +#, python-format +msgid "\n" +"* If you want sell quantity less than Min Quantity,Check \"force min quatity\" on product" +msgstr "" + +#. module: sale_restricted_qty +#: code:addons/sale_restricted_qty/models/sale.py:105 +#, python-format +msgid "\n" +"* If you want sell quantity not multiple Quantity,Set multiple quantity to 0 on product or product template or product category" +msgstr "" + +#. module: sale_restricted_qty +#: code:addons/sale_restricted_qty/models/sale.py:86 +#, python-format +msgid "Check maximum order quantity for this products: * \n" +"" +msgstr "" + +#. module: sale_restricted_qty +#: code:addons/sale_restricted_qty/models/sale.py:67 +#, python-format +msgid "Check minimum order quantity for this products: * \n" +"" +msgstr "" + +#. module: sale_restricted_qty +#: code:addons/sale_restricted_qty/models/sale.py:102 +#, python-format +msgid "Check multiple order quantity for this products: * \n" +"" +msgstr "" + +#. module: sale_restricted_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_category__force_sale_max_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_product__force_sale_max_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_restricted_qty_mixin__force_sale_max_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_template__force_sale_max_qty +msgid "Define if user can force sale max qty 'If not set', Odoo will use the value defined in the parent object.Hierarchy is in this order :Product/product Template/product category/parent categoroies " +msgstr "" + +#. module: sale_restricted_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_category__force_sale_min_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_product__force_sale_min_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_restricted_qty_mixin__force_sale_min_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_template__force_sale_min_qty +msgid "Define if user can force sale min qty 'If not set', Odoo will use the value defined in the parent object.Hierarchy is in this order :Product/product Template/product category/parent categoroies " +msgstr "" + +#. module: sale_restricted_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_category__sale_max_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_product__sale_max_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_restricted_qty_mixin__sale_max_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_template__sale_max_qty +msgid "Define sale max qty 'If not set, Odoo will use the value defined in the parent object.Hierarchy is in this order :Product/product Template/product category/parent categoroies " +msgstr "" + +#. module: sale_restricted_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_category__sale_min_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_product__sale_min_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_restricted_qty_mixin__sale_min_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_template__sale_min_qty +msgid "Define sale min qty 'If not set, Odoo will use the value defined in the parent object.Hierarchy is in this order :Product/product Template/product category/parent categoroies " +msgstr "" + +#. module: sale_restricted_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_category__sale_multiple_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_product__sale_multiple_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_restricted_qty_mixin__sale_multiple_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_template__sale_multiple_qty +msgid "Define sale multiple qty 'If not set', Odoo will use the value defined in the parent object.Hierarchy is in this order :Product/product Template/product category/parent categoroies " +msgstr "" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_restricted_qty_mixin__display_name +msgid "Display Name" +msgstr "" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_category__force_sale_max_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_product__force_sale_max_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_restricted_qty_mixin__force_sale_max_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_template__force_sale_max_qty +msgid "Force Max Qty" +msgstr "" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_category__force_sale_min_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_product__force_sale_min_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_restricted_qty_mixin__force_sale_min_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_template__force_sale_min_qty +msgid "Force Min Qty" +msgstr "" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_sale_order_line__force_sale_max_qty +msgid "Force Sale Max Qty" +msgstr "" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_sale_order_line__force_sale_min_qty +msgid "Force Sale Min Qty" +msgstr "" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_restricted_qty_mixin__id +msgid "ID" +msgstr "" + +#. module: sale_restricted_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_category__manual_force_sale_max_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_product__manual_force_sale_max_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_restricted_qty_mixin__manual_force_sale_max_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_template__manual_force_sale_max_qty +msgid "If force max qty is checked, the max quantity is only indicative value.If is not test we check parent value" +msgstr "" + +#. module: sale_restricted_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_category__manual_force_sale_min_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_product__manual_force_sale_min_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_restricted_qty_mixin__manual_force_sale_min_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_template__manual_force_sale_min_qty +msgid "If force min qty is checked, the min quantity is only indicative value.If is not test we check parent value" +msgstr "" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_restricted_qty_mixin____last_update +msgid "Last Modified on" +msgstr "" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_category__manual_force_sale_max_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_product__manual_force_sale_max_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_restricted_qty_mixin__manual_force_sale_max_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_template__manual_force_sale_max_qty +msgid "Manual Force Max Qty" +msgstr "" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_category__manual_force_sale_min_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_product__manual_force_sale_min_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_restricted_qty_mixin__manual_force_sale_min_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_template__manual_force_sale_min_qty +msgid "Manual Force Min Qty" +msgstr "" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_sale_order_line__sale_max_qty +msgid "Max Qty" +msgstr "" + +#. module: sale_restricted_qty +#: model_terms:ir.ui.view,arch_db:sale_restricted_qty.view_order_form +msgid "Max Quantity Exceeded" +msgstr "" + +#. module: sale_restricted_qty +#: model_terms:ir.ui.view,arch_db:sale_restricted_qty.view_order_form +msgid "Max Quantity Recommended" +msgstr "" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_category__manual_sale_max_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_product__manual_sale_max_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_restricted_qty_mixin__manual_sale_max_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_template__manual_sale_max_qty +msgid "Max Sale Qty" +msgstr "" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_sale_order_line__sale_min_qty +msgid "Min Qty" +msgstr "" + +#. module: sale_restricted_qty +#: model_terms:ir.ui.view,arch_db:sale_restricted_qty.view_order_form +msgid "Min Quantity Recommended" +msgstr "" + +#. module: sale_restricted_qty +#: model_terms:ir.ui.view,arch_db:sale_restricted_qty.view_order_form +msgid "Min Quantity Required" +msgstr "" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_category__manual_sale_min_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_product__manual_sale_min_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_restricted_qty_mixin__manual_sale_min_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_template__manual_sale_min_qty +msgid "Min Sale Qty" +msgstr "" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_sale_order_line__sale_multiple_qty +msgid "Multiple Qty" +msgstr "" + +#. module: sale_restricted_qty +#: model_terms:ir.ui.view,arch_db:sale_restricted_qty.view_order_form +msgid "Multiple Quantity Required" +msgstr "" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_category__manual_sale_multiple_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_product__manual_sale_multiple_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_restricted_qty_mixin__manual_sale_multiple_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_template__manual_sale_multiple_qty +msgid "Multiple Sale Qty" +msgstr "" + +#. module: sale_restricted_qty +#: selection:product.category,manual_force_sale_max_qty:0 +#: selection:product.category,manual_force_sale_min_qty:0 +#: selection:product.product,manual_force_sale_max_qty:0 +#: selection:product.product,manual_force_sale_min_qty:0 +#: selection:product.restricted.qty.mixin,manual_force_sale_max_qty:0 +#: selection:product.restricted.qty.mixin,manual_force_sale_min_qty:0 +#: selection:product.template,manual_force_sale_max_qty:0 +#: selection:product.template,manual_force_sale_min_qty:0 +msgid "No" +msgstr "" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_sale_order_line__is_qty_not_multiple_qty +msgid "Not Multiple Qty" +msgstr "" + +#. module: sale_restricted_qty +#: model:ir.model,name:sale_restricted_qty.model_product_product +msgid "Product" +msgstr "" + +#. module: sale_restricted_qty +#: code:addons/sale_restricted_qty/models/sale.py:62 +#, python-format +msgid "Product \"%s\": Min Quantity %s." +msgstr "" + +#. module: sale_restricted_qty +#: code:addons/sale_restricted_qty/models/sale.py:81 +#, python-format +msgid "Product \"%s\": max Quantity %s." +msgstr "" + +#. module: sale_restricted_qty +#: code:addons/sale_restricted_qty/models/sale.py:97 +#, python-format +msgid "Product \"%s\": multiple Quantity %s." +msgstr "" + +#. module: sale_restricted_qty +#: model:ir.model,name:sale_restricted_qty.model_product_category +msgid "Product Category" +msgstr "" + +#. module: sale_restricted_qty +#: model:ir.model,name:sale_restricted_qty.model_product_restricted_qty_mixin +msgid "Product Restrict Qty Mixin" +msgstr "" + +#. module: sale_restricted_qty +#: model:ir.model,name:sale_restricted_qty.model_product_template +msgid "Product Template" +msgstr "" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_sale_order_line__is_qty_less_min_qty +msgid "Qty < Min Qty" +msgstr "" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_sale_order_line__is_qty_bigger_max_qty +msgid "Qty > max Qty" +msgstr "" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_category__sale_max_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_product__sale_max_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_restricted_qty_mixin__sale_max_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_template__sale_max_qty +msgid "Sale Max Qty" +msgstr "" + +#. module: sale_restricted_qty +#: model_terms:ir.ui.view,arch_db:sale_restricted_qty.min_order_qty_product_product_form_view +#: model_terms:ir.ui.view,arch_db:sale_restricted_qty.view_product_category_form +#: model_terms:ir.ui.view,arch_db:sale_restricted_qty.view_product_template_form +msgid "Sale Max qty" +msgstr "" + +#. module: sale_restricted_qty +#: model_terms:ir.ui.view,arch_db:sale_restricted_qty.min_order_qty_product_product_form_view +#: model_terms:ir.ui.view,arch_db:sale_restricted_qty.view_product_category_form +#: model_terms:ir.ui.view,arch_db:sale_restricted_qty.view_product_template_form +msgid "Sale Miltiple qty" +msgstr "" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_category__sale_min_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_product__sale_min_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_restricted_qty_mixin__sale_min_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_template__sale_min_qty +msgid "Sale Min Qty" +msgstr "" + +#. module: sale_restricted_qty +#: model_terms:ir.ui.view,arch_db:sale_restricted_qty.min_order_qty_product_product_form_view +#: model_terms:ir.ui.view,arch_db:sale_restricted_qty.view_product_category_form +#: model_terms:ir.ui.view,arch_db:sale_restricted_qty.view_product_template_form +msgid "Sale Min qty" +msgstr "" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_category__sale_multiple_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_product__sale_multiple_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_restricted_qty_mixin__sale_multiple_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_template__sale_multiple_qty +msgid "Sale Multiple Qty" +msgstr "" + +#. module: sale_restricted_qty +#: model_terms:ir.ui.view,arch_db:sale_restricted_qty.min_order_qty_product_product_form_view +#: model_terms:ir.ui.view,arch_db:sale_restricted_qty.view_product_category_form +#: model_terms:ir.ui.view,arch_db:sale_restricted_qty.view_product_template_form +msgid "Sale restricted qty" +msgstr "" + +#. module: sale_restricted_qty +#: model:ir.model,name:sale_restricted_qty.model_sale_order_line +msgid "Sales Order Line" +msgstr "" + +#. module: sale_restricted_qty +#: selection:product.category,manual_force_sale_max_qty:0 +#: selection:product.category,manual_force_sale_min_qty:0 +#: selection:product.product,manual_force_sale_max_qty:0 +#: selection:product.product,manual_force_sale_min_qty:0 +#: selection:product.restricted.qty.mixin,manual_force_sale_max_qty:0 +#: selection:product.restricted.qty.mixin,manual_force_sale_min_qty:0 +#: selection:product.template,manual_force_sale_max_qty:0 +#: selection:product.template,manual_force_sale_min_qty:0 +msgid "Use Parent Setting" +msgstr "" + +#. module: sale_restricted_qty +#: selection:product.category,manual_force_sale_max_qty:0 +#: selection:product.category,manual_force_sale_min_qty:0 +#: selection:product.product,manual_force_sale_max_qty:0 +#: selection:product.product,manual_force_sale_min_qty:0 +#: selection:product.restricted.qty.mixin,manual_force_sale_max_qty:0 +#: selection:product.restricted.qty.mixin,manual_force_sale_min_qty:0 +#: selection:product.template,manual_force_sale_max_qty:0 +#: selection:product.template,manual_force_sale_min_qty:0 +msgid "Yes" +msgstr "" + diff --git a/sale_restricted_qty/models/sale.py b/sale_restricted_qty/models/sale.py index 9eda0bb043f..e5860d54797 100644 --- a/sale_restricted_qty/models/sale.py +++ b/sale_restricted_qty/models/sale.py @@ -117,7 +117,7 @@ def _get_product_qty_in_product_unit(self): self.product_uom_qty, self.product_id.uom_id ) - @api.depends("product_uom_qty", "sale_min_qty") + @api.depends("product_id", "product_uom_qty", "sale_min_qty") def _compute_is_qty_less_min_qty(self): for line in self: rounding = line.product_uom.rounding @@ -129,7 +129,7 @@ def _compute_is_qty_less_min_qty(self): < 0 ) or False - @api.depends("product_uom_qty", "sale_max_qty") + @api.depends("product_id", "product_uom_qty", "sale_max_qty") def _compute_is_qty_bigger_max_qty(self): for line in self: rounding = line.product_uom.rounding @@ -141,7 +141,7 @@ def _compute_is_qty_bigger_max_qty(self): > 0 ) or False - @api.depends("product_uom_qty", "sale_multiple_qty") + @api.depends("product_id", "product_uom_qty", "sale_multiple_qty") def _compute_is_qty_not_multiple_qty(self): for line in self: product_qty = line.product_uom._compute_quantity( diff --git a/sale_restricted_qty/static/description/index.html b/sale_restricted_qty/static/description/index.html index 3bada92d5b5..a86a4ad781e 100644 --- a/sale_restricted_qty/static/description/index.html +++ b/sale_restricted_qty/static/description/index.html @@ -367,13 +367,19 @@

Sale order min quantity

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

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

-

This module allows to define minimum order quantity on product. -Minimum order quantity is either required or recommanded. -If you check “Force min Qty” on product the min qty do not block sale +

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

+

This module allows to define min, max and multiple order quantity on product. +Min and Max order quantity is either required or recommanded. +If you check “Force mini/max Qty” on product the min/max qty do not block sale order. This can be used if the available stock is less then the min qty -or if you want to sell sample products.

+or if you want to sell sample products. +Max qty can be used if we dont want to sale more than available qty +(end of series product)

+

Setings are defined on product, if values are not set we use settings from +product template. +If settings are not set on product template we use settings from +product category otherwise parent category.

Table of contents

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

    +feedback.

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

@@ -434,7 +440,7 @@

Maintainers

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

-

This module is part of the OCA/sale-workflow project on GitHub.

+

This module is part of the OCA/sale-workflow project on GitHub.

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

From 9612d09263cd4c6f345a0bd5a2d446defe5a487e Mon Sep 17 00:00:00 2001 From: Samuele Mariani Date: Mon, 4 Oct 2021 16:08:31 +0000 Subject: [PATCH 10/27] Added translation using Weblate (Italian) --- sale_restricted_qty/i18n/it.po | 412 +++++++++++++++++++++++++++++++++ 1 file changed, 412 insertions(+) create mode 100644 sale_restricted_qty/i18n/it.po diff --git a/sale_restricted_qty/i18n/it.po b/sale_restricted_qty/i18n/it.po new file mode 100644 index 00000000000..bc3367ea42c --- /dev/null +++ b/sale_restricted_qty/i18n/it.po @@ -0,0 +1,412 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_restricted_qty +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2021-10-05 18:45+0000\n" +"Last-Translator: Samuele Mariani \n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.3.2\n" + +#. module: sale_restricted_qty +#: code:addons/sale_restricted_qty/models/sale.py:89 +#, python-format +msgid "\n" +"* If you want sell quantity bigger than max Quantity,Check \"force max quatity\" on product" +msgstr "" +"\n" +"* se vuoi vendere una quantità maggiore della Quantità massima, Seleziona " +"\"forza quantità massima\" nel prodotto" + +#. module: sale_restricted_qty +#: code:addons/sale_restricted_qty/models/sale.py:70 +#, python-format +msgid "\n" +"* If you want sell quantity less than Min Quantity,Check \"force min quatity\" on product" +msgstr "" +"\n" +"* se vuoi vendere una quantità inferiore alla Quantità Minima, Seleziona " +"\"forza quantità minima\" nel prodotto" + +#. module: sale_restricted_qty +#: code:addons/sale_restricted_qty/models/sale.py:105 +#, python-format +msgid "\n" +"* If you want sell quantity not multiple Quantity,Set multiple quantity to 0 on product or product template or product category" +msgstr "" +"\n" +"* se vuoi vendere quantità non multiple, Imposta quantità multiple a 0 nella " +"Prodotto o Modello Prodotto o Categoria Prodotto" + +#. module: sale_restricted_qty +#: code:addons/sale_restricted_qty/models/sale.py:86 +#, python-format +msgid "Check maximum order quantity for this products: * \n" +"" +msgstr "Controlla la quantità di vendita massima per questi prodotti: * \n" + +#. module: sale_restricted_qty +#: code:addons/sale_restricted_qty/models/sale.py:67 +#, python-format +msgid "Check minimum order quantity for this products: * \n" +"" +msgstr "Controlla la quantità di vendita minima per questi prodotti: * \n" + +#. module: sale_restricted_qty +#: code:addons/sale_restricted_qty/models/sale.py:102 +#, python-format +msgid "Check multiple order quantity for this products: * \n" +"" +msgstr "Controlla la quantità di vendita multipla per questi prodotti: * \n" + +#. module: sale_restricted_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_category__force_sale_max_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_product__force_sale_max_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_restricted_qty_mixin__force_sale_max_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_template__force_sale_max_qty +msgid "Define if user can force sale max qty 'If not set', Odoo will use the value defined in the parent object.Hierarchy is in this order :Product/product Template/product category/parent categoroies " +msgstr "" +"Definisci se l'utente può forzare la quantità massima di vendita 'Se non " +"impostato', Odoo utilizzerà il valore definito nell'oggetto padre. La " +"gerarchia è in questo ordine: Prodotto/Modello prodotto/categoria prodotto/" +"categorie padre " + +#. module: sale_restricted_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_category__force_sale_min_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_product__force_sale_min_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_restricted_qty_mixin__force_sale_min_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_template__force_sale_min_qty +msgid "Define if user can force sale min qty 'If not set', Odoo will use the value defined in the parent object.Hierarchy is in this order :Product/product Template/product category/parent categoroies " +msgstr "" +"Definisci se l'utente può forzare la quantità minima di vendita 'Se non " +"impostato', Odoo utilizzerà il valore definito nell'oggetto padre. La " +"gerarchia è in questo ordine: Prodotto/Modello prodotto/categoria prodotto/" +"categorie padre " + +#. module: sale_restricted_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_category__sale_max_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_product__sale_max_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_restricted_qty_mixin__sale_max_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_template__sale_max_qty +msgid "Define sale max qty 'If not set, Odoo will use the value defined in the parent object.Hierarchy is in this order :Product/product Template/product category/parent categoroies " +msgstr "" +"Definisci la qtà massima di vendita 'Se non impostato', Odoo utilizzerà il " +"valore definito nell'oggetto padre. La gerarchia è in questo ordine: " +"Prodotto/Modello prodotto/categoria prodotto/categorie padre " + +#. module: sale_restricted_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_category__sale_min_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_product__sale_min_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_restricted_qty_mixin__sale_min_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_template__sale_min_qty +msgid "Define sale min qty 'If not set, Odoo will use the value defined in the parent object.Hierarchy is in this order :Product/product Template/product category/parent categoroies " +msgstr "" +"Definisci la qtà minima di vendita 'Se non impostato', Odoo utilizzerà il " +"valore definito nell'oggetto padre. La gerarchia è in questo ordine: " +"Prodotto/Modello prodotto/categoria prodotto/categorie padre " + +#. module: sale_restricted_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_category__sale_multiple_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_product__sale_multiple_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_restricted_qty_mixin__sale_multiple_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_template__sale_multiple_qty +msgid "Define sale multiple qty 'If not set', Odoo will use the value defined in the parent object.Hierarchy is in this order :Product/product Template/product category/parent categoroies " +msgstr "" +"Definisci la qtà multipla di vendita 'Se non impostato', Odoo utilizzerà il " +"valore definito nell'oggetto padre. La gerarchia è in questo ordine: " +"Prodotto/Modello prodotto/categoria prodotto/categorie padre " + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_restricted_qty_mixin__display_name +msgid "Display Name" +msgstr "Nome Visualizzato" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_category__force_sale_max_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_product__force_sale_max_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_restricted_qty_mixin__force_sale_max_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_template__force_sale_max_qty +msgid "Force Max Qty" +msgstr "Forza Qtà Massima" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_category__force_sale_min_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_product__force_sale_min_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_restricted_qty_mixin__force_sale_min_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_template__force_sale_min_qty +msgid "Force Min Qty" +msgstr "Forza Qtà Minima" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_sale_order_line__force_sale_max_qty +msgid "Force Sale Max Qty" +msgstr "Forza Qtà Minima di Vendita" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_sale_order_line__force_sale_min_qty +msgid "Force Sale Min Qty" +msgstr "Forza Qtà Massima di Vendita" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_restricted_qty_mixin__id +msgid "ID" +msgstr "ID" + +#. module: sale_restricted_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_category__manual_force_sale_max_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_product__manual_force_sale_max_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_restricted_qty_mixin__manual_force_sale_max_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_template__manual_force_sale_max_qty +msgid "If force max qty is checked, the max quantity is only indicative value.If is not test we check parent value" +msgstr "" +"Se forza qtà massima è selezionata, la quantità massima è solo un valore " +"indicativo. Se non è test controlliamo il valore genitore" + +#. module: sale_restricted_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_category__manual_force_sale_min_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_product__manual_force_sale_min_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_restricted_qty_mixin__manual_force_sale_min_qty +#: model:ir.model.fields,help:sale_restricted_qty.field_product_template__manual_force_sale_min_qty +msgid "If force min qty is checked, the min quantity is only indicative value.If is not test we check parent value" +msgstr "" +"Se forza qtà minima è selezionata, la quantità minima è solo un valore " +"indicativo. Se non è test controlliamo il valore genitore" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_restricted_qty_mixin____last_update +msgid "Last Modified on" +msgstr "Ultima Modifica il" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_category__manual_force_sale_max_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_product__manual_force_sale_max_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_restricted_qty_mixin__manual_force_sale_max_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_template__manual_force_sale_max_qty +msgid "Manual Force Max Qty" +msgstr "Forza Manualmente Qtà Massima" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_category__manual_force_sale_min_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_product__manual_force_sale_min_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_restricted_qty_mixin__manual_force_sale_min_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_template__manual_force_sale_min_qty +msgid "Manual Force Min Qty" +msgstr "Forza Manualmente Qtà Minima" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_sale_order_line__sale_max_qty +msgid "Max Qty" +msgstr "Quantità Massima" + +#. module: sale_restricted_qty +#: model_terms:ir.ui.view,arch_db:sale_restricted_qty.view_order_form +msgid "Max Quantity Exceeded" +msgstr "Quantità Massima Superata" + +#. module: sale_restricted_qty +#: model_terms:ir.ui.view,arch_db:sale_restricted_qty.view_order_form +msgid "Max Quantity Recommended" +msgstr "Qtà Massima Consigliata" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_category__manual_sale_max_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_product__manual_sale_max_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_restricted_qty_mixin__manual_sale_max_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_template__manual_sale_max_qty +msgid "Max Sale Qty" +msgstr "Qtà di Vendita Massima" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_sale_order_line__sale_min_qty +msgid "Min Qty" +msgstr "Qtà Minima" + +#. module: sale_restricted_qty +#: model_terms:ir.ui.view,arch_db:sale_restricted_qty.view_order_form +msgid "Min Quantity Recommended" +msgstr "Qtà Minima Consigliata" + +#. module: sale_restricted_qty +#: model_terms:ir.ui.view,arch_db:sale_restricted_qty.view_order_form +msgid "Min Quantity Required" +msgstr "Qtà Minima Richiesta" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_category__manual_sale_min_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_product__manual_sale_min_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_restricted_qty_mixin__manual_sale_min_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_template__manual_sale_min_qty +msgid "Min Sale Qty" +msgstr "Qtà di Vendita Minima" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_sale_order_line__sale_multiple_qty +msgid "Multiple Qty" +msgstr "Qtà Multiple" + +#. module: sale_restricted_qty +#: model_terms:ir.ui.view,arch_db:sale_restricted_qty.view_order_form +msgid "Multiple Quantity Required" +msgstr "Quantità Multipla Richiesta" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_category__manual_sale_multiple_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_product__manual_sale_multiple_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_restricted_qty_mixin__manual_sale_multiple_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_template__manual_sale_multiple_qty +msgid "Multiple Sale Qty" +msgstr "Quantità di Vendita Multipla" + +#. module: sale_restricted_qty +#: selection:product.category,manual_force_sale_max_qty:0 +#: selection:product.category,manual_force_sale_min_qty:0 +#: selection:product.product,manual_force_sale_max_qty:0 +#: selection:product.product,manual_force_sale_min_qty:0 +#: selection:product.restricted.qty.mixin,manual_force_sale_max_qty:0 +#: selection:product.restricted.qty.mixin,manual_force_sale_min_qty:0 +#: selection:product.template,manual_force_sale_max_qty:0 +#: selection:product.template,manual_force_sale_min_qty:0 +msgid "No" +msgstr "No" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_sale_order_line__is_qty_not_multiple_qty +msgid "Not Multiple Qty" +msgstr "Qtà Non Multipla" + +#. module: sale_restricted_qty +#: model:ir.model,name:sale_restricted_qty.model_product_product +msgid "Product" +msgstr "Prodotto" + +#. module: sale_restricted_qty +#: code:addons/sale_restricted_qty/models/sale.py:62 +#, python-format +msgid "Product \"%s\": Min Quantity %s." +msgstr "Prodotto \"%s\": Quantità Minima %s." + +#. module: sale_restricted_qty +#: code:addons/sale_restricted_qty/models/sale.py:81 +#, python-format +msgid "Product \"%s\": max Quantity %s." +msgstr "Prodotto \"%s\": Quantità Massima %s." + +#. module: sale_restricted_qty +#: code:addons/sale_restricted_qty/models/sale.py:97 +#, python-format +msgid "Product \"%s\": multiple Quantity %s." +msgstr "prodotto \"%s\": Quantità Multipla %s." + +#. module: sale_restricted_qty +#: model:ir.model,name:sale_restricted_qty.model_product_category +msgid "Product Category" +msgstr "Categoria Prodotto" + +#. module: sale_restricted_qty +#: model:ir.model,name:sale_restricted_qty.model_product_restricted_qty_mixin +msgid "Product Restrict Qty Mixin" +msgstr "Mixin Limite di Quantità Prodotto" + +#. module: sale_restricted_qty +#: model:ir.model,name:sale_restricted_qty.model_product_template +msgid "Product Template" +msgstr "Modello Prodotto" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_sale_order_line__is_qty_less_min_qty +msgid "Qty < Min Qty" +msgstr "Qtà < Qtà Minima" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_sale_order_line__is_qty_bigger_max_qty +msgid "Qty > max Qty" +msgstr "Qtà > Qtà Massima" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_category__sale_max_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_product__sale_max_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_restricted_qty_mixin__sale_max_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_template__sale_max_qty +msgid "Sale Max Qty" +msgstr "Qtà Massima di Vendita" + +#. module: sale_restricted_qty +#: model_terms:ir.ui.view,arch_db:sale_restricted_qty.min_order_qty_product_product_form_view +#: model_terms:ir.ui.view,arch_db:sale_restricted_qty.view_product_category_form +#: model_terms:ir.ui.view,arch_db:sale_restricted_qty.view_product_template_form +msgid "Sale Max qty" +msgstr "qtà Massima di Vendita" + +#. module: sale_restricted_qty +#: model_terms:ir.ui.view,arch_db:sale_restricted_qty.min_order_qty_product_product_form_view +#: model_terms:ir.ui.view,arch_db:sale_restricted_qty.view_product_category_form +#: model_terms:ir.ui.view,arch_db:sale_restricted_qty.view_product_template_form +msgid "Sale Miltiple qty" +msgstr "qtà Multipla di Vendita" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_category__sale_min_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_product__sale_min_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_restricted_qty_mixin__sale_min_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_template__sale_min_qty +msgid "Sale Min Qty" +msgstr "Qtà Minima di Vendita" + +#. module: sale_restricted_qty +#: model_terms:ir.ui.view,arch_db:sale_restricted_qty.min_order_qty_product_product_form_view +#: model_terms:ir.ui.view,arch_db:sale_restricted_qty.view_product_category_form +#: model_terms:ir.ui.view,arch_db:sale_restricted_qty.view_product_template_form +msgid "Sale Min qty" +msgstr "qtà Minima di Vendita" + +#. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_category__sale_multiple_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_product__sale_multiple_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_restricted_qty_mixin__sale_multiple_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_template__sale_multiple_qty +msgid "Sale Multiple Qty" +msgstr "Qtà Multipla di Vendita" + +#. module: sale_restricted_qty +#: model_terms:ir.ui.view,arch_db:sale_restricted_qty.min_order_qty_product_product_form_view +#: model_terms:ir.ui.view,arch_db:sale_restricted_qty.view_product_category_form +#: model_terms:ir.ui.view,arch_db:sale_restricted_qty.view_product_template_form +msgid "Sale restricted qty" +msgstr "Quantità di vendita limitata" + +#. module: sale_restricted_qty +#: model:ir.model,name:sale_restricted_qty.model_sale_order_line +msgid "Sales Order Line" +msgstr "Riga Ordine di Vendita" + +#. module: sale_restricted_qty +#: selection:product.category,manual_force_sale_max_qty:0 +#: selection:product.category,manual_force_sale_min_qty:0 +#: selection:product.product,manual_force_sale_max_qty:0 +#: selection:product.product,manual_force_sale_min_qty:0 +#: selection:product.restricted.qty.mixin,manual_force_sale_max_qty:0 +#: selection:product.restricted.qty.mixin,manual_force_sale_min_qty:0 +#: selection:product.template,manual_force_sale_max_qty:0 +#: selection:product.template,manual_force_sale_min_qty:0 +msgid "Use Parent Setting" +msgstr "Usa Impostazioni Genitore" + +#. module: sale_restricted_qty +#: selection:product.category,manual_force_sale_max_qty:0 +#: selection:product.category,manual_force_sale_min_qty:0 +#: selection:product.product,manual_force_sale_max_qty:0 +#: selection:product.product,manual_force_sale_min_qty:0 +#: selection:product.restricted.qty.mixin,manual_force_sale_max_qty:0 +#: selection:product.restricted.qty.mixin,manual_force_sale_min_qty:0 +#: selection:product.template,manual_force_sale_max_qty:0 +#: selection:product.template,manual_force_sale_min_qty:0 +msgid "Yes" +msgstr "Si" From 357fe6e3323142decca7fcae98b7722ba14c882d Mon Sep 17 00:00:00 2001 From: Ashish Hirpara Date: Fri, 25 Mar 2022 15:27:36 +0530 Subject: [PATCH 11/27] [14.0][MIG] sale_restricted_qty: Migration to 14.0 --- sale_restricted_qty/README.rst | 22 ++- sale_restricted_qty/__manifest__.py | 10 +- .../i18n/sale_restricted_qty.pot | 130 +++++++++++------- .../models/product_category.py | 8 +- sale_restricted_qty/models/product_product.py | 8 +- .../models/product_restricted_qty_mixin.py | 37 +++-- .../models/product_template.py | 8 +- sale_restricted_qty/models/sale.py | 72 +++++----- sale_restricted_qty/readme/CONTRIBUTORS.rst | 4 + .../static/description/index.html | 12 +- sale_restricted_qty/tests/test_sale.py | 58 ++++---- .../views/product_category_views.xml | 60 +++++--- .../views/product_product_views.xml | 65 ++++++--- .../views/product_template_views.xml | 60 +++++--- sale_restricted_qty/views/sale_views.xml | 124 +++++++++++------ 15 files changed, 427 insertions(+), 251 deletions(-) diff --git a/sale_restricted_qty/README.rst b/sale_restricted_qty/README.rst index 265f0cb756c..be2b9b45883 100644 --- a/sale_restricted_qty/README.rst +++ b/sale_restricted_qty/README.rst @@ -14,13 +14,13 @@ Sale order min quantity :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fsale--workflow-lightgray.png?logo=github - :target: https://github.com/OCA/sale-workflow/tree/12.0/sale_restricted_qty + :target: https://github.com/OCA/sale-workflow/tree/14.0/sale_restricted_qty :alt: OCA/sale-workflow .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/sale-workflow-12-0/sale-workflow-12-0-sale_restricted_qty + :target: https://translation.odoo-community.org/projects/sale-workflow-14-0/sale-workflow-14-0-sale_restricted_qty :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png - :target: https://runbot.odoo-community.org/runbot/167/12.0 + :target: https://runbot.odoo-community.org/runbot/167/14.0 :alt: Try me on Runbot |badge1| |badge2| |badge3| |badge4| |badge5| @@ -61,7 +61,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 smashing it by providing a detailed and welcomed -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -78,6 +78,10 @@ Contributors * Mourad EL HADJ MIMOUN +* `Ooops `_: + + * Ashish Hirpara + Maintainers ~~~~~~~~~~~ @@ -91,6 +95,14 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use. -This module is part of the `OCA/sale-workflow `_ project on GitHub. +.. |maintainer-ashishhirapara| image:: https://github.com/ashishhirapara.png?size=40px + :target: https://github.com/ashishhirapara + :alt: ashishhirapara + +Current `maintainer `__: + +|maintainer-ashishhirapara| + +This module is part of the `OCA/sale-workflow `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/sale_restricted_qty/__manifest__.py b/sale_restricted_qty/__manifest__.py index 64d192f8ce0..8b0a8471ade 100644 --- a/sale_restricted_qty/__manifest__.py +++ b/sale_restricted_qty/__manifest__.py @@ -3,13 +3,15 @@ { "name": "Sale order min quantity", - "version": "12.0.2.0.0", + "version": "14.0.1.0.1", "category": "Sales Management", - "author": "Akretion, " "Odoo Community Association (OCA)", - "website": "https://github.com/OCA/sale-workflow/", + "author": "Akretion, Odoo Community Association (OCA)", + "contributors": ["Ashish Hirpara"], + "maintainers": ["ashishhirapara"], + "website": "https://github.com/OCA/sale-workflow", "license": "AGPL-3", "external_dependencies": { - 'python' : ['openupgradelib'], + "python": ["openupgradelib"], }, "depends": ["sale_management"], "data": [ diff --git a/sale_restricted_qty/i18n/sale_restricted_qty.pot b/sale_restricted_qty/i18n/sale_restricted_qty.pot index 4578596920f..c20b2373a25 100644 --- a/sale_restricted_qty/i18n/sale_restricted_qty.pot +++ b/sale_restricted_qty/i18n/sale_restricted_qty.pot @@ -1,12 +1,12 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: -# * sale_restricted_qty +# * sale_restricted_qty # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 12.0\n" +"Project-Id-Version: Odoo Server 14.0\n" "Report-Msgid-Bugs-To: \n" -"Last-Translator: <>\n" +"Last-Translator: \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -14,45 +14,45 @@ msgstr "" "Plural-Forms: \n" #. module: sale_restricted_qty -#: code:addons/sale_restricted_qty/models/sale.py:89 +#: code:addons/sale_restricted_qty/models/sale.py:0 #, python-format -msgid "\n" +msgid "" +"\n" "* If you want sell quantity bigger than max Quantity,Check \"force max quatity\" on product" msgstr "" #. module: sale_restricted_qty -#: code:addons/sale_restricted_qty/models/sale.py:70 +#: code:addons/sale_restricted_qty/models/sale.py:0 #, python-format -msgid "\n" +msgid "" +"\n" "* If you want sell quantity less than Min Quantity,Check \"force min quatity\" on product" msgstr "" #. module: sale_restricted_qty -#: code:addons/sale_restricted_qty/models/sale.py:105 +#: code:addons/sale_restricted_qty/models/sale.py:0 #, python-format -msgid "\n" +msgid "" +"\n" "* If you want sell quantity not multiple Quantity,Set multiple quantity to 0 on product or product template or product category" msgstr "" #. module: sale_restricted_qty -#: code:addons/sale_restricted_qty/models/sale.py:86 +#: code:addons/sale_restricted_qty/models/sale.py:0 #, python-format msgid "Check maximum order quantity for this products: * \n" -"" msgstr "" #. module: sale_restricted_qty -#: code:addons/sale_restricted_qty/models/sale.py:67 +#: code:addons/sale_restricted_qty/models/sale.py:0 #, python-format msgid "Check minimum order quantity for this products: * \n" -"" msgstr "" #. module: sale_restricted_qty -#: code:addons/sale_restricted_qty/models/sale.py:102 +#: code:addons/sale_restricted_qty/models/sale.py:0 #, python-format msgid "Check multiple order quantity for this products: * \n" -"" msgstr "" #. module: sale_restricted_qty @@ -60,7 +60,10 @@ msgstr "" #: model:ir.model.fields,help:sale_restricted_qty.field_product_product__force_sale_max_qty #: model:ir.model.fields,help:sale_restricted_qty.field_product_restricted_qty_mixin__force_sale_max_qty #: model:ir.model.fields,help:sale_restricted_qty.field_product_template__force_sale_max_qty -msgid "Define if user can force sale max qty 'If not set', Odoo will use the value defined in the parent object.Hierarchy is in this order :Product/product Template/product category/parent categoroies " +msgid "" +"Define if user can force sale max qty 'If not set', Odoo will use the value " +"defined in the parent object.Hierarchy is in this order :Product/product " +"Template/product category/parent categoroies " msgstr "" #. module: sale_restricted_qty @@ -68,7 +71,10 @@ msgstr "" #: model:ir.model.fields,help:sale_restricted_qty.field_product_product__force_sale_min_qty #: model:ir.model.fields,help:sale_restricted_qty.field_product_restricted_qty_mixin__force_sale_min_qty #: model:ir.model.fields,help:sale_restricted_qty.field_product_template__force_sale_min_qty -msgid "Define if user can force sale min qty 'If not set', Odoo will use the value defined in the parent object.Hierarchy is in this order :Product/product Template/product category/parent categoroies " +msgid "" +"Define if user can force sale min qty 'If not set', Odoo will use the value " +"defined in the parent object.Hierarchy is in this order :Product/product " +"Template/product category/parent categoroies " msgstr "" #. module: sale_restricted_qty @@ -76,7 +82,10 @@ msgstr "" #: model:ir.model.fields,help:sale_restricted_qty.field_product_product__sale_max_qty #: model:ir.model.fields,help:sale_restricted_qty.field_product_restricted_qty_mixin__sale_max_qty #: model:ir.model.fields,help:sale_restricted_qty.field_product_template__sale_max_qty -msgid "Define sale max qty 'If not set, Odoo will use the value defined in the parent object.Hierarchy is in this order :Product/product Template/product category/parent categoroies " +msgid "" +"Define sale max qty 'If not set, Odoo will use the value defined in the " +"parent object.Hierarchy is in this order :Product/product Template/product " +"category/parent categoroies " msgstr "" #. module: sale_restricted_qty @@ -84,7 +93,10 @@ msgstr "" #: model:ir.model.fields,help:sale_restricted_qty.field_product_product__sale_min_qty #: model:ir.model.fields,help:sale_restricted_qty.field_product_restricted_qty_mixin__sale_min_qty #: model:ir.model.fields,help:sale_restricted_qty.field_product_template__sale_min_qty -msgid "Define sale min qty 'If not set, Odoo will use the value defined in the parent object.Hierarchy is in this order :Product/product Template/product category/parent categoroies " +msgid "" +"Define sale min qty 'If not set, Odoo will use the value defined in the " +"parent object.Hierarchy is in this order :Product/product Template/product " +"category/parent categoroies " msgstr "" #. module: sale_restricted_qty @@ -92,11 +104,18 @@ msgstr "" #: model:ir.model.fields,help:sale_restricted_qty.field_product_product__sale_multiple_qty #: model:ir.model.fields,help:sale_restricted_qty.field_product_restricted_qty_mixin__sale_multiple_qty #: model:ir.model.fields,help:sale_restricted_qty.field_product_template__sale_multiple_qty -msgid "Define sale multiple qty 'If not set', Odoo will use the value defined in the parent object.Hierarchy is in this order :Product/product Template/product category/parent categoroies " +msgid "" +"Define sale multiple qty 'If not set', Odoo will use the value defined in " +"the parent object.Hierarchy is in this order :Product/product " +"Template/product category/parent categoroies " msgstr "" #. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_category__display_name +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_product__display_name #: model:ir.model.fields,field_description:sale_restricted_qty.field_product_restricted_qty_mixin__display_name +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_template__display_name +#: model:ir.model.fields,field_description:sale_restricted_qty.field_sale_order_line__display_name msgid "Display Name" msgstr "" @@ -127,7 +146,11 @@ msgid "Force Sale Min Qty" msgstr "" #. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_category__id +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_product__id #: model:ir.model.fields,field_description:sale_restricted_qty.field_product_restricted_qty_mixin__id +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_template__id +#: model:ir.model.fields,field_description:sale_restricted_qty.field_sale_order_line__id msgid "ID" msgstr "" @@ -136,7 +159,9 @@ msgstr "" #: model:ir.model.fields,help:sale_restricted_qty.field_product_product__manual_force_sale_max_qty #: model:ir.model.fields,help:sale_restricted_qty.field_product_restricted_qty_mixin__manual_force_sale_max_qty #: model:ir.model.fields,help:sale_restricted_qty.field_product_template__manual_force_sale_max_qty -msgid "If force max qty is checked, the max quantity is only indicative value.If is not test we check parent value" +msgid "" +"If force max qty is checked, the max quantity is only indicative value.If is" +" not test we check parent value" msgstr "" #. module: sale_restricted_qty @@ -144,11 +169,17 @@ msgstr "" #: model:ir.model.fields,help:sale_restricted_qty.field_product_product__manual_force_sale_min_qty #: model:ir.model.fields,help:sale_restricted_qty.field_product_restricted_qty_mixin__manual_force_sale_min_qty #: model:ir.model.fields,help:sale_restricted_qty.field_product_template__manual_force_sale_min_qty -msgid "If force min qty is checked, the min quantity is only indicative value.If is not test we check parent value" +msgid "" +"If force min qty is checked, the min quantity is only indicative value.If is" +" not test we check parent value" msgstr "" #. module: sale_restricted_qty +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_category____last_update +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_product____last_update #: model:ir.model.fields,field_description:sale_restricted_qty.field_product_restricted_qty_mixin____last_update +#: model:ir.model.fields,field_description:sale_restricted_qty.field_product_template____last_update +#: model:ir.model.fields,field_description:sale_restricted_qty.field_sale_order_line____last_update msgid "Last Modified on" msgstr "" @@ -233,14 +264,14 @@ msgid "Multiple Sale Qty" msgstr "" #. module: sale_restricted_qty -#: selection:product.category,manual_force_sale_max_qty:0 -#: selection:product.category,manual_force_sale_min_qty:0 -#: selection:product.product,manual_force_sale_max_qty:0 -#: selection:product.product,manual_force_sale_min_qty:0 -#: selection:product.restricted.qty.mixin,manual_force_sale_max_qty:0 -#: selection:product.restricted.qty.mixin,manual_force_sale_min_qty:0 -#: selection:product.template,manual_force_sale_max_qty:0 -#: selection:product.template,manual_force_sale_min_qty:0 +#: model:ir.model.fields.selection,name:sale_restricted_qty.selection__product_category__manual_force_sale_max_qty__not_force +#: model:ir.model.fields.selection,name:sale_restricted_qty.selection__product_category__manual_force_sale_min_qty__not_force +#: model:ir.model.fields.selection,name:sale_restricted_qty.selection__product_product__manual_force_sale_max_qty__not_force +#: model:ir.model.fields.selection,name:sale_restricted_qty.selection__product_product__manual_force_sale_min_qty__not_force +#: model:ir.model.fields.selection,name:sale_restricted_qty.selection__product_restricted_qty_mixin__manual_force_sale_max_qty__not_force +#: model:ir.model.fields.selection,name:sale_restricted_qty.selection__product_restricted_qty_mixin__manual_force_sale_min_qty__not_force +#: model:ir.model.fields.selection,name:sale_restricted_qty.selection__product_template__manual_force_sale_max_qty__not_force +#: model:ir.model.fields.selection,name:sale_restricted_qty.selection__product_template__manual_force_sale_min_qty__not_force msgid "No" msgstr "" @@ -255,19 +286,19 @@ msgid "Product" msgstr "" #. module: sale_restricted_qty -#: code:addons/sale_restricted_qty/models/sale.py:62 +#: code:addons/sale_restricted_qty/models/sale.py:0 #, python-format msgid "Product \"%s\": Min Quantity %s." msgstr "" #. module: sale_restricted_qty -#: code:addons/sale_restricted_qty/models/sale.py:81 +#: code:addons/sale_restricted_qty/models/sale.py:0 #, python-format msgid "Product \"%s\": max Quantity %s." msgstr "" #. module: sale_restricted_qty -#: code:addons/sale_restricted_qty/models/sale.py:97 +#: code:addons/sale_restricted_qty/models/sale.py:0 #, python-format msgid "Product \"%s\": multiple Quantity %s." msgstr "" @@ -355,26 +386,25 @@ msgid "Sales Order Line" msgstr "" #. module: sale_restricted_qty -#: selection:product.category,manual_force_sale_max_qty:0 -#: selection:product.category,manual_force_sale_min_qty:0 -#: selection:product.product,manual_force_sale_max_qty:0 -#: selection:product.product,manual_force_sale_min_qty:0 -#: selection:product.restricted.qty.mixin,manual_force_sale_max_qty:0 -#: selection:product.restricted.qty.mixin,manual_force_sale_min_qty:0 -#: selection:product.template,manual_force_sale_max_qty:0 -#: selection:product.template,manual_force_sale_min_qty:0 +#: model:ir.model.fields.selection,name:sale_restricted_qty.selection__product_category__manual_force_sale_max_qty__use_parent +#: model:ir.model.fields.selection,name:sale_restricted_qty.selection__product_category__manual_force_sale_min_qty__use_parent +#: model:ir.model.fields.selection,name:sale_restricted_qty.selection__product_product__manual_force_sale_max_qty__use_parent +#: model:ir.model.fields.selection,name:sale_restricted_qty.selection__product_product__manual_force_sale_min_qty__use_parent +#: model:ir.model.fields.selection,name:sale_restricted_qty.selection__product_restricted_qty_mixin__manual_force_sale_max_qty__use_parent +#: model:ir.model.fields.selection,name:sale_restricted_qty.selection__product_restricted_qty_mixin__manual_force_sale_min_qty__use_parent +#: model:ir.model.fields.selection,name:sale_restricted_qty.selection__product_template__manual_force_sale_max_qty__use_parent +#: model:ir.model.fields.selection,name:sale_restricted_qty.selection__product_template__manual_force_sale_min_qty__use_parent msgid "Use Parent Setting" msgstr "" #. module: sale_restricted_qty -#: selection:product.category,manual_force_sale_max_qty:0 -#: selection:product.category,manual_force_sale_min_qty:0 -#: selection:product.product,manual_force_sale_max_qty:0 -#: selection:product.product,manual_force_sale_min_qty:0 -#: selection:product.restricted.qty.mixin,manual_force_sale_max_qty:0 -#: selection:product.restricted.qty.mixin,manual_force_sale_min_qty:0 -#: selection:product.template,manual_force_sale_max_qty:0 -#: selection:product.template,manual_force_sale_min_qty:0 +#: model:ir.model.fields.selection,name:sale_restricted_qty.selection__product_category__manual_force_sale_max_qty__force +#: model:ir.model.fields.selection,name:sale_restricted_qty.selection__product_category__manual_force_sale_min_qty__force +#: model:ir.model.fields.selection,name:sale_restricted_qty.selection__product_product__manual_force_sale_max_qty__force +#: model:ir.model.fields.selection,name:sale_restricted_qty.selection__product_product__manual_force_sale_min_qty__force +#: model:ir.model.fields.selection,name:sale_restricted_qty.selection__product_restricted_qty_mixin__manual_force_sale_max_qty__force +#: model:ir.model.fields.selection,name:sale_restricted_qty.selection__product_restricted_qty_mixin__manual_force_sale_min_qty__force +#: model:ir.model.fields.selection,name:sale_restricted_qty.selection__product_template__manual_force_sale_max_qty__force +#: model:ir.model.fields.selection,name:sale_restricted_qty.selection__product_template__manual_force_sale_min_qty__force msgid "Yes" msgstr "" - diff --git a/sale_restricted_qty/models/product_category.py b/sale_restricted_qty/models/product_category.py index 7ea01efffff..05815c79a00 100644 --- a/sale_restricted_qty/models/product_category.py +++ b/sale_restricted_qty/models/product_category.py @@ -11,16 +11,16 @@ class ProductCategory(models.Model): def _get_sale_restricted_qty(self): res = super()._get_sale_restricted_qty() force_sale_min_qty = False - if self.manual_force_sale_min_qty == 'force': + if self.manual_force_sale_min_qty == "force": force_sale_min_qty = True - elif self.manual_force_sale_min_qty == 'not_force': + elif self.manual_force_sale_min_qty == "not_force": force_sale_min_qty = False else: force_sale_min_qty = self.parent_id.force_sale_min_qty force_sale_max_qty = False - if self.manual_force_sale_max_qty == 'force': + if self.manual_force_sale_max_qty == "force": force_sale_max_qty = True - elif self.manual_force_sale_max_qty == 'not_force': + elif self.manual_force_sale_max_qty == "not_force": force_sale_max_qty = False else: force_sale_max_qty = self.parent_id.force_sale_max_qty diff --git a/sale_restricted_qty/models/product_product.py b/sale_restricted_qty/models/product_product.py index 2ef51f58c64..3991b4eb86a 100644 --- a/sale_restricted_qty/models/product_product.py +++ b/sale_restricted_qty/models/product_product.py @@ -12,16 +12,16 @@ class ProductProduct(models.Model): def _get_sale_restricted_qty(self): res = super()._get_sale_restricted_qty() force_sale_min_qty = False - if self.manual_force_sale_min_qty == 'force': + if self.manual_force_sale_min_qty == "force": force_sale_min_qty = True - elif self.manual_force_sale_min_qty == 'not_force': + elif self.manual_force_sale_min_qty == "not_force": force_sale_min_qty = False else: force_sale_min_qty = self.product_tmpl_id.force_sale_min_qty force_sale_max_qty = False - if self.manual_force_sale_max_qty == 'force': + if self.manual_force_sale_max_qty == "force": force_sale_max_qty = True - elif self.manual_force_sale_max_qty == 'not_force': + elif self.manual_force_sale_max_qty == "not_force": force_sale_max_qty = False else: force_sale_max_qty = self.product_tmpl_id.force_sale_max_qty diff --git a/sale_restricted_qty/models/product_restricted_qty_mixin.py b/sale_restricted_qty/models/product_restricted_qty_mixin.py index 3213b200748..0d0f31cc96f 100644 --- a/sale_restricted_qty/models/product_restricted_qty_mixin.py +++ b/sale_restricted_qty/models/product_restricted_qty_mixin.py @@ -2,6 +2,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from odoo import api, fields, models + from odoo.addons import decimal_precision as dp @@ -45,11 +46,12 @@ class ProductMinMultipleMixin(models.AbstractModel): "Hierarchy is in this order :" "Product/product Template/product category/parent categoroies ", ) - manual_force_sale_min_qty = fields.Selection([ - ('use_parent', 'Use Parent Setting'), - ('force', 'Yes'), - ('not_force', 'No'), - ], + manual_force_sale_min_qty = fields.Selection( + [ + ("use_parent", "Use Parent Setting"), + ("force", "Yes"), + ("not_force", "No"), + ], string="Manual Force Min Qty", required=True, default="use_parent", @@ -80,11 +82,12 @@ class ProductMinMultipleMixin(models.AbstractModel): "Hierarchy is in this order :" "Product/product Template/product category/parent categoroies ", ) - manual_force_sale_max_qty = fields.Selection([ - ('use_parent', 'Use Parent Setting'), - ('force', 'Yes'), - ('not_force', 'No'), - ], + manual_force_sale_max_qty = fields.Selection( + [ + ("use_parent", "Use Parent Setting"), + ("force", "Yes"), + ("not_force", "No"), + ], required=True, default="use_parent", string="Manual Force Max Qty", @@ -97,16 +100,20 @@ def _get_sale_restricted_qty(self): self.ensure_one() res = { "sale_min_qty": self.manual_sale_min_qty, - "force_sale_min_qty": self.manual_force_sale_min_qty == 'force', + "force_sale_min_qty": self.manual_force_sale_min_qty == "force", "sale_max_qty": self.manual_sale_max_qty, - "force_sale_max_qty": self.manual_force_sale_max_qty == 'force', + "force_sale_max_qty": self.manual_force_sale_max_qty == "force", "sale_multiple_qty": self.manual_sale_multiple_qty, } return res - @api.depends("manual_force_sale_min_qty", "manual_sale_min_qty", - "manual_force_sale_max_qty", "manual_sale_max_qty", - "manual_sale_multiple_qty") + @api.depends( + "manual_force_sale_min_qty", + "manual_sale_min_qty", + "manual_force_sale_max_qty", + "manual_sale_max_qty", + "manual_sale_multiple_qty", + ) def _compute_sale_restricted_qty(self): for rec in self: rec.update(rec._get_sale_restricted_qty()) diff --git a/sale_restricted_qty/models/product_template.py b/sale_restricted_qty/models/product_template.py index 3aa9bebcf49..6b95d382d99 100644 --- a/sale_restricted_qty/models/product_template.py +++ b/sale_restricted_qty/models/product_template.py @@ -12,16 +12,16 @@ class ProductTemplate(models.Model): def _get_sale_restricted_qty(self): res = super()._get_sale_restricted_qty() force_sale_min_qty = False - if self.manual_force_sale_min_qty == 'force': + if self.manual_force_sale_min_qty == "force": force_sale_min_qty = True - elif self.manual_force_sale_min_qty == 'not_force': + elif self.manual_force_sale_min_qty == "not_force": force_sale_min_qty = False else: force_sale_min_qty = self.categ_id.force_sale_min_qty force_sale_max_qty = False - if self.manual_force_sale_max_qty == 'force': + if self.manual_force_sale_max_qty == "force": force_sale_max_qty = True - elif self.manual_force_sale_max_qty == 'not_force': + elif self.manual_force_sale_max_qty == "not_force": force_sale_max_qty = False else: force_sale_max_qty = self.categ_id.force_sale_max_qty diff --git a/sale_restricted_qty/models/sale.py b/sale_restricted_qty/models/sale.py index e5860d54797..1cd22ba8e91 100644 --- a/sale_restricted_qty/models/sale.py +++ b/sale_restricted_qty/models/sale.py @@ -2,10 +2,11 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from odoo import _, api, fields, models -from odoo.addons import decimal_precision as dp from odoo.exceptions import ValidationError from odoo.tools import float_compare +from odoo.addons import decimal_precision as dp + class SaleOrderLine(models.Model): _inherit = "sale.order.line" @@ -17,8 +18,7 @@ class SaleOrderLine(models.Model): digits=dp.get_precision("Product Unit of Measure"), ) force_sale_min_qty = fields.Boolean( - compute="_compute_sale_restricted_qty", - readonly=True, store=True + compute="_compute_sale_restricted_qty", readonly=True, store=True ) is_qty_less_min_qty = fields.Boolean( string="Qty < Min Qty", compute="_compute_is_qty_less_min_qty" @@ -31,8 +31,7 @@ class SaleOrderLine(models.Model): digits=dp.get_precision("Product Unit of Measure"), ) force_sale_max_qty = fields.Boolean( - compute="_compute_sale_restricted_qty", - readonly=True, store=True + compute="_compute_sale_restricted_qty", readonly=True, store=True ) is_qty_bigger_max_qty = fields.Boolean( string="Qty > max Qty", compute="_compute_is_qty_bigger_max_qty" @@ -48,14 +47,14 @@ class SaleOrderLine(models.Model): ) @api.constrains( - "product_uom_qty", "sale_min_qty", "sale_max_qty", "sale_multiple_qty") + "product_uom_qty", "sale_min_qty", "sale_max_qty", "sale_multiple_qty" + ) def check_constraint_restricted_qty(self): msg = "" invaild_min_lines = [] line_to_test = self.filtered( - lambda sl: not sl.product_id.force_sale_min_qty and - sl.is_qty_less_min_qty + lambda sl: not sl.product_id.force_sale_min_qty and sl.is_qty_less_min_qty ) for line in line_to_test: invaild_min_lines.append( @@ -73,8 +72,7 @@ def check_constraint_restricted_qty(self): ) invaild_max_lines = [] line_to_test = self.filtered( - lambda sl: not sl.product_id.force_sale_max_qty and - sl.is_qty_bigger_max_qty + lambda sl: not sl.product_id.force_sale_max_qty and sl.is_qty_bigger_max_qty ) for line in line_to_test: invaild_max_lines.append( @@ -115,31 +113,39 @@ def _get_product_qty_in_product_unit(self): self.ensure_one() return self.product_uom._compute_quantity( self.product_uom_qty, self.product_id.uom_id - ) + ) @api.depends("product_id", "product_uom_qty", "sale_min_qty") def _compute_is_qty_less_min_qty(self): for line in self: rounding = line.product_uom.rounding product_qty = line._get_product_qty_in_product_unit() - line.is_qty_less_min_qty = line.sale_min_qty and ( - float_compare( - product_qty, line.sale_min_qty, precision_rounding=rounding + line.is_qty_less_min_qty = ( + line.sale_min_qty + and ( + float_compare( + product_qty, line.sale_min_qty, precision_rounding=rounding + ) + < 0 ) - < 0 - ) or False + or False + ) @api.depends("product_id", "product_uom_qty", "sale_max_qty") def _compute_is_qty_bigger_max_qty(self): for line in self: rounding = line.product_uom.rounding product_qty = line._get_product_qty_in_product_unit() - line.is_qty_bigger_max_qty = line.sale_max_qty and ( - float_compare( - product_qty, line.sale_max_qty, precision_rounding=rounding + line.is_qty_bigger_max_qty = ( + line.sale_max_qty + and ( + float_compare( + product_qty, line.sale_max_qty, precision_rounding=rounding + ) + > 0 ) - > 0 - ) or False + or False + ) @api.depends("product_id", "product_uom_qty", "sale_multiple_qty") def _compute_is_qty_not_multiple_qty(self): @@ -148,24 +154,24 @@ def _compute_is_qty_not_multiple_qty(self): line.product_uom_qty, line.product_id.uom_id ) line.is_qty_not_multiple_qty = ( - line.sale_multiple_qty > 0 - and product_qty % line.sale_multiple_qty != 0 + line.sale_multiple_qty > 0 and product_qty % line.sale_multiple_qty != 0 ) def _get_sale_restricted_qty(self): """Overridable function to change qty values (ex: form stock)""" self.ensure_one() res = { - "sale_min_qty": (self.product_id and - self.product_id.sale_min_qty or 0), - "force_sale_min_qty": (self.product_id and - self.product_id.force_sale_min_qty or False), - "sale_max_qty": (self.product_id and - self.product_id.sale_max_qty or 0), - "force_sale_max_qty": (self.product_id and - self.product_id.force_sale_max_qty or False), - "sale_multiple_qty": (self.product_id and - self.product_id.sale_multiple_qty or 0), + "sale_min_qty": (self.product_id and self.product_id.sale_min_qty or 0), + "force_sale_min_qty": ( + self.product_id and self.product_id.force_sale_min_qty or False + ), + "sale_max_qty": (self.product_id and self.product_id.sale_max_qty or 0), + "force_sale_max_qty": ( + self.product_id and self.product_id.force_sale_max_qty or False + ), + "sale_multiple_qty": ( + self.product_id and self.product_id.sale_multiple_qty or 0 + ), } return res diff --git a/sale_restricted_qty/readme/CONTRIBUTORS.rst b/sale_restricted_qty/readme/CONTRIBUTORS.rst index e803c0a3fba..ac5fa9a437c 100644 --- a/sale_restricted_qty/readme/CONTRIBUTORS.rst +++ b/sale_restricted_qty/readme/CONTRIBUTORS.rst @@ -1 +1,5 @@ * Mourad EL HADJ MIMOUN + +* `Ooops `_: + + * Ashish Hirpara diff --git a/sale_restricted_qty/static/description/index.html b/sale_restricted_qty/static/description/index.html index a86a4ad781e..fd9a1082b62 100644 --- a/sale_restricted_qty/static/description/index.html +++ b/sale_restricted_qty/static/description/index.html @@ -367,7 +367,7 @@

Sale order min quantity

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

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

+

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

This module allows to define min, max and multiple order quantity on product. Min and Max order quantity is either required or recommanded. If you check “Force mini/max Qty” on product the min/max qty do not block sale @@ -416,7 +416,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 smashing it by providing a detailed and welcomed -feedback.

+feedback.

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

@@ -431,6 +431,10 @@

Authors

Contributors

@@ -440,7 +444,9 @@

Maintainers

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

-

This module is part of the OCA/sale-workflow project on GitHub.

+

Current maintainer:

+

ashishhirapara

+

This module is part of the OCA/sale-workflow project on GitHub.

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

diff --git a/sale_restricted_qty/tests/test_sale.py b/sale_restricted_qty/tests/test_sale.py index b1bd517f1ab..425c9c8687d 100644 --- a/sale_restricted_qty/tests/test_sale.py +++ b/sale_restricted_qty/tests/test_sale.py @@ -1,12 +1,13 @@ # Copyright 2019 Akretion +# Update (Migration) 2022 Ooops - Ashish Hirpara # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). import odoo.tests.common as common from odoo.exceptions import ValidationError +from odoo.tests import tagged -@common.at_install(False) -@common.post_install(True) +@tagged("post_install", "-at_install") class TestSaleOrderLineMinQty(common.TransactionCase): def setUp(self): super(TestSaleOrderLineMinQty, self).setUp() @@ -40,11 +41,13 @@ def setUp(self): } ) - self.product = self.product_model.create({ - "name": "Test product", - "force_sale_min_qty": False, - "force_sale_max_qty": False, - }) + self.product = self.product_model.create( + { + "name": "Test product", + "force_sale_min_qty": False, + "force_sale_max_qty": False, + } + ) def refrech_sale_values(self, sale_order): sale_order.order_line._compute_sale_restricted_qty() @@ -60,16 +63,21 @@ def test_check_sale_order_min_qty_required(self): # Create sale order line with Qty less than min Qty with self.assertRaises(ValidationError): sale_order = self.sale_order_model.new( - {"partner_id": self.partner.id, - "order_line": [(0, 0, line_values)]} + {"partner_id": self.partner.id, "order_line": [(0, 0, line_values)]} ) sale_values = self.refrech_sale_values(sale_order) + sale_values.update( + { + "partner_shipping_id": sale_values["partner_id"], + "partner_invoice_id": sale_values["partner_id"], + "pricelist_id": 1, + } + ) self.sale_order_model.create(sale_values) line_values["product_uom_qty"] = 12.0 # Create sale order line with Qty great then min Qty self.sale_order = self.sale_order_model.create( - {"partner_id": self.partner.id, - "order_line": [(0, 0, line_values)]} + {"partner_id": self.partner.id, "order_line": [(0, 0, line_values)]} ) self.sale_order.order_line._compute_sale_restricted_qty() self.assertFalse(self.sale_order.order_line.is_qty_less_min_qty) @@ -80,12 +88,11 @@ def test_check_sale_order_min_qty_recommended(self): line_values = {"product_id": self.product.id, "product_uom_qty": 5.0} self.product.manual_sale_min_qty = 10 # Set Force min Qty to true - self.product.manual_force_sale_min_qty = 'force' + self.product.manual_force_sale_min_qty = "force" # Create sale order line with Qty less than min Qty self.sale_order = self.sale_order_model.create( - {"partner_id": self.partner.id, - "order_line": [(0, 0, line_values)]} + {"partner_id": self.partner.id, "order_line": [(0, 0, line_values)]} ) self.sale_order.order_line._compute_sale_restricted_qty() self.assertTrue(self.sale_order.order_line.is_qty_less_min_qty) @@ -98,16 +105,14 @@ def test_check_sale_order_max_qty_required(self): # Create sale order line with Qty bigger than max Qty with self.assertRaises(ValidationError): sale_order = self.sale_order_model.create( - {"partner_id": self.partner.id, - "order_line": [(0, 0, line_values)]} + {"partner_id": self.partner.id, "order_line": [(0, 0, line_values)]} ) sale_values = self.refrech_sale_values(sale_order) self.sale_order_model.create(sale_values) line_values["product_uom_qty"] = 2.0 # Create sale order line with Qty great then max Qty self.sale_order = self.sale_order_model.create( - {"partner_id": self.partner.id, - "order_line": [(0, 0, line_values)]} + {"partner_id": self.partner.id, "order_line": [(0, 0, line_values)]} ) self.sale_order.order_line._compute_sale_restricted_qty() self.assertFalse(self.sale_order.order_line.is_qty_bigger_max_qty) @@ -118,12 +123,11 @@ def test_check_sale_order_max_qty_recommended(self): line_values = {"product_id": self.product.id, "product_uom_qty": 15.0} self.product.manual_sale_max_qty = 10 # Set Force max Qty to true - self.product.manual_force_sale_max_qty = 'force' + self.product.manual_force_sale_max_qty = "force" # Create sale order line with Qty bigger than max Qty self.sale_order = self.sale_order_model.create( - {"partner_id": self.partner.id, - "order_line": [(0, 0, line_values)]} + {"partner_id": self.partner.id, "order_line": [(0, 0, line_values)]} ) self.sale_order.order_line._compute_sale_restricted_qty() self.assertTrue(self.sale_order.order_line.is_qty_bigger_max_qty) @@ -137,16 +141,14 @@ def test_check_sale_order_multiple_qty_required(self): # Create sale order line with Qty not multiple Qty with self.assertRaises(ValidationError): sale_order = self.sale_order_model.create( - {"partner_id": self.partner.id, - "order_line": [(0, 0, line_values)]} + {"partner_id": self.partner.id, "order_line": [(0, 0, line_values)]} ) sale_values = self.refrech_sale_values(sale_order) self.sale_order_model.create(sale_values) line_values["product_uom_qty"] = 20 # Create sale order line with Qty multiple Qty self.sale_order = self.sale_order_model.create( - {"partner_id": self.partner.id, - "order_line": [(0, 0, line_values)]} + {"partner_id": self.partner.id, "order_line": [(0, 0, line_values)]} ) self.sale_order.order_line._compute_sale_restricted_qty() self.assertFalse(self.sale_order.order_line.is_qty_not_multiple_qty) @@ -163,18 +165,18 @@ def test_check_restricted_qty_category_hierarchy(self): self.assertEqual(self.product.sale_min_qty, 10) self.assertEqual(self.product.sale_max_qty, 100) self.assertEqual(self.product.sale_multiple_qty, 5) - self.categ_parent.manual_force_sale_min_qty = 'force' + self.categ_parent.manual_force_sale_min_qty = "force" self.assertEqual(self.product.manual_force_sale_min_qty, "use_parent") self.assertEqual(self.product.force_sale_min_qty, True) # Check Restricted Qty from category self.categ.manual_sale_min_qty = 15 self.categ.manual_sale_multiple_qty = 10 - self.categ.manual_force_sale_min_qty = 'not_force' + self.categ.manual_force_sale_min_qty = "not_force" self.assertEqual(self.product.sale_min_qty, 15) self.assertEqual(self.product.sale_multiple_qty, 10) self.assertEqual(self.product.force_sale_min_qty, False) self.categ.manual_sale_max_qty = 200 - self.categ.manual_force_sale_max_qty = 'not_force' + self.categ.manual_force_sale_max_qty = "not_force" self.assertEqual(self.product.sale_max_qty, 200) self.assertEqual(self.product.force_sale_max_qty, False) diff --git a/sale_restricted_qty/views/product_category_views.xml b/sale_restricted_qty/views/product_category_views.xml index e1e92773169..41d857c5c7f 100644 --- a/sale_restricted_qty/views/product_category_views.xml +++ b/sale_restricted_qty/views/product_category_views.xml @@ -1,31 +1,55 @@ - + product.category - + - + - - - - - - diff --git a/sale_restricted_qty/views/product_product_views.xml b/sale_restricted_qty/views/product_product_views.xml index 5389c4c2b7a..7c6122fb1c7 100644 --- a/sale_restricted_qty/views/product_product_views.xml +++ b/sale_restricted_qty/views/product_product_views.xml @@ -1,34 +1,57 @@ - + - + product.product.form product.product - + - - + + - - - - - - diff --git a/sale_restricted_qty/views/product_template_views.xml b/sale_restricted_qty/views/product_template_views.xml index 18379214104..fd49ebffe74 100644 --- a/sale_restricted_qty/views/product_template_views.xml +++ b/sale_restricted_qty/views/product_template_views.xml @@ -1,32 +1,56 @@ - + product.template - + - + - - - - - - diff --git a/sale_restricted_qty/views/sale_views.xml b/sale_restricted_qty/views/sale_views.xml index 6d42932c483..7459d72133f 100644 --- a/sale_restricted_qty/views/sale_views.xml +++ b/sale_restricted_qty/views/sale_views.xml @@ -1,56 +1,92 @@ - + sale.order - + - - - - - - - - - - From eaf0b302ebc50209bc51cdf52f2a8dcf14c27f50 Mon Sep 17 00:00:00 2001 From: Francesco Foresti Date: Fri, 8 Apr 2022 15:01:09 +0000 Subject: [PATCH 12/27] Translated using Weblate (Italian) Currently translated at 100.0% (54 of 54 strings) Translation: sale-workflow-14.0/sale-workflow-14.0-sale_restricted_qty Translate-URL: https://translation.odoo-community.org/projects/sale-workflow-14-0/sale-workflow-14-0-sale_restricted_qty/it/ --- sale_restricted_qty/i18n/it.po | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sale_restricted_qty/i18n/it.po b/sale_restricted_qty/i18n/it.po index bc3367ea42c..ff10808710e 100644 --- a/sale_restricted_qty/i18n/it.po +++ b/sale_restricted_qty/i18n/it.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 12.0\n" "Report-Msgid-Bugs-To: \n" -"PO-Revision-Date: 2021-10-05 18:45+0000\n" -"Last-Translator: Samuele Mariani \n" +"PO-Revision-Date: 2022-04-08 17:05+0000\n" +"Last-Translator: Francesco Foresti \n" "Language-Team: none\n" "Language: it\n" "MIME-Version: 1.0\n" @@ -385,7 +385,7 @@ msgstr "Quantità di vendita limitata" #. module: sale_restricted_qty #: model:ir.model,name:sale_restricted_qty.model_sale_order_line msgid "Sales Order Line" -msgstr "Riga Ordine di Vendita" +msgstr "Riga ordine di vendita" #. module: sale_restricted_qty #: selection:product.category,manual_force_sale_max_qty:0 From 81954301c099536a7d332ddd12050479149090fe Mon Sep 17 00:00:00 2001 From: Ilyas Date: Thu, 27 Oct 2022 12:57:10 +0600 Subject: [PATCH 13/27] [IMP] sale_restricted_qty: show sale_min sale_max optionally --- sale_restricted_qty/__manifest__.py | 2 +- sale_restricted_qty/views/sale_views.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sale_restricted_qty/__manifest__.py b/sale_restricted_qty/__manifest__.py index 8b0a8471ade..7036e63614a 100644 --- a/sale_restricted_qty/__manifest__.py +++ b/sale_restricted_qty/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Sale order min quantity", - "version": "14.0.1.0.1", + "version": "14.0.1.1.0", "category": "Sales Management", "author": "Akretion, Odoo Community Association (OCA)", "contributors": ["Ashish Hirpara"], diff --git a/sale_restricted_qty/views/sale_views.xml b/sale_restricted_qty/views/sale_views.xml index 7459d72133f..cae2196f01b 100644 --- a/sale_restricted_qty/views/sale_views.xml +++ b/sale_restricted_qty/views/sale_views.xml @@ -62,10 +62,10 @@ expr="//field[@name='order_line']/tree/field[@name='product_uom_qty']" position="after" > - + - + From ca5dd843787676bef25de69f2d16dee0e19c2ffa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Mart=C3=ADnez?= Date: Thu, 1 Dec 2022 14:01:27 +0100 Subject: [PATCH 14/27] [IMP] sale_restricted_qty: Change dp.get_precision digits --- sale_restricted_qty/__manifest__.py | 2 +- .../models/product_restricted_qty_mixin.py | 14 ++++++-------- sale_restricted_qty/models/sale.py | 8 +++----- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/sale_restricted_qty/__manifest__.py b/sale_restricted_qty/__manifest__.py index 7036e63614a..3af3de87e4d 100644 --- a/sale_restricted_qty/__manifest__.py +++ b/sale_restricted_qty/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Sale order min quantity", - "version": "14.0.1.1.0", + "version": "14.0.1.1.1", "category": "Sales Management", "author": "Akretion, Odoo Community Association (OCA)", "contributors": ["Ashish Hirpara"], diff --git a/sale_restricted_qty/models/product_restricted_qty_mixin.py b/sale_restricted_qty/models/product_restricted_qty_mixin.py index 0d0f31cc96f..22ecd557434 100644 --- a/sale_restricted_qty/models/product_restricted_qty_mixin.py +++ b/sale_restricted_qty/models/product_restricted_qty_mixin.py @@ -3,8 +3,6 @@ from odoo import api, fields, models -from odoo.addons import decimal_precision as dp - class ProductMinMultipleMixin(models.AbstractModel): _name = "product.restricted.qty.mixin" @@ -18,10 +16,10 @@ class ProductMinMultipleMixin(models.AbstractModel): " use the value defined in the parent object." "Hierarchy is in this order :" "Product/product Template/product category/parent categoroies ", - digits=dp.get_precision("Product Unit of Measure"), + digits="Product Unit of Measure", ) manual_sale_multiple_qty = fields.Float( - string="Multiple Sale Qty", digits=dp.get_precision("Product Unit of Measure") + string="Multiple Sale Qty", digits="Product Unit of Measure" ) sale_min_qty = fields.Float( compute="_compute_sale_restricted_qty", @@ -31,10 +29,10 @@ class ProductMinMultipleMixin(models.AbstractModel): " use the value defined in the parent object." "Hierarchy is in this order :" "Product/product Template/product category/parent categoroies ", - digits=dp.get_precision("Product Unit of Measure"), + digits="Product Unit of Measure", ) manual_sale_min_qty = fields.Float( - string="Min Sale Qty", digits=dp.get_precision("Product Unit of Measure") + string="Min Sale Qty", digits="Product Unit of Measure" ) force_sale_min_qty = fields.Boolean( compute="_compute_sale_restricted_qty", @@ -67,10 +65,10 @@ class ProductMinMultipleMixin(models.AbstractModel): " use the value defined in the parent object." "Hierarchy is in this order :" "Product/product Template/product category/parent categoroies ", - digits=dp.get_precision("Product Unit of Measure"), + digits="Product Unit of Measure", ) manual_sale_max_qty = fields.Float( - string="Max Sale Qty", digits=dp.get_precision("Product Unit of Measure") + string="Max Sale Qty", digits="Product Unit of Measure" ) force_sale_max_qty = fields.Boolean( compute="_compute_sale_restricted_qty", diff --git a/sale_restricted_qty/models/sale.py b/sale_restricted_qty/models/sale.py index 1cd22ba8e91..2ae03714d87 100644 --- a/sale_restricted_qty/models/sale.py +++ b/sale_restricted_qty/models/sale.py @@ -5,8 +5,6 @@ from odoo.exceptions import ValidationError from odoo.tools import float_compare -from odoo.addons import decimal_precision as dp - class SaleOrderLine(models.Model): _inherit = "sale.order.line" @@ -15,7 +13,7 @@ class SaleOrderLine(models.Model): string="Min Qty", compute="_compute_sale_restricted_qty", store=True, - digits=dp.get_precision("Product Unit of Measure"), + digits="Product Unit of Measure", ) force_sale_min_qty = fields.Boolean( compute="_compute_sale_restricted_qty", readonly=True, store=True @@ -28,7 +26,7 @@ class SaleOrderLine(models.Model): string="Max Qty", compute="_compute_sale_restricted_qty", store=True, - digits=dp.get_precision("Product Unit of Measure"), + digits="Product Unit of Measure", ) force_sale_max_qty = fields.Boolean( compute="_compute_sale_restricted_qty", readonly=True, store=True @@ -40,7 +38,7 @@ class SaleOrderLine(models.Model): string="Multiple Qty", compute="_compute_sale_restricted_qty", store=True, - digits=dp.get_precision("Product Unit of Measure"), + digits="Product Unit of Measure", ) is_qty_not_multiple_qty = fields.Boolean( string="Not Multiple Qty", compute="_compute_is_qty_not_multiple_qty" From e04e53edef92af3b9ce93b2e5c13b100ae4462fc Mon Sep 17 00:00:00 2001 From: Francesco Foresti Date: Tue, 3 Jan 2023 11:45:48 +0000 Subject: [PATCH 15/27] Translated using Weblate (Italian) Currently translated at 100.0% (54 of 54 strings) Translation: sale-workflow-14.0/sale-workflow-14.0-sale_restricted_qty Translate-URL: https://translation.odoo-community.org/projects/sale-workflow-14-0/sale-workflow-14-0-sale_restricted_qty/it/ --- sale_restricted_qty/README.rst | 15 ++++--- sale_restricted_qty/i18n/it.po | 12 +++--- .../static/description/index.html | 40 ++++++++++--------- 3 files changed, 36 insertions(+), 31 deletions(-) diff --git a/sale_restricted_qty/README.rst b/sale_restricted_qty/README.rst index be2b9b45883..4c6e334f40a 100644 --- a/sale_restricted_qty/README.rst +++ b/sale_restricted_qty/README.rst @@ -2,10 +2,13 @@ Sale order min quantity ======================= -.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:9d34c5ce0bf0071dd40afa3f708f0f3d3f2098e4ca0d4b26d65bb1a8a40a23e0 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png :target: https://odoo-community.org/page/development-status @@ -19,11 +22,11 @@ Sale order min quantity .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png :target: https://translation.odoo-community.org/projects/sale-workflow-14-0/sale-workflow-14-0-sale_restricted_qty :alt: Translate me on Weblate -.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png - :target: https://runbot.odoo-community.org/runbot/167/14.0 - :alt: Try me on Runbot +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/sale-workflow&target_branch=14.0 + :alt: Try me on Runboat -|badge1| |badge2| |badge3| |badge4| |badge5| +|badge1| |badge2| |badge3| |badge4| |badge5| This module allows to define min, max and multiple order quantity on product. Min and Max order quantity is either required or recommanded. @@ -60,7 +63,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 smashing it by providing a detailed and welcomed +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. diff --git a/sale_restricted_qty/i18n/it.po b/sale_restricted_qty/i18n/it.po index ff10808710e..c6df581e4b1 100644 --- a/sale_restricted_qty/i18n/it.po +++ b/sale_restricted_qty/i18n/it.po @@ -6,15 +6,15 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 12.0\n" "Report-Msgid-Bugs-To: \n" -"PO-Revision-Date: 2022-04-08 17:05+0000\n" -"Last-Translator: Francesco Foresti \n" +"PO-Revision-Date: 2023-06-20 14:09+0000\n" +"Last-Translator: mymage \n" "Language-Team: none\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.3.2\n" +"X-Generator: Weblate 4.17\n" #. module: sale_restricted_qty #: code:addons/sale_restricted_qty/models/sale.py:89 @@ -127,7 +127,7 @@ msgstr "" #. module: sale_restricted_qty #: model:ir.model.fields,field_description:sale_restricted_qty.field_product_restricted_qty_mixin__display_name msgid "Display Name" -msgstr "Nome Visualizzato" +msgstr "Nome visualizzato" #. module: sale_restricted_qty #: model:ir.model.fields,field_description:sale_restricted_qty.field_product_category__force_sale_max_qty @@ -308,7 +308,7 @@ msgstr "prodotto \"%s\": Quantità Multipla %s." #. module: sale_restricted_qty #: model:ir.model,name:sale_restricted_qty.model_product_category msgid "Product Category" -msgstr "Categoria Prodotto" +msgstr "Categoria prodotto" #. module: sale_restricted_qty #: model:ir.model,name:sale_restricted_qty.model_product_restricted_qty_mixin @@ -409,4 +409,4 @@ msgstr "Usa Impostazioni Genitore" #: selection:product.template,manual_force_sale_max_qty:0 #: selection:product.template,manual_force_sale_min_qty:0 msgid "Yes" -msgstr "Si" +msgstr "Sì" diff --git a/sale_restricted_qty/static/description/index.html b/sale_restricted_qty/static/description/index.html index fd9a1082b62..8cf3afe2190 100644 --- a/sale_restricted_qty/static/description/index.html +++ b/sale_restricted_qty/static/description/index.html @@ -1,20 +1,20 @@ - + - + Sale order min quantity -
-

Sale order min quantity

+
+

Sale order restricted quantity: min, max, multiple-of

Beta License: AGPL-3 OCA/sale-workflow Translate me on Weblate Try me on Runboat

-

This module allows to define min, max and multiple order quantity on -product. Min and Max order quantity is either required or recommanded. -If you check “Force mini/max Qty” on product the min/max qty do not -block sale order. This can be used if the available stock is less then -the min qty or if you want to sell sample products. Max qty can be used -if we dont want to sale more than available qty (end of series product)

-

Setings are defined on product, if values are not set we use settings -from product template. If settings are not set on product template we -use settings from product category otherwise parent category.

+

This module allows to set mininal, maximal, and multiple-of quantity +constraints on product categories and products, and to check and +optionally enforce these constraints on sale orders.

Table of contents

    @@ -394,23 +388,22 @@

    Sale order min quantity

Usage

-
    -
  1. -
    Go to Sales > Products > Product Variants and Set “Min Sale -Qty”.
    -
    If you dont want block product sale if qty is less the “Min Sale -Qty”, chek “Force Min Qty”
    -
    -
  2. -
  3. -
    Create a sale order and add a sale order line with this product and -check
    -
    if Qty is less then “Min Qty” A message in displayed “Min Qty -Required” Or “Min Qty Recommanded”. Check if Min qty is required -you can’t validate order.
    -
    -
  4. -
+

To set quantity constraints on a product: navigate to Sales > Products +> Products, open the product, and on the Sales tab in the Qty +Constraints section set corresponding values in the Min Qty, Max +Qty, or Multiple-Of Qty fields.

+

To set quantity constraints on a product variant: navigate to Sales > +Products > Product Variants, open the product variant, and on the +Sales tab in the Qty Constraints section set corresponding +values in the Min Qty, Max Qty, or Multiple-Of Qty fields.

+

To set quantity constraints on a product category: navigate to Sales > +Configuration > Product Categories, open the product category, and in +the Sales Qty Constraints section set corresponding values in the +Min Qty, Max Qty, or Multiple-Of Qty fields.

+

The settings are inherited from the product category to the product, and +from the product to the product variant. To override the inherited +settings, check the checkbox next to the corresponding value and set the +value in the product or product variant.

Bug Tracker

@@ -440,6 +433,10 @@

Contributors

  • Simone Rubino <simone.rubino@aion-tech.it>
  • +
  • CorporateHub +
  • diff --git a/sale_restricted_qty/tests/__init__.py b/sale_restricted_qty/tests/__init__.py index 67709762e39..d3cedbf214e 100644 --- a/sale_restricted_qty/tests/__init__.py +++ b/sale_restricted_qty/tests/__init__.py @@ -1 +1,6 @@ -from . import test_sale +from . import ( + test_product_category, + test_product_product, + test_product_template, + test_sale_order_line, +) diff --git a/sale_restricted_qty/tests/test_product_category.py b/sale_restricted_qty/tests/test_product_category.py new file mode 100644 index 00000000000..3c178b7f834 --- /dev/null +++ b/sale_restricted_qty/tests/test_product_category.py @@ -0,0 +1,126 @@ +# Copyright 2024 CorporateHub +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import odoo.tests.common as common +from odoo.tests import tagged + + +@tagged("post_install", "-at_install") +class TestProductCategory(common.TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + + cls.ProductCategory = cls.env["product.category"] + + def test_inheritance(self): + parent = self.ProductCategory.create( + { + "name": "Parent", + } + ) + self.assertEqual(parent.is_sale_min_qty_set, False) + self.assertEqual(parent.sale_min_qty, 0.0) + self.assertEqual(parent.is_sale_restrict_min_qty_set, False) + self.assertEqual(parent.sale_restrict_min_qty, "0") + self.assertEqual(parent.is_sale_max_qty_set, False) + self.assertEqual(parent.sale_max_qty, 0.0) + self.assertEqual(parent.is_sale_restrict_max_qty_set, False) + self.assertEqual(parent.sale_restrict_max_qty, "0") + self.assertEqual(parent.is_sale_multiple_of_qty_set, False) + self.assertEqual(parent.sale_multiple_of_qty, 0.0) + self.assertEqual(parent.is_sale_restrict_multiple_of_qty_set, False) + self.assertEqual(parent.sale_restrict_multiple_of_qty, "0") + + child = self.ProductCategory.create( + { + "name": "Child", + "parent_id": parent.id, + } + ) + self.assertEqual(child.sale_min_qty, 0.0) + self.assertEqual(child.sale_restrict_min_qty, "0") + self.assertEqual(child.sale_max_qty, 0.0) + self.assertEqual(child.sale_restrict_max_qty, "0") + self.assertEqual(child.sale_multiple_of_qty, 0.0) + self.assertEqual(child.sale_restrict_multiple_of_qty, "0") + + parent.update( + { + "sale_min_qty": 10.0, + "sale_restrict_min_qty": "1", + "sale_max_qty": 100.0, + "sale_restrict_max_qty": "1", + "sale_multiple_of_qty": 5.0, + "sale_restrict_multiple_of_qty": "1", + } + ) + self.assertTrue(child.is_sale_min_qty_set) + self.assertTrue(child.is_sale_restrict_min_qty_set) + self.assertTrue(child.is_sale_max_qty_set) + self.assertTrue(child.is_sale_restrict_max_qty_set) + self.assertTrue(child.is_sale_multiple_of_qty_set) + self.assertTrue(child.is_sale_restrict_multiple_of_qty_set) + self.assertFalse(child.is_sale_own_min_qty_set) + self.assertFalse(child.is_sale_own_restrict_min_qty_set) + self.assertFalse(child.is_sale_own_max_qty_set) + self.assertFalse(child.is_sale_own_restrict_max_qty_set) + self.assertFalse(child.is_sale_own_multiple_of_qty_set) + self.assertFalse(child.is_sale_own_restrict_multiple_of_qty_set) + self.assertEqual(child.sale_min_qty, 10.0) + self.assertEqual(child.sale_restrict_min_qty, "1") + self.assertEqual(child.sale_max_qty, 100.0) + self.assertEqual(child.sale_restrict_max_qty, "1") + self.assertEqual(child.sale_multiple_of_qty, 5.0) + self.assertEqual(child.sale_restrict_multiple_of_qty, "1") + + child.sale_min_qty = 20.0 + self.assertTrue(child.is_sale_own_min_qty_set) + self.assertEqual(child.sale_own_min_qty, 20.0) + + child.sale_restrict_min_qty = "0" + self.assertTrue(child.is_sale_own_restrict_min_qty_set) + self.assertEqual(child.sale_own_restrict_min_qty, "0") + + child.sale_max_qty = 200.0 + self.assertTrue(child.is_sale_own_max_qty_set) + self.assertEqual(child.sale_own_max_qty, 200.0) + + child.sale_restrict_max_qty = "0" + self.assertTrue(child.is_sale_own_restrict_max_qty_set) + self.assertEqual(child.sale_own_restrict_max_qty, "0") + + child.sale_multiple_of_qty = 10.0 + self.assertTrue(child.is_sale_own_multiple_of_qty_set) + self.assertEqual(child.sale_own_multiple_of_qty, 10.0) + + child.sale_restrict_multiple_of_qty = "0" + self.assertTrue(child.is_sale_own_restrict_multiple_of_qty_set) + self.assertEqual(child.sale_own_restrict_multiple_of_qty, "0") + + child.is_sale_own_min_qty_set = False + child._onchange_is_sale_min_qty_set() + self.assertEqual(child.sale_min_qty, 10.0) + self.assertEqual(child.sale_own_min_qty, 0.0) + + child.is_sale_own_restrict_min_qty_set = False + self.assertEqual(child.sale_restrict_min_qty, "1") + self.assertFalse(child.sale_own_restrict_min_qty) + + child.is_sale_own_max_qty_set = False + child._onchange_is_sale_max_qty_set() + self.assertEqual(child.sale_max_qty, 100.0) + self.assertEqual(child.sale_own_max_qty, 0.0) + + child.is_sale_own_restrict_max_qty_set = False + self.assertEqual(child.sale_restrict_max_qty, "1") + self.assertFalse(child.sale_own_restrict_max_qty) + + child.is_sale_own_multiple_of_qty_set = False + child._onchange_is_sale_multiple_of_qty_set() + self.assertEqual(child.sale_multiple_of_qty, 5.0) + self.assertEqual(child.sale_own_multiple_of_qty, 0.0) + + child.is_sale_own_restrict_multiple_of_qty_set = False + self.assertEqual(child.sale_restrict_multiple_of_qty, "1") + self.assertFalse(child.sale_own_restrict_multiple_of_qty) diff --git a/sale_restricted_qty/tests/test_product_product.py b/sale_restricted_qty/tests/test_product_product.py new file mode 100644 index 00000000000..49ae37e4cf6 --- /dev/null +++ b/sale_restricted_qty/tests/test_product_product.py @@ -0,0 +1,127 @@ +# Copyright 2024 CorporateHub +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import odoo.tests.common as common +from odoo.tests import tagged + + +@tagged("post_install", "-at_install") +class TestProductTemplate(common.TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + + cls.ProductTemplate = cls.env["product.template"] + cls.Product = cls.env["product.product"] + + def test_inheritance(self): + template = self.ProductTemplate.create( + { + "name": "Template", + } + ) + self.assertEqual(template.is_sale_min_qty_set, False) + self.assertEqual(template.sale_min_qty, 0.0) + self.assertEqual(template.is_sale_restrict_min_qty_set, False) + self.assertEqual(template.sale_restrict_min_qty, "0") + self.assertEqual(template.is_sale_max_qty_set, False) + self.assertEqual(template.sale_max_qty, 0.0) + self.assertEqual(template.is_sale_restrict_max_qty_set, False) + self.assertEqual(template.sale_restrict_max_qty, "0") + self.assertEqual(template.is_sale_multiple_of_qty_set, False) + self.assertEqual(template.sale_multiple_of_qty, 0.0) + self.assertEqual(template.is_sale_restrict_multiple_of_qty_set, False) + self.assertEqual(template.sale_restrict_multiple_of_qty, "0") + + product = self.Product.create( + { + "name": "Product", + "product_tmpl_id": template.id, + } + ) + self.assertEqual(product.sale_min_qty, 0.0) + self.assertEqual(product.sale_restrict_min_qty, "0") + self.assertEqual(product.sale_max_qty, 0.0) + self.assertEqual(product.sale_restrict_max_qty, "0") + self.assertEqual(product.sale_multiple_of_qty, 0.0) + self.assertEqual(product.sale_restrict_multiple_of_qty, "0") + + template.update( + { + "sale_min_qty": 10.0, + "sale_restrict_min_qty": "1", + "sale_max_qty": 100.0, + "sale_restrict_max_qty": "1", + "sale_multiple_of_qty": 5.0, + "sale_restrict_multiple_of_qty": "1", + } + ) + self.assertTrue(product.is_sale_min_qty_set) + self.assertTrue(product.is_sale_restrict_min_qty_set) + self.assertTrue(product.is_sale_max_qty_set) + self.assertTrue(product.is_sale_restrict_max_qty_set) + self.assertTrue(product.is_sale_multiple_of_qty_set) + self.assertTrue(product.is_sale_restrict_multiple_of_qty_set) + self.assertFalse(product.is_sale_own_min_qty_set) + self.assertFalse(product.is_sale_own_restrict_min_qty_set) + self.assertFalse(product.is_sale_own_max_qty_set) + self.assertFalse(product.is_sale_own_restrict_max_qty_set) + self.assertFalse(product.is_sale_own_multiple_of_qty_set) + self.assertFalse(product.is_sale_own_restrict_multiple_of_qty_set) + self.assertEqual(product.sale_min_qty, 10.0) + self.assertEqual(product.sale_restrict_min_qty, "1") + self.assertEqual(product.sale_max_qty, 100.0) + self.assertEqual(product.sale_restrict_max_qty, "1") + self.assertEqual(product.sale_multiple_of_qty, 5.0) + self.assertEqual(product.sale_restrict_multiple_of_qty, "1") + + product.sale_min_qty = 20.0 + self.assertTrue(product.is_sale_own_min_qty_set) + self.assertEqual(product.sale_own_min_qty, 20.0) + + product.sale_restrict_min_qty = "0" + self.assertTrue(product.is_sale_own_restrict_min_qty_set) + self.assertEqual(product.sale_own_restrict_min_qty, "0") + + product.sale_max_qty = 200.0 + self.assertTrue(product.is_sale_own_max_qty_set) + self.assertEqual(product.sale_own_max_qty, 200.0) + + product.sale_restrict_max_qty = "0" + self.assertTrue(product.is_sale_own_restrict_max_qty_set) + self.assertEqual(product.sale_own_restrict_max_qty, "0") + + product.sale_multiple_of_qty = 10.0 + self.assertTrue(product.is_sale_own_multiple_of_qty_set) + self.assertEqual(product.sale_own_multiple_of_qty, 10.0) + + product.sale_restrict_multiple_of_qty = "0" + self.assertTrue(product.is_sale_own_restrict_multiple_of_qty_set) + self.assertEqual(product.sale_own_restrict_multiple_of_qty, "0") + + product.is_sale_own_min_qty_set = False + product._onchange_is_sale_min_qty_set() + self.assertEqual(product.sale_min_qty, 10.0) + self.assertEqual(product.sale_own_min_qty, 0.0) + + product.is_sale_own_restrict_min_qty_set = False + self.assertEqual(product.sale_restrict_min_qty, "1") + self.assertFalse(product.sale_own_restrict_min_qty) + + product.is_sale_own_max_qty_set = False + product._onchange_is_sale_max_qty_set() + self.assertEqual(product.sale_max_qty, 100.0) + self.assertEqual(product.sale_own_max_qty, 0.0) + + product.is_sale_own_restrict_max_qty_set = False + self.assertEqual(product.sale_restrict_max_qty, "1") + self.assertFalse(product.sale_own_restrict_max_qty) + + product.is_sale_own_multiple_of_qty_set = False + product._onchange_is_sale_multiple_of_qty_set() + self.assertEqual(product.sale_multiple_of_qty, 5.0) + self.assertEqual(product.sale_own_multiple_of_qty, 0.0) + + product.is_sale_own_restrict_multiple_of_qty_set = False + self.assertEqual(product.sale_restrict_multiple_of_qty, "1") + self.assertFalse(product.sale_own_restrict_multiple_of_qty) diff --git a/sale_restricted_qty/tests/test_product_template.py b/sale_restricted_qty/tests/test_product_template.py new file mode 100644 index 00000000000..69d496f57e7 --- /dev/null +++ b/sale_restricted_qty/tests/test_product_template.py @@ -0,0 +1,150 @@ +# Copyright 2024 CorporateHub +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import odoo.tests.common as common +from odoo.tests import tagged + + +@tagged("post_install", "-at_install") +class TestProductTemplate(common.TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + + cls.ProductCategory = cls.env["product.category"] + cls.ProductTemplate = cls.env["product.template"] + + def test_inheritance(self): + category = self.ProductCategory.create( + { + "name": "Category", + } + ) + self.assertEqual(category.is_sale_min_qty_set, False) + self.assertEqual(category.sale_min_qty, 0.0) + self.assertEqual(category.is_sale_restrict_min_qty_set, False) + self.assertEqual(category.sale_restrict_min_qty, "0") + self.assertEqual(category.is_sale_max_qty_set, False) + self.assertEqual(category.sale_max_qty, 0.0) + self.assertEqual(category.is_sale_restrict_max_qty_set, False) + self.assertEqual(category.sale_restrict_max_qty, "0") + self.assertEqual(category.is_sale_multiple_of_qty_set, False) + self.assertEqual(category.sale_multiple_of_qty, 0.0) + self.assertEqual(category.is_sale_restrict_multiple_of_qty_set, False) + self.assertEqual(category.sale_restrict_multiple_of_qty, "0") + + template = self.ProductTemplate.create( + { + "name": "Child", + } + ) + self.assertEqual(template.is_sale_min_qty_set, False) + self.assertEqual(template.sale_min_qty, 0.0) + self.assertEqual(template.is_sale_restrict_min_qty_set, False) + self.assertEqual(template.sale_restrict_min_qty, "0") + self.assertEqual(template.is_sale_max_qty_set, False) + self.assertEqual(template.sale_max_qty, 0.0) + self.assertEqual(template.is_sale_restrict_max_qty_set, False) + self.assertEqual(template.sale_restrict_max_qty, "0") + self.assertEqual(template.is_sale_multiple_of_qty_set, False) + self.assertEqual(template.sale_multiple_of_qty, 0.0) + self.assertEqual(template.is_sale_restrict_multiple_of_qty_set, False) + self.assertEqual(template.sale_restrict_multiple_of_qty, "0") + + template.update( + { + "categ_id": category.id, + } + ) + self.assertEqual(template.is_sale_min_qty_set, False) + self.assertEqual(template.sale_min_qty, 0.0) + self.assertEqual(template.is_sale_restrict_min_qty_set, False) + self.assertEqual(template.sale_restrict_min_qty, "0") + self.assertEqual(template.is_sale_max_qty_set, False) + self.assertEqual(template.sale_max_qty, 0.0) + self.assertEqual(template.is_sale_restrict_max_qty_set, False) + self.assertEqual(template.sale_restrict_max_qty, "0") + self.assertEqual(template.is_sale_multiple_of_qty_set, False) + self.assertEqual(template.sale_multiple_of_qty, 0.0) + self.assertEqual(template.is_sale_restrict_multiple_of_qty_set, False) + self.assertEqual(template.sale_restrict_multiple_of_qty, "0") + + category.update( + { + "sale_min_qty": 10.0, + "sale_restrict_min_qty": "1", + "sale_max_qty": 100.0, + "sale_restrict_max_qty": "1", + "sale_multiple_of_qty": 5.0, + "sale_restrict_multiple_of_qty": "1", + } + ) + self.assertTrue(template.is_sale_min_qty_set) + self.assertTrue(template.is_sale_restrict_min_qty_set) + self.assertTrue(template.is_sale_max_qty_set) + self.assertTrue(template.is_sale_restrict_max_qty_set) + self.assertTrue(template.is_sale_multiple_of_qty_set) + self.assertTrue(template.is_sale_restrict_multiple_of_qty_set) + self.assertFalse(template.is_sale_own_min_qty_set) + self.assertFalse(template.is_sale_own_restrict_min_qty_set) + self.assertFalse(template.is_sale_own_max_qty_set) + self.assertFalse(template.is_sale_own_restrict_max_qty_set) + self.assertFalse(template.is_sale_own_multiple_of_qty_set) + self.assertFalse(template.is_sale_own_restrict_multiple_of_qty_set) + self.assertEqual(template.sale_min_qty, 10.0) + self.assertEqual(template.sale_restrict_min_qty, "1") + self.assertEqual(template.sale_max_qty, 100.0) + self.assertEqual(template.sale_restrict_max_qty, "1") + self.assertEqual(template.sale_multiple_of_qty, 5.0) + self.assertEqual(template.sale_restrict_multiple_of_qty, "1") + + template.sale_min_qty = 20.0 + self.assertTrue(template.is_sale_own_min_qty_set) + self.assertEqual(template.sale_own_min_qty, 20.0) + + template.sale_restrict_min_qty = "0" + self.assertTrue(template.is_sale_own_restrict_min_qty_set) + self.assertEqual(template.sale_own_restrict_min_qty, "0") + + template.sale_max_qty = 200.0 + self.assertTrue(template.is_sale_own_max_qty_set) + self.assertEqual(template.sale_own_max_qty, 200.0) + + template.sale_restrict_max_qty = "0" + self.assertTrue(template.is_sale_own_restrict_max_qty_set) + self.assertEqual(template.sale_own_restrict_max_qty, "0") + + template.sale_multiple_of_qty = 10.0 + self.assertTrue(template.is_sale_own_multiple_of_qty_set) + self.assertEqual(template.sale_own_multiple_of_qty, 10.0) + + template.sale_restrict_multiple_of_qty = "0" + self.assertTrue(template.is_sale_own_restrict_multiple_of_qty_set) + self.assertEqual(template.sale_own_restrict_multiple_of_qty, "0") + + template.is_sale_own_min_qty_set = False + template._onchange_is_sale_min_qty_set() + self.assertEqual(template.sale_min_qty, 10.0) + self.assertEqual(template.sale_own_min_qty, 0.0) + + template.is_sale_own_restrict_min_qty_set = False + self.assertEqual(template.sale_restrict_min_qty, "1") + self.assertFalse(template.sale_own_restrict_min_qty) + + template.is_sale_own_max_qty_set = False + template._onchange_is_sale_max_qty_set() + self.assertEqual(template.sale_max_qty, 100.0) + self.assertEqual(template.sale_own_max_qty, 0.0) + + template.is_sale_own_restrict_max_qty_set = False + self.assertEqual(template.sale_restrict_max_qty, "1") + self.assertFalse(template.sale_own_restrict_max_qty) + + template.is_sale_own_multiple_of_qty_set = False + template._onchange_is_sale_multiple_of_qty_set() + self.assertEqual(template.sale_multiple_of_qty, 5.0) + self.assertEqual(template.sale_own_multiple_of_qty, 0.0) + + template.is_sale_own_restrict_multiple_of_qty_set = False + self.assertEqual(template.sale_restrict_multiple_of_qty, "1") + self.assertFalse(template.sale_own_restrict_multiple_of_qty) diff --git a/sale_restricted_qty/tests/test_sale_order_line.py b/sale_restricted_qty/tests/test_sale_order_line.py new file mode 100644 index 00000000000..d4e8606cf1b --- /dev/null +++ b/sale_restricted_qty/tests/test_sale_order_line.py @@ -0,0 +1,137 @@ +# Copyright 2024 CorporateHub +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import odoo.tests.common as common +from odoo.exceptions import ValidationError +from odoo.tests import tagged + + +@tagged("post_install", "-at_install") +class TestSaleOrderLine(common.TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + + cls.Partner = cls.env["res.partner"] + cls.Product = cls.env["product.product"] + cls.SaleOrder = cls.env["sale.order"] + + cls.partner = cls.Partner.create({"name": "Partner"}) + + def test_min_qty(self): + product = self.Product.create( + { + "name": "Product", + "sale_min_qty": 10.0, + } + ) + self.assertTrue(product.is_sale_own_min_qty_set) + self.assertEqual(product.sale_own_min_qty, 10.0) + + sale_order = self.SaleOrder.create( + { + "partner_id": self.partner.id, + "order_line": [ + ( + 0, + 0, + { + "product_id": product.id, + "product_uom_qty": 5.0, + }, + ) + ], + } + ) + self.assertEqual(sale_order.order_line.min_qty, 10.0) + self.assertFalse(sale_order.order_line.restrict_min_qty) + self.assertTrue(sale_order.order_line.is_below_min_qty) + + def test_min_qty_restricted(self): + product = self.Product.create( + { + "name": "Product", + "sale_min_qty": 10.0, + "sale_restrict_min_qty": "1", + } + ) + self.assertTrue(product.is_sale_own_min_qty_set) + self.assertEqual(product.sale_own_min_qty, 10.0) + self.assertTrue(product.is_sale_own_restrict_min_qty_set) + self.assertEqual(product.sale_own_restrict_min_qty, "1") + + with self.assertRaises(ValidationError): + self.SaleOrder.create( + { + "partner_id": self.partner.id, + "order_line": [ + ( + 0, + 0, + { + "product_id": product.id, + "product_uom_qty": 5.0, + }, + ) + ], + } + ) + + def test_max_qty(self): + product = self.Product.create( + { + "name": "Product", + "sale_max_qty": 10.0, + } + ) + self.assertTrue(product.is_sale_own_max_qty_set) + self.assertEqual(product.sale_own_max_qty, 10.0) + + sale_order = self.SaleOrder.create( + { + "partner_id": self.partner.id, + "order_line": [ + ( + 0, + 0, + { + "product_id": product.id, + "product_uom_qty": 15.0, + }, + ) + ], + } + ) + self.assertEqual(sale_order.order_line.max_qty, 10.0) + self.assertFalse(sale_order.order_line.restrict_max_qty) + self.assertTrue(sale_order.order_line.is_above_max_qty) + + def test_max_qty_restricted(self): + product = self.Product.create( + { + "name": "Product", + "sale_max_qty": 10.0, + "sale_restrict_max_qty": "1", + } + ) + self.assertTrue(product.is_sale_own_max_qty_set) + self.assertEqual(product.sale_own_max_qty, 10.0) + self.assertTrue(product.is_sale_own_restrict_max_qty_set) + self.assertEqual(product.sale_own_restrict_max_qty, "1") + + with self.assertRaises(ValidationError): + self.SaleOrder.create( + { + "partner_id": self.partner.id, + "order_line": [ + ( + 0, + 0, + { + "product_id": product.id, + "product_uom_qty": 15.0, + }, + ) + ], + } + ) diff --git a/sale_restricted_qty/views/product_category_views.xml b/sale_restricted_qty/views/product_category_views.xml index 91c5a5c5ad3..21294be0104 100644 --- a/sale_restricted_qty/views/product_category_views.xml +++ b/sale_restricted_qty/views/product_category_views.xml @@ -1,58 +1,150 @@ + product.category - + - - - + diff --git a/sale_restricted_qty/views/product_template_views.xml b/sale_restricted_qty/views/product_template_views.xml index b5ffbef1925..5b4fb99617c 100644 --- a/sale_restricted_qty/views/product_template_views.xml +++ b/sale_restricted_qty/views/product_template_views.xml @@ -1,4 +1,9 @@ + product.template @@ -7,49 +12,136 @@ - - -