[ADD] website_sale_comparison: add comparison feature on eCommerce

This commit is contained in:
Jeremy Kersten
2017-02-24 20:29:17 +01:00
parent 2be56f9d81
commit 7b902fca02
16 changed files with 888 additions and 1 deletions
+2 -1
View File
@@ -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.""")
@@ -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>
@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import models
import controllers
@@ -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,
}
@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
import main
@@ -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
@@ -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>
@@ -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>
@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
import website_sale_comparison
@@ -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
@@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_product_attribute_category_public product.attribute.category public model_product_attribute_category 1 0 0 0
3 access_product_attribute_category_public_saleman product.attribute.category sale manager model_product_attribute_category sales_team.group_sale_manager 1 1 1 1
@@ -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');
});
});
@@ -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;
}
}
}
@@ -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>
@@ -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())&gt;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) &gt; 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 ) &gt; 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>&amp;nbsp;Add to Cart</a>
</form>
</div>
</td>
</thead>
<tbody>
<t t-foreach="specs.keys()" t-as="spec">
<t t-if="len(specs.keys())&gt;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 ) &gt; 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>
@@ -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>