diff --git a/website_sale_secondary_unit/README.rst b/website_sale_secondary_unit/README.rst new file mode 100644 index 0000000000..5875bb8e45 --- /dev/null +++ b/website_sale_secondary_unit/README.rst @@ -0,0 +1,103 @@ +=========================== +Website Sale Secondary Unit +=========================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:b22305a090cabad4fd64b941824c90c602eec985747acf0fc3039558a4b22968 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Fe--commerce-lightgray.png?logo=github + :target: https://github.com/OCA/e-commerce/tree/17.0/website_sale_secondary_unit + :alt: OCA/e-commerce +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/e-commerce-17-0/e-commerce-17-0-website_sale_secondary_unit + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/e-commerce&target_branch=17.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module extends the functionality of saleorder_secondary_unit module +to allow sell products in online store in secondary units defined. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +For define the secondary units, you should active *Manage multiples +units of measure* on the user that will be responsable of this function. + +Usage +===== + +To use this module you need to: + +- Go to *'Website > Products > Products'*. +- Select a template. +- Set the secondary units that you need. +- Go to Website Shop and buy this product, you will see a selectable + option with all secondary units defined in the product and visible in + website. +- If you do not want to sell in a base product unit and only allow sell + in a secondary unit you can disable the option *'Allow to sell in unit + of measure'* in a product sale tab. + +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 +------- + +* Tecnativa + +Contributors +------------ + +- `Tecnativa `__: + + - Sergio Teruel + - Carlos Roca + - Pilar Vargas + - Carlos Lopez + +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/e-commerce `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/website_sale_secondary_unit/__init__.py b/website_sale_secondary_unit/__init__.py new file mode 100644 index 0000000000..b754b9b624 --- /dev/null +++ b/website_sale_secondary_unit/__init__.py @@ -0,0 +1,4 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from . import controllers +from . import models +from .hooks import post_init_hook diff --git a/website_sale_secondary_unit/__manifest__.py b/website_sale_secondary_unit/__manifest__.py new file mode 100644 index 0000000000..2769c9bf30 --- /dev/null +++ b/website_sale_secondary_unit/__manifest__.py @@ -0,0 +1,33 @@ +# Copyright 2019 Tecnativa - Sergio Teruel +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +{ + "name": "Website Sale Secondary Unit", + "summary": "Allow manage secondary units in website shop", + "version": "17.0.1.0.0", + "development_status": "Beta", + "category": "Website", + "website": "https://github.com/OCA/e-commerce", + "author": "Tecnativa, Odoo Community Association (OCA)", + "license": "AGPL-3", + "application": False, + "installable": True, + "depends": ["website_sale", "sale_order_secondary_unit"], + "data": [ + "security/ir.model.access.csv", + "security/website_sale_secondary_unit.xml", + "views/product_secondary_unit_views.xml", + "views/product_template_views.xml", + "views/templates.xml", + ], + "demo": ["data/demo.xml"], + "post_init_hook": "post_init_hook", + "assets": { + "web.assets_frontend": [ + "/website_sale_secondary_unit/static/src/js/**/*.esm.js", + "/website_sale_secondary_unit/static/src/scss/website_sale_secondary_unit.scss", + ], + "web.assets_tests": [ + "/website_sale_secondary_unit/static/tests/tours/website_sale_secondary_unit_tour.esm.js" + ], + }, +} diff --git a/website_sale_secondary_unit/controllers/__init__.py b/website_sale_secondary_unit/controllers/__init__.py new file mode 100644 index 0000000000..dea930adca --- /dev/null +++ b/website_sale_secondary_unit/controllers/__init__.py @@ -0,0 +1,2 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from . import main diff --git a/website_sale_secondary_unit/controllers/main.py b/website_sale_secondary_unit/controllers/main.py new file mode 100644 index 0000000000..372c99cb29 --- /dev/null +++ b/website_sale_secondary_unit/controllers/main.py @@ -0,0 +1,69 @@ +# Copyright 2019 Tecnativa - Sergio Teruel +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import http +from odoo.http import request + +from odoo.addons.website_sale.controllers.main import WebsiteSale + + +class WebsiteSaleSecondaryUnit(WebsiteSale): + @http.route() + def cart_update(self, product_id, add_qty=1, set_qty=0, **kw): + # Add secondary uom info to session + request.session.pop("secondary_uom_id", None) + if kw.get("secondary_uom_id"): + secondary_uom = request.env["product.secondary.unit"].browse( + int(kw["secondary_uom_id"]) + ) + request.session["secondary_uom_id"] = secondary_uom.id + return super().cart_update(product_id, add_qty=add_qty, set_qty=set_qty, **kw) + + @http.route() + def cart_update_json( + self, product_id, line_id=None, add_qty=None, set_qty=None, display=True, **kw + ): + so_line = request.env["sale.order.line"].browse(line_id) + request.session.pop("secondary_uom_id", None) + if kw.get("secondary_uom_id"): + secondary_uom = request.env["product.secondary.unit"].browse( + int(kw["secondary_uom_id"]) + ) + request.session["secondary_uom_id"] = secondary_uom.id + if so_line.sudo().secondary_uom_id: + request.session["secondary_uom_id"] = so_line.sudo().secondary_uom_id.id + return super().cart_update_json( + product_id, + line_id=line_id, + add_qty=add_qty, + set_qty=set_qty, + display=display, + **kw, + ) + + def _prepare_product_values(self, product, category, search, **kwargs): + res = super()._prepare_product_values(product, category, search, **kwargs) + res["secondary_uom_ids"] = product.secondary_uom_ids.filtered( + lambda su: su.active and su.is_published + ) + return res + + def _get_cart_notification_information(self, order, line_ids): + res = super()._get_cart_notification_information(order, line_ids) + for line in res.get("lines", []): + sale_line = request.env["sale.order.line"].browse(line["id"]) + line["secondary_uom_name"] = "" + line["secondary_uom_qty"] = sale_line.secondary_uom_qty + secondary_uom = sale_line.secondary_uom_id + if not secondary_uom: + continue + factor = ( + int(secondary_uom.factor) == secondary_uom.factor + and int(secondary_uom.factor) + or secondary_uom.factor + ) + uom_name = secondary_uom.product_tmpl_id.sudo().uom_id.name + secondary_uom_name = f"{secondary_uom.name} {factor}" + if uom_name != secondary_uom.name: + secondary_uom_name += f" {uom_name}" + line["secondary_uom_name"] = secondary_uom_name + return res diff --git a/website_sale_secondary_unit/data/demo.xml b/website_sale_secondary_unit/data/demo.xml new file mode 100644 index 0000000000..bb7d01c47f --- /dev/null +++ b/website_sale_secondary_unit/data/demo.xml @@ -0,0 +1,31 @@ + + + + Box + + 5.0 + + True + + + Box + + 10.0 + + True + + + + + + diff --git a/website_sale_secondary_unit/hooks.py b/website_sale_secondary_unit/hooks.py new file mode 100644 index 0000000000..d02e3052f7 --- /dev/null +++ b/website_sale_secondary_unit/hooks.py @@ -0,0 +1,16 @@ +# Copyright 2019 Tecnativa - Sergio Teruel +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + + +def post_init_hook(env): + """ + At installation time, set allow_uom_sell field as true for all products + that have already been created. + """ + env.cr.execute( + """ + UPDATE product_template + SET allow_uom_sell=true + WHERE allow_uom_sell IS NULL; + """ + ) diff --git a/website_sale_secondary_unit/i18n/ca.po b/website_sale_secondary_unit/i18n/ca.po new file mode 100644 index 0000000000..a2790b9122 --- /dev/null +++ b/website_sale_secondary_unit/i18n/ca.po @@ -0,0 +1,102 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * website_sale_secondary_unit +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2021-01-20 20:44+0000\n" +"Last-Translator: claudiagn \n" +"Language-Team: none\n" +"Language: ca\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: website_sale_secondary_unit +#: model_terms:ir.ui.view,arch_db:website_sale_secondary_unit.cart_lines +#: model_terms:ir.ui.view,arch_db:website_sale_secondary_unit.secondary_qty +msgid "Add one" +msgstr "Afegir un" + +#. module: website_sale_secondary_unit +#: model:ir.model.fields,field_description:website_sale_secondary_unit.field_product_product__allow_uom_sell +#: model:ir.model.fields,field_description:website_sale_secondary_unit.field_product_template__allow_uom_sell +msgid "Allow to sell in unit of measure" +msgstr "Permetre vendre en una unitat de mesura" + +#. module: website_sale_secondary_unit +#: model:product.secondary.unit,name:website_sale_secondary_unit.secondary_unit_box_10 +#: model:product.secondary.unit,name:website_sale_secondary_unit.secondary_unit_box_5 +msgid "Box" +msgstr "" + +#. module: website_sale_secondary_unit +#: model:ir.model.fields,field_description:website_sale_secondary_unit.field_product_secondary_unit__can_publish +msgid "Can Publish" +msgstr "" + +#. module: website_sale_secondary_unit +#: model:ir.model.fields,field_description:website_sale_secondary_unit.field_product_secondary_unit__is_published +msgid "Is Published" +msgstr "" + +#. module: website_sale_secondary_unit +#: model:ir.model,name:website_sale_secondary_unit.model_product_secondary_unit +msgid "Product Secondary Unit" +msgstr "Unitat secundària de producte" + +#. module: website_sale_secondary_unit +#: model:ir.model,name:website_sale_secondary_unit.model_product_template +msgid "Product Template" +msgstr "Plantilla de producte" + +#. module: website_sale_secondary_unit +#: model_terms:ir.ui.view,arch_db:website_sale_secondary_unit.cart_popover +msgid "Qty:" +msgstr "Quantitat:" + +#. module: website_sale_secondary_unit +#: model_terms:ir.ui.view,arch_db:website_sale_secondary_unit.cart_lines +msgid "Quantity" +msgstr "Quantitat" + +#. module: website_sale_secondary_unit +#: model_terms:ir.ui.view,arch_db:website_sale_secondary_unit.cart_lines +#: model_terms:ir.ui.view,arch_db:website_sale_secondary_unit.secondary_qty +msgid "Remove one" +msgstr "Elimina un" + +#. module: website_sale_secondary_unit +#: model:ir.model,name:website_sale_secondary_unit.model_sale_order +msgid "Sales Order" +msgstr "" + +#. module: website_sale_secondary_unit +#: model:ir.model,name:website_sale_secondary_unit.model_sale_order_line +msgid "Sales Order Line" +msgstr "Línia de comanda de venda" + +#. module: website_sale_secondary_unit +#: model:ir.model.fields,help:website_sale_secondary_unit.field_product_secondary_unit__website_url +msgid "The full URL to access the document through the website." +msgstr "" + +#. module: website_sale_secondary_unit +#: model:ir.model.fields,field_description:website_sale_secondary_unit.field_product_secondary_unit__website_published +msgid "Visible on current website" +msgstr "" + +#. module: website_sale_secondary_unit +#: model:ir.model.fields,field_description:website_sale_secondary_unit.field_product_secondary_unit__website_url +msgid "Website URL" +msgstr "" + +#~ msgid "Is published" +#~ msgstr "Està publicat" + +#~ msgid "Sale Order" +#~ msgstr "Comanda de venda" diff --git a/website_sale_secondary_unit/i18n/de.po b/website_sale_secondary_unit/i18n/de.po new file mode 100644 index 0000000000..e21be4907c --- /dev/null +++ b/website_sale_secondary_unit/i18n/de.po @@ -0,0 +1,102 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * website_sale_secondary_unit +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2020-08-11 17:59+0000\n" +"Last-Translator: André Volksdorf \n" +"Language-Team: none\n" +"Language: de\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 3.10\n" + +#. module: website_sale_secondary_unit +#: model_terms:ir.ui.view,arch_db:website_sale_secondary_unit.cart_lines +#: model_terms:ir.ui.view,arch_db:website_sale_secondary_unit.secondary_qty +msgid "Add one" +msgstr "Hinzufügen" + +#. module: website_sale_secondary_unit +#: model:ir.model.fields,field_description:website_sale_secondary_unit.field_product_product__allow_uom_sell +#: model:ir.model.fields,field_description:website_sale_secondary_unit.field_product_template__allow_uom_sell +msgid "Allow to sell in unit of measure" +msgstr "Verkauf in Mengeneinheit erlauben" + +#. module: website_sale_secondary_unit +#: model:product.secondary.unit,name:website_sale_secondary_unit.secondary_unit_box_10 +#: model:product.secondary.unit,name:website_sale_secondary_unit.secondary_unit_box_5 +msgid "Box" +msgstr "" + +#. module: website_sale_secondary_unit +#: model:ir.model.fields,field_description:website_sale_secondary_unit.field_product_secondary_unit__can_publish +msgid "Can Publish" +msgstr "" + +#. module: website_sale_secondary_unit +#: model:ir.model.fields,field_description:website_sale_secondary_unit.field_product_secondary_unit__is_published +msgid "Is Published" +msgstr "" + +#. module: website_sale_secondary_unit +#: model:ir.model,name:website_sale_secondary_unit.model_product_secondary_unit +msgid "Product Secondary Unit" +msgstr "Sekundäre Produkt-Einheit" + +#. module: website_sale_secondary_unit +#: model:ir.model,name:website_sale_secondary_unit.model_product_template +msgid "Product Template" +msgstr "Produkt-Vorlage" + +#. module: website_sale_secondary_unit +#: model_terms:ir.ui.view,arch_db:website_sale_secondary_unit.cart_popover +msgid "Qty:" +msgstr "Menge:" + +#. module: website_sale_secondary_unit +#: model_terms:ir.ui.view,arch_db:website_sale_secondary_unit.cart_lines +msgid "Quantity" +msgstr "Menge" + +#. module: website_sale_secondary_unit +#: model_terms:ir.ui.view,arch_db:website_sale_secondary_unit.cart_lines +#: model_terms:ir.ui.view,arch_db:website_sale_secondary_unit.secondary_qty +msgid "Remove one" +msgstr "Entfernen" + +#. module: website_sale_secondary_unit +#: model:ir.model,name:website_sale_secondary_unit.model_sale_order +msgid "Sales Order" +msgstr "" + +#. module: website_sale_secondary_unit +#: model:ir.model,name:website_sale_secondary_unit.model_sale_order_line +msgid "Sales Order Line" +msgstr "Auftragsposition" + +#. module: website_sale_secondary_unit +#: model:ir.model.fields,help:website_sale_secondary_unit.field_product_secondary_unit__website_url +msgid "The full URL to access the document through the website." +msgstr "" + +#. module: website_sale_secondary_unit +#: model:ir.model.fields,field_description:website_sale_secondary_unit.field_product_secondary_unit__website_published +msgid "Visible on current website" +msgstr "" + +#. module: website_sale_secondary_unit +#: model:ir.model.fields,field_description:website_sale_secondary_unit.field_product_secondary_unit__website_url +msgid "Website URL" +msgstr "" + +#~ msgid "Is published" +#~ msgstr "Veröffentlicht" + +#~ msgid "Sale Order" +#~ msgstr "Auftrag" diff --git a/website_sale_secondary_unit/i18n/es.po b/website_sale_secondary_unit/i18n/es.po new file mode 100644 index 0000000000..fac21c18c2 --- /dev/null +++ b/website_sale_secondary_unit/i18n/es.po @@ -0,0 +1,112 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * website_sale_secondary_unit +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 11.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-03-13 16:54+0000\n" +"PO-Revision-Date: 2023-10-09 07:57+0000\n" +"Last-Translator: Ivorra78 \n" +"Language-Team: \n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.17\n" + +#. module: website_sale_secondary_unit +#: model_terms:ir.ui.view,arch_db:website_sale_secondary_unit.cart_lines +#: model_terms:ir.ui.view,arch_db:website_sale_secondary_unit.secondary_qty +msgid "Add one" +msgstr "Añadir uno" + +#. module: website_sale_secondary_unit +#: model:ir.model.fields,field_description:website_sale_secondary_unit.field_product_product__allow_uom_sell +#: model:ir.model.fields,field_description:website_sale_secondary_unit.field_product_template__allow_uom_sell +msgid "Allow to sell in unit of measure" +msgstr "Permitir vender en la unidad de medida" + +#. module: website_sale_secondary_unit +#: model:product.secondary.unit,name:website_sale_secondary_unit.secondary_unit_box_10 +#: model:product.secondary.unit,name:website_sale_secondary_unit.secondary_unit_box_5 +msgid "Box" +msgstr "Caja" + +#. module: website_sale_secondary_unit +#: model:ir.model.fields,field_description:website_sale_secondary_unit.field_product_secondary_unit__can_publish +msgid "Can Publish" +msgstr "Se puede publicar" + +#. module: website_sale_secondary_unit +#: model:ir.model.fields,field_description:website_sale_secondary_unit.field_product_secondary_unit__is_published +msgid "Is Published" +msgstr "Está publicado" + +#. module: website_sale_secondary_unit +#: model:ir.model,name:website_sale_secondary_unit.model_product_secondary_unit +msgid "Product Secondary Unit" +msgstr "Segunda unidad de producto" + +#. module: website_sale_secondary_unit +#: model:ir.model,name:website_sale_secondary_unit.model_product_template +msgid "Product Template" +msgstr "Plantilla de producto" + +#. module: website_sale_secondary_unit +#: model_terms:ir.ui.view,arch_db:website_sale_secondary_unit.cart_popover +msgid "Qty:" +msgstr "Cantidad:" + +#. module: website_sale_secondary_unit +#: model_terms:ir.ui.view,arch_db:website_sale_secondary_unit.cart_lines +msgid "Quantity" +msgstr "Cantidad" + +#. module: website_sale_secondary_unit +#: model_terms:ir.ui.view,arch_db:website_sale_secondary_unit.cart_lines +#: model_terms:ir.ui.view,arch_db:website_sale_secondary_unit.secondary_qty +msgid "Remove one" +msgstr "Quitar uno" + +#. module: website_sale_secondary_unit +#: model:ir.model,name:website_sale_secondary_unit.model_sale_order +msgid "Sales Order" +msgstr "Orden de venta" + +#. module: website_sale_secondary_unit +#: model:ir.model,name:website_sale_secondary_unit.model_sale_order_line +msgid "Sales Order Line" +msgstr "Línea de pedido" + +#. module: website_sale_secondary_unit +#: model:ir.model.fields,help:website_sale_secondary_unit.field_product_secondary_unit__website_url +msgid "The full URL to access the document through the website." +msgstr "La URL completa para acceder al documento a través del sitio web." + +#. module: website_sale_secondary_unit +#: model:ir.model.fields,field_description:website_sale_secondary_unit.field_product_secondary_unit__website_published +msgid "Visible on current website" +msgstr "Visible en la página web actual" + +#. module: website_sale_secondary_unit +#: model:ir.model.fields,field_description:website_sale_secondary_unit.field_product_secondary_unit__website_url +msgid "Website URL" +msgstr "URL de la página web" + +#~ msgid "Allowing sale_order_type to work with website_sale." +#~ msgstr "Permitir que sale_order_type funcione con website_sale." + +#~ msgid "Is published" +#~ msgstr "Está publicado" + +#~ msgid "Sale Order" +#~ msgstr "Pedido de venta" + +#~ msgid "Quotation" +#~ msgstr "Presupuesto" + +#~ msgid "Visible in Website" +#~ msgstr "Visible en website" diff --git a/website_sale_secondary_unit/i18n/fr.po b/website_sale_secondary_unit/i18n/fr.po new file mode 100644 index 0000000000..2534b953e3 --- /dev/null +++ b/website_sale_secondary_unit/i18n/fr.po @@ -0,0 +1,102 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * website_sale_secondary_unit +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2020-11-23 15:36+0000\n" +"Last-Translator: Yann Papouin \n" +"Language-Team: none\n" +"Language: fr\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 3.10\n" + +#. module: website_sale_secondary_unit +#: model_terms:ir.ui.view,arch_db:website_sale_secondary_unit.cart_lines +#: model_terms:ir.ui.view,arch_db:website_sale_secondary_unit.secondary_qty +msgid "Add one" +msgstr "Ajouter" + +#. module: website_sale_secondary_unit +#: model:ir.model.fields,field_description:website_sale_secondary_unit.field_product_product__allow_uom_sell +#: model:ir.model.fields,field_description:website_sale_secondary_unit.field_product_template__allow_uom_sell +msgid "Allow to sell in unit of measure" +msgstr "Autorise la vente dans l'unité de mesure par défaut" + +#. module: website_sale_secondary_unit +#: model:product.secondary.unit,name:website_sale_secondary_unit.secondary_unit_box_10 +#: model:product.secondary.unit,name:website_sale_secondary_unit.secondary_unit_box_5 +msgid "Box" +msgstr "" + +#. module: website_sale_secondary_unit +#: model:ir.model.fields,field_description:website_sale_secondary_unit.field_product_secondary_unit__can_publish +msgid "Can Publish" +msgstr "" + +#. module: website_sale_secondary_unit +#: model:ir.model.fields,field_description:website_sale_secondary_unit.field_product_secondary_unit__is_published +msgid "Is Published" +msgstr "" + +#. module: website_sale_secondary_unit +#: model:ir.model,name:website_sale_secondary_unit.model_product_secondary_unit +msgid "Product Secondary Unit" +msgstr "Unité de mesure secondaire" + +#. module: website_sale_secondary_unit +#: model:ir.model,name:website_sale_secondary_unit.model_product_template +msgid "Product Template" +msgstr "Modèle d'article" + +#. module: website_sale_secondary_unit +#: model_terms:ir.ui.view,arch_db:website_sale_secondary_unit.cart_popover +msgid "Qty:" +msgstr "Qté :" + +#. module: website_sale_secondary_unit +#: model_terms:ir.ui.view,arch_db:website_sale_secondary_unit.cart_lines +msgid "Quantity" +msgstr "Quantité" + +#. module: website_sale_secondary_unit +#: model_terms:ir.ui.view,arch_db:website_sale_secondary_unit.cart_lines +#: model_terms:ir.ui.view,arch_db:website_sale_secondary_unit.secondary_qty +msgid "Remove one" +msgstr "Supprimer" + +#. module: website_sale_secondary_unit +#: model:ir.model,name:website_sale_secondary_unit.model_sale_order +msgid "Sales Order" +msgstr "" + +#. module: website_sale_secondary_unit +#: model:ir.model,name:website_sale_secondary_unit.model_sale_order_line +msgid "Sales Order Line" +msgstr "Ligne de commande de vente" + +#. module: website_sale_secondary_unit +#: model:ir.model.fields,help:website_sale_secondary_unit.field_product_secondary_unit__website_url +msgid "The full URL to access the document through the website." +msgstr "" + +#. module: website_sale_secondary_unit +#: model:ir.model.fields,field_description:website_sale_secondary_unit.field_product_secondary_unit__website_published +msgid "Visible on current website" +msgstr "" + +#. module: website_sale_secondary_unit +#: model:ir.model.fields,field_description:website_sale_secondary_unit.field_product_secondary_unit__website_url +msgid "Website URL" +msgstr "" + +#~ msgid "Is published" +#~ msgstr "Est publié" + +#~ msgid "Sale Order" +#~ msgstr "Commande client" diff --git a/website_sale_secondary_unit/i18n/it.po b/website_sale_secondary_unit/i18n/it.po new file mode 100644 index 0000000000..6fc537b83a --- /dev/null +++ b/website_sale_secondary_unit/i18n/it.po @@ -0,0 +1,105 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * website_sale_secondary_unit +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2023-10-31 14:39+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.17\n" + +#. module: website_sale_secondary_unit +#: model_terms:ir.ui.view,arch_db:website_sale_secondary_unit.cart_lines +#: model_terms:ir.ui.view,arch_db:website_sale_secondary_unit.secondary_qty +msgid "Add one" +msgstr "Aggiungi uno" + +#. module: website_sale_secondary_unit +#: model:ir.model.fields,field_description:website_sale_secondary_unit.field_product_product__allow_uom_sell +#: model:ir.model.fields,field_description:website_sale_secondary_unit.field_product_template__allow_uom_sell +msgid "Allow to sell in unit of measure" +msgstr "Permetti vendita in unità di misura base" + +#. module: website_sale_secondary_unit +#: model:product.secondary.unit,name:website_sale_secondary_unit.secondary_unit_box_10 +#: model:product.secondary.unit,name:website_sale_secondary_unit.secondary_unit_box_5 +msgid "Box" +msgstr "Cartone" + +#. module: website_sale_secondary_unit +#: model:ir.model.fields,field_description:website_sale_secondary_unit.field_product_secondary_unit__can_publish +msgid "Can Publish" +msgstr "Può pubblicare" + +#. module: website_sale_secondary_unit +#: model:ir.model.fields,field_description:website_sale_secondary_unit.field_product_secondary_unit__is_published +msgid "Is Published" +msgstr "È pubblicato" + +#. module: website_sale_secondary_unit +#: model:ir.model,name:website_sale_secondary_unit.model_product_secondary_unit +msgid "Product Secondary Unit" +msgstr "Unità di misura secondaria del prodotto" + +#. module: website_sale_secondary_unit +#: model:ir.model,name:website_sale_secondary_unit.model_product_template +msgid "Product Template" +msgstr "Modello prodotto" + +#. module: website_sale_secondary_unit +#: model_terms:ir.ui.view,arch_db:website_sale_secondary_unit.cart_popover +msgid "Qty:" +msgstr "Q.tà:" + +#. module: website_sale_secondary_unit +#: model_terms:ir.ui.view,arch_db:website_sale_secondary_unit.cart_lines +msgid "Quantity" +msgstr "Quantità" + +#. module: website_sale_secondary_unit +#: model_terms:ir.ui.view,arch_db:website_sale_secondary_unit.cart_lines +#: model_terms:ir.ui.view,arch_db:website_sale_secondary_unit.secondary_qty +msgid "Remove one" +msgstr "Rimuovi uno" + +#. module: website_sale_secondary_unit +#: model:ir.model,name:website_sale_secondary_unit.model_sale_order +msgid "Sales Order" +msgstr "Ordine di vendita" + +#. module: website_sale_secondary_unit +#: model:ir.model,name:website_sale_secondary_unit.model_sale_order_line +msgid "Sales Order Line" +msgstr "Riga ordine di vendita" + +#. module: website_sale_secondary_unit +#: model:ir.model.fields,help:website_sale_secondary_unit.field_product_secondary_unit__website_url +msgid "The full URL to access the document through the website." +msgstr "L'URL completo per accedere al documento attraverso il sito web." + +#. module: website_sale_secondary_unit +#: model:ir.model.fields,field_description:website_sale_secondary_unit.field_product_secondary_unit__website_published +msgid "Visible on current website" +msgstr "Visibile sul sito web attuale" + +#. module: website_sale_secondary_unit +#: model:ir.model.fields,field_description:website_sale_secondary_unit.field_product_secondary_unit__website_url +msgid "Website URL" +msgstr "URL sito web" + +#~ msgid "Allowing sale_order_type to work with website_sale." +#~ msgstr "Permette a sale_order_type di funzionare con website_sale." + +#~ msgid "Is published" +#~ msgstr "E' pubblicato" + +#~ msgid "Sale Order" +#~ msgstr "Ordine di Vendita" diff --git a/website_sale_secondary_unit/i18n/website_sale_secondary_unit.pot b/website_sale_secondary_unit/i18n/website_sale_secondary_unit.pot new file mode 100644 index 0000000000..d2f6c60fda --- /dev/null +++ b/website_sale_secondary_unit/i18n/website_sale_secondary_unit.pot @@ -0,0 +1,93 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * website_sale_secondary_unit +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.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: website_sale_secondary_unit +#: model_terms:ir.ui.view,arch_db:website_sale_secondary_unit.cart_lines +#: model_terms:ir.ui.view,arch_db:website_sale_secondary_unit.secondary_qty +msgid "Add one" +msgstr "" + +#. module: website_sale_secondary_unit +#: model:ir.model.fields,field_description:website_sale_secondary_unit.field_product_product__allow_uom_sell +#: model:ir.model.fields,field_description:website_sale_secondary_unit.field_product_template__allow_uom_sell +msgid "Allow to sell in unit of measure" +msgstr "" + +#. module: website_sale_secondary_unit +#: model:product.secondary.unit,name:website_sale_secondary_unit.secondary_unit_box_10 +#: model:product.secondary.unit,name:website_sale_secondary_unit.secondary_unit_box_5 +msgid "Box" +msgstr "" + +#. module: website_sale_secondary_unit +#: model:ir.model.fields,field_description:website_sale_secondary_unit.field_product_secondary_unit__can_publish +msgid "Can Publish" +msgstr "" + +#. module: website_sale_secondary_unit +#: model:ir.model.fields,field_description:website_sale_secondary_unit.field_product_secondary_unit__is_published +msgid "Is Published" +msgstr "" + +#. module: website_sale_secondary_unit +#: model:ir.model,name:website_sale_secondary_unit.model_product_secondary_unit +msgid "Product Secondary Unit" +msgstr "" + +#. module: website_sale_secondary_unit +#: model:ir.model,name:website_sale_secondary_unit.model_product_template +msgid "Product Template" +msgstr "" + +#. module: website_sale_secondary_unit +#: model_terms:ir.ui.view,arch_db:website_sale_secondary_unit.cart_popover +msgid "Qty:" +msgstr "" + +#. module: website_sale_secondary_unit +#: model_terms:ir.ui.view,arch_db:website_sale_secondary_unit.cart_lines +msgid "Quantity" +msgstr "" + +#. module: website_sale_secondary_unit +#: model_terms:ir.ui.view,arch_db:website_sale_secondary_unit.cart_lines +#: model_terms:ir.ui.view,arch_db:website_sale_secondary_unit.secondary_qty +msgid "Remove one" +msgstr "" + +#. module: website_sale_secondary_unit +#: model:ir.model,name:website_sale_secondary_unit.model_sale_order +msgid "Sales Order" +msgstr "" + +#. module: website_sale_secondary_unit +#: model:ir.model,name:website_sale_secondary_unit.model_sale_order_line +msgid "Sales Order Line" +msgstr "" + +#. module: website_sale_secondary_unit +#: model:ir.model.fields,help:website_sale_secondary_unit.field_product_secondary_unit__website_url +msgid "The full URL to access the document through the website." +msgstr "" + +#. module: website_sale_secondary_unit +#: model:ir.model.fields,field_description:website_sale_secondary_unit.field_product_secondary_unit__website_published +msgid "Visible on current website" +msgstr "" + +#. module: website_sale_secondary_unit +#: model:ir.model.fields,field_description:website_sale_secondary_unit.field_product_secondary_unit__website_url +msgid "Website URL" +msgstr "" diff --git a/website_sale_secondary_unit/models/__init__.py b/website_sale_secondary_unit/models/__init__.py new file mode 100644 index 0000000000..b4c2be4f9d --- /dev/null +++ b/website_sale_secondary_unit/models/__init__.py @@ -0,0 +1,4 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from . import product_template +from . import product_secondary_unit +from . import sale_order diff --git a/website_sale_secondary_unit/models/product_secondary_unit.py b/website_sale_secondary_unit/models/product_secondary_unit.py new file mode 100644 index 0000000000..9534704257 --- /dev/null +++ b/website_sale_secondary_unit/models/product_secondary_unit.py @@ -0,0 +1,10 @@ +# Copyright 2019 Tecnativa - Sergio Teruel +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class ProductSecondaryUnit(models.Model): + _inherit = ["product.secondary.unit", "website.published.mixin"] + _name = "product.secondary.unit" + + is_published = fields.Boolean(default=True) diff --git a/website_sale_secondary_unit/models/product_template.py b/website_sale_secondary_unit/models/product_template.py new file mode 100644 index 0000000000..777ea1aa06 --- /dev/null +++ b/website_sale_secondary_unit/models/product_template.py @@ -0,0 +1,30 @@ +# Copyright 2019 Tecnativa - Sergio Teruel +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class ProductTemplate(models.Model): + _inherit = "product.template" + + allow_uom_sell = fields.Boolean( + string="Allow to sell in unit of measure", + default=True, + ) + + def _get_combination_info( + self, + combination=False, + product_id=False, + add_qty=1, + parent_combination=False, + only_template=False, + ): + combination_info = super()._get_combination_info( + combination=combination, + product_id=product_id, + add_qty=add_qty, + parent_combination=parent_combination, + only_template=only_template, + ) + combination_info.update({"has_secondary_uom": bool(self.secondary_uom_ids)}) + return combination_info diff --git a/website_sale_secondary_unit/models/sale_order.py b/website_sale_secondary_unit/models/sale_order.py new file mode 100644 index 0000000000..8f7f428053 --- /dev/null +++ b/website_sale_secondary_unit/models/sale_order.py @@ -0,0 +1,157 @@ +# Copyright 2019 Tecnativa - Sergio Teruel +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import api, models +from odoo.http import request +from odoo.tools.float_utils import float_round + + +class SaleOrder(models.Model): + _inherit = "sale.order" + + def _cart_find_product_line(self, product_id=None, line_id=None, **kwargs): + """ + Search sale order lines with secondary units + """ + so_lines = super()._cart_find_product_line( + product_id=product_id, line_id=line_id, **kwargs + ) + if so_lines: + if line_id: + sol = self.env["sale.order.line"].browse(line_id) + secondary_uom_id = sol.secondary_uom_id.id + else: + secondary_uom_id = self.env.context.get("secondary_uom_id", False) + so_lines = so_lines.filtered( + lambda x: x.secondary_uom_id.id == secondary_uom_id + ) + return so_lines + + def _prepare_order_line_values( + self, + product_id, + quantity, + linked_line_id=False, + no_variant_attribute_values=None, + product_custom_attribute_values=None, + **kwargs, + ): + values = super()._prepare_order_line_values( + product_id, + quantity, + linked_line_id=linked_line_id, + no_variant_attribute_values=no_variant_attribute_values, + product_custom_attribute_values=product_custom_attribute_values, + **kwargs, + ) + values["secondary_uom_id"] = self.env.context.get("secondary_uom_id") + return values + + def _prepare_order_line_update_values( + self, order_line, quantity, linked_line_id=False, **kwargs + ): + values = super()._prepare_order_line_update_values( + order_line, quantity, linked_line_id=linked_line_id, **kwargs + ) + secondary_uom_id = self.env.context.get("secondary_uom_id") + if secondary_uom_id != order_line.secondary_uom_id.id: + values["secondary_uom_id"] = secondary_uom_id + return values + + def _cart_update( + self, + product_id=None, + line_id=None, + add_qty=0, + set_qty=0, + attributes=None, + **kwargs, + ): + if line_id: + sol = self.env["sale.order.line"].browse(line_id) + secondary_uom_id = sol.secondary_uom_id.id + else: + secondary_uom_id = request.session.get("secondary_uom_id", False) + self.env.context.copy() + if not secondary_uom_id: + # Check the default value for secondary uom or is a product can + # not allow to sell in base unit, so the default secondary uom + # will be the first secondary uom record. + product = self.env["product.product"].browse(product_id) + if not product.allow_uom_sell: + secondary_uom = ( + product.sale_secondary_uom_id or product.secondary_uom_ids[:1] + ) + if secondary_uom and add_qty: + add_qty = float_round( + float(add_qty) * secondary_uom.factor, + precision_rounding=secondary_uom.uom_id.rounding, + ) + secondary_uom_id = secondary_uom.id + return super( + SaleOrder, self.with_context(secondary_uom_id=secondary_uom_id) + )._cart_update( + product_id=product_id, + line_id=line_id, + add_qty=add_qty, + set_qty=set_qty, + attributes=attributes, + **kwargs, + ) + + def _compute_cart_info(self): + res = super()._compute_cart_info() + for order in self: + secondary_unit_lines = order.website_order_line.filtered("secondary_uom_id") + if secondary_unit_lines: + cart_secondary_quantity = int( + sum(secondary_unit_lines.mapped("secondary_uom_qty")) + ) + so_lines = order.website_order_line - secondary_unit_lines + cart_quantity = int(sum(so_lines.mapped("product_uom_qty"))) + order.cart_quantity = cart_quantity + cart_secondary_quantity + return res + + +class SaleOrderLine(models.Model): + _inherit = "sale.order.line" + + @api.model_create_multi + def create(self, vals_list): + SecondaryUom = self.env["product.secondary.unit"] + Uom = self.env["uom.uom"] + for vals in vals_list: + secondary_uom = SecondaryUom.browse(vals.get("secondary_uom_id", False)) + uom = Uom.browse(vals.get("product_uom", False)) + if secondary_uom: + factor = secondary_uom.factor * (uom.factor or 1.0) + vals["secondary_uom_qty"] = float_round( + vals["product_uom_qty"] / (factor or 1.0), + precision_rounding=secondary_uom.uom_id.rounding, + ) + return super().create(vals_list) + + def write(self, vals): + SecondaryUom = self.env["product.secondary.unit"] + Uom = self.env["uom.uom"] + for line in self: + secondary_uom = ( + "secondary_uom_id" in vals + and SecondaryUom.browse(vals["secondary_uom_id"]) + or line.secondary_uom_id + ) + uom = ( + "product_uom" in vals + and Uom.browse(vals["product_uom"]) + or line.product_uom + ) + if ( + "product_uom_qty" in vals + and secondary_uom + and secondary_uom.dependency_type == "dependent" + ): + factor = secondary_uom.factor * uom.factor + vals["secondary_uom_qty"] = float_round( + vals["product_uom_qty"] / (factor or 1.0), + precision_rounding=secondary_uom.uom_id.rounding, + ) + return super().write(vals) diff --git a/website_sale_secondary_unit/pyproject.toml b/website_sale_secondary_unit/pyproject.toml new file mode 100644 index 0000000000..4231d0cccb --- /dev/null +++ b/website_sale_secondary_unit/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/website_sale_secondary_unit/readme/CONFIGURE.md b/website_sale_secondary_unit/readme/CONFIGURE.md new file mode 100644 index 0000000000..98a223ffb7 --- /dev/null +++ b/website_sale_secondary_unit/readme/CONFIGURE.md @@ -0,0 +1,2 @@ +For define the secondary units, you should active *Manage multiples +units of measure* on the user that will be responsable of this function. diff --git a/website_sale_secondary_unit/readme/CONTRIBUTORS.md b/website_sale_secondary_unit/readme/CONTRIBUTORS.md new file mode 100644 index 0000000000..6b40fbde45 --- /dev/null +++ b/website_sale_secondary_unit/readme/CONTRIBUTORS.md @@ -0,0 +1,5 @@ +- [Tecnativa](https://www.tecnativa.com): + - Sergio Teruel + - Carlos Roca + - Pilar Vargas + - Carlos Lopez diff --git a/website_sale_secondary_unit/readme/DESCRIPTION.md b/website_sale_secondary_unit/readme/DESCRIPTION.md new file mode 100644 index 0000000000..498cf4e510 --- /dev/null +++ b/website_sale_secondary_unit/readme/DESCRIPTION.md @@ -0,0 +1,2 @@ +This module extends the functionality of saleorder_secondary_unit module +to allow sell products in online store in secondary units defined. diff --git a/website_sale_secondary_unit/readme/USAGE.md b/website_sale_secondary_unit/readme/USAGE.md new file mode 100644 index 0000000000..508a9b8b58 --- /dev/null +++ b/website_sale_secondary_unit/readme/USAGE.md @@ -0,0 +1,11 @@ +To use this module you need to: + +- Go to *'Website \> Products \> Products'*. +- Select a template. +- Set the secondary units that you need. +- Go to Website Shop and buy this product, you will see a selectable + option with all secondary units defined in the product and visible in + website. +- If you do not want to sell in a base product unit and only allow sell + in a secondary unit you can disable the option *'Allow to sell in unit + of measure'* in a product sale tab. diff --git a/website_sale_secondary_unit/security/ir.model.access.csv b/website_sale_secondary_unit/security/ir.model.access.csv new file mode 100644 index 0000000000..75e2d825f6 --- /dev/null +++ b/website_sale_secondary_unit/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_product_secondary_unit_user_public,access_product_second_unit_user_public,model_product_secondary_unit,base.group_public,1,0,0,0 +access_product_secondary_unit_user_portal,access_product_second_unit_user_portal,model_product_secondary_unit,base.group_portal,1,0,0,0 diff --git a/website_sale_secondary_unit/security/website_sale_secondary_unit.xml b/website_sale_secondary_unit/security/website_sale_secondary_unit.xml new file mode 100644 index 0000000000..bfe82ee86f --- /dev/null +++ b/website_sale_secondary_unit/security/website_sale_secondary_unit.xml @@ -0,0 +1,19 @@ + + + + Public secondary unit + + [('website_published', '=', True)] + + + + + + + diff --git a/website_sale_secondary_unit/static/description/icon.png b/website_sale_secondary_unit/static/description/icon.png new file mode 100644 index 0000000000..3a0328b516 Binary files /dev/null and b/website_sale_secondary_unit/static/description/icon.png differ diff --git a/website_sale_secondary_unit/static/description/index.html b/website_sale_secondary_unit/static/description/index.html new file mode 100644 index 0000000000..14ca5e78ec --- /dev/null +++ b/website_sale_secondary_unit/static/description/index.html @@ -0,0 +1,452 @@ + + + + + +Website Sale Secondary Unit + + + +
+

Website Sale Secondary Unit

+ + +

Beta License: AGPL-3 OCA/e-commerce Translate me on Weblate Try me on Runboat

+

This module extends the functionality of saleorder_secondary_unit module +to allow sell products in online store in secondary units defined.

+

Table of contents

+ +
+

Configuration

+

For define the secondary units, you should active Manage multiples +units of measure on the user that will be responsable of this function.

+
+
+

Usage

+

To use this module you need to:

+
    +
  • Go to ‘Website > Products > Products’.
  • +
  • Select a template.
  • +
  • Set the secondary units that you need.
  • +
  • Go to Website Shop and buy this product, you will see a selectable +option with all secondary units defined in the product and visible in +website.
  • +
  • If you do not want to sell in a base product unit and only allow sell +in a secondary unit you can disable the option ‘Allow to sell in unit +of measure’ in a product sale tab.
  • +
+
+
+

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

+
    +
  • Tecnativa
  • +
+
+
+

Contributors

+
    +
  • Tecnativa:
      +
    • Sergio Teruel
    • +
    • Carlos Roca
    • +
    • Pilar Vargas
    • +
    • Carlos Lopez
    • +
    +
  • +
+
+
+

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/e-commerce project on GitHub.

+

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

+
+
+
+ + diff --git a/website_sale_secondary_unit/static/src/js/notification/add_to_cart_notification/add_to_cart_notification.esm.js b/website_sale_secondary_unit/static/src/js/notification/add_to_cart_notification/add_to_cart_notification.esm.js new file mode 100644 index 0000000000..759e1554fb --- /dev/null +++ b/website_sale_secondary_unit/static/src/js/notification/add_to_cart_notification/add_to_cart_notification.esm.js @@ -0,0 +1,47 @@ +/** @odoo-module **/ +/* Copyright 2025 Carlos Lopez - Tecnativa + * License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */ + +import {AddToCartNotification} from "@website_sale/js/notification/add_to_cart_notification/add_to_cart_notification"; +import {patch} from "@web/core/utils/patch"; + +patch(AddToCartNotification.prototype, { + /** + * Return the product summary based on the line information. + * + * If the line has a secondary unit of measure, + * the product summary is computed based on the secondary unit of measure quantity and name, + * + * @param {Object} line - The line element for which to return the product summary. + * @returns {String} - The product summary. + */ + getProductSummary(line) { + if (line.secondary_uom_name) { + return ( + line.secondary_uom_qty + + " x " + + line.secondary_uom_name + + " " + + line.name + ); + } + return super.getProductSummary(...arguments); + }, +}); + +const extendedShape = { + ...AddToCartNotification.props.lines.element.shape, + secondary_uom_name: String, + secondary_uom_qty: {type: Number, optional: true}, +}; + +AddToCartNotification.props = { + ...AddToCartNotification.props, + lines: { + ...AddToCartNotification.props.lines, + element: { + ...AddToCartNotification.props.lines.element, + shape: extendedShape, + }, + }, +}; diff --git a/website_sale_secondary_unit/static/src/js/notification/cart_notification/cart_notification.esm.js b/website_sale_secondary_unit/static/src/js/notification/cart_notification/cart_notification.esm.js new file mode 100644 index 0000000000..5a9ca2e2fb --- /dev/null +++ b/website_sale_secondary_unit/static/src/js/notification/cart_notification/cart_notification.esm.js @@ -0,0 +1,22 @@ +/** @odoo-module **/ +/* Copyright 2025 Carlos Lopez - Tecnativa + * License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */ + +import {CartNotification} from "@website_sale/js/notification/cart_notification/cart_notification"; + +const extendedShape = { + ...CartNotification.props.lines.element.shape, + secondary_uom_name: String, + secondary_uom_qty: {type: Number, optional: true}, +}; + +CartNotification.props = { + ...CartNotification.props, + lines: { + ...CartNotification.props.lines, + element: { + ...CartNotification.props.lines.element, + shape: extendedShape, + }, + }, +}; diff --git a/website_sale_secondary_unit/static/src/js/website_sale_secondary_unit.esm.js b/website_sale_secondary_unit/static/src/js/website_sale_secondary_unit.esm.js new file mode 100644 index 0000000000..470efb14cf --- /dev/null +++ b/website_sale_secondary_unit/static/src/js/website_sale_secondary_unit.esm.js @@ -0,0 +1,137 @@ +/** @odoo-module **/ +/* Copyright 2019 Sergio Teruel + * Copyright 2025 Carlos Lopez - Tecnativa + * License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */ + +import "@website_sale/js/website_sale"; +import VariantMixin from "@website_sale/js/sale_variant_mixin"; +import publicWidget from "@web/legacy/js/public/public_widget"; + +publicWidget.registry.sale_secondary_unit = publicWidget.Widget.extend(VariantMixin, { + selector: ".secondary-unit", + // eslint-disable-next-line no-unused-vars + init: function (parent, editableMode) { + this._super.apply(this, arguments); + this.$secondary_uom = null; + this.$secondary_uom_qty = null; + this.$product_qty = null; + this.secondary_uom_qty = null; + this.secondary_uom_factor = null; + this.product_uom_factor = null; + this.product_qty = null; + }, + start: function () { + const _this = this; + this.$secondary_uom = $("#secondary_uom"); + this.$secondary_uom_qty = $(".secondary-quantity"); + this.$product_qty = $(".quantity"); + this._setValues(); + this.$target.on( + "change", + ".secondary-quantity", + this._onChangeSecondaryUom.bind(this) + ); + this.$target.on( + "change", + "#secondary_uom", + this._onChangeSecondaryUom.bind(this) + ); + this.$product_qty.on("change", null, this._onChangeProductQty.bind(this)); + return this._super.apply(this, arguments).then(function () { + _this._onChangeSecondaryUom(); + }); + }, + _setValues: function () { + this.secondary_uom_qty = Number(this.$target.find(".secondary-quantity").val()); + this.secondary_uom_factor = Number( + $("option:selected", this.$secondary_uom).data("secondary-uom-factor") + ); + this.product_uom_factor = Number( + $("option:selected", this.$secondary_uom).data("product-uom-factor") + ); + this.product_qty = Number($(".quantity").val()); + }, + + _onChangeSecondaryUom: function (ev) { + if (!ev) { + // HACK: Create a fake event to locate the form on "onChangeAddQuantity" + // odoo method + ev = jQuery.Event("fakeEvent"); + ev.currentTarget = $(".form-control.quantity"); + } + this._setValues(); + const factor = this.secondary_uom_factor * this.product_uom_factor; + this.$product_qty.val(this.secondary_uom_qty * factor); + this.onChangeAddQuantity(ev); + }, + _onChangeProductQty: function () { + this._setValues(); + const factor = this.secondary_uom_factor * this.product_uom_factor; + this.$secondary_uom_qty.val(this.product_qty / factor); + }, +}); + +publicWidget.registry.sale_secondary_unit_cart = publicWidget.Widget.extend({ + selector: ".oe_cart", + // eslint-disable-next-line no-unused-vars + init: function (parent, editableMode) { + this._super.apply(this, arguments); + this.$product_qty = null; + this.secondary_uom_qty = null; + this.secondary_uom_factor = null; + this.product_uom_factor = null; + this.product_qty = null; + }, + start: function () { + var _this = this; + this.$target.on( + "change", + "input.js_secondary_quantity[data-line-id]", + function () { + _this._onChangeSecondaryUom(this); + } + ); + }, + _setValues: function (order_line) { + this.$product_qty = this.$target.find( + ".quantity[data-line-id=" + order_line.dataset.lineId + "]" + ); + this.secondary_uom_qty = Number(order_line.value); + this.secondary_uom_factor = Number(order_line.dataset.secondaryUomFactor); + this.product_uom_factor = Number(order_line.dataset.productUomFactor); + }, + _onChangeSecondaryUom: function (order_line) { + this._setValues(order_line); + const factor = this.secondary_uom_factor * this.product_uom_factor; + this.$product_qty.val(this.secondary_uom_qty * factor); + this.$product_qty.trigger("change"); + }, +}); + +publicWidget.registry.WebsiteSale.include({ + _onChangeCombination: function (ev, $parent, combination) { + const quantity = $parent.find(".css_quantity:not(.secondary_qty)"); + const res = this._super(...arguments); + if (combination.has_secondary_uom) { + quantity.removeClass("d-inline-flex").addClass("d-none"); + } else { + quantity.removeClass("d-none").addClass("d-inline-flex"); + } + return res; + }, + _submitForm: function () { + if ( + !("secondary_uom_id" in this.rootProduct) && + $(this.$target).find("#secondary_uom").length + ) { + this.rootProduct.secondary_uom_id = $(this.$target) + .find("#secondary_uom") + .val(); + this.rootProduct.secondary_uom_qty = $(this.$target) + .find(".secondary-quantity") + .val(); + } + + this._super.apply(this, arguments); + }, +}); diff --git a/website_sale_secondary_unit/static/src/scss/website_sale_secondary_unit.scss b/website_sale_secondary_unit/static/src/scss/website_sale_secondary_unit.scss new file mode 100644 index 0000000000..4d3aef3013 --- /dev/null +++ b/website_sale_secondary_unit/static/src/scss/website_sale_secondary_unit.scss @@ -0,0 +1,7 @@ +.css_secondary_quantity { + max-width: 150px; +} +.oe_website_sale input.js_secondary_quantity { + min-width: 48px; + text-align: center; +} diff --git a/website_sale_secondary_unit/static/tests/tours/website_sale_secondary_unit_tour.esm.js b/website_sale_secondary_unit/static/tests/tours/website_sale_secondary_unit_tour.esm.js new file mode 100644 index 0000000000..a23767c516 --- /dev/null +++ b/website_sale_secondary_unit/static/tests/tours/website_sale_secondary_unit_tour.esm.js @@ -0,0 +1,55 @@ +/** @odoo-module */ +/* Copyright 2019 Sergio Teruel + * License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */ + +import {registry} from "@web/core/registry"; +registry.category("web_tour.tours").add("website_sale_secondary_unit", { + test: true, + url: "/shop", + steps: () => [ + { + trigger: "a:contains('Test product')", + }, + { + trigger: "#secondary_uom", + run: "text(Box 5 Units)", + }, + { + trigger: "#add_to_cart", + extra_trigger: + ".js_product:has(input[name='add_qty']:propValueContains(5)):has(.price_uom)", + }, + { + trigger: "a[href='/shop/cart']", + }, + { + trigger: "a[href='/shop']", + extra_trigger: "span:contains(Box 5 Units)", + }, + { + trigger: "a:contains('Test product')", + }, + { + trigger: "#add_to_cart", + extra_trigger: + ".js_product:has(input[name='add_qty']:propValueContains(1))", + }, + { + trigger: "a[href='/shop/cart']", + }, + { + trigger: "a[href='/shop/checkout?express=1']", + extra_trigger: "span:containsExact(Units)", + }, + { + trigger: "div[id='o_wsale_total_accordion'] button.accordion-button", + }, + { + trigger: "h6[name='secondary_uom_qty'] span:containsExact(Box 5)", + }, + { + trigger: "a[href='/shop']", + extra_trigger: "table:has(span:contains(Box 5)):has(span:contains(Units))", + }, + ], +}); diff --git a/website_sale_secondary_unit/tests/__init__.py b/website_sale_secondary_unit/tests/__init__.py new file mode 100644 index 0000000000..e6063c833b --- /dev/null +++ b/website_sale_secondary_unit/tests/__init__.py @@ -0,0 +1 @@ +from . import test_website_sale_secondary_unit diff --git a/website_sale_secondary_unit/tests/test_website_sale_secondary_unit.py b/website_sale_secondary_unit/tests/test_website_sale_secondary_unit.py new file mode 100644 index 0000000000..79080e70ab --- /dev/null +++ b/website_sale_secondary_unit/tests/test_website_sale_secondary_unit.py @@ -0,0 +1,52 @@ +# Copyright 2019 Tecnativa - Sergio Teruel +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo.tests.common import HttpCase + +from odoo.addons.base.tests.common import DISABLED_MAIL_CONTEXT + + +class WebsiteSaleSecondaryUnitHttpCase(HttpCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env(context=dict(cls.env.context, **DISABLED_MAIL_CONTEXT)) + # Models + ProductSecondaryUnit = cls.env["product.secondary.unit"] + product_uom_unit = cls.env.ref("uom.product_uom_unit") + cls.product_template = cls.env["product.template"].create( + { + "name": "Test product", + "is_published": True, + "website_sequence": 1, + "type": "consu", + } + ) + vals = { + "name": "Box", + "uom_id": product_uom_unit.id, + "factor": 5.0, + "product_tmpl_id": cls.product_template.id, + "website_published": True, + } + cls.secondary_unit_box_5 = ProductSecondaryUnit.create(vals) + cls.secondary_unit_box_10 = ProductSecondaryUnit.create(dict(vals, factor=10.0)) + cls.product_template.write( + { + "secondary_uom_ids": [ + ( + 6, + 0, + [cls.secondary_unit_box_5.id, cls.secondary_unit_box_10.id], + ), + ], + } + ) + # Add group "Manage Multiple Units of Measure" to admin + admin = cls.env.ref("base.user_admin") + admin.groups_id |= cls.env.ref("uom.group_uom") + + def test_ui_website(self): + """Test frontend tour.""" + self.start_tour( + "/shop", "website_sale_secondary_unit", login="admin", step_delay=1000 + ) diff --git a/website_sale_secondary_unit/views/product_secondary_unit_views.xml b/website_sale_secondary_unit/views/product_secondary_unit_views.xml new file mode 100644 index 0000000000..b932023cb7 --- /dev/null +++ b/website_sale_secondary_unit/views/product_secondary_unit_views.xml @@ -0,0 +1,18 @@ + + + + + product.template + + + + + + + + diff --git a/website_sale_secondary_unit/views/product_template_views.xml b/website_sale_secondary_unit/views/product_template_views.xml new file mode 100644 index 0000000000..d45665fab3 --- /dev/null +++ b/website_sale_secondary_unit/views/product_template_views.xml @@ -0,0 +1,15 @@ + + + + + product.template.website.secondary.unit.form + product.template + + + + + + + + diff --git a/website_sale_secondary_unit/views/templates.xml b/website_sale_secondary_unit/views/templates.xml new file mode 100644 index 0000000000..7e1085b618 --- /dev/null +++ b/website_sale_secondary_unit/views/templates.xml @@ -0,0 +1,210 @@ + + + + + + + + + + +