diff options
| author | Jeremy Kersten <jke@odoo.com> | 2017-02-24 19:06:17 +0100 |
|---|---|---|
| committer | Jeremy Kersten <jke@odoo.com> | 2017-02-24 20:29:17 +0100 |
| commit | 7b902fca022d9e9eeba72b59874c6e8186c2ccda (patch) | |
| tree | 3b1852d0327f9e14718ae68dcc453d21908c0056 | |
| parent | 2be56f9d81a279ecfb5235d7764a397dd2e247e3 (diff) | |
[ADD] website_sale_comparison: add comparison feature on eCommerce
16 files changed, 888 insertions, 1 deletions
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 @@ </div> </div> </div> + <div class="col-xs-12 col-md-6 o_setting_box" id="comparator_option_setting"> + <div class="o_setting_left_pane"> + <field name="module_website_sale_comparison"/> + </div> + <div class="o_setting_right_pane"> + <label for="module_website_sale_comparison"/> + <div class="text-muted"> + Allow shoppers to compare products based on their attributes + </div> + </div> + </div> </div> <h2>Checkout Process</h2> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + <record id="product_attribute_category_general_features" model="product.attribute.category"> + <field name="name">General Features</field> + <field name="sequence">1</field> + </record> +</odoo>
\ 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + + <record id="product_attribute_category_1" model="product.attribute.category"> + <field name="name">Camera</field> + <field name="sequence">3</field> + </record> + <record id="product_attribute_category_2" model="product.attribute.category"> + <field name="name">Dimensions</field> + <field name="sequence">7</field> + </record> + <record id="product_attribute_category_3" model="product.attribute.category"> + <field name="name">Display</field> + <field name="sequence">6</field> + </record> + <record id="product_attribute_category_4" model="product.attribute.category"> + <field name="name">Platform</field> + <field name="sequence">10</field> + </record> + <record id="product_attribute_category_5" model="product.attribute.category"> + <field name="name">Battery</field> + <field name="sequence">8</field> + </record> + <record id="product_attribute_category_6" model="product.attribute.category"> + <field name="name">Memory And Storage</field> + <field name="sequence">9</field> + </record> + <record id="product_attribute_category_8" model="product.attribute.category"> + <field name="name">Internet And Connectivity</field> + <field name="sequence">4</field> + </record> + <record id="product_attribute_category_9" model="product.attribute.category"> + <field name="name">Multimedia</field> + <field name="sequence">2</field> + </record> + <record id="product_attribute_category_10" model="product.attribute.category"> + <field name="name">Other Features</field> + <field name="sequence">11</field> + </record> + <record id="product_attribute_category_11" model="product.attribute.category"> + <field name="name">Warranty</field> + <field name="sequence">5</field> + </record> + + <record id="product.product_attribute_1" model="product.attribute"> + <field name="category_id" ref="product_attribute_category_6"/> + </record> + <record id="product.product_attribute_2" model="product.attribute"> + <field name="category_id" ref="product_attribute_category_general_features"/> + </record> + <record id="product.product_attribute_3" model="product.attribute"> + <field name="category_id" ref="product_attribute_category_8"/> + </record> + + <record id="product_attribute_1" model="product.attribute"> + <field name="name">Brand</field> + <field name="category_id" ref="product_attribute_category_general_features"/> + </record> + <record id="product_attribute_value_1" model="product.attribute.value"> + <field name="name">Apple</field> + <field name="attribute_id" ref="product_attribute_1"/> + </record> + + <record id="product_attribute_2" model="product.attribute"> + <field name="name">Processor</field> + <field name="category_id" ref="product_attribute_category_general_features"/> + </record> + <record id="product_attribute_value_2" model="product.attribute.value"> + <field name="name">Dual Core A5</field> + <field name="attribute_id" ref="product_attribute_2"/> + </record> + + <record id="product_attribute_3" model="product.attribute"> + <field name="name">Video Formats Supported</field> + <field name="category_id" ref="product_attribute_category_9"/> + </record> + <record id="product_attribute_value_3" model="product.attribute.value"> + <field name="name">.mp4, .avi, .m4v, H.264, AAC-LC, .mov, MPEG-4, .m4v, .mp4, .mov, Motion JPEG (M-JPEG)</field> + <field name="attribute_id" ref="product_attribute_3"/> + </record> + + <record id="product_attribute_4" model="product.attribute"> + <field name="name">Audio Formats Supported</field> + <field name="category_id" ref="product_attribute_category_9"/> + </record> + <record id="product_attribute_value_4" model="product.attribute.value"> + <field name="name">3, 4, Protected AAC, MP3, MP3 VBR, AAX and AAX), Apple Lossless, WAV, AAC, HE-AAC, Audible (Formats 2, Audible Enhanced Audio, AIFF</field> + <field name="attribute_id" ref="product_attribute_4"/> + </record> + + <record id="product_attribute_5" model="product.attribute"> + <field name="name">Primary Camera</field> + <field name="category_id" ref="product_attribute_category_1"/> + </record> + <record id="product_attribute_value_5" model="product.attribute.value"> + <field name="name">5 megapixels</field> + <field name="attribute_id" ref="product_attribute_5"/> + </record> + + <record id="product_attribute_6" model="product.attribute"> + <field name="name">Secondary Camera</field> + <field name="category_id" ref="product_attribute_category_1"/> + </record> + <record id="product_attribute_value_6" model="product.attribute.value"> + <field name="name">1.2 megapixels</field> + <field name="attribute_id" ref="product_attribute_6"/> + </record> + + <record id="product_attribute_7" model="product.attribute"> + <field name="name">Weight</field> + <field name="category_id" ref="product_attribute_category_2"/> + </record> + <record id="product_attribute_value_7" model="product.attribute.value"> + <field name="name">308 g</field> + <field name="attribute_id" ref="product_attribute_7"/> + </record> + + <record id="product_attribute_8" model="product.attribute"> + <field name="name">Dimensions</field> + <field name="category_id" ref="product_attribute_category_2"/> + </record> + <record id="product_attribute_value_8" model="product.attribute.value"> + <field name="name">134.7 x 200 x 7.2 mm</field> + <field name="attribute_id" ref="product_attribute_8"/> + </record> + + <record id="product_attribute_9" model="product.attribute"> + <field name="name">4G</field> + <field name="category_id" ref="product_attribute_category_8"/> + </record> + <record id="product_attribute_value_9" model="product.attribute.value"> + <field name="name">No</field> + <field name="attribute_id" ref="product_attribute_9"/> + </record> + + <record id="product_attribute_10" model="product.attribute"> + <field name="name">3G</field> + <field name="category_id" ref="product_attribute_category_8"/> + </record> + <record id="product_attribute_value_10" model="product.attribute.value"> + <field name="name">Yes</field> + <field name="attribute_id" ref="product_attribute_10"/> + </record> + + <record id="product_attribute_11" model="product.attribute"> + <field name="name">Display Type</field> + <field name="category_id" ref="product_attribute_category_3"/> + </record> + <record id="product_attribute_value_11" model="product.attribute.value"> + <field name="name">7.9 inch LCD Assistive Touchscreen with 1024 x 768 pixels</field> + <field name="attribute_id" ref="product_attribute_11"/> + </record> + + <record id="product_attribute_12" model="product.attribute"> + <field name="name">Other Display Features</field> + <field name="category_id" ref="product_attribute_category_3"/> + </record> + <record id="product_attribute_value_12" model="product.attribute.value"> + <field name="name">LED Backlit Multi-touch Display with IPS Technology, Fingerprint-resistant Oleophobic Coating</field> + <field name="attribute_id" ref="product_attribute_12"/> + </record> + + <record id="product_attribute_13" model="product.attribute"> + <field name="name">Warranty Summary</field> + <field name="category_id" ref="product_attribute_category_11"/> + </record> + <record id="product_attribute_value_13" model="product.attribute.value"> + <field name="name">1 Year Warranty</field> + <field name="attribute_id" ref="product_attribute_13"/> + </record> + + <record id="product_attribute_14" model="product.attribute"> + <field name="name">Battery Type</field> + <field name="category_id" ref="product_attribute_category_5"/> + </record> + <record id="product_attribute_value_14" model="product.attribute.value"> + <field name="name">Lithium - Polymer</field> + <field name="attribute_id" ref="product_attribute_14"/> + </record> + + <record id="product_attribute_15" model="product.attribute"> + <field name="name">Play Time</field> + <field name="category_id" ref="product_attribute_category_5"/> + </record> + <record id="product_attribute_value_15" model="product.attribute.value"> + <field name="name">10 hrs</field> + <field name="attribute_id" ref="product_attribute_15"/> + </record> + + <record id="product_attribute_16" model="product.attribute"> + <field name="name">Operating System</field> + <field name="category_id" ref="product_attribute_category_4"/> + </record> + <record id="product_attribute_value_16" model="product.attribute.value"> + <field name="name">iOS 6</field> + <field name="attribute_id" ref="product_attribute_16"/> + </record> + + <record id="product_attribute_17" model="product.attribute"> + <field name="name">Sensors</field> + <field name="category_id" ref="product_attribute_category_4"/> + </record> + <record id="product_attribute_value_17" model="product.attribute.value"> + <field name="name">Accelerometer, Three-axis Gyro, Ambient Light Sensor, Digital Compass</field> + <field name="attribute_id" ref="product_attribute_17"/> + </record> + + <record id="product_attribute_18" model="product.attribute"> + <field name="name">Bluetooth</field> + <field name="category_id" ref="product_attribute_category_8"/> + </record> + <record id="product_attribute_value_18" model="product.attribute.value"> + <field name="name">v4</field> + <field name="attribute_id" ref="product_attribute_18"/> + </record> + + <record id="product_attribute_line_1" model="product.attribute.line"> + <field name="product_tmpl_id" ref="product.product_product_6_product_template"/> + <field name="attribute_id" ref="product_attribute_1"/> + <field name="value_ids" eval="[(6,0,[ref('product_attribute_value_1')])]"/> + </record> + <record id="product_attribute_line_2" model="product.attribute.line"> + <field name="product_tmpl_id" ref="product.product_product_6_product_template"/> + <field name="attribute_id" ref="product_attribute_2"/> + <field name="value_ids" eval="[(6,0,[ref('product_attribute_value_2')])]"/> + </record> + <record id="product_attribute_line_3" model="product.attribute.line"> + <field name="product_tmpl_id" ref="product.product_product_6_product_template"/> + <field name="attribute_id" ref="product_attribute_3"/> + <field name="value_ids" eval="[(6,0,[ref('product_attribute_value_3')])]"/> + </record> + <record id="product_attribute_line_4" model="product.attribute.line"> + <field name="product_tmpl_id" ref="product.product_product_6_product_template"/> + <field name="attribute_id" ref="product_attribute_4"/> + <field name="value_ids" eval="[(6,0,[ref('product_attribute_value_4')])]"/> + </record> + <record id="product_attribute_line_5" model="product.attribute.line"> + <field name="product_tmpl_id" ref="product.product_product_6_product_template"/> + <field name="attribute_id" ref="product_attribute_5"/> + <field name="value_ids" eval="[(6,0,[ref('product_attribute_value_5')])]"/> + </record> + <record id="product_attribute_line_6" model="product.attribute.line"> + <field name="product_tmpl_id" ref="product.product_product_6_product_template"/> + <field name="attribute_id" ref="product_attribute_6"/> + <field name="value_ids" eval="[(6,0,[ref('product_attribute_value_6')])]"/> + </record> + <record id="product_attribute_line_7" model="product.attribute.line"> + <field name="product_tmpl_id" ref="product.product_product_6_product_template"/> + <field name="attribute_id" ref="product_attribute_7"/> + <field name="value_ids" eval="[(6,0,[ref('product_attribute_value_7')])]"/> + </record> + <record id="product_attribute_line_8" model="product.attribute.line"> + <field name="product_tmpl_id" ref="product.product_product_6_product_template"/> + <field name="attribute_id" ref="product_attribute_8"/> + <field name="value_ids" eval="[(6,0,[ref('product_attribute_value_8')])]"/> + </record> + <record id="product_attribute_line_9" model="product.attribute.line"> + <field name="product_tmpl_id" ref="product.product_product_6_product_template"/> + <field name="attribute_id" ref="product_attribute_9"/> + <field name="value_ids" eval="[(6,0,[ref('product_attribute_value_9')])]"/> + </record> + <record id="product_attribute_line_10" model="product.attribute.line"> + <field name="product_tmpl_id" ref="product.product_product_6_product_template"/> + <field name="attribute_id" ref="product_attribute_10"/> + <field name="value_ids" eval="[(6,0,[ref('product_attribute_value_10')])]"/> + </record> + <record id="product_attribute_line_11" model="product.attribute.line"> + <field name="product_tmpl_id" ref="product.product_product_6_product_template"/> + <field name="attribute_id" ref="product_attribute_11"/> + <field name="value_ids" eval="[(6,0,[ref('product_attribute_value_11')])]"/> + </record> + <record id="product_attribute_line_12" model="product.attribute.line"> + <field name="product_tmpl_id" ref="product.product_product_6_product_template"/> + <field name="attribute_id" ref="product_attribute_12"/> + <field name="value_ids" eval="[(6,0,[ref('product_attribute_value_12')])]"/> + </record> + <record id="product_attribute_line_13" model="product.attribute.line"> + <field name="product_tmpl_id" ref="product.product_product_6_product_template"/> + <field name="attribute_id" ref="product_attribute_13"/> + <field name="value_ids" eval="[(6,0,[ref('product_attribute_value_13')])]"/> + </record> + <record id="product_attribute_line_14" model="product.attribute.line"> + <field name="product_tmpl_id" ref="product.product_product_6_product_template"/> + <field name="attribute_id" ref="product_attribute_14"/> + <field name="value_ids" eval="[(6,0,[ref('product_attribute_value_14')])]"/> + </record> + <record id="product_attribute_line_15" model="product.attribute.line"> + <field name="product_tmpl_id" ref="product.product_product_6_product_template"/> + <field name="attribute_id" ref="product_attribute_15"/> + <field name="value_ids" eval="[(6,0,[ref('product_attribute_value_15')])]"/> + </record> + <record id="product_attribute_line_16" model="product.attribute.line"> + <field name="product_tmpl_id" ref="product.product_product_6_product_template"/> + <field name="attribute_id" ref="product_attribute_16"/> + <field name="value_ids" eval="[(6,0,[ref('product_attribute_value_16')])]"/> + </record> + <record id="product_attribute_line_17" model="product.attribute.line"> + <field name="product_tmpl_id" ref="product.product_product_6_product_template"/> + <field name="attribute_id" ref="product_attribute_17"/> + <field name="value_ids" eval="[(6,0,[ref('product_attribute_value_17')])]"/> + </record> + <record id="product_attribute_line_18" model="product.attribute.line"> + <field name="product_tmpl_id" ref="product.product_product_6_product_template"/> + <field name="attribute_id" ref="product_attribute_18"/> + <field name="value_ids" eval="[(6,0,[ref('product_attribute_value_18')])]"/> + </record> + <record id="product_attribute_line_19" model="product.attribute.line"> + <field name="product_tmpl_id" ref="product.product_product_6_product_template"/> + <field name="attribute_id" ref="product.product_attribute_2"/> + <field name="value_ids" eval="[(6,0,[ref('product.product_attribute_value_3')])]"/> + </record> + +</odoo> 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: '<div style="width:600px;" class="popover comparator-popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>', + 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 @@ +<templates id="compare_products" xml:space="preserve"> + + <t t-name="product_comparison_template"> + <div class="o_product_feature_panel hidden-xs css_editable_mode_hidden" style="display:none"> + <span class="o_product_panel" id="comparelist"> + <span class="o_product_panel_header"> + <span class="o_product_icon"><i class="fa fa-exchange"></i></span> + <span class="o_product_text">Compare</span> + <span class="o_product_circle o_animate_blink label label-primary">0</span> + </span> + <span class="o_product_panel_content"> + <div class="o_comparelist_products"> + <div class="o_comparelist_limit_warning" style="display:none"> + <div class="o_shortlog alert alert-warning"> + <span><i class="fa fa-warning text-danger"></i> You can compare max 4 products.</span> + </div> + </div> + </div> + <div class="o_comparelist_button" style='display:none'> + <a class="btn btn-primary btn-block" href="#"><i class="fa fa-exchange mr4"></i>Compare</a> + </div> + </span> + </span> + </div> + </t> + +</templates>
\ 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + + <template id="assets_frontend" inherit_id="website.assets_frontend" name="Comparison assets frontend"> + <xpath expr="." position="inside"> + <link type="text/less" rel='stylesheet' href='/website_sale_comparison/static/src/less/website_sale_comparison.less'/> + <script type="text/javascript" src="/website_sale_comparison/static/src/js/website_sale_comparison.js"></script> + </xpath> + </template> + + <template id="add_to_compare" inherit_id="website_sale.products_item" active="True" customize_show="True" name="Comparison List"> + <xpath expr="//div[@class='product_price']" position="inside"> + <a class="hidden-xs btn btn-default btn-xs o_add_compare" title="Compare" t-att-data-product-product-id="product.product_variant_ids[0].id" data-action="o_comparelist"><span class="fa fa-exchange"></span></a> + </xpath> + </template> + + <template id="product_add_to_compare" name='Add to comparison in product page' inherit_id="website_sale.product"> + <xpath expr="//a[@id='add_to_cart']" position="after"> + <t t-if="product.env.ref('website_sale_comparison.add_to_compare').active"> + <a class="hidden-xs btn btn-default btn-lg mt8 o_add_compare_dyn" title="Compare" t-att-data-product-product-id="product.product_variant_ids[0].id" data-action="o_comparelist"><span class="fa fa-exchange"></span></a> + </t> + </xpath> + </template> + + <template id="product_attributes_body" inherit_id="website_sale.product" customize_show="True" active="True" name="Product attributes table"> + <xpath expr="//hr[@id='hr_product_attributes_simple']" position="replace"/> + <xpath expr="//p[@id='product_attributes_simple']" position="replace"/> + <xpath expr="//div[@id='product_full_description']" position="after"> + <section class="container" id="product_full_spec"> + <t t-set="atts" t-value="product.get_variant_groups()"/> + <t t-if='len(atts)'> + <h3 class="text-center mb32">Specifications for <t t-esc="product.name"/></h3> + <div class="row"> + <div class='col-md-8 col-md-offset-2' id='product_specifications'> + <table class='table table-striped table-condensed table-hover'> + <t t-foreach="atts.keys()" t-as="spec"> + <t t-if="len(atts.keys())>1"> + <tr class="success text-left breadcrumb clickable" data-toggle="collapse" t-att-data-target="'.'+spec.split()[0]"> + <th t-att-colspan="2"><t t-esc="spec"/></th> + </tr> + </t> + <tr t-foreach="atts[spec]" t-as="att" t-att-class="'collapse in ' + spec.split()[0]"> + <td t-esc="att.attribute_id.name"/> + <td> + <t t-set='or_separator'>or</t> + <t t-esc="(' %s ' % or_separator).join(att.value_ids.mapped('name'))"/> + </td> + </tr> + </t> + </table> + </div> + </div> + </t> + </section> + </xpath> + </template> + + <template id='recommended_product' inherit_id="website_sale.recommended_products" name="Alternative Products"> + <xpath expr="//h3" position="replace"> + <t t-set="ids" t-value="str(product.id) + ','+ ','.join([str(a) for a in product.alternative_product_ids.ids])"></t> + <div><span class='h3'>Suggested alternatives: </span><a class="btn btn-primary" t-attf-href="/shop/compare/?products=#{ids}"><i class="fa fa-exchange"></i> Compare</a></div> + </xpath> + </template> + + <template id="product_compare" name="Comparator Page"> + <t t-call="website.layout"> + <t t-set="additional_title">Shop Comparator</t> + <div id="wrap" class="js_sale"> + <div class="oe_structure"/> + <div class="container oe_website_sale"> + <section class="container"> + <h3>Comparator</h3> + <table class="table table-bordered table-hover text-center mt16 table-comparator" id="o_comparelist_table"> + <thead> + <td class='td-top-left'></td> + <td t-foreach="products" t-as="product"> + <a t-attf-href="/shop/compare/?products=#{','.join([str(id) for id in products._ids if id != product.id])}" t-att-data-product_product_id="product.id" class="o_comparelist_remove" t-if="len(products) > 2"> + <strong class='pull-right'>x</strong> + </a> + <a t-att-href="'/shop/product/%s' % slug(product.product_tmpl_id)"> + <img t-attf-src="/web/image/product.product/#{product.id}/image/200x150" class="img img-responsive" style="margin:auto;"/> + </a> + <div class='product_summary'> + <a class="o_product_comparison_table" t-attf-href="/shop/product/#{ slug(product.product_tmpl_id) }"> + <span t-field='product.display_name'></span><br/> + </a> + + <span> + <strong>Price:</strong> + <t t-if="(compute_currency(product.lst_price) - product.website_price ) > 0.01 and website.get_current_pricelist().discount_policy=='without_discount'"> + <del class="text-danger mr8" style="white-space: nowrap;" t-esc="compute_currency(product.website_public_price)" t-options="{'widget': 'monetary', 'display_currency': website.get_current_pricelist().currency_id, 'from_currency': website.currency_id}" /> + </t> + <span t-esc="product.website_price" t-options="{'widget': 'monetary', 'display_currency': website.currency_id}"></span> + </span> + + <form action="/shop/cart/update" method="post" class="text-center"> + <input name="product_id" t-att-value="product.id" type="hidden"/> + <a class="btn btn-primary btn-block a-submit"><i class="fa fa-shopping-cart"></i>&nbsp;Add to Cart</a> + </form> + </div> + </td> + </thead> + <tbody> + <t t-foreach="specs.keys()" t-as="spec"> + <t t-if="len(specs.keys())>1"> + <tr class="success text-left breadcrumb clickable" data-toggle="collapse" t-att-data-target="'.'+spec.split()[0]"> + <th t-att-colspan="len(products) + 1"><i class="fa fa-chevron-circle-down o_product_comparison_collpase"></i><t t-esc="spec"/></th> + </tr> + </t> + <tr t-foreach="specs[spec].keys()" t-as="att" t-att-class="'collapse in ' + spec.split()[0]"> + <td t-esc="att"/> + <td t-foreach="specs[spec][att]" t-as="val"><t t-esc="val"/></td> + </tr> + </t> + </tbody> + </table> + </section> + </div> + </div> + </t> + </template> + + <template id="product_product" name="Comparator - Product row in comparator popover"> + <div class="row mt8 o_product_row" t-att-data-category_ids="product.public_categ_ids.ids"> + <div class="col-sm-3 col-xs-3 mb8"> + <img class="img img-responsive" t-attf-src="/web/image/product.product/#{product.id}/image_small" style="max-height: 50px;" /> + </div> + <div class="col-sm-7 col-xs-7 mb8 text-left"> + <h5> + <strong><a t-attf-href="/shop/product/#{product.product_tmpl_id[0].id}"><t t-esc="product.display_name" /></a></strong> + </h5> + <h5 class="mt0 mb0"> + <strong> + <t t-if="(compute_currency(product.lst_price) - product.website_price ) > 0.01 and website.get_current_pricelist().discount_policy=='without_discount'"> + <del class="text-danger mr8" style="white-space: nowrap;" t-esc="compute_currency(product.website_public_price)" t-options="{'widget': 'monetary', 'display_currency': website.get_current_pricelist().currency_id, 'from_currency': website.currency_id}" /> + </t> + <span t-esc="product.website_price" t-options="{'widget': 'monetary', 'display_currency': website.currency_id}"></span> + </strong> + </h5> + </div> + <div class="col-sm-1 col-xs-1 mt32"> + <a href='#' class="o_remove" title="Remove" t-att-data-product_product_id="product.id"> + <i class="fa fa-trash" ></i> + </a> + </div> + </div> + </template> + +</odoo> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + + <record id="product_attribute_category_tree_view" model="ir.ui.view"> + <field name="name">product.attribute.category.tree</field> + <field name="model">product.attribute.category</field> + <field name="arch" type="xml"> + <tree string="Product Attribute Category" editable="top"> + <field name="sequence"/> + <field name="name"/> + </tree> + </field> + </record> + + <record id="product_attribute_category_action" model="ir.actions.act_window"> + <field name="name">Website attribute categories</field> + <field name="type">ir.actions.act_window</field> + <field name="res_model">product.attribute.category</field> + <field name="view_mode">tree</field> + <field name="view_type">form</field> + </record> + + <menuitem action="product_attribute_category_action" + id="menu_attribute_category_action" + parent="website_sale.menu_ecommerce_settings" sequence="11"/> + + <record id="product_attribute_tree_view_inherit" model="ir.ui.view"> + <field name="name">product.attribute.tree.inherit</field> + <field name="model">product.attribute</field> + <field name="inherit_id" ref="product.attribute_tree_view"/> + <field name="arch" type="xml"> + <field name="name" position="after"> + <field name="category_id"/> + </field> + </field> + </record> + + <record id="product_attribute_view_form" model="ir.ui.view"> + <field name="name">product.attribute.form.inherit</field> + <field name="model">product.attribute</field> + <field name="inherit_id" ref="website_sale.product_attribute_view_form"/> + <field name="priority" eval="8"/> + <field name="arch" type="xml"> + <field name='name' position='after'> + <field name="category_id"/> + </field> + </field> + </record> + +</odoo> |
