diff --git a/setup/website_sale_product_multi_website/odoo/addons/website_sale_product_multi_website b/setup/website_sale_product_multi_website/odoo/addons/website_sale_product_multi_website new file mode 120000 index 0000000000..d59fd585ec --- /dev/null +++ b/setup/website_sale_product_multi_website/odoo/addons/website_sale_product_multi_website @@ -0,0 +1 @@ +../../../../website_sale_product_multi_website \ No newline at end of file diff --git a/setup/website_sale_product_multi_website/setup.py b/setup/website_sale_product_multi_website/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/website_sale_product_multi_website/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/website_sale_product_multi_website/README.rst b/website_sale_product_multi_website/README.rst new file mode 100644 index 0000000000..e69de29bb2 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..bf33b03e5a --- /dev/null +++ b/website_sale_product_multi_website/__manifest__.py @@ -0,0 +1,14 @@ +{ + "name": "Multi-website product", + "summary": "Show products in many web-sites", + "version": "13.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", +} diff --git a/website_sale_product_multi_website/hooks.py b/website_sale_product_multi_website/hooks.py new file mode 100644 index 0000000000..f667849a08 --- /dev/null +++ b/website_sale_product_multi_website/hooks.py @@ -0,0 +1,7 @@ +from odoo import SUPERUSER_ID, api + + +def post_init_hook(cr, registry): + env = api.Environment(cr, SUPERUSER_ID, {}) + 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..1b794382c0 --- /dev/null +++ b/website_sale_product_multi_website/models/product_template.py @@ -0,0 +1,35 @@ +from odoo import api, fields, models +from odoo.http import request +import logging + +_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..8ad55cbcd5 --- /dev/null +++ b/website_sale_product_multi_website/models/website.py @@ -0,0 +1,16 @@ +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/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..0aa9a2d250 --- /dev/null +++ b/website_sale_product_multi_website/static/tests/tours/website_sale_complete_flow.js @@ -0,0 +1,386 @@ +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)', + run: function () {}, // it's a check + }, + { + 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)', + run: function () {}, // it's a check + }, + { + content: "Check Price b2b Sale Tax(15%)", + trigger: 'tr#order_total_taxes .oe_currency_value:containsExact(23.70)', + run: function () {}, // it's a check + }, + { + content: "Check Price b2b Total amount", + trigger: 'tr#order_total .oe_currency_value:containsExact(181.70)', + run: function () {}, // it's a check + }, + { + 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)', + run: function () {}, // it's a check + }, + { + 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)', + run: function () {}, // it's a check + }, + { + 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)', + run: function () {}, // it's a check + }, + { + 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)', + run: function () {}, // it's a check + }, + { + 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)', + run: function () {}, // it's a check + }, + { + content: "Check Price b2c Sale Tax(15%)", + trigger: 'tr#order_total_taxes .oe_currency_value:containsExact(23.70)', + run: function () {}, // it's a check + }, + { + content: "Check Price b2c Total amount", + trigger: 'tr#order_total .oe_currency_value:containsExact(181.70)', + run: function () {}, // it's a check + }, + { + 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)', + run: function () {}, // it's a check + }, + { + 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)', + run: function () {}, // it's a check + }, + { + 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..da977396ff --- /dev/null +++ b/website_sale_product_multi_website/tests/website_sale_product_multi_website.py @@ -0,0 +1,22 @@ +from odoo.tests + + +class TestWebsiteSaleProductMultiWebsite(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..3291907205 --- /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.tree + product.product + + + + 1 + + + + + + + + product_template_view_tree.view.tree + product.template + + + + 1 + + + + + + +