diff --git a/website_sale_product_multi_website/README.rst b/website_sale_product_multi_website/README.rst new file mode 100644 index 0000000000..a47782636a --- /dev/null +++ b/website_sale_product_multi_website/README.rst @@ -0,0 +1,98 @@ +================================== +Website sale product multi website +================================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:0f39a547d2e1ceb92b5aa6b8751b67a1e263224a712a6519667c94d370f1a8f1 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/16.0/website_sale_product_multi_website + :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-16-0/e-commerce-16-0-website_sale_product_multi_website + :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=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +By default, Odoo allows to set just one (or all) website for products to be sold on eCommerces, by setting the field product_template.website_ids (a many2one field). This module allows to set more than one value (website) in this field (convert it in a many2many field). + +**Table of contents** + +.. contents:: + :local: + +Usage +===== +#. Just install it. + +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 +~~~~~~~ + +* Agile Business Group + +Contributors +~~~~~~~~~~~~ + +* Simone Rubino + + +* `Tecnativa `_: + + * João Marques + * Pilar Vargas + * Stefan Ungureanu + +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. + +.. |maintainer-stefan-tecnativa| image:: https://github.com/stefan-tecnativa.png?size=40px + :target: https://github.com/stefan-tecnativa + :alt: stefan-tecnativa +.. |maintainer-pilarvargas-tecnativa| image:: https://github.com/pilarvargas-tecnativa.png?size=40px + :target: https://github.com/pilarvargas-tecnativa + :alt: pilarvargas-tecnativa + +Current `maintainers `__: + +|maintainer-stefan-tecnativa| |maintainer-pilarvargas-tecnativa| + +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_product_multi_website/__init__.py b/website_sale_product_multi_website/__init__.py new file mode 100644 index 0000000000..cc6b6354ad --- /dev/null +++ b/website_sale_product_multi_website/__init__.py @@ -0,0 +1,2 @@ +from . import models +from .hooks import post_init_hook diff --git a/website_sale_product_multi_website/__manifest__.py b/website_sale_product_multi_website/__manifest__.py new file mode 100644 index 0000000000..07eaffda4f --- /dev/null +++ b/website_sale_product_multi_website/__manifest__.py @@ -0,0 +1,15 @@ +{ + "name": "Multi-website product", + "summary": "Show products in many web-sites", + "version": "18.0.1.0.0", + "category": "Website", + "author": "Odoo Community Association (OCA), Adhoc S.A.", + "license": "AGPL-3", + "application": False, + "installable": True, + "depends": ["website_sale"], + "data": ["views/product_template_views.xml"], + "demo": [], + "post_init_hook": "post_init_hook", + "website": "https://github.com/OCA/e-commerce", +} diff --git a/website_sale_product_multi_website/hooks.py b/website_sale_product_multi_website/hooks.py new file mode 100644 index 0000000000..1e54f97c06 --- /dev/null +++ b/website_sale_product_multi_website/hooks.py @@ -0,0 +1,3 @@ +def post_init_hook(env): + for rec in env["product.template"].with_context(active_test=False).search([]): + rec.website_ids += rec.website_id diff --git a/website_sale_product_multi_website/models/__init__.py b/website_sale_product_multi_website/models/__init__.py new file mode 100644 index 0000000000..eafa9a1069 --- /dev/null +++ b/website_sale_product_multi_website/models/__init__.py @@ -0,0 +1,2 @@ +from . import product_template +from . import website diff --git a/website_sale_product_multi_website/models/product_template.py b/website_sale_product_multi_website/models/product_template.py new file mode 100644 index 0000000000..5d8ff52dcd --- /dev/null +++ b/website_sale_product_multi_website/models/product_template.py @@ -0,0 +1,43 @@ +import logging + +from odoo import api, fields, models +from odoo.http import request + +_logger = logging.getLogger(__name__) + + +class ProductTemplate(models.Model): + _inherit = "product.template" + + website_ids = fields.Many2many("website", string="Websites") + + def can_access_from_current_website(self, website_id=False): + """We overwrite this method completely in order to + use the website_ids logic instead of website_id""" + website_id = website_id or request.website.id + for rec in self.filtered(lambda x: x.website_ids): + if website_id not in rec.website_ids.ids: + return False + return True + + @api.depends("is_published", "website_ids") + @api.depends_context("website_id") + def _compute_website_published(self): + """We overwrite this method completely in order to + use the website_ids logic instead of website_id""" + current_website_id = self._context.get("website_id") + for record in self: + if current_website_id: + record.website_published = record.is_published and ( + not record.website_ids + or current_website_id in record.website_ids.ids + ) + else: + record.website_published = record.is_published + + def _search_website_published(self, operator, value): + """We overwrite this method completely in order to + use the website_ids logic instead of website_id""" + return super( + ProductTemplate, self.with_context(multi_website_domain=True) + )._search_website_published(operator, value) diff --git a/website_sale_product_multi_website/models/website.py b/website_sale_product_multi_website/models/website.py new file mode 100644 index 0000000000..7eb587bb8e --- /dev/null +++ b/website_sale_product_multi_website/models/website.py @@ -0,0 +1,21 @@ +from odoo import api, models + + +class Website(models.Model): + _inherit = "website" + + @api.model + def website_domain(self, website_id=False): + if self._context.get("multi_website_domain"): + return [ + "|", + ("website_ids", "=", False), + ("website_ids", "=", website_id or self.id), + ] + return super().website_domain(website_id=website_id) + + def sale_product_domain(self): + """We add a context in order to change the way that website_domain behavies""" + return super( + Website, self.with_context(multi_website_domain=True) + ).sale_product_domain() diff --git a/website_sale_product_multi_website/pyproject.toml b/website_sale_product_multi_website/pyproject.toml new file mode 100644 index 0000000000..4231d0cccb --- /dev/null +++ b/website_sale_product_multi_website/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/website_sale_product_multi_website/static/tests/tours/website_sale_complete_flow.js b/website_sale_product_multi_website/static/tests/tours/website_sale_complete_flow.js new file mode 100644 index 0000000000..350ab92ad4 --- /dev/null +++ b/website_sale_product_multi_website/static/tests/tours/website_sale_complete_flow.js @@ -0,0 +1,419 @@ +odoo.define("website_sale_tour.tour", function (require) { + "use strict"; + + var tour = require("web_tour.tour"); + var rpc = require("web.rpc"); + + tour.register( + "website_sale_tour", + { + test: true, + url: "/shop?search=Storage Box", + }, + [ + // Testing b2c with Tax-Excluded Prices. + { + content: "Open product page", + trigger: '.oe_product_cart a:contains("Storage Box")', + }, + { + content: "Add one more storage box", + trigger: ".js_add_cart_json:eq(1)", + }, + { + content: "Check b2b Tax-Excluded Prices", + trigger: + ".product_price .oe_price .oe_currency_value:containsExact(79.00)", + isChecked: true, + }, + { + content: "Click on add to cart", + trigger: "#add_to_cart", + }, + { + content: "Check for 2 products in cart and proceed to checkout", + extra_trigger: + '#cart_products tr:contains("Storage Box") input.js_quantity:propValue(2)', + trigger: 'a[href*="/shop/checkout"]', + }, + { + content: "Check Price b2b subtotal", + trigger: + "tr#order_total_untaxed .oe_currency_value:containsExact(158.00)", + isChecked: true, + }, + { + content: "Check Price b2b Sale Tax(15%)", + trigger: "tr#order_total_taxes .oe_currency_value:containsExact(23.70)", + isChecked: true, + }, + { + content: "Check Price b2b Total amount", + trigger: "tr#order_total .oe_currency_value:containsExact(181.70)", + isChecked: true, + }, + { + content: "Fulfill billing address form", + trigger: 'select[name="country_id"]', + run: function () { + $('input[name="name"]').val("abc"); + $('input[name="phone"]').val("99999999"); + $('input[name="email"]').val("abc@odoo.com"); + $('input[name="street"]').val("SO1 Billing Street, 33"); + $('input[name="city"]').val("SO1BillingCity"); + $("#country_id option:eq(1)").attr("selected", true); + }, + }, + { + content: "Shipping address is not same as billing address", + trigger: "#shipping_use_same", + }, + { + content: "Click on next button", + trigger: '.oe_cart .btn:contains("Next")', + }, + { + content: "Fulfill shipping address form", + trigger: 'select[name="country_id"]', + extra_trigger: 'h2:contains("Shipping Address")', + run: function () { + $('input[name="name"]').val("def"); + $('input[name="phone"]').val("8888888888"); + $('input[name="street"]').val("17, SO1 Shipping Road"); + $('input[name="city"]').val("SO1ShippingCity"); + $("#country_id option:eq(1)").attr("selected", true); + }, + }, + { + content: "Click on next button", + trigger: '.oe_cart .btn:contains("Next")', + }, + { + content: + "Check selected billing address is same as typed in previous step", + trigger: + "#shipping_and_billing:contains(SO1 Billing Street, 33):contains(SO1BillingCity):contains(Afghanistan)", + isChecked: true, + }, + { + content: + "Check selected shipping address is same as typed in previous step", + trigger: + "#shipping_and_billing:contains(17, SO1 Shipping Road):contains(SO1ShippingCity):contains(Afghanistan)", + isChecked: true, + }, + { + content: "Click for edit address", + trigger: 'a:contains("Edit") i', + }, + { + content: "Click for edit billing address", + trigger: ".js_edit_address:first", + }, + { + content: "Change billing address form", + trigger: 'select[name="country_id"]', + extra_trigger: 'h2:contains("Your Address")', + run: function () { + $('input[name="name"]').val("abcd"); + $('input[name="phone"]').val("11111111"); + $('input[name="street"]').val("SO1 Billing Street Edited, 33"); + $('input[name="city"]').val("SO1BillingCityEdited"); + }, + }, + { + content: "Click on next button", + trigger: '.oe_cart .btn:contains("Next")', + }, + { + content: "Confirm Address", + trigger: 'a.btn:contains("Confirm")', + }, + { + content: + "Check selected billing address is same as typed in previous step", + trigger: + "#shipping_and_billing:contains(SO1 Billing Street Edited, 33):contains(SO1BillingCityEdited):contains(Afghanistan)", + isChecked: true, + }, + { + content: "Select `Wire Transfer` payment method", + trigger: '#payment_method label:contains("Wire Transfer")', + }, + { + content: "Pay Now", + extra_trigger: + '#payment_method label:contains("Wire Transfer") input:checked,#payment_method:not(:has("input:radio:visible"))', + trigger: 'button[id="o_payment_form_pay"]:visible:not(:disabled)', + }, + { + content: "Sign up", + trigger: '.oe_cart a:contains("Sign Up")', + }, + { + content: "Submit login", + trigger: ".oe_signup_form", + run: function () { + $('.oe_signup_form input[name="password"]').val("1admin@admin"); + $('.oe_signup_form input[name="confirm_password"]').val( + "1admin@admin" + ); + $(".oe_signup_form").submit(); + }, + }, + { + content: "See Quotations", + trigger: '.o_portal_docs a:contains("Quotations")', + }, + // Sign in as admin change config auth_signup -> b2b, sale_show_tax -> total and Logout + { + content: "Open Dropdown for logout", + trigger: '#top_menu li.dropdown:visible a:contains("abcd")', + }, + { + content: "Logout", + trigger: '#o_logout:contains("Logout")', + }, + { + content: "Sign in as admin", + trigger: '#top_menu li a b:contains("Sign in")', + }, + { + content: "Submit login", + trigger: ".oe_login_form", + run: function () { + $('.oe_login_form input[name="login"]').val("admin"); + $('.oe_login_form input[name="password"]').val("admin"); + $('.oe_login_form input[name="redirect"]').val("/"); + $(".oe_login_form").submit(); + }, + }, + { + content: + "Configuration Settings for 'Tax Included' and sign up 'On Invitation'", + extra_trigger: ".o_connected_user #wrapwrap", + trigger: "#wrapwrap", + run: function () { + var def1 = rpc.query({ + model: "res.config.settings", + method: "create", + args: [ + { + auth_signup_uninvited: "b2b", + show_line_subtotals_tax_selection: "tax_included", + group_show_line_subtotals_tax_excluded: false, + group_show_line_subtotals_tax_included: true, + }, + ], + }); + var def2 = def1.then(function (resId) { + return rpc.query({ + model: "res.config.settings", + method: "execute", + args: [[resId]], + }); + }); + def2.then(function () { + window.location.href = + "/web/session/logout?redirect=/shop?search=Storage Box"; + }); + }, + }, + // Testing b2b with Tax-Included Prices + { + content: "Open product page", + trigger: '.oe_product_cart a:contains("Storage Box")', + }, + { + content: "Add one more Storage Box", + trigger: ".js_add_cart_json:eq(1)", + }, + { + content: "Check b2c Tax-Included Prices", + trigger: + ".product_price .oe_price .oe_currency_value:containsExact(90.85)", + isChecked: true, + }, + { + content: "Click on add to cart", + trigger: "#add_to_cart", + }, + { + content: "Check for 2 products in cart and proceed to checkout", + extra_trigger: + '#cart_products tr:contains("Storage Box") input.js_quantity:propValue(2)', + trigger: 'a[href*="/shop/checkout"]', + }, + { + content: "Check Price b2c total", + trigger: + "tr#order_total_untaxed .oe_currency_value:containsExact(158.00)", + isChecked: true, + }, + { + content: "Check Price b2c Sale Tax(15%)", + trigger: "tr#order_total_taxes .oe_currency_value:containsExact(23.70)", + isChecked: true, + }, + { + content: "Check Price b2c Total amount", + trigger: "tr#order_total .oe_currency_value:containsExact(181.70)", + isChecked: true, + }, + { + content: "Click on Login Button", + trigger: '.oe_cart a.btn:contains("Log In")', + }, + { + content: "Submit login", + trigger: ".oe_login_form", + run: function () { + $('.oe_login_form input[name="login"]').val("abc@odoo.com"); + $('.oe_login_form input[name="password"]').val("1admin@admin"); + $(".oe_login_form").submit(); + }, + }, + { + content: "Add new shipping address", + trigger: '.one_kanban form[action^="/shop/address"] .btn', + }, + { + content: "Fulfill shipping address form", + trigger: 'select[name="country_id"]', + run: function () { + $('input[name="name"]').val("ghi"); + $('input[name="phone"]').val("7777777777"); + $('input[name="street"]').val("SO2New Shipping Street, 5"); + $('input[name="city"]').val("SO2NewShipping"); + $("#country_id option:eq(1)").attr("selected", true); + }, + }, + { + content: "Click on next button", + trigger: '.oe_cart .btn:contains("Next")', + }, + { + content: "Select `Wire Transfer` payment method", + trigger: '#payment_method label:contains("Wire Transfer")', + }, + { + content: "Pay Now", + extra_trigger: + '#payment_method label:contains("Wire Transfer") input:checked,#payment_method:not(:has("input:radio:visible"))', + trigger: 'button[id="o_payment_form_pay"]:visible:not(:disabled)', + }, + { + content: "Open Dropdown for See quotation", + extra_trigger: ".oe_cart .oe_website_sale_tx_status", + trigger: '#top_menu li.dropdown:visible a:contains("abc")', + }, + { + content: "My account", + extra_trigger: "#top_menu li.dropdown .js_usermenu.show", + trigger: '#top_menu .dropdown-menu a[href="/my/home"]:visible', + }, + { + content: "See Quotations", + trigger: + '.o_portal_docs a:contains("Quotations") .badge:containsExact(2)', + }, + + // Enable extra step on website checkout and check extra step on checkout process + { + content: "Open Dropdown for logout", + trigger: '#top_menu li.dropdown:visible a:contains("abc")', + }, + { + content: "Logout", + trigger: '#o_logout:contains("Logout")', + }, + { + content: "Sign in as admin", + trigger: '#top_menu li a b:contains("Sign in")', + }, + { + content: "Submit login", + trigger: ".oe_login_form", + run: function () { + $('.oe_login_form input[name="login"]').val("admin"); + $('.oe_login_form input[name="password"]').val("admin"); + $('.oe_login_form input[name="redirect"]').val("/shop/cart"); + $(".oe_login_form").submit(); + }, + }, + { + content: "Open Customize menu", + trigger: '.o_menu_sections a:contains("Customize")', + }, + { + content: "Enable Extra step", + trigger: 'a.dropdown-item label:contains("Extra Step Option")', + }, + { + content: "Open Dropdown for logout", + extra_trigger: '.progress-wizard-step:contains("Extra Info")', + trigger: '#top_menu li.dropdown:visible a:contains("Mitchell Admin")', + }, + { + content: "Logout", + trigger: '#o_logout:contains("Logout")', + }, + { + content: "Sign in as abc", + trigger: '#top_menu li a b:contains("Sign in")', + }, + { + content: "Submit login", + trigger: ".oe_login_form", + run: function () { + $('.oe_login_form input[name="login"]').val("abc@odoo.com"); + $('.oe_login_form input[name="password"]').val("1admin@admin"); + $('.oe_login_form input[name="redirect"]').val( + "/shop?search=Storage Box" + ); + $(".oe_login_form").submit(); + }, + }, + { + content: "Open product page", + trigger: '.oe_product_cart a:contains("Storage Box")', + }, + { + content: "Click on add to cart", + trigger: "#add_to_cart", + }, + { + content: "Proceed to checkout", + trigger: 'a[href*="/shop/checkout"]', + }, + { + content: "Click on next button", + trigger: '.oe_cart .btn:contains("Next")', + }, + { + content: + "Check selected billing address is same as typed in previous step", + trigger: + "#shipping_and_billing:contains(SO1 Billing Street Edited, 33):contains(SO1BillingCityEdited):contains(Afghanistan)", + isChecked: true, + }, + { + content: + "Check selected shipping address is same as typed in previous step", + trigger: + "#shipping_and_billing:contains(SO2New Shipping Street, 5):contains(SO2NewShipping):contains(Afghanistan)", + isChecked: true, + }, + { + content: "Select `Wire Transfer` payment method", + trigger: '#payment_method label:contains("Wire Transfer")', + }, + { + content: "Pay Now", + extra_trigger: + '#payment_method label:contains("Wire Transfer") input:checked,#payment_method:not(:has("input:radio:visible"))', + trigger: 'button[id="o_payment_form_pay"]:visible', + }, + ] + ); +}); diff --git a/website_sale_product_multi_website/tests/__init__.py b/website_sale_product_multi_website/tests/__init__.py new file mode 100644 index 0000000000..79d7d2aa85 --- /dev/null +++ b/website_sale_product_multi_website/tests/__init__.py @@ -0,0 +1,2 @@ +# TODO wip +# from . import website_sale_product_multi_website diff --git a/website_sale_product_multi_website/tests/website_sale_product_multi_website.py b/website_sale_product_multi_website/tests/website_sale_product_multi_website.py new file mode 100644 index 0000000000..255039db66 --- /dev/null +++ b/website_sale_product_multi_website/tests/website_sale_product_multi_website.py @@ -0,0 +1,23 @@ +import odoo.tests + + +class TestWebsiteSaleProductMultiWebsite(odoo.tests.HttpCase): + def setUp(self): + super().setUp() + # Create multiple websites + self.website0 = self.env["website"].create({"name": "web0"}) + self.website1 = self.env["website"].create({"name": "web1"}) + self.website2 = self.env["website"].create({"name": "web2"}) + + # create a product template + self.product_template = self.env["product.template"].create( + { + "name": "Test Product", + "is_published": True, + "list_price": 750, + } + ) + + def test_01(self): + """Prueba que si el producto no tiene websites + dicho producto esta disponible en todos los websites""" diff --git a/website_sale_product_multi_website/views/product_template_views.xml b/website_sale_product_multi_website/views/product_template_views.xml new file mode 100644 index 0000000000..2756ff0ef5 --- /dev/null +++ b/website_sale_product_multi_website/views/product_template_views.xml @@ -0,0 +1,56 @@ + + + product.template.inherit.view.form + product.template + + + + 1 + + + + + + + + product.product.website.inherit.view.list + product.product + + + + 1 + + + + + + + + product_template_view_tree.view.list + product.template + + + + 1 + + + + + + +