diff --git a/addons/website_sale/models/res_config.py b/addons/website_sale/models/res_config.py index 67f73085efe..d30213724d3 100644 --- a/addons/website_sale/models/res_config.py +++ b/addons/website_sale/models/res_config.py @@ -36,8 +36,9 @@ class WebsiteConfigSettings(models.TransientModel): module_website_sale_options = fields.Boolean("Optional Products", help='Installs *e-Commerce Optional Products*') module_website_sale_digital = fields.Boolean("Digital Content") - module_website_sale_wishlist = fields.Boolean("Wishlists ", help='Installs *e-Commerce Wishlist*') + module_website_sale_comparison = fields.Boolean("Product Comparator", help='Installs *e-Commerce Comparator*') + module_sale_stock = fields.Boolean("Delivery Orders") module_portal = fields.Boolean("Activate the customer portal", help="""Give your customers access to their documents.""") diff --git a/addons/website_sale/views/res_config_view.xml b/addons/website_sale/views/res_config_view.xml index 2eaf895ca0f..dac56e614bc 100644 --- a/addons/website_sale/views/res_config_view.xml +++ b/addons/website_sale/views/res_config_view.xml @@ -157,6 +157,17 @@ +
+
+ +
+
+
+

Checkout Process

diff --git a/addons/website_sale_comparison/__init__.py b/addons/website_sale_comparison/__init__.py new file mode 100644 index 00000000000..5efbd253ebb --- /dev/null +++ b/addons/website_sale_comparison/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import models +import controllers diff --git a/addons/website_sale_comparison/__manifest__.py b/addons/website_sale_comparison/__manifest__.py new file mode 100644 index 00000000000..d84ed90f478 --- /dev/null +++ b/addons/website_sale_comparison/__manifest__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +{ + 'name': 'Product Comparison And Product Attribute Category', + 'description': 'Product Comparison, Product Attribute Category and specification table', + 'author': 'Odoo SA', + 'website': 'https://www.odoo.com', + 'category': 'Website', + 'version': '1.0', + 'depends': ['website_sale'], + 'data': [ + 'security/ir.model.access.csv', + 'views/website_sale_comparison_template.xml', + 'views/website_sale_comparison_view.xml', + ], + 'demo': [ + 'data/website_sale_comparison_data.xml', + 'data/website_sale_comparison_demo.xml', + ], + 'installable': True, +} diff --git a/addons/website_sale_comparison/controllers/__init__.py b/addons/website_sale_comparison/controllers/__init__.py new file mode 100644 index 00000000000..a84d81a791b --- /dev/null +++ b/addons/website_sale_comparison/controllers/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +import main diff --git a/addons/website_sale_comparison/controllers/main.py b/addons/website_sale_comparison/controllers/main.py new file mode 100644 index 00000000000..18c98f639e2 --- /dev/null +++ b/addons/website_sale_comparison/controllers/main.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +from odoo import http, _ +from odoo.http import request +from odoo.addons.website_sale.controllers.main import WebsiteSale +import json + + +class WebsiteSaleProductComparison(WebsiteSale): + + @http.route('/shop/compare/', type='http', auth="public", website=True) + def product_compare(self, **post): + values = {} + product_ids = [int(i) for i in post.get('products', '').split(',') if i.isdigit()] + if not product_ids: + return request.redirect("/shop") + # use search to check read access on each record/ids + products = request.env['product.product'].search([('id', 'in', product_ids)]) + values['products'] = products.with_context(display_default_code=False) + + res = {} + for num, product in enumerate(products): + for var in product.attribute_line_ids: + cat_name = var.attribute_id.category_id.name or _('Uncategorized') + att_name = var.attribute_id.name + if not product.attribute_value_ids: # create_variant = False + continue + res.setdefault(cat_name, {}).setdefault(att_name, [' - '] * len(products)) + val = product.attribute_value_ids.filtered(lambda x: x.attribute_id == var.attribute_id) + res[cat_name][att_name][num] = val[0].name + values['specs'] = res + values['compute_currency'] = self._get_compute_currency_and_context()[0] + return request.render("website_sale_comparison.product_compare", values) + + @http.route(['/shop/get_product_data'], type='json', auth="public", website=True) + def get_product_data(self, product_ids, cookies=None): + ret = {} + compute_currency, pricelist_context, _ = self._get_compute_currency_and_context() + prods = request.env['product.product'].with_context(pricelist_context, display_default_code=False).search([('id', 'in', product_ids)]) + + if cookies is not None: + ret['cookies'] = json.dumps(request.env['product.product'].search([('id', 'in', list(set(product_ids + cookies)))]).ids) + + prods.mapped('name') + for prod in prods: + ret[prod.id] = { + 'render': request.env['ir.ui.view'].render_template( + "website_sale_comparison.product_product", + {'compute_currency': compute_currency, 'product': prod, 'website': request.website} + ), + 'product': dict(id=prod.id, name=prod.name, display_name=prod.display_name), + } + return ret diff --git a/addons/website_sale_comparison/data/website_sale_comparison_data.xml b/addons/website_sale_comparison/data/website_sale_comparison_data.xml new file mode 100644 index 00000000000..42b029e6537 --- /dev/null +++ b/addons/website_sale_comparison/data/website_sale_comparison_data.xml @@ -0,0 +1,7 @@ + + + + General Features + 1 + + \ No newline at end of file diff --git a/addons/website_sale_comparison/data/website_sale_comparison_demo.xml b/addons/website_sale_comparison/data/website_sale_comparison_demo.xml new file mode 100644 index 00000000000..0b109d05ea1 --- /dev/null +++ b/addons/website_sale_comparison/data/website_sale_comparison_demo.xml @@ -0,0 +1,313 @@ + + + + + Camera + 3 + + + Dimensions + 7 + + + Display + 6 + + + Platform + 10 + + + Battery + 8 + + + Memory And Storage + 9 + + + Internet And Connectivity + 4 + + + Multimedia + 2 + + + Other Features + 11 + + + Warranty + 5 + + + + + + + + + + + + + + Brand + + + + Apple + + + + + Processor + + + + Dual Core A5 + + + + + Video Formats Supported + + + + .mp4, .avi, .m4v, H.264, AAC-LC, .mov, MPEG-4, .m4v, .mp4, .mov, Motion JPEG (M-JPEG) + + + + + Audio Formats Supported + + + + 3, 4, Protected AAC, MP3, MP3 VBR, AAX and AAX), Apple Lossless, WAV, AAC, HE-AAC, Audible (Formats 2, Audible Enhanced Audio, AIFF + + + + + Primary Camera + + + + 5 megapixels + + + + + Secondary Camera + + + + 1.2 megapixels + + + + + Weight + + + + 308 g + + + + + Dimensions + + + + 134.7 x 200 x 7.2 mm + + + + + 4G + + + + No + + + + + 3G + + + + Yes + + + + + Display Type + + + + 7.9 inch LCD Assistive Touchscreen with 1024 x 768 pixels + + + + + Other Display Features + + + + LED Backlit Multi-touch Display with IPS Technology, Fingerprint-resistant Oleophobic Coating + + + + + Warranty Summary + + + + 1 Year Warranty + + + + + Battery Type + + + + Lithium - Polymer + + + + + Play Time + + + + 10 hrs + + + + + Operating System + + + + iOS 6 + + + + + Sensors + + + + Accelerometer, Three-axis Gyro, Ambient Light Sensor, Digital Compass + + + + + Bluetooth + + + + v4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/website_sale_comparison/models/__init__.py b/addons/website_sale_comparison/models/__init__.py new file mode 100644 index 00000000000..7e9ada39cd5 --- /dev/null +++ b/addons/website_sale_comparison/models/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +import website_sale_comparison \ No newline at end of file diff --git a/addons/website_sale_comparison/models/website_sale_comparison.py b/addons/website_sale_comparison/models/website_sale_comparison.py new file mode 100644 index 00000000000..5a1ea69ca84 --- /dev/null +++ b/addons/website_sale_comparison/models/website_sale_comparison.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +from collections import OrderedDict + +from odoo import fields, models, _ + + +class ProductAttributeCategory(models.Model): + _name = "product.attribute.category" + _description = "Product Attribute Category" + _order = 'sequence' + + name = fields.Char("Category Name", required=True) + sequence = fields.Integer("Sequence", default=10) + + +class ProductAttribute(models.Model): + _inherit = 'product.attribute' + + category_id = fields.Many2one('product.attribute.category', string="Category", + help="Set a category to regroup similar attributes under " + "the same section in the Comparison page of eCommerce") + + +class ProductTemplate(models.Model): + _inherit = 'product.template' + + def get_variant_groups(self): + res = OrderedDict() + for var in self.attribute_line_ids: + res.setdefault(var.attribute_id.category_id.name or _('Uncategorized'), []).append(var) + return res diff --git a/addons/website_sale_comparison/security/ir.model.access.csv b/addons/website_sale_comparison/security/ir.model.access.csv new file mode 100644 index 00000000000..b19845b70d7 --- /dev/null +++ b/addons/website_sale_comparison/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_attribute_category_public,product.attribute.category public,model_product_attribute_category,,1,0,0,0 +access_product_attribute_category_public_saleman,product.attribute.category sale manager,model_product_attribute_category,sales_team.group_sale_manager,1,1,1,1 \ No newline at end of file diff --git a/addons/website_sale_comparison/static/src/js/website_sale_comparison.js b/addons/website_sale_comparison/static/src/js/website_sale_comparison.js new file mode 100644 index 00000000000..9286dc47778 --- /dev/null +++ b/addons/website_sale_comparison/static/src/js/website_sale_comparison.js @@ -0,0 +1,159 @@ +odoo.define('website_sale_comparison.comparison', function (require) { +"use strict"; + +var ajax = require('web.ajax'); +var core = require('web.core'); +var _t = core._t; +var utils = require('web.utils'); +var Widget = require('web.Widget'); +var website = require('web_editor.base'); +var website_sale_utils = require('website_sale.utils'); + +if(!$('.oe_website_sale').length) { + return $.Deferred().reject("DOM doesn't contain '.oe_website_sale'"); +} + +var qweb = core.qweb; +ajax.loadXML('/website_sale_comparison/static/src/xml/comparison.xml', qweb); + +var ProductComparison = Widget.extend({ + template:"product_comparison_template", + events: { + 'click .o_product_panel_header': 'toggle_panel', + }, + product_data: {}, + init: function(){ + this.comparelist_product_ids = JSON.parse(utils.get_cookie('comparelist_product_ids') || '[]'); + this.product_compare_limit = 4; + }, + start:function(){ + var self = this; + self.load_products(this.comparelist_product_ids).then(function() { + self.update_content(self.comparelist_product_ids, true); + if (self.comparelist_product_ids.length) { + $('.o_product_feature_panel').show(); + self.update_comparelist_view(); + } + }); + + self.popover = $('#comparelist .o_product_panel_header').popover({ + trigger: 'manual', + animation: true, + html: true, + title: function () { + return _t("Compare Products"); + }, + container: '.o_product_feature_panel', + placement: 'top', + template: '', + content: function() { + return $('#comparelist .o_product_panel_content').html(); + } + }); + $('.oe_website_sale .o_add_compare, .oe_website_sale .o_add_compare_dyn').click(function (e){ + if (self.comparelist_product_ids.length < self.product_compare_limit) { + var prod = $(this).data('product-product-id'); + if (e.currentTarget.classList.contains('o_add_compare_dyn')) { + prod = parseInt($(this).parent().find('.product_id').val()); + } + self.add_new_products(prod); + website_sale_utils.animate_clone($('#comparelist .o_product_panel_header'), $(this).closest('form'), -50, 10); + } else { + self.$('.o_comparelist_limit_warning').show(); + self.show_panel(true); + } + }); + + $('body').on('click', '.comparator-popover .o_comparelist_products .o_remove', function (e){ + self.rm_from_comparelist(e); + }); + $("#o_comparelist_table tr").click(function(){ + $($(this).data('target')).children().slideToggle(100); + $(this).find('.fa-chevron-circle-down, .fa-chevron-circle-right').toggleClass('fa-chevron-circle-down fa-chevron-circle-right'); + }); + }, + load_products:function(product_ids) { + var self = this; + return ajax.jsonRpc('/shop/get_product_data', 'call', { + 'product_ids': product_ids, + 'cookies': JSON.parse(utils.get_cookie('comparelist_product_ids') || '[]'), + }).then(function (data) { + self.comparelist_product_ids = JSON.parse(data.cookies); + delete data.cookies; + _.each(data, function(product) { + self.product_data[product.product.id] = product; + }); + }); + }, + toggle_panel: function() { + $('#comparelist .o_product_panel_header').popover('toggle'); + }, + show_panel: function(force) { + if ((!$('.comparator-popover').length) || force) { + $('#comparelist .o_product_panel_header').popover('show'); + } + }, + refresh_panel: function() { + if ($('.comparator-popover').length) { + $('#comparelist .o_product_panel_header').popover('show'); + } + }, + add_new_products:function(product_id){ + var self = this; + $('.o_product_feature_panel').show(); + if (!_.contains(self.comparelist_product_ids, product_id)) { + self.comparelist_product_ids.push(product_id); + if(_.has(self.product_data, product_id)){ + self.update_content([product_id], false); + } else { + self.load_products([product_id]).then(function(){ + self.update_content([product_id], false); + }); + } + } + self.update_cookie(); + }, + update_content:function(product_ids, reset) { + var self = this; + if (reset) { + self.$('.o_comparelist_products .o_product_row').remove(); + } + _.each(product_ids, function(res) { + var $template = self.product_data[res].render; + self.$('.o_comparelist_products').append($template); + }); + this.refresh_panel(); + }, + rm_from_comparelist: function(e){ + this.comparelist_product_ids = _.without(this.comparelist_product_ids, $(e.currentTarget).data('product_product_id')); + $(e.currentTarget).parents('.o_product_row').remove(); + this.update_cookie(); + $('.o_comparelist_limit_warning').hide(); + // force refresh to reposition popover + this.update_content(this.comparelist_product_ids, true); + }, + update_cookie: function(){ + document.cookie = 'comparelist_product_ids=' + JSON.stringify(this.comparelist_product_ids) + '; path=/'; + this.update_comparelist_view(); + }, + update_comparelist_view: function() { + this.$('.o_product_circle').text(this.comparelist_product_ids.length); + this.$('.o_comparelist_button').hide(); + if (_.isEmpty(this.comparelist_product_ids)) { + $('.o_product_feature_panel').hide(); + this.toggle_panel(); + } else { + this.$('.o_comparelist_products').show(); + if (this.comparelist_product_ids.length >=2) { + this.$('.o_comparelist_button').show(); + this.$('.o_comparelist_button a').attr('href', '/shop/compare/?products='+this.comparelist_product_ids.toString()); + } + } + } +}); + +website.ready().done(function() { + new ProductComparison().appendTo('.oe_website_sale'); +}); + +}); diff --git a/addons/website_sale_comparison/static/src/less/website_sale_comparison.less b/addons/website_sale_comparison/static/src/less/website_sale_comparison.less new file mode 100644 index 00000000000..ff167568e8b --- /dev/null +++ b/addons/website_sale_comparison/static/src/less/website_sale_comparison.less @@ -0,0 +1,54 @@ +.o_product_feature_panel { + position: fixed; + bottom: 0; + left: 50%; + transform: translateX(-50%); + z-index:10; + border-top-left-radius: 10px; + border-top-right-radius: 10px; + border-bottom: 0px; + padding: 10px 20px; + text-align: center; + border: 2px solid @brand-primary; + border-bottom: 0px; + background-color: white; + opacity: 0.95; + + .o_product_panel { + position: relative; + .o_product_panel_header { + color: @navbar-default-link-color; + margin: 0 10px 0 10px; + cursor: pointer; + .o_product_icon { + margin-right: 5px; + } + .o_product_text { + text-transform: uppercase; + vertical-align: middle; + font-size: 16px; + } + .o_product_circle { + vertical-align: 6px; + padding: 0 3px; + line-height: 14px; + } + } + .o_product_panel_content { + display: none !important; + } + } +} + +.oe_website_sale { + .product_summary > *{ + display: block; + margin: 15px 0 15px 0; + } + .table-comparator { + .o_product_comparison_collpase { + margin-right: 8px; + } + } +} + diff --git a/addons/website_sale_comparison/static/src/xml/comparison.xml b/addons/website_sale_comparison/static/src/xml/comparison.xml new file mode 100644 index 00000000000..76009b7c657 --- /dev/null +++ b/addons/website_sale_comparison/static/src/xml/comparison.xml @@ -0,0 +1,27 @@ + + + + + + + \ No newline at end of file diff --git a/addons/website_sale_comparison/views/website_sale_comparison_template.xml b/addons/website_sale_comparison/views/website_sale_comparison_template.xml new file mode 100644 index 00000000000..07f0616c1d4 --- /dev/null +++ b/addons/website_sale_comparison/views/website_sale_comparison_template.xml @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + diff --git a/addons/website_sale_comparison/views/website_sale_comparison_view.xml b/addons/website_sale_comparison/views/website_sale_comparison_view.xml new file mode 100644 index 00000000000..8be3711c313 --- /dev/null +++ b/addons/website_sale_comparison/views/website_sale_comparison_view.xml @@ -0,0 +1,50 @@ + + + + + product.attribute.category.tree + product.attribute.category + + + + + + + + + + Website attribute categories + ir.actions.act_window + product.attribute.category + tree + form + + + + + + product.attribute.tree.inherit + product.attribute + + + + + + + + + + product.attribute.form.inherit + product.attribute + + + + + + + + + +