diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 00000000..55a07d96 --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,3 @@ +odoo_test_helper +odoo-addon-product_pack @ git+https://github.com/OCA/product-pack.git@refs/pull/189/head#subdirectory=product_pack +odoo-addon-sale_product_pack @ git+https://github.com/OCA/product-pack.git@refs/pull/190/head#subdirectory=sale_product_pack diff --git a/website_sale_product_pack/README.rst b/website_sale_product_pack/README.rst new file mode 100644 index 00000000..cbf60ec4 --- /dev/null +++ b/website_sale_product_pack/README.rst @@ -0,0 +1,107 @@ +========================= +Website Sale Product Pack +========================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:b253d0a32950c83c03fbde45ce4e7b7335fc773f15534306c65a0ec5344121b8 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fproduct--pack-lightgray.png?logo=github + :target: https://github.com/OCA/product-pack/tree/18.0/website_sale_product_pack + :alt: OCA/product-pack +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/product-pack-18-0/product-pack-18-0-website_sale_product_pack + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/product-pack&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module introduces compatibility of product packs with e-commerce: + +- In the cart summary, the components aren't editable and are shown + hanging from the main pack reference. +- When we remove a pack from the cart, their components are removed as + well. +- The cart popup summary only shows the main pack line and discards the + sublines in the units count. +- The cart summary shows the component lines hanging from the main one + as well. +- It's ensured the the prices are shown correctly for the whole pack and + that they're correctly summarized depending on the pack type. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +There are several demo packs to test the module. Publish them and add +them to the cart from the frontend. You should have the same quotation +as if you do it in the backend. + +Known issues / Roadmap +====================== + +- Improve pack cart display. +- When we have subpacks (a pack inside a pack) we should improve + visually how it's shown in the cart. + +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 `__: + + - David Vidal + +- `ADHOC SA `__: + + - Nicolas Mac Rouillon + +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/product-pack `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/website_sale_product_pack/__init__.py b/website_sale_product_pack/__init__.py new file mode 100644 index 00000000..f7209b17 --- /dev/null +++ b/website_sale_product_pack/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import controllers diff --git a/website_sale_product_pack/__manifest__.py b/website_sale_product_pack/__manifest__.py new file mode 100644 index 00000000..1b54876f --- /dev/null +++ b/website_sale_product_pack/__manifest__.py @@ -0,0 +1,19 @@ +# Copyright 2021 Tecnativa - David Vidal +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +{ + "name": "Website Sale Product Pack", + "category": "E-Commerce", + "summary": "Compatibility module of product pack with e-commerce", + "version": "18.0.1.0.0", + "license": "AGPL-3", + "depends": ["website_sale", "sale_product_pack"], + "data": ["views/templates.xml"], + "assets": { + "web.assets_tests": [ + "website_sale_product_pack/static/tests/tours/website_sale_product_pack_tour.js", + ], + }, + "author": "Tecnativa, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/product-pack", + "installable": True, +} diff --git a/website_sale_product_pack/controllers/__init__.py b/website_sale_product_pack/controllers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/website_sale_product_pack/controllers/main.py b/website_sale_product_pack/controllers/main.py new file mode 100644 index 00000000..e69de29b diff --git a/website_sale_product_pack/controllers/variant.py b/website_sale_product_pack/controllers/variant.py new file mode 100644 index 00000000..e69de29b diff --git a/website_sale_product_pack/i18n/it.po b/website_sale_product_pack/i18n/it.po new file mode 100644 index 00000000..d51b9904 --- /dev/null +++ b/website_sale_product_pack/i18n/it.po @@ -0,0 +1,89 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * website_sale_product_pack +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2024-10-09 09:06+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 5.6.2\n" + +#. module: website_sale_product_pack +#: model_terms:ir.ui.view,arch_db:website_sale_product_pack.cart_lines +#: model_terms:ir.ui.view,arch_db:website_sale_product_pack.checkout_layout +msgid "" +msgstr "" + +#. module: website_sale_product_pack +#: model:ir.model,name:website_sale_product_pack.model_product_template +msgid "Product" +msgstr "Prodotto" + +#. module: website_sale_product_pack +#: model:ir.model,name:website_sale_product_pack.model_product_product +msgid "Product Variant" +msgstr "Variante prodotto" + +#. module: website_sale_product_pack +#: model:ir.model,name:website_sale_product_pack.model_sale_order +msgid "Sales Order" +msgstr "Ordine di vendita" + +#. module: website_sale_product_pack +#: model:ir.model,name:website_sale_product_pack.model_sale_order_line +msgid "Sales Order Line" +msgstr "Riga ordine di vendita" + +#. module: website_sale_product_pack +#: model:ir.model,name:website_sale_product_pack.model_website +msgid "Website" +msgstr "Sito web" + +#. module: website_sale_product_pack +#. odoo-python +#: code:addons/website_sale_product_pack/models/product_product.py:0 +#, python-format +msgid "" +"You can't add unpublished products (%(unpublished_products)s)to a published " +"pack (%(pack_name)s)" +msgstr "" +"Non si possono aggiungere prodotti non pubblicati (%(unpublished_products)s) " +"ad un pacco pubblicato (%(pack_name)s)" + +#. module: website_sale_product_pack +#. odoo-python +#: code:addons/website_sale_product_pack/models/product_template.py:0 +#, python-format +msgid "" +"You can't unpublished product (%(product_name)s) for apublished pack parents " +"(%(pack_parents)s)" +msgstr "" +"Non si può non pubblicare un prodotto (%(product_name)s) per un collo padre " +"pubblicato (%(pack_parents)s)" + +#. module: website_sale_product_pack +#. odoo-python +#: code:addons/website_sale_product_pack/models/product_template.py:0 +#, python-format +msgid "" +"You can't unpublished products (%(unpublished_products)s) to apublished pack " +"(%(pack_name)s)" +msgstr "" +"Non si può non pubblicare un prodotto (%(unpublished_products)s) di un collo " +"pubblicato (%(pack_name)s)" + +#, python-format +#~ msgid "" +#~ "You can't add unpublished products (%(unpublished_products)s) toa " +#~ "published pack (%(pack_name)s)" +#~ msgstr "" +#~ "Non si possono aggiungere prodotti non pubblicati " +#~ "(%(unpublished_products)s) ad un collo pubblicato (%(pack_name)s)" diff --git a/website_sale_product_pack/i18n/website_sale_product_pack.pot b/website_sale_product_pack/i18n/website_sale_product_pack.pot new file mode 100644 index 00000000..c5b4d319 --- /dev/null +++ b/website_sale_product_pack/i18n/website_sale_product_pack.pot @@ -0,0 +1,72 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * website_sale_product_pack +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.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_product_pack +#: model_terms:ir.ui.view,arch_db:website_sale_product_pack.cart_lines +#: model_terms:ir.ui.view,arch_db:website_sale_product_pack.checkout_layout +msgid "" +msgstr "" + +#. module: website_sale_product_pack +#: model:ir.model,name:website_sale_product_pack.model_product_template +msgid "Product" +msgstr "" + +#. module: website_sale_product_pack +#: model:ir.model,name:website_sale_product_pack.model_product_product +msgid "Product Variant" +msgstr "" + +#. module: website_sale_product_pack +#: model:ir.model,name:website_sale_product_pack.model_sale_order +msgid "Sales Order" +msgstr "" + +#. module: website_sale_product_pack +#: model:ir.model,name:website_sale_product_pack.model_sale_order_line +msgid "Sales Order Line" +msgstr "" + +#. module: website_sale_product_pack +#: model:ir.model,name:website_sale_product_pack.model_website +msgid "Website" +msgstr "" + +#. module: website_sale_product_pack +#. odoo-python +#: code:addons/website_sale_product_pack/models/product_product.py:0 +#, python-format +msgid "" +"You can't add unpublished products (%(unpublished_products)s)to a published " +"pack (%(pack_name)s)" +msgstr "" + +#. module: website_sale_product_pack +#. odoo-python +#: code:addons/website_sale_product_pack/models/product_template.py:0 +#, python-format +msgid "" +"You can't unpublished product (%(product_name)s) for apublished pack parents" +" (%(pack_parents)s)" +msgstr "" + +#. module: website_sale_product_pack +#. odoo-python +#: code:addons/website_sale_product_pack/models/product_template.py:0 +#, python-format +msgid "" +"You can't unpublished products (%(unpublished_products)s) to apublished pack" +" (%(pack_name)s)" +msgstr "" diff --git a/website_sale_product_pack/models/__init__.py b/website_sale_product_pack/models/__init__.py new file mode 100644 index 00000000..423fc6e0 --- /dev/null +++ b/website_sale_product_pack/models/__init__.py @@ -0,0 +1,4 @@ +from . import sale_order +from . import website +from . import product_product +from . import product_template diff --git a/website_sale_product_pack/models/product_product.py b/website_sale_product_pack/models/product_product.py new file mode 100644 index 00000000..197fc413 --- /dev/null +++ b/website_sale_product_pack/models/product_product.py @@ -0,0 +1,28 @@ +# Copyright 2019 Tecnativa - Ernesto Tejeda +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import _, api, models +from odoo.exceptions import ValidationError + + +class ProductProduct(models.Model): + _inherit = "product.product" + + @api.constrains("pack_line_ids") + def check_website_published(self): + for rec in self.filtered("is_published"): + unpublished = rec.pack_line_ids.mapped("product_id").filtered( + lambda x: not x.is_published + ) + if unpublished: + raise ValidationError( + _( + "You can't add unpublished products " + "(%(unpublished_products)s)" + "to a published pack (%(pack_name)s)" + ) + % { + "unpublished_products": ", ".join(unpublished.mapped("name")), + "pack_name": rec.name, + } + ) diff --git a/website_sale_product_pack/models/product_template.py b/website_sale_product_pack/models/product_template.py new file mode 100644 index 00000000..6cb1ad75 --- /dev/null +++ b/website_sale_product_pack/models/product_template.py @@ -0,0 +1,83 @@ +# Copyright 2019 Tecnativa - Ernesto Tejeda +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import _, api, models +from odoo.exceptions import ValidationError + + +class ProductTemplate(models.Model): + _inherit = "product.template" + + @api.constrains("is_published") + def check_website_published(self): + """For keep the consistent and prevent bugs within the e-commerce, + we force that all childs of a parent pack + stay publish when the parent is published. + Also if any of the childs of the parent pack became unpublish, + we unpublish the parent.""" + for rec in self.filtered(lambda x: x.pack_ok and x.is_published): + unpublished = rec.pack_line_ids.mapped("product_id").filtered( + lambda p: not p.is_published + ) + if unpublished: + raise ValidationError( + _( + "You can't unpublished products (%(unpublished_products)s) to a" + "published pack (%(pack_name)s)" + ) + % { + "unpublished_products": ", ".join(unpublished.mapped("name")), + "pack_name": rec.name, + } + ) + + for rec in self.filtered( + lambda x: not x.is_published and x.used_in_pack_line_ids + ): + published = rec.used_in_pack_line_ids.mapped("parent_product_id").filtered( + "is_published" + ) + if published: + raise ValidationError( + _( + "You can't unpublished product (%(product_name)s) for a" + "published pack parents (%(pack_parents)s)" + ) + % { + "product_name": rec.name, + "pack_parents": ", ".join(published.mapped("name")), + } + ) + + def _get_additionnal_combination_info( + self, product_or_template, quantity, date, website + ): + """Override to add the information about renting for rental products""" + res = super()._get_additionnal_combination_info( + product_or_template, quantity, date, website + ) + + if not product_or_template.pack_ok: + return res + + currency = website.currency_id + pricelist = website.pricelist_id + res["price"] = pricelist.with_context(whole_pack_price=True)._get_product_price( + product=product_or_template, + quantity=quantity, + currency=currency, + ) + + return res + + def _get_sales_prices(self, website): + prices = super()._get_sales_prices(website) + pricelist = website.pricelist_id + + for template in self: + if not template.pack_ok: + continue + prices[template.id]["price_reduce"] = pricelist.with_context( + whole_pack_price=True + )._get_product_price(product=template, quantity=1.0) + return prices diff --git a/website_sale_product_pack/models/sale_order.py b/website_sale_product_pack/models/sale_order.py new file mode 100644 index 00000000..4a58fb41 --- /dev/null +++ b/website_sale_product_pack/models/sale_order.py @@ -0,0 +1,53 @@ +# Copyright 2021 Tecnativa - David Vidal +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from odoo import api, models + + +class SaleOrder(models.Model): + _inherit = "sale.order" + + def _cart_update(self, *args, **kwargs): + """We need to keep the discount defined on the components when checking out. + Also when a line comes from a totalized pack, we should flag it to avoid + changing it's price in a cart step.""" + line_id = kwargs.get("line_id") + if line_id: + line = self.env["sale.order.line"].browse(line_id) + if line and line.pack_parent_line_id: + pack = line.pack_parent_line_id.product_id + detailed_totalized_pack = ( + pack.pack_type == "detailed" + and pack.pack_component_price in {"totalized", "ignored"} + ) + return super( + SaleOrder, + self.with_context( + pack_discount=line.discount, + detailed_totalized_pack=detailed_totalized_pack, + ), + )._cart_update(*args, **kwargs) + return super()._cart_update(*args, **kwargs) + + @api.depends("order_line.product_uom_qty", "order_line.product_id") + def _compute_cart_info(self): + """We only want to count the main pack line, not the component lines""" + res = super()._compute_cart_info() + for order in self: + order.cart_quantity = int( + sum( + order.website_order_line.filtered( + lambda x: not x.pack_parent_line_id + ).mapped("product_uom_qty") + ) + ) + return res + + +class SaleOrderLine(models.Model): + _inherit = "sale.order.line" + + def unlink(self): + """The website calls this method specifically. We want to get rid of + the children lines so the user doesn't have to""" + join_pack_children = self + self.mapped("pack_child_line_ids") + return super(SaleOrderLine, join_pack_children.exists()).unlink() diff --git a/website_sale_product_pack/models/website.py b/website_sale_product_pack/models/website.py new file mode 100644 index 00000000..3916b11f --- /dev/null +++ b/website_sale_product_pack/models/website.py @@ -0,0 +1,7 @@ +# Copyright 2021 Tecnativa - David Vidal +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from odoo import models + + +class Website(models.Model): + _inherit = "website" diff --git a/website_sale_product_pack/pyproject.toml b/website_sale_product_pack/pyproject.toml new file mode 100644 index 00000000..4231d0cc --- /dev/null +++ b/website_sale_product_pack/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/website_sale_product_pack/readme/CONTRIBUTORS.md b/website_sale_product_pack/readme/CONTRIBUTORS.md new file mode 100644 index 00000000..4080f2d3 --- /dev/null +++ b/website_sale_product_pack/readme/CONTRIBUTORS.md @@ -0,0 +1,4 @@ +- [Tecnativa](https://www.tecnativa.com): + - David Vidal +- [ADHOC SA](https://www.adhoc.com.ar): + - Nicolas Mac Rouillon diff --git a/website_sale_product_pack/readme/DESCRIPTION.md b/website_sale_product_pack/readme/DESCRIPTION.md new file mode 100644 index 00000000..e493be77 --- /dev/null +++ b/website_sale_product_pack/readme/DESCRIPTION.md @@ -0,0 +1,12 @@ +This module introduces compatibility of product packs with e-commerce: + +- In the cart summary, the components aren't editable and are shown + hanging from the main pack reference. +- When we remove a pack from the cart, their components are removed as + well. +- The cart popup summary only shows the main pack line and discards the + sublines in the units count. +- The cart summary shows the component lines hanging from the main one + as well. +- It's ensured the the prices are shown correctly for the whole pack and + that they're correctly summarized depending on the pack type. diff --git a/website_sale_product_pack/readme/ROADMAP.md b/website_sale_product_pack/readme/ROADMAP.md new file mode 100644 index 00000000..47cd568f --- /dev/null +++ b/website_sale_product_pack/readme/ROADMAP.md @@ -0,0 +1,3 @@ +- Improve pack cart display. +- When we have subpacks (a pack inside a pack) we should improve + visually how it's shown in the cart. diff --git a/website_sale_product_pack/readme/USAGE.md b/website_sale_product_pack/readme/USAGE.md new file mode 100644 index 00000000..f5f9b1ef --- /dev/null +++ b/website_sale_product_pack/readme/USAGE.md @@ -0,0 +1,3 @@ +There are several demo packs to test the module. Publish them and add +them to the cart from the frontend. You should have the same quotation +as if you do it in the backend. diff --git a/website_sale_product_pack/static/description/icon.png b/website_sale_product_pack/static/description/icon.png new file mode 100644 index 00000000..3a0328b5 Binary files /dev/null and b/website_sale_product_pack/static/description/icon.png differ diff --git a/website_sale_product_pack/static/description/index.html b/website_sale_product_pack/static/description/index.html new file mode 100644 index 00000000..838070bb --- /dev/null +++ b/website_sale_product_pack/static/description/index.html @@ -0,0 +1,458 @@ + + + + + +Website Sale Product Pack + + + +
+

Website Sale Product Pack

+ + +

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

+

This module introduces compatibility of product packs with e-commerce:

+
    +
  • In the cart summary, the components aren’t editable and are shown +hanging from the main pack reference.
  • +
  • When we remove a pack from the cart, their components are removed as +well.
  • +
  • The cart popup summary only shows the main pack line and discards the +sublines in the units count.
  • +
  • The cart summary shows the component lines hanging from the main one +as well.
  • +
  • It’s ensured the the prices are shown correctly for the whole pack and +that they’re correctly summarized depending on the pack type.
  • +
+

Table of contents

+ +
+

Usage

+

There are several demo packs to test the module. Publish them and add +them to the cart from the frontend. You should have the same quotation +as if you do it in the backend.

+
+
+

Known issues / Roadmap

+
    +
  • Improve pack cart display.
  • +
  • When we have subpacks (a pack inside a pack) we should improve +visually how it’s shown in the cart.
  • +
+
+
+

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

+ +
+
+

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/product-pack project on GitHub.

+

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

+
+
+
+ + diff --git a/website_sale_product_pack/static/tests/tours/website_sale_product_pack_tour.js b/website_sale_product_pack/static/tests/tours/website_sale_product_pack_tour.js new file mode 100644 index 00000000..202db7b3 --- /dev/null +++ b/website_sale_product_pack/static/tests/tours/website_sale_product_pack_tour.js @@ -0,0 +1,99 @@ +/** @odoo-module **/ +/* Copyright 2021 Tecnativa - David Vidal + * License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */ + +import {registry} from "@web/core/registry"; +import * as tourUtils from "@website_sale/js/tours/tour_utils"; + +registry.category("web_tour.tours").add("create_components_price_order_line", { + url: "/shop", + steps: () => [ + ...tourUtils.searchProduct("Pack CPU (Detailed - Displayed Components Price)"), + { + content: "select Pack CPU (Detailed - Displayed Components Price)", + trigger: + '.oe_product_cart:first a:contains("Pack CPU (Detailed - Displayed Components Price)")', + run: "click", + }, + { + content: "click on add to cart", + trigger: '#product_detail form[action^="/shop/cart/update"] #add_to_cart', + run: "click", + }, + tourUtils.goToCart(), + ], +}); + +registry.category("web_tour.tours").add("create_ignored_price_order_line", { + url: "/shop", + steps: () => [ + ...tourUtils.searchProduct("Pack CPU (Detailed - Ignored Components Price)"), + { + content: "select Pack CPU (Detailed - Ignored Components Price)", + trigger: + '.oe_product_cart:first a:contains("Pack CPU (Detailed - Ignored Components Price)")', + run: "click", + }, + { + content: "click on add to cart", + trigger: '#product_detail form[action^="/shop/cart/update"] #add_to_cart', + run: "click", + }, + tourUtils.goToCart(), + ], +}); + +registry.category("web_tour.tours").add("create_totalized_price_order_line", { + url: "/shop", + steps: () => [ + ...tourUtils.searchProduct("Pack CPU (Detailed - Totalized Components Price)"), + { + content: "select Pack CPU (Detailed - Totalized Components Price)", + trigger: + '.oe_product_cart:first a:contains("Pack CPU (Detailed - Totalized Components Price)")', + run: "click", + }, + { + content: "click on add to cart", + trigger: '#product_detail form[action^="/shop/cart/update"] #add_to_cart', + run: "click", + }, + tourUtils.goToCart(), + ], +}); + +registry.category("web_tour.tours").add("create_non_detailed_price_order_line", { + url: "/shop", + steps: () => [ + ...tourUtils.searchProduct("Non Detailed - Totalized Components Price"), + { + content: "select Non Detailed - Totalized Components Price", + trigger: + '.oe_product_cart:first a:contains("Non Detailed - Totalized Components Price")', + run: "click", + }, + { + content: "click on add to cart", + trigger: '#product_detail form[action^="/shop/cart/update"] #add_to_cart', + run: "click", + }, + tourUtils.goToCart(), + ], +}); + +registry.category("web_tour.tours").add("update_pack_qty", { + url: "/shop", + steps: () => [ + ...tourUtils.searchProduct("Pack CPU (Detailed - Displayed Components Price)"), + { + content: "select Pack CPU (Detailed - Displayed Components Price)", + trigger: + '.oe_product_cart:first a:contains("Pack CPU (Detailed - Displayed Components Price)")', + }, + { + content: "click on add to cart", + trigger: '#product_detail form[action^="/shop/cart/update"] #add_to_cart', + }, + tourUtils.goToCart({quantity: 1}), + ], +}); diff --git a/website_sale_product_pack/tests/__init__.py b/website_sale_product_pack/tests/__init__.py new file mode 100644 index 00000000..05aebdc1 --- /dev/null +++ b/website_sale_product_pack/tests/__init__.py @@ -0,0 +1 @@ +from . import test_website_sale_product_pack diff --git a/website_sale_product_pack/tests/test_website_sale_product_pack.py b/website_sale_product_pack/tests/test_website_sale_product_pack.py new file mode 100644 index 00000000..61181377 --- /dev/null +++ b/website_sale_product_pack/tests/test_website_sale_product_pack.py @@ -0,0 +1,132 @@ +# Copyright 2021 Tecnativa - David Vidal +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo.exceptions import ValidationError +from odoo.tests.common import HttpCase, tagged + + +@tagged("post_install", "-at_install") +class WebsiteSaleHttpCase(HttpCase): + def setUp(self): + super().setUp() + self.user_portal = self.env.ref("base.demo_user0") + self.product_pdc = self.env.ref( + "product_pack.product_pack_cpu_detailed_components" + ) + self.product_pdi = self.env.ref( + "product_pack.product_pack_cpu_detailed_ignored" + ) + self.product_pdt = self.env.ref( + "product_pack.product_pack_cpu_detailed_totalized" + ) + self.product_pnd = self.env.ref("product_pack.product_pack_cpu_non_detailed") + self.packs = ( + self.product_pdc + self.product_pdi + self.product_pdt + self.product_pnd + ) + # Publish the products components + self.packs.mapped("pack_line_ids.product_id").write({"is_published": True}) + # Publish the products and put them in the first results + self.packs.write({"is_published": True, "website_sequence": 0}) + # Create and select a specific pricelist for avoiding problems in integrated + # environments where the default pricelist currency has been changed + website = self.env["website"].get_current_website() + pricelist = self.env["product.pricelist"].create( + { + "name": "website_sale_product_pack public", + "currency_id": website.user_id.company_id.currency_id.id, + "selectable": True, + } + ) + self.user_portal.property_product_pricelist = pricelist + website.user_id.property_product_pricelist = pricelist + admin = self.env.ref("base.user_admin") + admin.property_product_pricelist = pricelist + + def _get_component_prices_sum(self, product_pack): + component_prices = 0.0 + for pack_line in product_pack.get_pack_lines(): + product_line_price = pack_line.product_id.list_price * ( + 1 - (pack_line.sale_discount or 0.0) / 100.0 + ) + component_prices += product_line_price * pack_line.quantity + return component_prices + + def test_create_components_price_order_line(self): + """Test with the same premise that in sale_product_pack but in a + frontend tour""" + self.start_tour("/shop", "create_components_price_order_line", login="portal") + sale = self.env["sale.order"].search([], limit=1) + # After create, there will be four lines + self.assertEqual(len(sale.order_line), 4) + # The products of those four lines are the main product pack and its + # product components + self.assertEqual( + sale.order_line.mapped("product_id"), + self.product_pdc | self.product_pdc.get_pack_lines().mapped("product_id"), + ) + + def test_create_ignored_price_order_line(self): + """Test with the same premise that in sale_product_pack but in a frontend + tour""" + self.start_tour("/shop", "create_ignored_price_order_line", login="portal") + sale = self.env["sale.order"].search([], limit=1) + line = sale.order_line.filtered(lambda x: x.product_id == self.product_pdi) + # After create, there will be four lines + self.assertEqual(len(sale.order_line), 4) + # The products of those four lines are the main product pack and its + # product components + self.assertEqual( + sale.order_line.mapped("product_id"), + self.product_pdi | self.product_pdi.get_pack_lines().mapped("product_id"), + ) + # All component lines have zero as subtotal + self.assertEqual((sale.order_line - line).mapped("price_subtotal"), [0, 0, 0]) + # Pack price is different from the sum of component prices + self.assertEqual(line.price_subtotal, 30.75) + self.assertNotEqual(self._get_component_prices_sum(self.product_pdi), 30.75) + + def test_create_totalized_price_order_line(self): + """Test with the same premise that in sale_product_pack but in a frontend tour + with a detailed totalized pack""" + self.start_tour("/shop", "create_totalized_price_order_line", login="portal") + sale = self.env["sale.order"].search([], limit=1) + line = sale.order_line.filtered(lambda x: x.product_id == self.product_pdt) + # After create, there will be four lines + self.assertEqual(len(sale.order_line), 4) + # The products of those four lines are the main product pack and its + # product components + self.assertEqual( + sale.order_line.mapped("product_id"), + self.product_pdt | self.product_pdt.get_pack_lines().mapped("product_id"), + ) + # All component lines have zero as subtotal + self.assertEqual((sale.order_line - line).mapped("price_subtotal"), [0, 0, 0]) + # Pack price is equal to the sum of component prices + self.assertEqual(line.price_subtotal, 2662.5) + self.assertEqual(self._get_component_prices_sum(self.product_pdt), 2662.5) + + def test_create_non_detailed_price_order_line(self): + """Test with the same premise that in sale_product_pack but in a frontend + tour""" + self.start_tour("/shop", "create_non_detailed_price_order_line", login="portal") + sale = self.env["sale.order"].search([], limit=1) + line = sale.order_line.filtered(lambda x: x.product_id == self.product_pnd) + # After create, there will be only one line, because product_type is + # not a detailed one + self.assertEqual(len(line), 1) + # Pack price is equal to the sum of component prices + self.assertEqual(line.price_subtotal, 2662.5) + self.assertEqual(self._get_component_prices_sum(self.product_pnd), 2662.5) + + def test__check_to_add_pack_component_pusblished(self): + """ + Test when create a product pack with only published products as components. + """ + with self.assertRaises(ValidationError): + product_component = self.env.ref("product.product_product_25") + product_component.write({"is_published": False}) + vals = { + "product_id": product_component.id, + "parent_product_id": self.product_pdc.id, + } + pack_line = self.env["product.pack.line"].create(vals) + self.product_pdc.write({"pack_line_ids": [(4, pack_line.id)]}) diff --git a/website_sale_product_pack/views/templates.xml b/website_sale_product_pack/views/templates.xml new file mode 100644 index 00000000..f4e1583a --- /dev/null +++ b/website_sale_product_pack/views/templates.xml @@ -0,0 +1,103 @@ + + + + + +