[IMP] website_sale: Optional product with variant suggestion

This commit is contained in:
Christophe Matthieu 2014-06-12 16:58:44 +02:00
parent 8319356630
commit f62673c266
7 changed files with 264 additions and 167 deletions

View File

@ -191,6 +191,7 @@ class website_sale(http.Controller):
def product(self, product, category='', search='', **kwargs):
cr, uid, context, pool = request.cr, request.uid, request.context, request.registry
category_obj = pool['product.public.category']
template_obj = pool['product.template']
context.update(active_id=product.id)
@ -208,20 +209,13 @@ class website_sale(http.Controller):
if not context.get('pricelist'):
context['pricelist'] = int(self.get_pricelist())
product = request.registry.get('product.template').browse(request.cr, request.uid, int(product), context=context)
product = template_obj.browse(request.cr, request.uid, int(product), context=context)
variants = [[p.id, map(int, p.attribute_value_ids), p.price] for p in product.product_variant_ids]
optional_products = {}
for o in product.optional_product_ids:
if not optional_products.get(o.product_tmpl_id.id):
optional_products[o.product_tmpl_id.id] = {
'product_tmpl_id': o.product_tmpl_id,
'product_ids': []
}
if o not in optional_products[o.product_tmpl_id.id]:
optional_products[o.product_tmpl_id.id]['product_ids'].append(o)
optional_product_ids = []
for p in product.optional_product_ids:
ctx = context.copy()
ctx.update(active_id=p.id)
optional_product_ids.append(template_obj.browse(cr, uid, p.id, context=ctx))
values = {
'search': search,
@ -233,8 +227,7 @@ class website_sale(http.Controller):
'category_list': category_list,
'main_object': product,
'product': product,
'variants': variants,
'optional_products': optional_products
'optional_product_ids': optional_product_ids
}
return request.website.render("website_sale.product", values)
@ -270,19 +263,16 @@ class website_sale(http.Controller):
def cart_update(self, product_id, add_qty=1, set_qty=0, **kw):
cr, uid, context, pool = request.cr, request.uid, request.context, request.registry
order = request.website.sale_get_order(force_create=1)
print kw
for key,val in kw.items():
if 'option-' in key:
order._cart_update(product_id=int(key.split("-")[1]), add_qty=1)
order._cart_update(product_id=int(product_id), add_qty=add_qty, set_qty=set_qty)
if add_qty or set_qty:
order._cart_update(product_id=int(product_id), add_qty=int(add_qty), set_qty=int(set_qty))
return request.redirect("/shop/cart")
@http.route(['/shop/cart/update_json'], type='json', auth="public", methods=['POST'], website=True)
def cart_update_json(self, product_id, line_id, add_qty=None, set_qty=None):
def cart_update_json(self, product_id, line_id, add_qty=None, set_qty=None, display=True):
order = request.website.sale_get_order(force_create=1)
quantity = order._cart_update(product_id=product_id, line_id=line_id, add_qty=add_qty, set_qty=set_qty)
if not display:
return None
return {
'quantity': quantity,
'cart_quantity': order.cart_quantity,

View File

@ -132,7 +132,7 @@ class product_template(osv.Model):
'website_sequence': fields.integer('Sequence', help="Determine the display order in the Website E-commerce"),
'website_url': fields.function(_website_url, string="Website url", type="char"),
'public_categ_ids': fields.many2many('product.public.category', string='Public Category', help="Those categories are used to group similar products for e-commerce."),
'optional_product_ids': fields.many2many('product.product', string='Optional Products'),
'optional_product_ids': fields.many2many('product.template','product_optional_rel','src_id','dest_id',string='Optional Products'),
}
def _defaults_website_sequence(self, cr, uid, *l, **kwargs):

View File

@ -302,10 +302,12 @@
color: #cccccc;
}
.js_add_cart_variants label.css_not_available {
opacity: 0.3;
opacity: 0.6;
}
.js_add_cart_variants label.css_not_available input {
opacity: 0;
.js_add_cart_variants label.css_attribute_color.css_not_available {
opacity: 1;
background-image: url("/website_sale/static/src/img/redcross.png");
background-size: cover;
}
.product_detail_img {

View File

@ -261,9 +261,11 @@
option.css_not_available
color: #ccc
label.css_not_available
opacity: 0.3
input
opacity: 0
opacity: 0.6
label.css_attribute_color.css_not_available
opacity: 1
background-image: url('/website_sale/static/src/img/redcross.png')
background-size: cover
.product_detail_img
margin-left: auto

Binary file not shown.

After

Width:  |  Height:  |  Size: 669 B

View File

@ -38,7 +38,8 @@ $(document).ready(function () {
ev.preventDefault();
var $link = $(ev.currentTarget);
var $input = $link.parent().parent().find("input");
$input.val(($link.has(".fa-minus").length ? -1 : 1) + parseFloat($input.val(),10));
var quantity = ($link.has(".fa-minus").length ? -1 : 1) + parseFloat($input.val(),10);
$input.val(quantity > 0 ? quantity : 0);
$input.change();
return false;
});
@ -50,18 +51,10 @@ $(document).ready(function () {
$(this).closest("form").submit();
});
// modal to select optional product in my cart
$("#modal_optional_products label").mousedown(function(event) {
$(event.currentTarget).parents('li.optional_product_tmpl:first').find("input[type=checkbox]").each(function () {
if($(this).parent()[0] != event.currentTarget) $(this).removeAttr('checked');
});
});
// change price when they are variants
var $price = $(".oe_price .oe_currency_value");
$('form.js_add_cart_json label').on('mouseup', function (ev) {
ev.preventDefault();
var $label = $(ev.currentTarget);
var $label = $(this);
var $price = $label.parents("form:first").find(".oe_price .oe_currency_value");
if (!$price.data("price")) {
$price.data("price", parseFloat($price.text()));
}
@ -75,53 +68,82 @@ $(document).ready(function () {
$('.css_attribute_color:has(input:checked)').addClass("active");
});
var $form_var = $('form.js_add_cart_variants');
var variant_ids = $form_var.data("attribute_value_ids");
$form_var.on('change', 'input.js_variant_change, select.js_variant_change', function (ev) {
$('input.js_variant_change, select.js_variant_change').change(function (ev) {
var $ul = $(this).parents('ul.js_add_cart_variants:first');
var $parent = $ul.parents('.js_product:first');
var $porduct_id = $parent.find('input.product_id, input.optional_product_id').first();
var $price = $parent.find(".oe_price .oe_currency_value:first");
var variant_ids = $ul.data("attribute_value_ids");
var values = [];
$form_var.find("label").removeClass("text-muted css_not_available");
$form_var.find(".a-submit").removeProp("disabled");
$form_var.find('input.js_variant_change:checked, select').each(function () {
$parent.find('input.js_variant_change:checked, select.js_variant_change').each(function () {
values.push(+$(this).val());
});
var available = false;
$parent.find("label").removeClass("text-muted css_not_available");
var product_id = false;
for (var k in variant_ids) {
if (_.isEqual(variant_ids[k][1], values)) {
var dec = variant_ids[k][2] % 1;
$('input[name="product_id"]').val(variant_ids[k][0]);
$price.html(variant_ids[k][2] + (dec < 0.01 ? ".00" : (dec < 1 ? "0" : "") ));
available = true;
var dec = ((variant_ids[k][2] % 1) * 100) | 0;
$price.html(variant_ids[k][2] + (dec ? '' : '.0') + (dec%10 ? '' : '0'));
product_id = variant_ids[k][0];
break;
}
}
$form_var.find("input.js_variant_change:radio, select.js_variant_change").each(function () {
var id = +$(this).val();
$parent.find("input.js_variant_change:radio, select.js_variant_change").each(function () {
var $input = $(this);
var id = +$input.val();
var values = [id];
$form_var.find(">ul>li:not(:has(input.js_variant_change[value='" + id + "'])) input.js_variant_change:checked, select").each(function () {
$parent.find("ul:not(:has(input.js_variant_change[value='" + id + "'])) input.js_variant_change:checked, select").each(function () {
values.push(+$(this).val());
});
for (var k in variant_ids) {
if (!_.difference(values, variant_ids[k][1]).length) {
return;
}
}
$(this).parents("label:not(.css_attribute_color):first").addClass("text-muted");
$(this).parents("label.css_attribute_color:first").addClass("css_not_available");
$(this).find("option[value='" + id + "']").addClass("css_not_available");
$input.parents("label:first").addClass("css_not_available");
$input.find("option[value='" + id + "']").addClass("css_not_available");
});
if (available) {
$(".oe_price_h4").removeClass("hidden");
$(".oe_not_available").addClass("hidden");
if (product_id) {
$parent.removeClass("oe_not_available");
$porduct_id.val(product_id);
$parent.find(".js_check_product").removeAttr("disabled");
} else {
$(".oe_price_h4").addClass("hidden");
$(".oe_not_available").removeClass("hidden");
$form_var.find('input[name="product_id"]').val(0);
$form_var.find(".js_check_product").prop("disabled", "disabled");
$parent.addClass("oe_not_available");
$porduct_id.val(0);
$parent.find(".js_check_product").attr("disabled", "disabled");
}
});
$form_var.find("input.js_variant_change:first").trigger('change');
$('ul.js_add_cart_variants').each(function () {
$('input.js_variant_change, select.js_variant_change', this).first().trigger('change');
});
$('#product_detail form[action^="/shop/cart/update"] .a-submit').off("click").click(function (event) {
event.preventDefault();
var $link = $(this);
var $form = $link.parents("form:first");
var defs = [];
$link.attr('disabled', 'disabled');
$('.js_product', $form).each(function () {
var product_id = parseInt($('input.optional_product_id', this).val(),10);
var quantity = parseInt($('input.js_quantity', this).val(),10);
if (product_id && quantity) {
defs.push(openerp.jsonRpc("/shop/cart/update_json", 'call', {
'line_id': null,
'product_id': product_id,
'add_qty': quantity,
'display': false}));
}
});
$.when.apply($.when, defs).then(function () {
$form.submit();
});
return false;
});
});

View File

@ -387,11 +387,14 @@
<t t-call="website_sale.product_price"/>
<a t-if="not optional_products" class="btn btn-primary btn-lg mt8 a-submit js_check_product">Add to Cart</a>
<a t-if="optional_products" class="btn btn-primary btn-lg mt8 js_check_product" href="#" data-toggle="modal" data-target="#modal_optional_products">Add to Cart</a>
<t t-call="website_sale.product_modal_optional_products"/>
<t t-name="button">
<a t-if="not optional_product_ids" class="btn btn-primary btn-lg mt8 a-submit js_check_product">Add to Cart</a>
<a t-if="optional_product_ids" class="btn btn-primary btn-lg mt8 js_check_product" href="#" data-toggle="modal" data-target="#modal_optional_products">Add to Cart</a>
<t t-if="optional_products">
<t t-call="website_sale.product_modal_optional_products"/>
</t>
</t>
</form>
<hr t-if="product.description_sale"/>
@ -411,45 +414,113 @@
</t>
</template>
<template id="product_preview" inherit_id="website_sale.product" optional="enabled" name="Product Preview">
<xpath expr="//t[@t-name='button']" position="replace">
<a t-if="optional_products" class="btn btn-primary btn-lg mt8 js_check_product" href="#" data-toggle="modal" data-target="#modal_optional_products">Add to Cart</a>
<t t-call="website_sale.product_modal_optional_products"/>
</xpath>
</template>
<template id="product_modal_optional_products">
<div t-if="optional_products" id="modal_optional_products" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div id="modal_optional_products" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button>
<h4 class="modal-title" id="myModalLabel">Choose your optional products</h4>
<h4 class="modal-title" id="myModalLabel">Product to add to your shopping cart</h4>
</div>
<div class="modal-body">
<ul>
<li class="optional_product_tmpl" t-foreach="optional_products" t-as="t">
<t t-set="o_tmpl" t-value="optional_products[t]"/>
<t t-if="len(o_tmpl['product_ids']) == 1">
<label><input t-att-name="'option-%s' % o_tmpl['product_ids'][0].id" type="checkbox"/> <span t-field="o_tmpl['product_tmpl_id'].name"/></label>
<span class="badge" t-if="o_tmpl['product_ids'][0].price">
+ <span t-field="o_tmpl['product_ids'][0].price" t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
}'/>
</span>
</t>
<t t-if="len(o_tmpl['product_ids']) > 1">
<span t-field="o_tmpl['product_tmpl_id'].name"/>
<ul>
<li t-foreach="o_tmpl['product_ids']" t-as="product_id">
<label><input t-att-name="'option-%s' % product_id.id" type="checkbox"/> <span t-esc="', '.join([value_id.name for value_id in product_id.attribute_value_ids])"/></label>
<span class="badge" t-if="product_id.price">
+ <span t-field="product_id.price" t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
}'/>
<div class="row">
<div class="col-xs-2">
<strong t-field="product.name"/>
<span t-field="product.image_medium" t-field-options='{"widget": "image", "class": "img-rounded shadow" }'/>
</div>
<div class="col-xs-6">
</div>
<div class="col-xs-2">
<strong>Price</strong><br/>
<t t-if="product.lst_price != product.price">
<span class="text-danger" style="text-decoration: line-through;"
t-field="product.lst_price"
t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
}'/><br/>
</t>
<span class="oe_price"
t-field="product.price"
t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
}'/>
</div>
<div class="col-xs-2">
<strong>Number</strong><br/>
<div class="input-group">
<span class="input-group-addon">
<a t-attf-href="#" class="mb8 js_add_cart_json">
<i class="fa fa-minus"></i>
</a>
</span>
</li>
</ul>
</t>
</li>
</ul>
<input type="text" class="js_quantity form-control" name="add_qty" value="1"/>
<span class="input-group-addon">
<a t-attf-href="#" class="mb8 float_left js_add_cart_json">
<i class="fa fa-plus"></i>
</a>
</span>
</div>
</div>
</div>
<hr/>
<t t-set="option_inc" t-value="0"/>
<div class="js_product" t-foreach="optional_product_ids" t-as="product">
<strong class="media-heading" t-field="product.name"/>
<div class="row">
<div class="col-xs-2">
<span t-field="product.image_small" t-field-options='{"widget": "image", "class": "img-rounded shadow" }'/>
</div>
<div class="col-xs-6">
<input type="hidden" class="optional_product_id" t-attf-name="optional-product-#{option_inc}" t-att-value="int(product.product_variant_ids[0]) if len(product.product_variant_ids) == 1 else '0'"/>
<t t-call="website_sale.variants"/>
</div>
<div class="col-xs-2">
<strong>Price</strong><br/>
<t t-if="product.lst_price != product.price">
<span class="text-danger" style="text-decoration: line-through;"
t-field="product.lst_price"
t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
}'/><br/>
</t>
<span class="oe_price"
t-field="product.price"
t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
}'/>
</div>
<div class="col-xs-2">
<strong>Number</strong><br/>
<div class="input-group">
<span class="input-group-addon">
<a t-attf-href="#" class="mb8 js_add_cart_json">
<i class="fa fa-minus"></i>
</a>
</span>
<input type="text" class="js_quantity form-control" t-attf-name="optional-quantity-#{option_inc}" value="0"/>
<span class="input-group-addon">
<a t-attf-href="#" class="mb8 float_left js_add_cart_json">
<i class="fa fa-plus"></i>
</a>
</span>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<a class="btn btn-primary a-submit">Continue</a>
@ -480,86 +551,96 @@
<span itemprop="price" style="display:none;" t-esc="product.price"/>
<span itemprop="priceCurrency" style="display:none;" t-esc="website.pricelist_id.currency_id.name"/>
</h4>
<h4 class="hidden oe_not_available bg-warning">Product not available</h4>
</div>
</template>
<template id="product_variants" inherit_id="website_sale.product" optional="enabled" name="Product Variants">
<xpath expr="//form[@action='/shop/cart/update']" position="replace">
<form action="/shop/cart/update" class="js_add_cart_variants" method="POST" t-att-data-attribute_value_ids="variants">
<input type="hidden" name="product_id" t-att-value="int(product.product_variant_ids[0]) if len(product.product_variant_ids) == 1 else '0'"/>
<ul class="nav nav-pills nav-stacked">
<t t-foreach="product.attribute_line_ids" t-as="variant_id">
<li t-if="len(variant_id.value_ids) > 1">
<form action="/shop/cart/update" class="js_add_cart_variants" method="POST">
<strong t-field="variant_id.attribute_id.name"/>
<t t-call="website_sale.product_modal_optional_products"/>
<t t-if="variant_id.attribute_id.type == 'select'">
<select class="form-control js_variant_change" t-att-name="'attribute-%s' % variant_id.attribute_id.id">
<t t-foreach="variant_id.value_ids" t-as="value_id">
<option t-att-value="value_id.id">
<div class="js_product">
<input type="hidden" class="product_id" name="product_id" t-att-value="int(product.product_variant_ids[0]) if len(product.product_variant_ids) == 1 else '0'"/>
<t t-call="website_sale.variants">
<t t-set="ul_class" t-value="'nav-stacked'"/>
</t>
<t t-call="website_sale.product_price"/>
<p class="hidden oe_not_available bg-danger" style="padding: 15px;">Product not available</p>
<a t-if="not optional_product_ids" disabled="disabled" class="btn btn-primary btn-lg mt8 a-submit js_check_product">Add to Cart</a>
<a t-if="optional_product_ids" disabled="disabled" class="btn btn-primary btn-lg mt8 js_check_product" href="#" data-toggle="modal" data-target="#modal_optional_products">Add to Cart</a>
</div>
</form>
</xpath>
</template>
<template id="variants">
<ul t-attf-class="nav nav-pills js_add_cart_variants #{ul_class}" t-att-data-attribute_value_ids="[[p.id, map(int, p.attribute_value_ids), p.price] for p in product.product_variant_ids]">
<t t-foreach="product.attribute_line_ids" t-as="variant_id">
<li t-if="len(variant_id.value_ids) > 1">
<strong t-field="variant_id.attribute_id.name"/>
<t t-if="variant_id.attribute_id.type == 'select'">
<select class="form-control js_variant_change" t-att-name="'attribute-%s-%s' % (product.id, variant_id.attribute_id.id)">
<t t-foreach="variant_id.value_ids" t-as="value_id">
<option t-att-value="value_id.id">
<span t-field="value_id.name"/>
<span t-if="value_id.price_extra">
<t t-esc="value_id.price_extra > 0 and '+' or ''"/><span t-field="value_id.price_extra" t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
}'/>
</span>
</option>
</t>
</select>
</t>
<t t-if="variant_id.attribute_id.type == 'radio'">
<ul class="list-unstyled">
<t t-set="inc" t-value="0"/>
<t t-foreach="variant_id.value_ids" t-as="value_id">
<li t-if="value_id.product_ids" class="form-group js_attribute_value" style="margin: 0;">
<label class="control-label" style="margin: 0 20px;">
<input type="radio" class="js_variant_change" t-att-checked="'checked' if not inc else ''" t-att-name="'attribute-%s-%s' % (product.id, variant_id.attribute_id.id)" t-att-value="value_id.id" style="vertical-align: top; margin-right: 10px;"/>
<span t-field="value_id.name"/>
<span t-if="value_id.price_extra">
<span class="badge" t-if="value_id.price_extra">
<t t-esc="value_id.price_extra > 0 and '+' or ''"/><span t-field="value_id.price_extra" t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
}'/>
</span>
</option>
</t>
</select>
</t>
<t t-if="variant_id.attribute_id.type == 'radio'">
<ul class="nav nav-pills nav-stacked">
<t t-set="inc" t-value="0"/>
<t t-foreach="variant_id.value_ids" t-as="value_id">
<li t-if="value_id.product_ids" class="form-group js_attribute_value" style="margin: 0;">
<label class="control-label" style="margin: 0 20px;">
<input type="radio" class="js_variant_change" t-att-checked="'checked' if not inc else ''" t-att-name="'attribute-%s' % variant_id.attribute_id.id" t-att-value="value_id.id" style="vertical-align: top; margin-right: 10px;"/>
<span t-field="value_id.name"/>
<span class="badge" t-if="value_id.price_extra">
<t t-esc="value_id.price_extra > 0 and '+' or ''"/><span t-field="value_id.price_extra" t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
}'/>
</span>
</label>
</li>
<t t-set="inc" t-value="inc+1"/>
</t>
</ul>
</t>
<t t-if="variant_id.attribute_id.type == 'color'">
<ul class="nav nav-pills nav-stacked">
<t t-set="inc" t-value="0"/>
<t t-foreach="variant_id.value_ids" t-as="value_id">
<label t-attf-style="background-color:#{value_id.color or value_id.name}"
t-attf-class="css_attribute_color #{'active' if not inc else ''}">
<input type="radio" class="js_variant_change"
t-att-checked="'checked' if not inc else ''"
t-att-name="'attribute-%s' % variant_id.attribute_id.id"
t-att-value="value_id.id"
t-att-title="value_id.name"/>
</label>
<t t-set="inc" t-value="inc+1"/>
</t>
</ul>
</t>
</label>
</li>
<t t-set="inc" t-value="inc+1"/>
</t>
</ul>
</t>
<t t-if="variant_id.attribute_id.type == 'color'">
<ul class="list-inline">
<t t-set="inc" t-value="0"/>
<li t-foreach="variant_id.value_ids" t-as="value_id">
<label t-attf-style="background-color:#{value_id.color or value_id.name}"
t-attf-class="css_attribute_color #{'active' if not inc else ''}">
<input type="radio" class="js_variant_change"
t-att-checked="'checked' if not inc else ''"
t-att-name="'attribute-%s-%s' % (product.id, variant_id.attribute_id.id)"
t-att-value="value_id.id"
t-att-title="value_id.name"/>
</label>
<t t-set="inc" t-value="inc+1"/>
</li>
</t>
</ul>
</ul>
</t>
<t t-call="website_sale.product_price"/>
<a t-if="not optional_products" class="btn btn-primary btn-lg mt8 a-submit">Add to Cart</a>
<a t-if="optional_products" class="btn btn-primary btn-lg mt8 " href="#" data-toggle="modal" data-target="#modal_optional_products">Add to Cart</a>
<t t-call="website_sale.product_modal_optional_products"/>
</form>
</xpath>
</li>
</t>
</ul>
</template>
<template id="recommended_products" inherit_id="website_sale.product" optional="enabled" name="Alternative Products">