diff --git a/sale_quotation/README.rst b/sale_quotation/README.rst index b87feb7a5a..3e8882045f 100644 --- a/sale_quotation/README.rst +++ b/sale_quotation/README.rst @@ -24,27 +24,8 @@ Sale Quotation This module is used in the context of e-commerce project (like shopinvader) - -It allows to specify whether your product requires a quotation. -This can be set per "product.template" or per "product.variant" - - -For example, you have a product where the supplier price fluctuates a lot, -you can not give a public price but your customer should be able to request a quotation. - -.. image:: https://raw.githubusercontent.com/shopinvader/odoo-shopinvader/16.0/sale_quotation/static/description/product.png - :width: 400px - :alt: Widget in action - - - -On your shopinvader website when a customer has added a product that requires a quotation, -instead of validating the cart it will "request a quotation". - - - -On Odoo Backoffice the menu quotation has been improved and a new state (quotation_state) has been added. -So you can easily process and follow the quotation request from your external system. +On Odoo Backoffice this module imporves the menu quotation by adding a new state (quotation_state). +Then, you can easily process and follow the quotation request from your external system. .. image:: https://raw.githubusercontent.com/shopinvader/odoo-shopinvader/16.0/sale_quotation/static/description/quotation.png :width: 400px @@ -82,6 +63,7 @@ Contributors * Matthieu Saison * Iván Todorovich * Simone Orsi +* Nicolas DELBOVIER Maintainers ~~~~~~~~~~~ diff --git a/sale_quotation/__manifest__.py b/sale_quotation/__manifest__.py index a98348c2c1..2cb1baa444 100644 --- a/sale_quotation/__manifest__.py +++ b/sale_quotation/__manifest__.py @@ -4,15 +4,17 @@ { "name": "Sale Quotation", "summary": "Sale Quotation", - "version": "16.0.0.2.0", + "version": "16.0.0.3.0", "category": "e-commerce", "development_status": "Production/Stable", "website": "https://github.com/shopinvader/odoo-shopinvader", "author": "Akretion", "license": "AGPL-3", - "depends": ["sale_cart"], + "depends": [ + "sale_cart", + "shopinvader_product_order_mode_on_request", + ], "data": [ - "views/product_view.xml", "views/sale_view.xml", ], "installable": True, diff --git a/sale_quotation/i18n/sale_quotation.pot b/sale_quotation/i18n/sale_quotation.pot index 94c88c9105..aad07b6837 100644 --- a/sale_quotation/i18n/sale_quotation.pot +++ b/sale_quotation/i18n/sale_quotation.pot @@ -1,120 +1,15 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: -# * sale_quotation # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 16.0\n" +"Project-Id-Version: Odoo Server 16.0+e\n" "Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-05-16 13:39+0000\n" +"PO-Revision-Date: 2025-05-16 13:39+0000\n" "Last-Translator: \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" "Plural-Forms: \n" - -#. module: sale_quotation -#: model:ir.model.fields.selection,name:sale_quotation.selection__sale_order__quotation_state__accepted -#: model_terms:ir.ui.view,arch_db:sale_quotation.sale_order_view_search_inherit_quotation -msgid "Accepted" -msgstr "" - -#. module: sale_quotation -#: model:ir.model.fields.selection,name:sale_quotation.selection__product_template__shop_only_quotation__all_variant -msgid "All Variant" -msgstr "" - -#. module: sale_quotation -#: model:ir.model.fields.selection,name:sale_quotation.selection__sale_order__quotation_state__cancel -#: model_terms:ir.ui.view,arch_db:sale_quotation.sale_order_view_search_inherit_quotation -msgid "Cancel" -msgstr "" - -#. module: sale_quotation -#: model:ir.model.fields.selection,name:sale_quotation.selection__sale_order__quotation_state__customer_request -msgid "Customer Request" -msgstr "" - -#. module: sale_quotation -#: model_terms:ir.ui.view,arch_db:sale_quotation.sale_order_view_search_inherit_quotation -msgid "Customer request" -msgstr "" - -#. module: sale_quotation -#: model:ir.model.fields.selection,name:sale_quotation.selection__sale_order__quotation_state__draft -#: model_terms:ir.ui.view,arch_db:sale_quotation.sale_order_view_search_inherit_quotation -msgid "Draft" -msgstr "" - -#. module: sale_quotation -#: model:ir.model.fields.selection,name:sale_quotation.selection__product_template__shop_only_quotation__manually_on_variant -msgid "Manually on Variant" -msgstr "" - -#. module: sale_quotation -#: model:ir.model.fields.selection,name:sale_quotation.selection__product_template__shop_only_quotation__never -msgid "Never" -msgstr "" - -#. module: sale_quotation -#: model_terms:ir.ui.view,arch_db:sale_quotation.product_template_form_view -msgid "Only for Quotation" -msgstr "" - -#. module: sale_quotation -#. odoo-python -#: code:addons/sale_quotation/models/sale_order.py:0 -#, python-format -msgid "" -"Only orders of cart typology in draft state can be converted to quotation" -msgstr "" - -#. module: sale_quotation -#. odoo-python -#: code:addons/sale_quotation/models/sale_order.py:0 -#, python-format -msgid "Only quotation with the state 'waiting_acceptation' can be Confirmed." -msgstr "" - -#. module: sale_quotation -#: model:ir.model,name:sale_quotation.model_product_template -msgid "Product" -msgstr "" - -#. module: sale_quotation -#: model:ir.model,name:sale_quotation.model_product_product -msgid "Product Variant" -msgstr "" - -#. module: sale_quotation -#: model:ir.model.fields,field_description:sale_quotation.field_sale_order__quotation_state -msgid "Quotation State" -msgstr "" - -#. module: sale_quotation -#: model:ir.model,name:sale_quotation.model_sale_order -msgid "Sales Order" -msgstr "" - -#. module: sale_quotation -#: model:ir.model.fields,field_description:sale_quotation.field_sale_order__shop_only_quotation -msgid "Shop Only Quotation" -msgstr "" - -#. module: sale_quotation -#: model:ir.model.fields,field_description:sale_quotation.field_product_product__shop_only_quotation -#: model:ir.model.fields,field_description:sale_quotation.field_product_template__shop_only_quotation -msgid "Shopinvader: Only for Quotation" -msgstr "" - -#. module: sale_quotation -#: model_terms:ir.ui.view,arch_db:sale_quotation.sale_order_view_search_inherit_quotation -#: model_terms:ir.ui.view,arch_db:sale_quotation.view_quotation_tree -msgid "State" -msgstr "" - -#. module: sale_quotation -#: model:ir.model.fields.selection,name:sale_quotation.selection__sale_order__quotation_state__waiting_acceptation -#: model_terms:ir.ui.view,arch_db:sale_quotation.sale_order_view_search_inherit_quotation -msgid "Waiting Acceptation" -msgstr "" diff --git a/sale_quotation/migrations/16.0.0.3.0/pre-migrate.py b/sale_quotation/migrations/16.0.0.3.0/pre-migrate.py new file mode 100644 index 0000000000..7772572fc5 --- /dev/null +++ b/sale_quotation/migrations/16.0.0.3.0/pre-migrate.py @@ -0,0 +1,47 @@ +# Copyright 2025 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from openupgradelib import openupgrade + + +def migrate(cr, version): + """ + For each product and for each template where 'shop_only_quotation' is True, + set 'shop_order_mode' to 'quotation_only'. + """ + openupgrade.logged_query( + cr, + """ + UPDATE product_template + SET shop_order_mode = 'quotation_only' + WHERE shop_only_quotation = 'all_variant'; + """, + ) + openupgrade.logged_query( + cr, + """ + UPDATE product_template + SET shop_order_mode = 'direct_sale_only' + WHERE shop_only_quotation = 'never'; + """, + ) + openupgrade.logged_query( + cr, + """ + UPDATE product_template + SET is_shop_order_mode_enabled_on_variant = TRUE + WHERE shop_only_quotation = 'manually_on_variant'; + """, + ) + + openupgrade.logged_query( + cr, + """ + UPDATE product_product + SET shop_order_mode = 'quotation_only' + FROM product_template AS pt + WHERE product_product.product_tmpl_id = pt.id + AND pt.is_shop_order_mode_enabled_on_variant = TRUE + AND product_product.shop_only_quotation = TRUE; + """, + ) diff --git a/sale_quotation/models/__init__.py b/sale_quotation/models/__init__.py index de7a683548..6aacb75313 100644 --- a/sale_quotation/models/__init__.py +++ b/sale_quotation/models/__init__.py @@ -1,3 +1 @@ -from . import product_product -from . import product_template from . import sale_order diff --git a/sale_quotation/models/product_product.py b/sale_quotation/models/product_product.py deleted file mode 100644 index 87bf509dbc..0000000000 --- a/sale_quotation/models/product_product.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2021 Camptocamp (https://www.camptocamp.com). -# @author Iván Todorovich -# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). - -from odoo import fields, models - - -class ProductProduct(models.Model): - _inherit = "product.product" - - shop_only_quotation = fields.Boolean( - string="Shopinvader: Only for Quotation", - ) diff --git a/sale_quotation/models/product_template.py b/sale_quotation/models/product_template.py deleted file mode 100644 index 1f58f4e52b..0000000000 --- a/sale_quotation/models/product_template.py +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright 2017-2018 Akretion (http://www.akretion.com). -# Copyright 2021 Camptocamp (https://www.camptocamp.com). -# @author Benoît GUILLOT -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from odoo import api, fields, models - - -class ProductTemplate(models.Model): - _inherit = "product.template" - - shop_only_quotation = fields.Selection( - selection=[ - ("all_variant", "All Variant"), - ("manually_on_variant", "Manually on Variant"), - ("never", "Never"), - ], - string="Shopinvader: Only for Quotation", - compute="_compute_shop_only_quotation", - inverse="_inverse_shop_only_quotation", - store=True, - ) - - @api.depends("product_variant_ids.shop_only_quotation") - def _compute_shop_only_quotation(self): - # True only if true for all its variants - for rec in self: - if not rec.product_variant_ids or not any( - rec.product_variant_ids.mapped("shop_only_quotation") - ): - rec.shop_only_quotation = "never" - elif all(rec.product_variant_ids.mapped("shop_only_quotation")): - rec.shop_only_quotation = "all_variant" - elif any(rec.product_variant_ids.mapped("shop_only_quotation")): - rec.shop_only_quotation = "manually_on_variant" - - def _inverse_shop_only_quotation(self): - # Sets the value on all its variants - for rec in self: - if rec.shop_only_quotation == "all_variant": - rec.product_variant_ids.shop_only_quotation = True - elif rec.shop_only_quotation == "never": - rec.product_variant_ids.shop_only_quotation = False - - def _create_variant_ids(self): - # Make sure new variants have the same value than the template. - res = super()._create_variant_ids() - for rec in self: - if rec.shop_only_quotation == "all_variant": - rec.product_variant_ids.shop_only_quotation = True - return res diff --git a/sale_quotation/models/sale_order.py b/sale_quotation/models/sale_order.py index aae40d577a..02302497f6 100644 --- a/sale_quotation/models/sale_order.py +++ b/sale_quotation/models/sale_order.py @@ -1,5 +1,6 @@ # Copyright 2017-2018 Akretion (http://www.akretion.com). # Copyright 2021 Camptocamp (http://www.camptocamp.com) +# Copyright 2025 ACSONE SA/NV # @author Benoît GUILLOT # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). @@ -24,7 +25,6 @@ class SaleOrder(models.Model): readonly=False, copy=False, ) - shop_only_quotation = fields.Boolean(compute="_compute_shop_only_quotation") @api.depends("state") def _compute_quotation_state(self): @@ -53,12 +53,6 @@ def action_request_quotation(self): self.write({"quotation_state": "customer_request", "typology": "sale"}) return True - def _compute_shop_only_quotation(self): - for record in self: - record.shop_only_quotation = any( - record.order_line.product_id.mapped("shop_only_quotation") - ) - def action_confirm_quotation(self): if any(rec.quotation_state != "waiting_acceptation" for rec in self): raise UserError( diff --git a/sale_quotation/readme/CONTRIBUTORS.rst b/sale_quotation/readme/CONTRIBUTORS.rst index 5116fef324..0b22e8639c 100644 --- a/sale_quotation/readme/CONTRIBUTORS.rst +++ b/sale_quotation/readme/CONTRIBUTORS.rst @@ -2,4 +2,5 @@ * Benoît GUILLOT * Matthieu Saison * Iván Todorovich -* Simone Orsi \ No newline at end of file +* Simone Orsi +* Nicolas DELBOVIER \ No newline at end of file diff --git a/sale_quotation/readme/DESCRIPTION.rst b/sale_quotation/readme/DESCRIPTION.rst index 2c68999acc..6cd8f3500a 100644 --- a/sale_quotation/readme/DESCRIPTION.rst +++ b/sale_quotation/readme/DESCRIPTION.rst @@ -1,26 +1,7 @@ This module is used in the context of e-commerce project (like shopinvader) - -It allows to specify whether your product requires a quotation. -This can be set per "product.template" or per "product.variant" - - -For example, you have a product where the supplier price fluctuates a lot, -you can not give a public price but your customer should be able to request a quotation. - -.. image:: ../static/description/product.png - :width: 400px - :alt: Widget in action - - - -On your shopinvader website when a customer has added a product that requires a quotation, -instead of validating the cart it will "request a quotation". - - - -On Odoo Backoffice the menu quotation has been improved and a new state (quotation_state) has been added. -So you can easily process and follow the quotation request from your external system. +On Odoo Backoffice this module imporves the menu quotation by adding a new state (quotation_state). +Then, you can easily process and follow the quotation request from your external system. .. image:: ../static/description/quotation.png :width: 400px diff --git a/sale_quotation/static/description/index.html b/sale_quotation/static/description/index.html index dc42117149..bc77cc85c2 100644 --- a/sale_quotation/static/description/index.html +++ b/sale_quotation/static/description/index.html @@ -371,15 +371,8 @@

Sale Quotation

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Production/Stable License: AGPL-3 shopinvader/odoo-shopinvader

This module is used in the context of e-commerce project (like shopinvader)

-

It allows to specify whether your product requires a quotation. -This can be set per “product.template” or per “product.variant”

-

For example, you have a product where the supplier price fluctuates a lot, -you can not give a public price but your customer should be able to request a quotation.

-Widget in action -

On your shopinvader website when a customer has added a product that requires a quotation, -instead of validating the cart it will “request a quotation”.

-

On Odoo Backoffice the menu quotation has been improved and a new state (quotation_state) has been added. -So you can easily process and follow the quotation request from your external system.

+

On Odoo Backoffice this module imporves the menu quotation by adding a new state (quotation_state). +Then, you can easily process and follow the quotation request from your external system.

Widget in action

Table of contents

@@ -417,6 +410,7 @@

Contributors

  • Matthieu Saison <matthieu.saison@akretion.com>
  • Iván Todorovich <ivan.todorovich@gmail.com>
  • Simone Orsi <simone.orsi@camptocamp.com>
  • +
  • Nicolas DELBOVIER <nicolas.delbovier@acsone.eu>
  • diff --git a/sale_quotation/static/description/product.png b/sale_quotation/static/description/product.png deleted file mode 100644 index 9f287fdf19..0000000000 Binary files a/sale_quotation/static/description/product.png and /dev/null differ diff --git a/sale_quotation/tests/__init__.py b/sale_quotation/tests/__init__.py index 626580ed4e..43ae815229 100644 --- a/sale_quotation/tests/__init__.py +++ b/sale_quotation/tests/__init__.py @@ -1 +1 @@ -from . import test_product +from . import test_quotation diff --git a/sale_quotation/tests/test_product.py b/sale_quotation/tests/test_product.py deleted file mode 100644 index 39d15fab51..0000000000 --- a/sale_quotation/tests/test_product.py +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright 2021 Camptocamp (https://www.camptocamp.com). -# @author Iván Todorovich -# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). - -from odoo.tests.common import SavepointCase - - -class TestProduct(SavepointCase): - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) - cls.attribute = cls.env.ref("product.product_attribute_2") - cls.value_1 = cls.env.ref("product.product_attribute_value_3") - cls.value_2 = cls.env.ref("product.product_attribute_value_4") - cls.product_template = cls.env["product.template"].create( - { - "name": "Test Product", - } - ) - - def test_simple_product(self): - # Back and forth flow, setting and reading on template and single variant - self.assertEqual(self.product_template.shop_only_quotation, "never") - self.product_template.shop_only_quotation = "all_variant" - self.assertTrue(self.product_template.product_variant_ids.shop_only_quotation) - self.product_template.product_variant_ids.shop_only_quotation = False - self.assertEqual(self.product_template.shop_only_quotation, "never") - - def test_multi_product(self): - # Configure product with multiple variants - self.product_template.attribute_line_ids = [ - ( - 0, - 0, - { - "attribute_id": self.attribute.id, - "value_ids": [(6, 0, [self.value_1.id, self.value_2.id])], - }, - ) - ] - self.assertEqual(len(self.product_template.product_variant_ids), 2) - template = self.product_template - product_1 = self.product_template.product_variant_ids[0] - product_2 = self.product_template.product_variant_ids[1] - # Set on the template should set all variants - template.shop_only_quotation = "all_variant" - self.assertTrue(product_1.shop_only_quotation) - self.assertTrue(product_2.shop_only_quotation) - # Set a variant to false should set the template to false - # It's only true if it's true for all variants - product_1.shop_only_quotation = False - self.assertEqual(template.shop_only_quotation, "manually_on_variant") - self.assertTrue(product_2.shop_only_quotation, "Other variant shouldn't change") - # Set false on template should set all variants to false - template.shop_only_quotation = "never" - self.assertFalse(product_1.shop_only_quotation) - self.assertFalse(product_2.shop_only_quotation) diff --git a/sale_quotation/views/product_view.xml b/sale_quotation/views/product_view.xml deleted file mode 100644 index 08ce8aaa26..0000000000 --- a/sale_quotation/views/product_view.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - product.template - - - - - - - - diff --git a/setup/shopinvader_product_order_mode/odoo/addons/shopinvader_product_order_mode b/setup/shopinvader_product_order_mode/odoo/addons/shopinvader_product_order_mode new file mode 120000 index 0000000000..304624b9e4 --- /dev/null +++ b/setup/shopinvader_product_order_mode/odoo/addons/shopinvader_product_order_mode @@ -0,0 +1 @@ +../../../../shopinvader_product_order_mode \ No newline at end of file diff --git a/setup/shopinvader_product_order_mode/setup.py b/setup/shopinvader_product_order_mode/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/shopinvader_product_order_mode/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/setup/shopinvader_product_order_mode_on_request/odoo/addons/shopinvader_product_order_mode_on_request b/setup/shopinvader_product_order_mode_on_request/odoo/addons/shopinvader_product_order_mode_on_request new file mode 120000 index 0000000000..4b8a7d922b --- /dev/null +++ b/setup/shopinvader_product_order_mode_on_request/odoo/addons/shopinvader_product_order_mode_on_request @@ -0,0 +1 @@ +../../../../shopinvader_product_order_mode_on_request \ No newline at end of file diff --git a/setup/shopinvader_product_order_mode_on_request/setup.py b/setup/shopinvader_product_order_mode_on_request/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/shopinvader_product_order_mode_on_request/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/shopinvader_product_order_mode/README.rst b/shopinvader_product_order_mode/README.rst new file mode 100644 index 0000000000..2a68385836 --- /dev/null +++ b/shopinvader_product_order_mode/README.rst @@ -0,0 +1,60 @@ +============================== +Shopinvader Product Order Mode +============================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:276e322df2fe0bcec29a719db9b5ed5ce83469bd68da22df82a44616e3075471 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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-shopinvader%2Fodoo--shopinvader-lightgray.png?logo=github + :target: https://github.com/shopinvader/odoo-shopinvader/tree/16.0/shopinvader_product_order_mode + :alt: shopinvader/odoo-shopinvader + +|badge1| |badge2| |badge3| + +This module is a base for generating generic shop order modes on products + +**Table of contents** + +.. contents:: + :local: + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* ACSONE SA/NV + +Contributors +~~~~~~~~~~~~ + +* Nicolas DELBOVIER + +Maintainers +~~~~~~~~~~~ + +This module is part of the `shopinvader/odoo-shopinvader `_ project on GitHub. + +You are welcome to contribute. diff --git a/shopinvader_product_order_mode/__init__.py b/shopinvader_product_order_mode/__init__.py new file mode 100644 index 0000000000..0650744f6b --- /dev/null +++ b/shopinvader_product_order_mode/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/shopinvader_product_order_mode/__manifest__.py b/shopinvader_product_order_mode/__manifest__.py new file mode 100644 index 0000000000..d7280981ad --- /dev/null +++ b/shopinvader_product_order_mode/__manifest__.py @@ -0,0 +1,19 @@ +# Copyright 2025 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Shopinvader Product Order Mode", + "summary": """Add a field for specifying the order mode of a product in a generic way.""", + "version": "16.0.1.0.0", + "license": "AGPL-3", + "author": "ACSONE SA/NV", + "website": "https://github.com/shopinvader/odoo-shopinvader", + "depends": [ + "product", + ], + "data": [ + "views/product_product.xml", + "views/product_template.xml", + ], + "demo": [], +} diff --git a/shopinvader_product_order_mode/i18n/shopinvader_product_order_mode.pot b/shopinvader_product_order_mode/i18n/shopinvader_product_order_mode.pot new file mode 100644 index 0000000000..688df078d3 --- /dev/null +++ b/shopinvader_product_order_mode/i18n/shopinvader_product_order_mode.pot @@ -0,0 +1,81 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * shopinvader_product_order_mode +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-05-16 13:41+0000\n" +"PO-Revision-Date: 2025-05-16 13:41+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: shopinvader_product_order_mode +#: model:ir.model.fields,help:shopinvader_product_order_mode.field_product_template__shop_order_mode +msgid "Deault order mode for this template's products in Shopinvader." +msgstr "" + +#. module: shopinvader_product_order_mode +#. odoo-python +#: code:addons/shopinvader_product_order_mode/models/product_product.py:0 +#: code:addons/shopinvader_product_order_mode/models/product_template.py:0 +#: model:ir.model.fields.selection,name:shopinvader_product_order_mode.selection__product_product__shop_order_mode__direct_sale_only +#: model:ir.model.fields.selection,name:shopinvader_product_order_mode.selection__product_template__shop_order_mode__direct_sale_only +#, python-format +msgid "Direct Sale Only" +msgstr "" + +#. module: shopinvader_product_order_mode +#: model:ir.model.fields,help:shopinvader_product_order_mode.field_product_product__is_shop_order_mode_enabled +#: model:ir.model.fields,help:shopinvader_product_order_mode.field_product_product__is_shop_order_mode_enabled_on_variant +#: model:ir.model.fields,help:shopinvader_product_order_mode.field_product_template__is_shop_order_mode_enabled_on_variant +msgid "" +"If True, unables product variants to have a different value for " +"'shop_order_mode' than this product template." +msgstr "" + +#. module: shopinvader_product_order_mode +#: model:ir.model.fields,help:shopinvader_product_order_mode.field_product_product__shop_order_mode +msgid "Order mode for the product in Shopinvader." +msgstr "" + +#. module: shopinvader_product_order_mode +#: model:ir.model,name:shopinvader_product_order_mode.model_product_template +msgid "Product" +msgstr "" + +#. module: shopinvader_product_order_mode +#: model:ir.model,name:shopinvader_product_order_mode.model_product_product +msgid "Product Variant" +msgstr "" + +#. module: shopinvader_product_order_mode +#: model:ir.model.fields,field_description:shopinvader_product_order_mode.field_product_product__shop_order_mode +#: model:ir.model.fields,field_description:shopinvader_product_order_mode.field_product_template__shop_order_mode +msgid "Shopinvader Order Mode" +msgstr "" + +#. module: shopinvader_product_order_mode +#: model:ir.model.fields,field_description:shopinvader_product_order_mode.field_product_product__is_shop_order_mode_enabled_on_variant +#: model:ir.model.fields,field_description:shopinvader_product_order_mode.field_product_template__is_shop_order_mode_enabled_on_variant +msgid "Shopinvader Order Mode Unabled on Variant" +msgstr "" + +#. module: shopinvader_product_order_mode +#: model:ir.model.fields,field_description:shopinvader_product_order_mode.field_product_product__is_shop_order_mode_enabled +msgid "Template Shop Order Mode Disabled" +msgstr "" + +#. module: shopinvader_product_order_mode +#. odoo-python +#: code:addons/shopinvader_product_order_mode/models/product_product.py:0 +#, python-format +msgid "" +"Unable to modify 'shop_order_mode' on the product '%(product_name)s' as it " +"is blocked by the template '%(template_name)s'." +msgstr "" diff --git a/shopinvader_product_order_mode/models/__init__.py b/shopinvader_product_order_mode/models/__init__.py new file mode 100644 index 0000000000..049669dd0f --- /dev/null +++ b/shopinvader_product_order_mode/models/__init__.py @@ -0,0 +1,2 @@ +from . import product_template +from . import product_product diff --git a/shopinvader_product_order_mode/models/product_product.py b/shopinvader_product_order_mode/models/product_product.py new file mode 100644 index 0000000000..4b8bd8e0ee --- /dev/null +++ b/shopinvader_product_order_mode/models/product_product.py @@ -0,0 +1,53 @@ +# Copyright 2025 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class ProductProduct(models.Model): + _inherit = "product.product" + + shop_order_mode = fields.Selection( + selection=[("direct_sale_only", _("Direct Sale Only"))], + string="Shopinvader Order Mode", + store=True, + readonly=False, + compute="_compute_shop_order_mode", + inverse="_inverse_shop_order_mode", + help="Order mode for the product in Shopinvader.", + ) + is_shop_order_mode_enabled = fields.Boolean( + string="Template Shop Order Mode Disabled", + related="product_tmpl_id.is_shop_order_mode_enabled_on_variant", + store=True, + readonly=True, + ) + + @api.depends( + "product_tmpl_id.shop_order_mode", + "product_tmpl_id.is_shop_order_mode_enabled_on_variant", + ) + def _compute_shop_order_mode(self): + for product in self: + if not product.product_tmpl_id: + product.shop_order_mode = False + elif not product.product_tmpl_id.is_shop_order_mode_enabled_on_variant: + product.shop_order_mode = product.product_tmpl_id.shop_order_mode + + def _inverse_shop_order_mode(self): + for product in self: + if not product.is_shop_order_mode_enabled and not self.env.context.get( + "skip_shop_order_mode_validation" + ): + raise ValidationError( + _( + "Unable to modify 'shop_order_mode' on the product " + "'%(product_name)s' as it is blocked by the template " + "'%(template_name)s'." + ) + % { + "product_name": product.name, + "template_name": product.product_tmpl_id.name, + } + ) diff --git a/shopinvader_product_order_mode/models/product_template.py b/shopinvader_product_order_mode/models/product_template.py new file mode 100644 index 0000000000..d85bc46855 --- /dev/null +++ b/shopinvader_product_order_mode/models/product_template.py @@ -0,0 +1,22 @@ +# Copyright 2025 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import _, fields, models + + +class ProductTemplate(models.Model): + _inherit = "product.template" + + shop_order_mode = fields.Selection( + selection=[("direct_sale_only", _("Direct Sale Only"))], + string="Shopinvader Order Mode", + default="direct_sale_only", + help="Deault order mode for this template's products in Shopinvader.", + ) + + is_shop_order_mode_enabled_on_variant = fields.Boolean( + string="Shopinvader Order Mode Unabled on Variant", + help="If True, unables product variants to have a different value for" + " 'shop_order_mode' than this product template.", + default=False, + ) diff --git a/shopinvader_product_order_mode/readme/CONTRIBUTORS.rst b/shopinvader_product_order_mode/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..5341bc4906 --- /dev/null +++ b/shopinvader_product_order_mode/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Nicolas DELBOVIER \ No newline at end of file diff --git a/shopinvader_product_order_mode/readme/DESCRIPTION.rst b/shopinvader_product_order_mode/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..21de8412d5 --- /dev/null +++ b/shopinvader_product_order_mode/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +This module is a base for generating generic shop order modes on products \ No newline at end of file diff --git a/shopinvader_product_order_mode/static/description/index.html b/shopinvader_product_order_mode/static/description/index.html new file mode 100644 index 0000000000..49aaeb58e0 --- /dev/null +++ b/shopinvader_product_order_mode/static/description/index.html @@ -0,0 +1,416 @@ + + + + + +Shopinvader Product Order Mode + + + +
    +

    Shopinvader Product Order Mode

    + + +

    Beta License: AGPL-3 shopinvader/odoo-shopinvader

    +

    This module is a base for generating generic shop order modes on products

    +

    Table of contents

    + +
    +

    Bug Tracker

    +

    Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

    +

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

    +
    +
    +

    Credits

    +
    +

    Authors

    +
      +
    • ACSONE SA/NV
    • +
    +
    +
    +

    Contributors

    + +
    +
    +

    Maintainers

    +

    This module is part of the shopinvader/odoo-shopinvader project on GitHub.

    +

    You are welcome to contribute.

    +
    +
    +
    + + diff --git a/shopinvader_product_order_mode/tests/__init__.py b/shopinvader_product_order_mode/tests/__init__.py new file mode 100644 index 0000000000..0ce6d0db23 --- /dev/null +++ b/shopinvader_product_order_mode/tests/__init__.py @@ -0,0 +1 @@ +from . import test_shop_order_mode diff --git a/shopinvader_product_order_mode/tests/test_shop_order_mode.py b/shopinvader_product_order_mode/tests/test_shop_order_mode.py new file mode 100644 index 0000000000..0695dd6813 --- /dev/null +++ b/shopinvader_product_order_mode/tests/test_shop_order_mode.py @@ -0,0 +1,77 @@ +# Copyright 2025 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + + +from odoo import Command +from odoo.exceptions import ValidationError +from odoo.tests.common import TransactionCase + + +class TestShopOrderMode(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + + # Define different attributes to allow to have mulitple products for the same template + cls.test_attribute = cls.env["product.attribute"].create( + {"name": "Test attribute"} + ) + cls.test_attribute_1 = cls.env["product.attribute.value"].create( + {"name": "Test attribute value 1", "attribute_id": cls.test_attribute.id} + ) + cls.test_attribute_2 = cls.env["product.attribute.value"].create( + {"name": "Test attribute value 2", "attribute_id": cls.test_attribute.id} + ) + + cls.template = cls.env["product.template"].create( + { + "name": "Test Product Template", + "attribute_line_ids": [ + Command.create( + { + "attribute_id": cls.test_attribute.id, + "value_ids": [ + Command.link(cls.test_attribute_1.id), + Command.link(cls.test_attribute_2.id), + ], + } + ) + ], + "shop_order_mode": "direct_sale_only", + "is_shop_order_mode_enabled_on_variant": False, + } + ) + + cls.product = cls.template.product_variant_ids[0] + cls.product_2 = cls.template.product_variant_ids[1] + + def test_default_order_mode_on_products(self): + """ + Test that products order mode is initiated to the order mode of the template. + """ + self.assertEqual(self.product.shop_order_mode, self.template.shop_order_mode) + + def test_product_shop_order_mode_constraint(self): + """ + Test that the shop_order_mode field on product.product is not editable + when is_shop_order_mode_enabled_on_variant is False on product.template. + """ + + with self.assertRaises(ValidationError): + self.product.shop_order_mode = None + + self.template.is_shop_order_mode_enabled_on_variant = True + + self.product.shop_order_mode = None + self.assertNotEqual(self.product.shop_order_mode, "direct_sale_only") + self.assertEqual(self.template.shop_order_mode, "direct_sale_only") + + def test_shop_order_mode_reset(self): + """ + Test that the `shop_order_mode` field on product.product is reset to the + value of the product.template when `is_shop_order_mode_enabled_on_variant` is True. + """ + self.template.is_shop_order_mode_enabled_on_variant = True + self.product.shop_order_mode = None + self.template.is_shop_order_mode_enabled_on_variant = False + self.assertEqual(self.product.shop_order_mode, self.template.shop_order_mode) diff --git a/shopinvader_product_order_mode/views/product_product.xml b/shopinvader_product_order_mode/views/product_product.xml new file mode 100644 index 0000000000..d31619f9b3 --- /dev/null +++ b/shopinvader_product_order_mode/views/product_product.xml @@ -0,0 +1,25 @@ + + + + + + product.product + + + + + + + + + + + + diff --git a/shopinvader_product_order_mode/views/product_template.xml b/shopinvader_product_order_mode/views/product_template.xml new file mode 100644 index 0000000000..179ab3cd15 --- /dev/null +++ b/shopinvader_product_order_mode/views/product_template.xml @@ -0,0 +1,21 @@ + + + + + + product.template + + + + + + + + + + + diff --git a/shopinvader_product_order_mode_on_request/README.rst b/shopinvader_product_order_mode_on_request/README.rst new file mode 100644 index 0000000000..88abdc3ec6 --- /dev/null +++ b/shopinvader_product_order_mode_on_request/README.rst @@ -0,0 +1,60 @@ +========================================= +Shopinvader Product Order Mode On Request +========================================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:98b6bb5bcac769474c7ce34ae341ba3ede691e6cdeca6896af9e0b058cb95d85 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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-shopinvader%2Fodoo--shopinvader-lightgray.png?logo=github + :target: https://github.com/shopinvader/odoo-shopinvader/tree/16.0/shopinvader_product_order_mode_on_request + :alt: shopinvader/odoo-shopinvader + +|badge1| |badge2| |badge3| + +This module adds 2 shop order modes for products: "Quotation Only" and "Direct Sale Or Quotation" + +**Table of contents** + +.. contents:: + :local: + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* ACSONE SA/NV + +Contributors +~~~~~~~~~~~~ + +* Nicolas DELBOVIER + +Maintainers +~~~~~~~~~~~ + +This module is part of the `shopinvader/odoo-shopinvader `_ project on GitHub. + +You are welcome to contribute. diff --git a/shopinvader_product_order_mode_on_request/__init__.py b/shopinvader_product_order_mode_on_request/__init__.py new file mode 100644 index 0000000000..0650744f6b --- /dev/null +++ b/shopinvader_product_order_mode_on_request/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/shopinvader_product_order_mode_on_request/__manifest__.py b/shopinvader_product_order_mode_on_request/__manifest__.py new file mode 100644 index 0000000000..53c452551e --- /dev/null +++ b/shopinvader_product_order_mode_on_request/__manifest__.py @@ -0,0 +1,17 @@ +# Copyright 2025 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Shopinvader Product Order Mode On Request", + "summary": """ Add new order modes on products.""", + "version": "16.0.1.0.0", + "license": "AGPL-3", + "author": "ACSONE SA/NV", + "website": "https://github.com/shopinvader/odoo-shopinvader", + "depends": [ + "shopinvader_product_order_mode", + "sale_cart", + ], + "data": [], + "demo": [], +} diff --git a/shopinvader_product_order_mode_on_request/i18n/shopinvader_product_order_mode_on_request.pot b/shopinvader_product_order_mode_on_request/i18n/shopinvader_product_order_mode_on_request.pot new file mode 100644 index 0000000000..88ca915f76 --- /dev/null +++ b/shopinvader_product_order_mode_on_request/i18n/shopinvader_product_order_mode_on_request.pot @@ -0,0 +1,72 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * shopinvader_product_order_mode_on_request +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-05-16 13:41+0000\n" +"PO-Revision-Date: 2025-05-16 13:41+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: shopinvader_product_order_mode_on_request +#: model:ir.model.fields,help:shopinvader_product_order_mode_on_request.field_product_template__shop_order_mode +msgid "Deault order mode for this template's products in Shopinvader." +msgstr "" + +#. module: shopinvader_product_order_mode_on_request +#. odoo-python +#: code:addons/shopinvader_product_order_mode_on_request/models/product_product.py:0 +#: code:addons/shopinvader_product_order_mode_on_request/models/product_template.py:0 +#: model:ir.model.fields.selection,name:shopinvader_product_order_mode_on_request.selection__product_product__shop_order_mode__direct_sale_or_quotation +#: model:ir.model.fields.selection,name:shopinvader_product_order_mode_on_request.selection__product_template__shop_order_mode__direct_sale_or_quotation +#, python-format +msgid "Direct Sale or Quotation" +msgstr "" + +#. module: shopinvader_product_order_mode_on_request +#: model:ir.model.fields,help:shopinvader_product_order_mode_on_request.field_product_product__shop_order_mode +msgid "Order mode for the product in Shopinvader." +msgstr "" + +#. module: shopinvader_product_order_mode_on_request +#: model:ir.model,name:shopinvader_product_order_mode_on_request.model_product_template +msgid "Product" +msgstr "" + +#. module: shopinvader_product_order_mode_on_request +#: model:ir.model,name:shopinvader_product_order_mode_on_request.model_product_product +msgid "Product Variant" +msgstr "" + +#. module: shopinvader_product_order_mode_on_request +#. odoo-python +#: code:addons/shopinvader_product_order_mode_on_request/models/product_product.py:0 +#: code:addons/shopinvader_product_order_mode_on_request/models/product_template.py:0 +#: model:ir.model.fields.selection,name:shopinvader_product_order_mode_on_request.selection__product_product__shop_order_mode__quotation_only +#: model:ir.model.fields.selection,name:shopinvader_product_order_mode_on_request.selection__product_template__shop_order_mode__quotation_only +#, python-format +msgid "Quotation Only" +msgstr "" + +#. module: shopinvader_product_order_mode_on_request +#: model:ir.model,name:shopinvader_product_order_mode_on_request.model_sale_order +msgid "Sales Order" +msgstr "" + +#. module: shopinvader_product_order_mode_on_request +#: model:ir.model.fields,field_description:shopinvader_product_order_mode_on_request.field_sale_order__shop_only_quotation +msgid "Shop Only Quotation" +msgstr "" + +#. module: shopinvader_product_order_mode_on_request +#: model:ir.model.fields,field_description:shopinvader_product_order_mode_on_request.field_product_product__shop_order_mode +#: model:ir.model.fields,field_description:shopinvader_product_order_mode_on_request.field_product_template__shop_order_mode +msgid "Shopinvader Order Mode" +msgstr "" diff --git a/shopinvader_product_order_mode_on_request/models/__init__.py b/shopinvader_product_order_mode_on_request/models/__init__.py new file mode 100644 index 0000000000..de7a683548 --- /dev/null +++ b/shopinvader_product_order_mode_on_request/models/__init__.py @@ -0,0 +1,3 @@ +from . import product_product +from . import product_template +from . import sale_order diff --git a/shopinvader_product_order_mode_on_request/models/product_product.py b/shopinvader_product_order_mode_on_request/models/product_product.py new file mode 100644 index 0000000000..cc890a94ca --- /dev/null +++ b/shopinvader_product_order_mode_on_request/models/product_product.py @@ -0,0 +1,19 @@ +# Copyright 2025 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import _, fields, models + + +class ProductProduct(models.Model): + _inherit = "product.product" + + shop_order_mode = fields.Selection( + selection_add=[ + ("quotation_only", _("Quotation Only")), + ("direct_sale_or_quotation", _("Direct Sale or Quotation")), + ], + ondelete={ + "quotation_only": "set null", + "direct_sale_or_quotation": "set null", + }, + ) diff --git a/shopinvader_product_order_mode_on_request/models/product_template.py b/shopinvader_product_order_mode_on_request/models/product_template.py new file mode 100644 index 0000000000..e96d8ab084 --- /dev/null +++ b/shopinvader_product_order_mode_on_request/models/product_template.py @@ -0,0 +1,19 @@ +# Copyright 2025 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import _, fields, models + + +class ProductTemplate(models.Model): + _inherit = "product.template" + + shop_order_mode = fields.Selection( + selection_add=[ + ("quotation_only", _("Quotation Only")), + ("direct_sale_or_quotation", _("Direct Sale or Quotation")), + ], + ondelete={ + "quotation_only": "set null", + "direct_sale_or_quotation": "set null", + }, + ) diff --git a/shopinvader_product_order_mode_on_request/models/sale_order.py b/shopinvader_product_order_mode_on_request/models/sale_order.py new file mode 100644 index 0000000000..0ffc858c42 --- /dev/null +++ b/shopinvader_product_order_mode_on_request/models/sale_order.py @@ -0,0 +1,21 @@ +# Copyright 2025 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class SaleOrder(models.Model): + _inherit = "sale.order" + + shop_only_quotation = fields.Boolean( + compute="_compute_shop_only_quotation", help="" + ) + + @api.depends("order_line.product_id.shop_order_mode") + def _compute_shop_only_quotation(self): + for record in self: + record.shop_only_quotation = any( + record.order_line.product_id.filtered( + lambda p: p.shop_order_mode == "quotation_only" + ) + ) diff --git a/shopinvader_product_order_mode_on_request/readme/CONTRIBUTORS.rst b/shopinvader_product_order_mode_on_request/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..5341bc4906 --- /dev/null +++ b/shopinvader_product_order_mode_on_request/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Nicolas DELBOVIER \ No newline at end of file diff --git a/shopinvader_product_order_mode_on_request/readme/DESCRIPTION.rst b/shopinvader_product_order_mode_on_request/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..0b4373ac01 --- /dev/null +++ b/shopinvader_product_order_mode_on_request/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +This module adds 2 shop order modes for products: "Quotation Only" and "Direct Sale Or Quotation" \ No newline at end of file diff --git a/shopinvader_product_order_mode_on_request/static/description/index.html b/shopinvader_product_order_mode_on_request/static/description/index.html new file mode 100644 index 0000000000..60fbaefd81 --- /dev/null +++ b/shopinvader_product_order_mode_on_request/static/description/index.html @@ -0,0 +1,416 @@ + + + + + +Shopinvader Product Order Mode On Request + + + +
    +

    Shopinvader Product Order Mode On Request

    + + +

    Beta License: AGPL-3 shopinvader/odoo-shopinvader

    +

    This module adds 2 shop order modes for products: “Quotation Only” and “Direct Sale Or Quotation”

    +

    Table of contents

    + +
    +

    Bug Tracker

    +

    Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

    +

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

    +
    +
    +

    Credits

    +
    +

    Authors

    +
      +
    • ACSONE SA/NV
    • +
    +
    +
    +

    Contributors

    + +
    +
    +

    Maintainers

    +

    This module is part of the shopinvader/odoo-shopinvader project on GitHub.

    +

    You are welcome to contribute.

    +
    +
    +
    + + diff --git a/shopinvader_product_order_mode_on_request/tests/__init__.py b/shopinvader_product_order_mode_on_request/tests/__init__.py new file mode 100644 index 0000000000..43ae815229 --- /dev/null +++ b/shopinvader_product_order_mode_on_request/tests/__init__.py @@ -0,0 +1 @@ +from . import test_quotation diff --git a/shopinvader_product_order_mode_on_request/tests/test_quotation.py b/shopinvader_product_order_mode_on_request/tests/test_quotation.py new file mode 100644 index 0000000000..a3747d66d9 --- /dev/null +++ b/shopinvader_product_order_mode_on_request/tests/test_quotation.py @@ -0,0 +1,102 @@ +# Copyright 2025 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import Command +from odoo.tests.common import TransactionCase + + +class TestQuotation(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + + # as the shop_order_mode is readonly by default except if unabled in the template + # we need to create the product template first and then get the product variant + cls.product_quotation_only = ( + cls.env["product.template"] + .create( + {"name": "Quotation only Product", "shop_order_mode": "quotation_only"} + ) + .product_variant_id + ) + + cls.product_direct_sale_only = ( + cls.env["product.template"] + .create( + { + "name": "Direct sale only Product", + "shop_order_mode": "direct_sale_only", + } + ) + .product_variant_id + ) + + cls.product_direct_sale_or_quotation = ( + cls.env["product.template"] + .create( + { + "name": "Direct Sale Or Quotation Product", + "shop_order_mode": "direct_sale_or_quotation", + } + ) + .product_variant_id + ) + + cls.partner = cls.env["res.partner"].create( + { + "name": "Test Partner", + } + ) + + def test_sale_order_shop_only_quotation(self): + so = self.env["sale.order"].create( + { + "name": "Sale order that should be quotation only", + "partner_id": self.partner.id, + "order_line": [ + Command.create( + { + "product_id": self.product_quotation_only.id, + "product_uom_qty": 1, + } + ), + Command.create( + { + "product_id": self.product_direct_sale_only.id, + "product_uom_qty": 1, + } + ), + Command.create( + { + "product_id": self.product_direct_sale_or_quotation.id, + "product_uom_qty": 1, + } + ), + ], + } + ) + + self.assertTrue(so.shop_only_quotation) + + so_2 = self.env["sale.order"].create( + { + "name": "Sale order that should NOT be quotation only", + "partner_id": self.partner.id, + "order_line": [ + Command.create( + { + "product_id": self.product_direct_sale_only.id, + "product_uom_qty": 1, + } + ), + Command.create( + { + "product_id": self.product_direct_sale_or_quotation.id, + "product_uom_qty": 1, + } + ), + ], + } + ) + + self.assertFalse(so_2.shop_only_quotation)