[MERGE] website_sale optional-products

This commit is contained in:
Antony Lesuisse 2014-06-26 12:31:15 +02:00
commit 893aace122
41 changed files with 1048 additions and 387 deletions

View File

@ -9,9 +9,10 @@
<record model="ir.ui.view" id="membership_product_search_form_view">
<field name="name">membership.product.search.form</field>
<field name="model">product.template</field>
<field name="priority">50</field>
<field name="arch" type="xml">
<search string="Membership Products">
<field name="name" filter_domain="['|',('name','ilike',self),('code','ilike',self)]" string="Membership Product"/>
<field name="name" string="Membership Product"/>
<filter string="Inactive" icon="terp-gdu-smart-failing" domain="[('active','=',0)]"/>
<field name="categ_id" operator="child_of"/>
<group expand='0' string='Group by...'>

View File

@ -816,10 +816,8 @@ class product_product(osv.osv):
uom.id, product.list_price, context['uom'])
else:
res[product.id] = product.list_price
price_extra = 0.0
for variant_id in product.attribute_value_ids:
price_extra += variant_id.price_extra
res[product.id] = (res[product.id] or 0.0) + price_extra
res[product.id] = res[product.id] + product.price_extra
return res
def _get_partner_code_name(self, cr, uid, ids, product, partner_id, context=None):

View File

@ -283,15 +283,30 @@
<record id="product_kanban_view" model="ir.ui.view">
<field name="name">Product Kanban</field>
<field name="model">product.product</field>
<field name="mode">primary</field>
<field name="inherit_id" ref="product.product_template_kanban_view"/>
<field name="arch" type="xml">
<field name="name" position="after">
<field name="attribute_value_ids"/>
</field>
<xpath expr="//img[@class='oe_kanban_image']" position="replace">
<img t-att-src="kanban_image('product.product', 'image_small', record.id.value)" class="oe_kanban_image"/>
</xpath>
<kanban>
<field name="image_small"/>
<field name="lst_price"/>
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_vignette oe_semantic_html_override">
<a type="open"><img t-att-src="kanban_image('product.product', 'image_small', record.id.value)" class="oe_kanban_image"/></a>
<div class="oe_kanban_details">
<h4>
<a type="open">
<field name="name"/>
<field name="attribute_value_ids"/>
</a>
</h4>
<div name="tags"/>
<ul>
<li>Price: <field name="lst_price"></field></li>
</ul>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
@ -343,7 +358,7 @@
<div class="oe_right">
<button class="oe_inline oe_stat_button" string="Variant Prices" name="%(product.variants_template_action)d" type="action" icon="fa-strikethrough"/>
<button class="oe_inline oe_stat_button" name="%(product.product_variant_action)d" type="action" icon="fa-sitemap">
<field string="List of Variants" name="product_variant_count" widget="statinfo" />
<field string="Variants" name="product_variant_count" widget="statinfo" />
</button>
</div>
</field>
@ -361,7 +376,7 @@
<field name="product_variant_ids"/>
</field>
<h4 position="after">
<a name="%(product.product_variant_action)d" type="action" t-if="!record.is_product_variant.raw_value &amp; record.product_variant_count.raw_value&gt;1">
<a name="%(product.product_variant_action)d" type="action">
<t t-esc="record.product_variant_count.value"/> Variants
</a>
</h4>

View File

@ -25,14 +25,13 @@
</record>
<!-- Product -->
<record id="product_normal_form_view" model="ir.ui.view">
<field name="name">product.normal.form</field>
<field name="model">product.product</field>
<field name="inherit_id" ref="product.product_normal_form_view"/>
<record id="product_template_form_view" model="ir.ui.view">
<field name="name">product.template.form</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_form_view"/>
<field name="arch" type="xml">
<field name="product_manager" position="after">
<field name="intrastat_id"
attrs="{'readonly': [('is_product_variant', '=', False)]}"/>
<field name="intrastat_id"/>
</field>
</field>
</record>

View File

@ -62,7 +62,7 @@ class ir_http(orm.AbstractModel):
self.geo_ip_resolver = GeoIP.open('/usr/share/GeoIP/GeoIP.dat', GeoIP.GEOIP_STANDARD)
except ImportError:
self.geo_ip_resolver = False
if self.geo_ip_resolver:
if self.geo_ip_resolver and request.httprequest.remote_addr:
record = self.geo_ip_resolver.record_by_addr(request.httprequest.remote_addr) or {}
request.session['geoip'] = record

View File

@ -452,7 +452,6 @@
openerp.jsonRpc('/website/customize_template_get', 'call', { 'xml_id': view_name }).then(
function(result) {
_.each(result, function (item) {
if (item.xml_id === "website.debugger" && !window.location.search.match(/[&?]debug(&|$)/)) return;
if (item.header) {
menu.append('<li class="dropdown-header">' + item.name + '</li>');
} else {

View File

@ -251,12 +251,6 @@
<script type="text/javascript" src="/website/static/src/js/jQuery.transfo.js"></script>
</template>
<template id="debugger" inherit_id="website.layout" optional="disabled" name="Debugger &amp; Tests">
<xpath expr='//t[@name="layout_head"]' position="after">
<t t-set="debugger_hook" t-value="1" />
</xpath>
</template>
<template id="login_layout" inherit_id="web.login_layout" name="Website Login Layout">
<xpath expr="t" position="replace">
<t t-call="website.layout">

View File

@ -8,7 +8,7 @@
</xpath>
</template>
<template id="editor_head" inherit_id="website.assets_editor" name="Event Editor" groups="event.group_event_manager">
<template id="assets_editor" inherit_id="website.assets_editor" name="Event Editor" groups="event.group_event_manager">
<xpath expr="." position="inside">
<script type="text/javascript" src="/website_event/static/src/js/website_event.editor.js"></script>
<script type="text/javascript" src="/website_event/static/src/js/website.tour.event.js"></script>

View File

@ -7,21 +7,18 @@ from openerp.tools.translate import _
class sale_order(osv.Model):
_inherit = "sale.order"
def _cart_find_product_line(self, cr, uid, ids, product_id=None, line_id=None, context=None):
def _cart_find_product_line(self, cr, uid, ids, product_id=None, line_id=None, context=None, **kwargs):
line_ids = super(sale_order, self)._cart_find_product_line(cr, uid, ids, product_id, line_id, context=context)
if line_id:
return line_ids
for so in self.browse(cr, uid, ids, context=context):
order_line_id = None
domain = [('order_id', '=', so.id), ('product_id', '=', product_id)]
if line_id:
domain += [('id', '=', line_id)]
elif context.get("event_ticket_id"):
domain = [('id', 'in', line_ids)]
if context.get("event_ticket_id"):
domain += [('event_ticket_id', '=', context.get("event_ticket_id"))]
order_line_ids = self.pool.get('sale.order.line').search(cr, SUPERUSER_ID, domain, context=context)
if order_line_ids:
order_line_id = order_line_ids[0]
return order_line_id
return self.pool.get('sale.order.line').search(cr, SUPERUSER_ID, domain, context=context)
def _website_product_id_change(self, cr, uid, ids, order_id, product_id, line_id=None, context=None):
values = super(sale_order,self)._website_product_id_change(cr, uid, ids, order_id, product_id, line_id=None, context=None)
values = super(sale_order,self)._website_product_id_change(cr, uid, ids, order_id, product_id, line_id=line_id, context=None)
event_ticket_id = None
if context.get("event_ticket_id"):

View File

@ -4,7 +4,6 @@ import openerp.tests
inject = [
("openerp.Tour", os.path.join(os.path.dirname(__file__), '../../web/static/src/js/tour.js')),
("openerp.Tour.ShopTest", os.path.join(os.path.dirname(__file__), "../static/src/js/website.tour.event_sale.js")),
]
@openerp.tests.common.at_install(False)

View File

@ -2,13 +2,12 @@
<openerp>
<data>
<template id="debugger" inherit_id="website.debugger" name="Event Debugger">
<xpath expr='//t[@t-set="debugger_hook"]' position="after">
<script type="text/javascript" src="/website_event_sale/static/src/js/website.tour.event_sale.js"></script>
</xpath>
<template id="assets_editor" inherit_id="website.assets_frontend" name="Event Sale">
<xpath expr="." position="inside">
<script type="text/javascript" src="/website_event_sale/static/src/js/website.tour.event_sale.js"></script>
</xpath>
</template>
<template id="index" inherit_id="website_event.index" name="Event's Ticket">
<xpath expr="//li[@t-foreach='event_ids']/div/h4" position="before">
<t t-if="event.state in ['draft', 'confirm'] and event.event_ticket_ids">

View File

@ -110,6 +110,21 @@ class website_sale(http.Controller):
pricelist = partner.property_product_pricelist
return pricelist
def get_attribute_value_ids(self, product):
cr, uid, context, pool = request.cr, request.uid, request.context, request.registry
currency_obj = pool['res.currency']
attribute_value_ids = []
if request.website.pricelist_id.id != context['pricelist']:
website_currency_id = request.website.currency_id.id
currency_id = self.get_pricelist().currency_id.id
for p in product.product_variant_ids:
price = currency_obj.compute(cr, uid, website_currency_id, currency_id, p.lst_price)
attribute_value_ids.append([p.id, map(int, p.attribute_value_ids), p.price, price])
else:
attribute_value_ids = [[p.id, map(int, p.attribute_value_ids), p.price, p.lst_price] for p in product.product_variant_ids]
return attribute_value_ids
@http.route(['/shop',
'/shop/page/<int:page>',
'/shop/category/<model("product.public.category"):category>',
@ -125,7 +140,10 @@ class website_sale(http.Controller):
if category:
domain += [('product_variant_ids.public_categ_ids', 'child_of', int(category))]
attrib_values = [map(int,v.split(",")) for v in request.httprequest.args.getlist('attrib') if v]
attrib_list = request.httprequest.args.getlist('attrib')
attrib_values = [map(int,v.split("-")) for v in attrib_list if v]
attrib_set = set([v[1] for v in attrib_values])
if attrib_values:
attrib = None
ids = []
@ -142,8 +160,7 @@ class website_sale(http.Controller):
if attrib:
domain += [('attribute_line_ids.value_ids', 'in', ids)]
attrib_set = set([v[1] for v in attrib_values])
keep = QueryURL('/shop', category=category and int(category), search=search, attrib=attrib_set)
keep = QueryURL('/shop', category=category and int(category), search=search, attrib=attrib_list)
if not context.get('pricelist'):
context['pricelist'] = int(self.get_pricelist())
@ -191,16 +208,18 @@ 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)
if category:
category = category_obj.browse(request.cr, request.uid, int(category), context=request.context)
category = category_obj.browse(cr, uid, int(category), context=context)
attrib_values = [map(int,v.split(",")) for v in request.httprequest.args.getlist('attrib') if v]
attrib_list = request.httprequest.args.getlist('attrib')
attrib_values = [map(int,v.split("-")) for v in attrib_list if v]
attrib_set = set([v[1] for v in attrib_values])
keep = QueryURL('/shop', category=category and category.id, search=search, attrib=attrib_set)
keep = QueryURL('/shop', category=category and category.id, search=search, attrib=attrib_list)
category_ids = category_obj.search(cr, uid, [], context=context)
category_list = category_obj.name_get(cr, uid, category_ids, context=context)
@ -208,9 +227,7 @@ 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)
variants = [[p.id, map(int, p.attribute_value_ids), p.price] for p in product.product_variant_ids]
product = template_obj.browse(cr, uid, int(product), context=context)
values = {
'search': search,
@ -222,7 +239,7 @@ class website_sale(http.Controller):
'category_list': category_list,
'main_object': product,
'product': product,
'variants': variants,
'get_attribute_value_ids': self.get_attribute_value_ids
}
return request.website.render("website_sale.product", values)
@ -238,36 +255,45 @@ class website_sale(http.Controller):
context=dict(context, mail_create_nosubcribe=True))
return werkzeug.utils.redirect(request.httprequest.referrer + "#comments")
@http.route(['/shop/pricelist'], type='http', auth="public", website=True)
def pricelist(self, promo, **post):
cr, uid, context = request.cr, request.uid, request.context
request.website.sale_get_order(code=promo, context=context)
return request.redirect("/shop/cart")
@http.route(['/shop/cart'], type='http', auth="public", website=True)
def cart(self, **post):
cr, uid, context, pool = request.cr, request.uid, request.context, request.registry
order = request.website.sale_get_order()
values = {
'order': order,
'suggested_products': [],
}
if order:
if not request.context.get('pricelist'):
request.context['pricelist'] = order.pricelist_id.id
values['suggested_products'] = order._cart_accessories(context=request.context)
if not context.get('pricelist'):
context['pricelist'] = order.pricelist_id.id
values['suggested_products'] = order._cart_accessories(context=context)
return request.website.render("website_sale.cart", values)
@http.route(['/shop/cart/update'], type='http', auth="public", methods=['POST'], website=True)
def cart_update(self, product_id, add_qty=1, set_qty=0, **kw):
cr, uid, context = request.cr, request.uid, request.context
request.website.sale_get_order(force_create=1)._cart_update(product_id=int(product_id), add_qty=add_qty, set_qty=set_qty)
request.website.sale_get_order(force_create=1)._cart_update(product_id=int(product_id), add_qty=float(add_qty), set_qty=float(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)
return {
'quantity': quantity,
'cart_quantity': order.cart_quantity,
'website_sale.total': request.website._render("website_sale.total", {
'website_sale_order': request.website.sale_get_order()
})
}
value = order._cart_update(product_id=product_id, line_id=line_id, add_qty=add_qty, set_qty=set_qty)
if not display:
return None
value['cart_quantity'] = order.cart_quantity
value['website_sale.total'] = request.website._render("website_sale.total", {
'website_sale_order': request.website.sale_get_order()
})
return value
#------------------------------------------------------
# Checkout
@ -323,12 +349,20 @@ class website_sale(http.Controller):
checkout.update(self.checkout_parse('shipping', data))
checkout["shipping_different"] = True
# Default search by user country
country_code = request.session['geoip'].get('country_code')
if country_code:
country_ids = request.registry.get('res.country').search(cr, uid, [('code', '=', country_code)], context=context)
if country_ids:
checkout['country_id'] = country_ids[0]
values = {
'countries': countries,
'states': states,
'checkout': checkout,
'shipping_different': checkout.get('shipping_different'),
'error': {},
'has_check_vat': hasattr(registry['res.partner'], 'check_vat')
}
return values
@ -445,7 +479,7 @@ class website_sale(http.Controller):
@http.route(['/shop/checkout'], type='http', auth="public", website=True)
def checkout(self, **post):
cr, uid, context, registry = request.cr, request.uid, request.context, request.registry
cr, uid, context = request.cr, request.uid, request.context
order = request.website.sale_get_order(force_create=1, context=context)

View File

@ -555,8 +555,6 @@ Weight: 1.1 ounces</field>
<record model="crm.case.section" id="website.salesteam_website_sales">
<field name="member_ids" eval="[(4, ref('base.user_root'), ref('base.user_demo'))]"/>
</record>
<!-- product.public.category -->

View File

@ -201,7 +201,7 @@ class product_product(osv.Model):
class product_attribute(osv.Model):
_inherit = "product.attribute"
_columns = {
'type': fields.selection([('radio', 'Radio'), ('select', 'Select'), ('color', 'Color')], string="Type", type="char"),
'type': fields.selection([('radio', 'Radio'), ('select', 'Select'), ('color', 'Color'), ('hidden', 'Hidden')], string="Type", type="char"),
}
_defaults = {
'type': lambda *a: 'radio',
@ -210,5 +210,5 @@ class product_attribute(osv.Model):
class product_attribute_value(osv.Model):
_inherit = "product.attribute.value"
_columns = {
'color': fields.char("Color for Color Attributes"),
'color': fields.char("HTML Color Index", help="Here you can set a specific HTML color index (e.g. #ff0000) to display the color on the website if the attibute type is 'Color'."),
}

View File

@ -43,16 +43,12 @@ class sale_order(osv.Model):
'order': order
}
def _cart_find_product_line(self, cr, uid, ids, product_id=None, line_id=None, context=None):
def _cart_find_product_line(self, cr, uid, ids, product_id=None, line_id=None, context=None, **kwargs):
for so in self.browse(cr, uid, ids, context=context):
order_line_id = None
domain = [('order_id', '=', so.id), ('product_id', '=', product_id)]
if line_id:
domain += [('id', '=', line_id)]
order_line_ids = self.pool.get('sale.order.line').search(cr, SUPERUSER_ID, domain, context=context)
if order_line_ids:
order_line_id = order_line_ids[0]
return order_line_id
return self.pool.get('sale.order.line').search(cr, SUPERUSER_ID, domain, context=context)
def _website_product_id_change(self, cr, uid, ids, order_id, product_id, line_id=None, context=None):
so = self.pool.get('sale.order').browse(cr, uid, order_id, context=context)
@ -69,7 +65,7 @@ class sale_order(osv.Model):
values['name'] = line.name
else:
product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
values['name'] = product.name_get()[0][1]
values['name'] = product.description_sale or product.name
values['product_id'] = product_id
values['order_id'] = order_id
@ -77,13 +73,16 @@ class sale_order(osv.Model):
values['tax_id'] = [(6, 0, values['tax_id'])]
return values
def _cart_update(self, cr, uid, ids, product_id=None, line_id=None, add_qty=0, set_qty=0, context=None):
def _cart_update(self, cr, uid, ids, product_id=None, line_id=None, add_qty=0, set_qty=0, context=None, **kwargs):
""" Add or set product quantity, add_qty can be negative """
sol = self.pool.get('sale.order.line')
quantity = 0
for so in self.browse(cr, uid, ids, context=context):
line_id = so._cart_find_product_line(product_id, line_id, context=context)
if line_id != False:
line_ids = so._cart_find_product_line(product_id, line_id, context=context, **kwargs)
if line_ids:
line_id = line_ids[0]
# Create line if no line with product_id can be located
if not line_id:
@ -107,7 +106,7 @@ class sale_order(osv.Model):
values['product_uom_qty'] = quantity
sol.write(cr, SUPERUSER_ID, [line_id], values, context=context)
return quantity
return {'line_id': line_id, 'quantity': quantity}
def _cart_accessories(self, cr, uid, ids, context=None):
for order in self.browse(cr, uid, ids, context=context):
@ -121,7 +120,9 @@ class website(orm.Model):
_columns = {
'pricelist_id': fields.related('user_id','partner_id','property_product_pricelist',
type='many2one', relation='product.pricelist', string='Default pricelist')
type='many2one', relation='product.pricelist', string='Default pricelist'),
'currency_id': fields.related('pricelist_id','currency_id',
type='many2one', relation='res.currency', string='Default pricelist'),
}
def sale_product_domain(self, cr, uid, ids, context=None):
@ -215,4 +216,9 @@ class website(orm.Model):
'sale_order_code_pricelist_id': False,
})
def compute_curency(self, cr, uid, ids, from_amount, from_currency_id=None, context=None):
from_currency_id = from_currency_id or self.browse(cr, SUPERUSER_ID, ids[0]).currency_id.id
to_currency_id = self.pool.get("res.users").browse(cr, uid, uid).partner_id.property_product_pricelist.currency_id.id
return self.pool['res.currency'].compute(cr, uid, from_currency_id, to_currency_id, from_amount, context=context)
# vim:et:

View File

@ -298,14 +298,32 @@
margin: 6px;
}
.js_add_cart_variants option.css_not_available {
.css_not_available_msg {
display: none;
}
.css_not_available.js_product > *:nth-child(4) > * {
display: none;
}
.css_not_available.js_product .product_price, .css_not_available.js_product .css_quantity {
display: none;
}
.css_not_available.js_product .css_not_available_msg {
display: block;
}
option.css_not_available {
color: #cccccc;
}
.js_add_cart_variants label.css_not_available {
opacity: 0.3;
label.css_not_available {
opacity: 0.6;
}
.js_add_cart_variants label.css_not_available input {
opacity: 0;
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

@ -257,13 +257,24 @@
&.active input
margin: 6px
.js_add_cart_variants
option.css_not_available
color: #ccc
label.css_not_available
opacity: 0.3
input
opacity: 0
.css_not_available_msg
display: none
.css_not_available.js_product
> *:nth-child(4) > *
display: none
.product_price, .css_quantity
display: none
.css_not_available_msg
display: block
option.css_not_available
color: #ccc
label.css_not_available
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

@ -1,38 +1,5 @@
(function () {
'use strict';
openerp.Tour.register({
id: 'shop_customize',
name: "Customize the page and search a product",
path: '/shop',
mode: 'test',
steps: [
{
title: "open customize menu",
element: '#customize-menu-button',
},
{
title: "click on 'Product Attribute's Filters'",
element: "#customize-menu a:contains(Product Attribute's Filters)",
},
{
title: "select product attribute memory 16 Go",
element: 'form.js_attributes label:contains(16 Go) input:not(:checked)',
},
{
title: "check the selection",
waitFor: 'form.js_attributes label:contains(16 Go) input:checked',
},
{
title: "select ipod",
waitNot: '.oe_website_sale .oe_product_cart:eq(2)',
element: '.oe_product_cart a:contains("iPod")',
},
{
title: "finish",
waitFor: 'form[action="/shop/cart/update"] label:contains(32 Go) input',
}
]
});
openerp.Tour.register({
id: 'shop_buy_product',
@ -57,7 +24,7 @@
{
title: "add suggested",
waitNot: '#cart_products:contains("[A8767] Apple In-Ear Headphones")',
element: 'form[action="/shop/cart/update"] .btn-link:contains("Add to Cart")',
element: '.oe_cart a:contains("Add to Cart")',
},
{
title: "add one more iPod",

View File

@ -1,23 +1,27 @@
$(document).ready(function () {
var $shippingDifferent = $(".oe_website_sale input[name='shipping_different']");
$('.oe_website_sale').each(function () {
var oe_website_sale = this;
var $shippingDifferent = $("input[name='shipping_different']", oe_website_sale);
if ($shippingDifferent.is(':checked')) {
$(".oe_website_sale .js_shipping").show();
$(".js_shipping", oe_website_sale).show();
}
$shippingDifferent.change(function () {
$(".oe_website_sale .js_shipping").toggle();
$(".js_shipping", oe_website_sale).toggle();
});
// change for css
$(document).on('mouseup', '.js_publish', function (ev) {
$(oe_website_sale).on('mouseup touchend', '.js_publish', function (ev) {
$(ev.currentTarget).parents(".thumbnail").toggleClass("disabled");
});
$(".oe_website_sale .oe_cart input.js_quantity").change(function () {
$(oe_website_sale).on("change", ".oe_cart input.js_quantity", function () {
var $input = $(this);
var value = parseInt($input.val(), 10);
var line_id = parseInt($input.data('line-id'),10);
if (isNaN(value)) value = 0;
openerp.jsonRpc("/shop/cart/update_json", 'call', {
'line_id': parseInt($input.data('line-id'),10),
'line_id': line_id,
'product_id': parseInt($input.data('product-id'),10),
'set_qty': value})
.then(function (data) {
@ -28,33 +32,38 @@ $(document).ready(function () {
var $q = $(".my_cart_quantity");
$q.parent().parent().removeClass("hidden", !data.quantity);
$q.html(data.cart_quantity).hide().fadeIn(600);
$input.val(data.quantity);
$('.js_quantity[data-line-id='+line_id+']').val(data.quantity).html(data.quantity);
$("#cart_total").replaceWith(data['website_sale.total']);
});
});
// hack to add and rome from cart with json
$('.oe_website_sale a.js_add_cart_json').on('click', function (ev) {
$(oe_website_sale).on('click', 'a.js_add_cart_json', function (ev) {
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 min = parseFloat($input.data("min") || 0);
var max = parseFloat($input.data("max") || Infinity);
var quantity = ($link.has(".fa-minus").length ? -1 : 1) + parseFloat($input.val(),10);
$input.val(quantity > min ? (quantity < max ? quantity : max) : min);
$('input[name="'+$input.attr("name")+'"]').val(quantity > min ? (quantity < max ? quantity : max) : min);
$input.change();
return false;
});
$('.a-submit').on('click', function () {
$('.a-submit', oe_website_sale).off('click').on('click', function () {
$(this).closest('form').submit();
});
$('form.js_attributes input, form.js_attributes select').on('change', function () {
$('.form.js_attributes input, form.js_attributes select', oe_website_sale).on('change', function () {
$(this).closest("form").submit();
});
// 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);
$('form.js_add_cart_json label', oe_website_sale).on('mouseup touchend', function (ev) {
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()));
}
@ -63,62 +72,85 @@ $(document).ready(function () {
$price.html(value + (dec < 0.01 ? ".00" : (dec < 1 ? "0" : "") ));
});
// hightlight selected color
$('.css_attribute_color input').on('change', function (ev) {
$('.css_attribute_color input', oe_website_sale).on('change', function (ev) {
$('.css_attribute_color').removeClass("active");
$('.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, select', function (ev) {
var values = [];
$form_var.find("label").removeClass("text-muted css_not_available");
$form_var.find(".a-submit").removeAttr("disabled");
function price_to_str(price) {
price = Math.round(price * 100) / 100;
var dec = Math.round((price % 1) * 100);
return price + (dec ? '' : '.0') + (dec%10 ? '' : '0');
}
$form_var.find('input:checked, select').each(function () {
$(oe_website_sale).on('change', 'input.js_variant_change, select.js_variant_change', function (ev) {
var $ul = $(this).parents('ul.js_add_cart_variants:first');
var $parent = $ul.closest('.js_product');
var $product_id = $parent.find('input.product_id').first();
var $price = $parent.find(".oe_price:first .oe_currency_value");
var $default_price = $parent.find(".oe_default_price:first .oe_currency_value");
var variant_ids = $ul.data("attribute_value_ids");
var values = [];
$parent.find('input.js_variant_change:checked, select.js_variant_change').each(function () {
values.push(+$(this).val());
});
$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;
$price.html(price_to_str(variant_ids[k][2]));
$default_price.html(price_to_str(variant_ids[k][3]));
$default_price.parent().toggle(variant_ids[k][3]-variant_ids[k][2]>0.2);
product_id = variant_ids[k][0];
$('input[name="product_id"]').val(product_id);
$price.html(variant_ids[k][2] + (dec < 0.01 ? ".00" : (dec < 1 ? "0" : "") ));
break;
}
}
if (product_id) {
$("#product_detail .product_detail_img").attr("src", "/website/image?field=image&model=product.product&id="+product_id);
}
$form_var.find("input:radio, select").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[value='" + id + "'])) input: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.closest("label").addClass("css_not_available");
$input.find("option[value='" + id + "']").addClass("css_not_available");
});
if (product_id) {
$(".oe_price_h4").removeClass("hidden");
$(".oe_not_available").addClass("hidden");
$parent.removeClass("css_not_available");
$product_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(".a-submit").attr("disabled", "disabled");
$parent.addClass("css_not_available");
$product_id.val(0);
$parent.find(".js_check_product").attr("disabled", "disabled");
}
});
$form_var.find("input:first").trigger('change');
$('ul.js_add_cart_variants', oe_website_sale).each(function () {
$('input.js_variant_change, select.js_variant_change', this).first().trigger('change');
});
$(oe_website_sale).on('change', "select[name='country_id']", function () {
var $select = $("select[name='state_id']");
$select.find("option:not(:first)").hide();
var nb = $select.find("option[data-country_id="+($(this).val() || 0)+"]").show().size();
$select.parent().toggle(nb>1);
}).change();
$(oe_website_sale).on('change', "select[name='shipping_country_id']", function () {
var $select = $("select[name='shipping_state_id']");
$select.find("option:not(:first)").hide();
var nb = $select.find("option[data-country_id="+($(this).val() || 0)+"]").show().size();
$select.parent().toggle(nb>1);
}).change();
});
});

View File

@ -10,7 +10,7 @@ $(document).ready(function () {
.find("input[name='acquirer']:checked").click();
// When clicking on payment button: create the tx using json then continue to the acquirer
$('button[type="submit"]').on("click", function (ev) {
$payment.on("click", 'button[type="submit"]', function (ev) {
ev.preventDefault();
ev.stopPropagation();
var $form = $(ev.currentTarget).parents('form');

View File

@ -4,7 +4,6 @@ import openerp.tests
inject = [
("openerp.Tour", os.path.join(os.path.dirname(__file__), '../../web/static/src/js/tour.js')),
("openerp.Tour.ShopTest", os.path.join(os.path.dirname(__file__), "../static/src/js/website.tour.sale.js")),
]
@openerp.tests.common.at_install(False)
@ -14,7 +13,6 @@ class TestUi(openerp.tests.HttpCase):
self.phantom_js("/", "openerp.Tour.run('shop', 'test')", "openerp.Tour.tours.shop", login="admin")
def test_02_admin_checkout(self):
self.phantom_js("/", "openerp.Tour.run('shop_customize', 'test')", "openerp.Tour.tours.shop_customize", login="admin", inject=inject)
self.phantom_js("/", "openerp.Tour.run('shop_buy_product', 'test')", "openerp.Tour.tours.shop_buy_product", login="admin", inject=inject)
def test_03_demo_checkout(self):

View File

@ -4,12 +4,6 @@
<!-- Layout and common templates -->
<template id="debugger" inherit_id="website.debugger" name="Event Debugger">
<xpath expr='//t[@t-set="debugger_hook"]' position="after">
<script type="text/javascript" src="/website_sale/static/src/js/website.tour.sale.js"></script>
</xpath>
</template>
<template id="assets_editor" inherit_id="website.assets_editor" name="Shop Editor" groups="base.group_sale_manager">
<xpath expr="." position="inside">
<script type="text/javascript" src="/website_sale/static/src/js/website_sale.editor.js"></script>
@ -17,6 +11,17 @@
</xpath>
</template>
<template id="assets_frontend" inherit_id="website.assets_frontend" name="Shop">
<xpath expr="." position="inside">
<script type="text/javascript" src="/website_sale/static/src/js/website.tour.sale.js"></script>
<script type="text/javascript" src="/website_sale/static/src/js/website_sale.js"></script>
<link rel='stylesheet' href='/website_sale/static/src/css/website_sale.css'/>
<link rel='stylesheet' href='/website_sale/static/src/css/website_mail.css'/>
<script type="text/javascript" src="/website_sale/static/src/js/website_sale_payment.js"></script>
<script type="text/javascript" src="/website_sale/static/src/js/website_sale_validate.js"></script>
</xpath>
</template>
<template id="header" inherit_id="website.layout" name="Header Shop My Cart Link">
<xpath expr="//header//ul[@id='top_menu']/li" position="before">
<t t-set="website_sale_order" t-value="website.sale_get_order()"/>
@ -31,7 +36,7 @@
<template id="search" name="Search hidden fields">
<t t-if="attrib_values">
<t t-foreach="attrib_values" t-as="a">
<input type="hidden" name="attrib" t-att-value="'%s,%s' % (a[0], a[1])"/>
<input type="hidden" name="attrib" t-att-value="'%s-%s' % (a[0], a[1])"/>
</t>
</t>
<input t-if="category" type="hidden" name="category" t-att-value="int(category or 0)"/>
@ -60,33 +65,39 @@
<!-- Product item used by /shop and /shop/cart -->
<template id="products_item" name="Product item">
<form action="/shop/cart/update" method="post" style="display: inline-block;">
<div itemscope="itemscope" itemtype="http://schema.org/Product">
<div class="ribbon-wrapper">
<div class="ribbon btn btn-danger">Sale</div>
</div>
<div class="oe_product_image">
<a itemprop="url" t-attf-href="/shop/product/{{ slug(product) }}/?{{ keep_query('search', 'attrib', category=(category and int(category)), page=(pager['page']['num'] if pager['page']['num']>1 else None)) }}">
<a itemprop="url" t-att-href="keep('/shop/product/%s' % slug(product), page=(pager['page']['num'] if pager['page']['num']>1 else None))">
<img itemprop="image" class="img img-responsive" t-attf-src="/website/image/product.template/#{product.id}/image#{'' if product_image_big else '?max_width=300&amp;max_height=300'}"/>
</a>
</div>
<section>
<h5><strong><a itemprop="name" t-attf-href="/shop/product/{{ slug(product) }}/?{{ keep_query('search', 'attrib', category=(category and int(category)), page=(pager['page']['num'] if pager['page']['num']>1 else None)) }}" t-field="product.name"/></strong></h5>
<h5><strong><a itemprop="name" t-att-href="keep('/shop/product/%s' % slug(product), page=(pager['page']['num'] if pager['page']['num']>1 else None))" t-field="product.name"/></strong></h5>
<div itemprop="offers" itemscope="itemscope" itemtype="http://schema.org/Offer" class="product_price" t-if="product.product_variant_ids">
<b>
<t t-if="abs(product.lst_price - product.price) &gt; 0.2">
<del class="text-danger" t-field="product.lst_price" t-field-options='{ "widget": "monetary", "display_currency": "pricelist.currency_id" }'/>&amp;nbsp;
<t t-if="(website.compute_curency(product.lst_price) - product.price) &gt; 0.1">
<del class="text-danger" style="white-space: nowrap;" t-field="product.lst_price" t-field-options='{
"widget": "monetary",
"from_currency": "website.currency_id",
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
}'/>&amp;nbsp;
</t>
<span t-field="product.price" t-field-options='{
<span t-field="product.price" style="white-space: nowrap;" t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
}'>
</span>
<span itemprop="price" style="display:none;" t-esc="product.price"/>
<span itemprop="priceCurrency" style="display:none;" t-esc="website.pricelist_id.currency_id.name"/>
<span itemprop="priceCurrency" style="display:none;" t-esc="user_id.partner_id.property_product_pricelist.currency_id.name"/>
</b>
</div>
</section>
</div>
</form>
</template>
<template id="products_description" inherit_id="website_sale.products_item" optional="disabled" name="Product Description">
@ -99,10 +110,8 @@
<template id="products_add_to_cart" inherit_id="website_sale.products_item" optional="disabled" name="Add to Cart">
<xpath expr="//div[@class='product_price']" position="inside">
<form action="/shop/cart/update" method="post" style="display: inline-block;">
<input name="product_id" t-att-value="product.product_variant_ids[0].id" type="hidden"/>
<a class="btn btn-default btn-xs fa fa-shopping-cart a-submit"/>
</form>
<input name="product_id" t-att-value="product.product_variant_ids[0].id" type="hidden"/>
<a class="btn btn-default btn-xs fa fa-shopping-cart a-submit"/>
</xpath>
</template>
@ -110,22 +119,20 @@
<template id="products" name="Products">
<t t-call="website.layout">
<t t-set="head">
<!--t t-set="head">
<t t-call="web.jqueryui_conflict">
<script type="text/javascript" src="/web/static/lib/jquery.ui/js/jquery-ui-1.9.1.custom.js"></script>
</t>
<link rel='stylesheet' href="/web/static/lib/jquery.ui/css/smoothness/jquery-ui-1.9.1.custom.css"/>
<script type="text/javascript" src="/website_sale/static/src/js/website_sale.js"></script>
<link rel='stylesheet' href='/website_sale/static/src/css/website_sale.css'/>
<t t-raw="head or ''"/>
</t>
</t-->
<t t-set="additional_title">Shop</t>
<div id="wrap" class="js_sale">
<div class="oe_structure"/>
<div class="container oe_website_sale">
<div class="products_pager">
<div class="row">
<form t-att-action="keep('/shop',search=0)" method="get" class="pagination form-inline col-md-3">
<form t-att-action="keep('/shop',search='')" method="get" class="pagination form-inline col-md-3">
<t t-call="website_sale.search"/>
</form>
<t t-call="website.pager"/>
@ -273,13 +280,14 @@
<input type="hidden" name="search" t-att-value="search"/>
<ul class="nav nav-pills nav-stacked mt16">
<t t-foreach="attributes" t-as="a">
<t t-if="a.type != 'hidden'">
<li t-if="a.value_ids and len(a.value_ids) > 1">
<div><strong t-field="a.name"/></div>
<t t-if="a.type == 'select'">
<select class="form-control" name="attrib">
<option value=""/>
<t t-foreach="a.value_ids" t-as="v">
<option t-att-value="'%s,%s' % (a.id,v.id)" t-field="v.name" t-att-selected="'selected' if v.id in attrib_set else ''"/>
<option t-att-value="'%s-%s' % (a.id,v.id)" t-field="v.name" t-att-selected="'selected' if v.id in attrib_set else ''"/>
</t>
</select>
</t>
@ -288,7 +296,7 @@
<t t-foreach="a.value_ids" t-as="v">
<li t-att-class="'active' if v.id in attrib_set else ''">
<label style="margin: 0 20px;">
<input type="checkbox" name="attrib" t-att-value="'%s,%s' % (a.id,v.id)" t-att-checked="'checked' if v.id in attrib_set else ''"/>
<input type="checkbox" name="attrib" t-att-value="'%s-%s' % (a.id,v.id)" t-att-checked="'checked' if v.id in attrib_set else ''"/>
<span style="font-weight: normal" t-field="v.name"/>
</label>
</li>
@ -301,13 +309,14 @@
t-attf-class="css_attribute_color #{'active' if v.id in attrib_set else ''}">
<input type="checkbox"
name="attrib"
t-att-value="'%s,%s' % (a.id,v.id)"
t-att-value="'%s-%s' % (a.id,v.id)"
t-att-checked="'checked' if v.id in attrib_set else ''"
t-att-title="v.name"/>
</label>
</t>
</t>
</li>
</t>
</t>
</ul>
</form>
@ -334,11 +343,6 @@
<template id="product" name="Product">
<t t-call="website.layout">
<t t-set="head">
<script type="text/javascript" src="/website_sale/static/src/js/website_sale.js"></script>
<link rel='stylesheet' href='/website_sale/static/src/css/website_sale.css'/>
<link rel='stylesheet' href='/website_sale/static/src/css/website_mail.css'/>
</t>
<t t-set="additional_title" t-value="product.name"/>
<div itemscope="itemscope" itemtype="http://schema.org/Product" id="wrap" class="js_sale">
@ -373,21 +377,23 @@
</div><div class="col-sm-5 col-md-5 col-lg-4 col-lg-offset-1">
<h1 itemprop="name" t-field="product.name">Product Name</h1>
<span itemprop="url" style="display:none;" t-esc="'/shop/product/%s' % slug(product)"/>
<form action="/shop/cart/update" class="js_add_cart_json" method="POST">
<input type="hidden" t-if="len(product.product_variant_ids) == 1" name="product_id" t-att-value="product.product_variant_ids[0].id"/>
<t t-if="len(product.product_variant_ids) &gt; 1">
<label label-default="label-default" class="radio" t-foreach="product.product_variant_ids" t-as="variant_id">
<input type="radio" name="product_id" t-att-value="variant_id.id"/>
<span t-esc="variant_id.name_get()[0][1]"/>
<span class="badge" t-if="variant_id.price_extra">
<t t-esc="variant_id.price_extra > 0 and '+' or ''"/><span t-field="variant_id.price_extra" t-field-options='{ "widget": "monetary", "display_currency": "pricelist.currency_id" }'/>
</span>
</label>
<form t-att-action="keep('/shop/cart/update')" class="js_add_cart_variants" method="POST">
<div class="js_product">
<t t-placeholder="select">
<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 t-call="website_sale.product_price"/>
<p t-if="len(product.product_variant_ids) > 1" class="css_not_available_msg bg-danger" style="padding: 15px;">Product not available</p>
<a id="add_to_cart" class="btn btn-primary btn-lg mt8 js_check_product a-submit" href="#">Add to Cart</a>
</div>
<a class="btn btn-primary btn-lg mt8 a-submit">Add to Cart</a>
</form>
<hr t-if="product.description_sale"/>
@ -402,29 +408,47 @@
</div>
</section>
<div itemprop="description" t-field="product.website_description" class="oe_structure mt16" id="product_full_description"/>
</div>
</t>
</template>
<template id="product_quantity" inherit_id="website_sale.product" optional="enabled" name="Select Quantity">
<xpath expr="//a[@id='add_to_cart']" position="before">
<div class="css_quantity input-group" style="width: 108px;">
<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" data-min="1" 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>
</xpath>
</template>
<template id="product_price">
<div itemprop="offers" itemscope="itemscope" itemtype="http://schema.org/Offer" class="product_price mt16">
<h4 class="oe_price_h4 css_editable_mode_hidden">
<t t-if="product.lst_price != product.price">
<span class="text-danger" style="text-decoration: line-through;"
<span class="text-danger oe_default_price" t-att-style="'' if (website.compute_curency(product.lst_price) - product.price) &gt; 0.1 else 'display: none;'" style="text-decoration: line-through; white-space: nowrap;"
t-field="product.lst_price"
t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
"widget": "monetary",
"from_currency": "website.currency_id",
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
}'/><br/>
</t>
<b class="oe_price"
<b class="oe_price" style="white-space: nowrap;"
t-field="product.price"
t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
}'/>
<span itemprop="price" style="display:none;" t-esc="product.price"/>
<span itemprop="priceCurrency" style="display:none;" t-esc="website.pricelist_id.currency_id.name"/>
<span itemprop="priceCurrency" style="display:none;" t-esc="user_id.partner_id.property_product_pricelist.currency_id.name"/>
</h4>
<h4 class="css_editable_mode_display" style="display: none;">
<span t-field="product.lst_price"
@ -437,81 +461,94 @@
</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">
<strong t-field="variant_id.attribute_id.name"/>
<t t-if="variant_id.attribute_id.type == 'select'">
<select class="form-control" 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">
<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="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" 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"
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>
</li>
</t>
</ul>
<t t-call="website_sale.product_price"/>
<a class="btn btn-primary btn-lg mt8 a-submit">Add to Cart</a>
</form>
<template id="product_variants" inherit_id="website_sale.product" optional="disabled" name="List View of Variants">
<xpath expr="//t[@t-placeholder='select']" position="replace">
<input type="hidden" t-if="len(product.product_variant_ids) == 1" name="product_id" t-att-value="product.product_variant_ids[0].id"/>
<t t-if="len(product.product_variant_ids) &gt; 1">
<label label-default="label-default" class="radio" t-foreach="product.product_variant_ids" t-as="variant_id">
<input type="radio" name="product_id" t-att-value="variant_id.id"/>
<span t-esc="variant_id.name_get()[0][1]"/>
<span class="badge" t-if="variant_id.price_extra">
<t t-esc="variant_id.price_extra > 0 and '+' or ''"/><span t-field="variant_id.price_extra" style="white-space: nowrap;" t-field-options='{
"widget": "monetary",
"from_currency": "website.currency_id",
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
}'/>
</span>
</label>
</t>
</xpath>
</template>
<template id="variants">
<t t-set="attribute_value_ids" t-value="get_attribute_value_ids(product)"/>
<ul t-attf-class="list-unstyled js_add_cart_variants #{ul_class}" t-att-data-attribute_value_ids="attribute_value_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 in ['select', 'hidden']">
<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" style="white-space: nowrap;" t-field-options='{
"widget": "monetary",
"from_currency": "website.currency_id",
"display_currency": "user_id.partner_id.property_product_pricelist.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 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" style="white-space: nowrap;" t-field-options='{
"widget": "monetary",
"from_currency": "website.currency_id",
"display_currency": "user_id.partner_id.property_product_pricelist.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="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>
</ul>
</t>
</li>
</t>
</ul>
</template>
<template id="recommended_products" inherit_id="website_sale.product" optional="enabled" name="Alternative Products">
<xpath expr="//div[@id='product_full_description']" position="after">
<div class="container mt32" t-if="product.alternative_product_ids">
@ -611,11 +648,6 @@
<template id="cart" name="Shopping Cart">
<t t-call="website.layout">
<t t-set="head">
<script type="text/javascript" src="/website_sale/static/src/js/website_sale.js"></script>
<link rel='stylesheet' href='/website_sale/static/src/css/website_sale.css'/>
<t t-raw="head or ''"/>
</t>
<div id="wrap">
<div class="container oe_website_sale">
@ -641,7 +673,8 @@
</tr>
</thead>
<tbody>
<tr t-foreach="website_sale_order.website_order_line" t-as="line">
<t t-foreach="website_sale_order.website_order_line" t-as="line">
<tr>
<td colspan="2" t-if="not line.product_id.product_tmpl_id"></td>
<td align="center" t-if="line.product_id.product_tmpl_id">
<span t-field="line.product_id.image_small"
@ -653,22 +686,23 @@
<strong t-esc="line.product_id.name_get()[0][1]"/>
</a>
</div>
<div class="text-muted" t-field="line.product_id.description_sale"/>
<div class="text-muted" t-field="line.name"/>
</td>
<td class="text-center" name="price">
<t t-if="abs(line.product_id.lst_price - line.price_unit) &gt; 0.2">
<del class="text-danger"
<t t-if="(website.compute_curency(line.product_id.lst_price) - line.price_unit) &gt; 0.1">
<del class="text-danger" style="white-space: nowrap;"
t-field="line.product_id.lst_price" t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
"widget": "monetary",
"from_currency": "website.currency_id",
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
}'/>&amp;nbsp;
</t>
<span t-field="line.price_unit" t-field-options='{
<span t-field="line.price_unit" style="white-space: nowrap;" t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
}'/>
</td>
<td>
<td class="text-center">
<div class="input-group">
<span class="input-group-addon">
<a t-attf-href="#" class="mb8 js_add_cart_json" data-no-instant="">
@ -685,14 +719,16 @@
</a>
</span>
</div>
</td>
</tr>
</t>
</tbody>
</table>
<t t-call="website_sale.total"/>
<div class="clearfix"/>
<a t-if="website_sale_order and website_sale_order.website_order_line" href="/shop/checkout" class="btn btn-primary pull-right mb32">Process Checkout <span class="fa fa-long-arrow-right"/></a>
<a t-if="not optional_products and website_sale_order and website_sale_order.website_order_line" class="btn btn-primary pull-right mb32" href="/shop/checkout">Process Checkout <span class="fa fa-long-arrow-right"/></a>
<div class="oe_structure"/>
</div>
<div class="col-lg-3 col-lg-offset-1 col-sm-3 col-md-3 text-muted" id="right_column">
@ -726,7 +762,7 @@
</colgroup>
<thead>
<tr>
<th colspan="2">Suggested products</th>
<th colspan="4">Suggested products:</th>
</tr>
</thead>
<tbody>
@ -747,16 +783,17 @@
<div class="text-muted" t-field="product.description_sale"/>
</td>
<td>
<t t-if="abs(product.lst_price - product.price) &gt; 0.2">
<del class="text-danger"
<t t-if="(website.compute_curency(product.lst_price) - product.price) &gt; 0.1">
<del class="text-danger" style="white-space: nowrap;"
t-field="product.lst_price" t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
"widget": "monetary",
"from_currency": "website.currency_id",
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
}'/>&amp;nbsp;
</t>
<span t-field="product.price" t-field-options='{
<span t-field="product.price" style="white-space: nowrap;" t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
}'/>
</td>
<td class="text-center">
@ -798,11 +835,6 @@
<template id="checkout">
<t t-call="website.layout">
<t t-set="head">
<script type="text/javascript" src="/website_sale/static/src/js/website_sale.js"></script>
<link rel='stylesheet' href='/website_sale/static/src/css/website_sale.css'/>
<t t-raw="head or ''"/>
</t>
<t t-set="additional_title">Shop - Checkout</t>
<div id="wrap">
<div class="container oe_website_sale">
@ -827,12 +859,12 @@
<label class="control-label" for="contact_name">Your Name</label>
<input type="text" name="name" class="form-control" t-att-value="checkout.get('name')"/>
</div>
<div t-if="has_check_vat" class="clearfix"/>
<div t-attf-class="form-group #{error.get('street2') and 'has-error' or ''} col-lg-6">
<label class="control-label" for="street2" style="font-weight: normal">Company Name</label>
<input type="text" name="street2" class="form-control" t-att-value="checkout.get('street2')"/>
</div>
<div class="col-lg-6"> </div>
<div t-attf-class="form-group #{error.get('vat') and 'has-error' or ''} col-lg-6">
<div t-if="has_check_vat" t-attf-class="form-group #{error.get('vat') and 'has-error' or ''} col-lg-6">
<label class="control-label" for="vat" style="font-weight: normal">VAT Number</label>
<input type="text" name="vat" class="form-control" t-att-value="checkout.get('vat')"/>
</div>
@ -859,21 +891,21 @@
<label class="control-label" for="zip">Zip / Postal Code</label>
<input type="text" name="zip" class="form-control" t-att-value="checkout.get('zip')"/>
</div>
<div t-attf-class="form-group #{error.get('country_id') and 'has-error' or ''} col-lg-6">
<label class="control-label" for="country_id">Country</label>
<select name="country_id" class="form-control">
<option value="">Country...</option>
<t t-foreach="countries or []" t-as="country">
<option t-att-value="country.id" t-att-selected="country.id == checkout.get('country_id')"><t t-esc="country.name"/></option>
</t>
</select>
</div>
<div t-attf-class="form-group #{error.get('state_id') and 'has-error' or ''} col-lg-6">
<label class="control-label" for="state_id" style="font-weight: normal">State / Province</label>
<select name="state_id" class="form-control">
<option value="">select...</option>
<t t-foreach="states or []" t-as="state">
<option t-att-value="state.id" t-att-selected="state.id == checkout.get('state_id')"><t t-esc="state.name"/></option>
</t>
</select>
</div>
<div t-attf-class="form-group #{error.get('country_id') and 'has-error' or ''} col-lg-6">
<label class="control-label" for="contact_name">Country</label>
<select name="country_id" class="form-control">
<option value="">Country...</option>
<t t-foreach="countries or []" t-as="country">
<option t-att-value="country.id" t-att-selected="country.id == checkout.get('country_id')"><t t-esc="country.name"/></option>
<option t-att-value="state.id" style="display:none;" t-att-data-country_id="state.country_id.id" t-att-selected="state.id == checkout.get('state_id')"><t t-esc="state.name"/></option>
</t>
</select>
</div>
@ -891,37 +923,28 @@
<h3 class="oe_shipping col-lg-12 mt16">Shipping Information</h3>
<div t-attf-class="form-group #{error.get('shipping_name') and 'has-error' or ''} col-lg-6">
<label class="control-label" for="contact_name">Name (Shipping)</label>
<label class="control-label" for="shipping_name">Name (Shipping)</label>
<input type="text" name="shipping_name" class="form-control" t-att-value="checkout.get('shipping_name', '')"/>
</div>
<div t-attf-class="form-group #{error.get('shipping_phone') and 'has-error' or ''} col-lg-6">
<label class="control-label" for="contact_name">Phone</label>
<label class="control-label" for="shipping_phone">Phone</label>
<input type="tel" name="shipping_phone" class="form-control" t-att-value="checkout.get('shipping_phone', '')"/>
</div>
<div t-attf-class="form-group #{error.get('shipping_street') and 'has-error' or ''} col-lg-6">
<label class="control-label" for="contact_name">Street</label>
<label class="control-label" for="shipping_street">Street</label>
<input type="text" name="shipping_street" class="form-control" t-att-value="checkout.get('shipping_street', '')"/>
</div>
<div class="clearfix"/>
<div t-attf-class="form-group #{error.get('shipping_city') and 'has-error' or ''} col-lg-6">
<label class="control-label" for="contact_name">City</label>
<label class="control-label" for="shipping_city">City</label>
<input type="text" name="shipping_city" class="form-control" t-att-value="checkout.get('shipping_city', '')"/>
</div>
<div t-attf-class="form-group #{error.get('shipping_zip') and 'has-error' or ''} col-lg-6">
<label class="control-label" for="contact_name">Zip / Postal Code</label>
<label class="control-label" for="shipping_zip">Zip / Postal Code</label>
<input type="text" name="shipping_zip" class="form-control" t-att-value="checkout.get('shipping_zip', '')"/>
</div>
<div t-attf-class="form-group #{error.get('shipping_state_id') and 'has-error' or ''} col-lg-6">
<label class="control-label" for="contact_name" style="font-weight: normal">State / Province</label>
<select name="shipping_state_id" class="form-control">
<option value="">State / Province...</option>
<t t-foreach="states or []" t-as="state">
<option t-att-value="state.id" t-att-selected="state.id == checkout.get('shipping_state_id')"><t t-esc="state.name"/></option>
</t>
</select>
</div>
<div t-attf-class="form-group #{error.get('shipping_country_id') and 'has-error' or ''} col-lg-6">
<label class="control-label" for="contact_name">Country</label>
<label class="control-label" for="shipping_country_id">Country</label>
<select name="shipping_country_id" class="form-control">
<option value="">Country...</option>
<t t-foreach="countries or []" t-as="country">
@ -929,27 +952,37 @@
</t>
</select>
</div>
<div t-attf-class="form-group #{error.get('shipping_state_id') and 'has-error' or ''} col-lg-6">
<label class="control-label" for="shipping_state_id" style="font-weight: normal">State / Province</label>
<select name="shipping_state_id" class="form-control">
<option value="">State / Province...</option>
<t t-foreach="states or []" t-as="state">
<option t-att-value="state.id" style="display:none;" t-att-data-country_id="state.country_id.id" t-att-selected="state.id == checkout.get('shipping_state_id')"><t t-esc="state.name"/></option>
</t>
</select>
</div>
</div>
<a href="/shop/cart" class="btn btn-default mb32"><span class="fa fa-long-arrow-left"/> Return to Cart</a>
<a class="btn btn-default btn-primary pull-right mb32 a-submit">Confirm <span class="fa fa-long-arrow-right"/></a>
</div>
<div class="col-lg-offset-1 col-lg-3 col-md-3 text-muted">
<h3 class="page-header mt16">Your Order <small><a href="/shop/cart"><span class="fa fa-arrow-right"/> change</a></small></h3>
<t t-set="website_sale_order" t-value="website.sale_get_order()"/>
<div class="row">
<div class="col-sm-6 text-right">Subtotal:</div>
<div class="col-sm-6"><span t-field="website_sale_order.amount_untaxed" t-field-options='{
<div class="col-sm-6 text-right">Subtotal: </div>
<div class="col-sm-6"><span style="white-space: nowrap;" t-field="website_sale_order.amount_untaxed" t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
}'/></div>
<div class="col-sm-6 text-right">Taxes:</div>
<div class="col-sm-6"><span t-field="website_sale_order.amount_tax" t-field-options='{
<div class="col-sm-6 text-right">Taxes: </div>
<div class="col-sm-6"><span style="white-space: nowrap;" t-field="website_sale_order.amount_tax" t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
}'/></div>
<div class="col-sm-6 text-right"><h4>Total To Pay:</h4></div>
<div class="col-sm-6"><h4><span t-field="website_sale_order.amount_total" t-field-options='{
<div class="col-sm-6 text-right"><h4>Total To Pay: </h4></div>
<div class="col-sm-6"><h4><span style="white-space: nowrap;" t-field="website_sale_order.amount_total" t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
}'/></h4></div>
</div>
</div>
@ -964,12 +997,6 @@
<template id="payment">
<t t-call="website.layout">
<t t-set="head">
<script type="text/javascript" src="/website_sale/static/src/js/website_sale.js"></script>
<script type="text/javascript" src="/website_sale/static/src/js/website_sale_payment.js"></script>
<link rel='stylesheet' href='/website_sale/static/src/css/website_sale.css'/>
<t t-raw="head or ''"/>
</t>
<t t-set="additional_title">Shop - Select Payment Mode</t>
<div id="wrap">
<div class="container oe_website_sale">
@ -1005,9 +1032,9 @@
<strong t-field="line.product_id.name"/>
</td>
<td class="text-center">
<span t-field="line.price_unit" t-field-options='{
<span t-field="line.price_unit" style="white-space: nowrap;" t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
}'/>
</td>
<td>
@ -1088,11 +1115,6 @@
<template id="confirmation">
<t t-call="website.layout">
<t t-set="head">
<link rel='stylesheet' href='/website_sale/static/src/css/website_sale.css'/>
<script type="text/javascript" src="/website_sale/static/src/js/website_sale_validate.js"></script>
<t t-raw="head or ''"/>
</t>
<t t-set="additional_title">Shop - Confirmed</t>
<div id="wrap">
<div class="container oe_website_sale">
@ -1146,18 +1168,18 @@
<tr width="100" style="border-top: 1px solid #000" id="order_total">
<th><h3>Total: </h3></th>
<th class="text-right">
<h3><span t-field="website_sale_order.amount_total" t-field-options='{
<h3><span t-field="website_sale_order.amount_total" style="white-space: nowrap;" t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
}'/></h3>
</th>
</tr>
<tr width="120" class="text-muted" id="order_total_taxes">
<td><abbr title="Taxes may be updated after providing shipping address">Taxes:</abbr></td>
<td class="text-right">
<span t-field="website_sale_order.amount_tax" t-field-options='{
<span t-field="website_sale_order.amount_tax" style="white-space: nowrap;" t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
}'/>
</td>
</tr>

View File

@ -0,0 +1,2 @@
import controllers
import models

View File

@ -0,0 +1,22 @@
{
'name': 'eCommerce Optional Products',
'category': 'Website',
'version': '1.0',
'description': """
OpenERP E-Commerce
==================
""",
'author': 'OpenERP SA',
'depends': ['website_sale'],
'data': [
'views/views.xml',
'views/templates.xml',
],
'demo': [
'data/demo.xml',
],
'qweb': ['static/src/xml/*.xml'],
'installable': True,
'application': True,
}

View File

@ -0,0 +1 @@
import main

View File

@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
from openerp import SUPERUSER_ID
from openerp.addons.web import http
from openerp.addons.web.http import request
from openerp.addons.website_sale.controllers.main import website_sale
class website_sale_options(website_sale):
@http.route(['/shop/product/<model("product.template"):product>'], type='http', auth="public", website=True)
def product(self, product, category='', search='', **kwargs):
r = super(website_sale_options, self).product(product, category, search, **kwargs)
cr, uid, context, pool = request.cr, request.uid, request.context, request.registry
template_obj = pool['product.template']
optional_product_ids = []
for p in product.optional_product_ids:
ctx = dict(context, active_id=p.id)
optional_product_ids.append(template_obj.browse(cr, uid, p.id, context=ctx))
r.qcontext['optional_product_ids'] = optional_product_ids
return r
@http.route(['/shop/cart/update_option'], type='http', auth="public", methods=['POST'], website=True)
def cart_options_update_json(self, product_id, add_qty=1, set_qty=0, goto_shop=None, **kw):
cr, uid, context, pool = request.cr, request.uid, request.context, request.registry
order = request.website.sale_get_order(force_create=1)
product = pool['product.product'].browse(cr, uid, int(product_id), context=context)
option_ids = [p.id for tmpl in product.optional_product_ids for p in tmpl.product_variant_ids]
optional_product_ids = []
for k, v in kw.items():
if "optional-product-" in k and int(kw.get(k.replace("product", "add"))) and int(v) in option_ids:
optional_product_ids.append(int(v))
value = {}
if add_qty or set_qty:
value = order._cart_update(product_id=int(product_id),
add_qty=int(add_qty), set_qty=int(set_qty),
optional_product_ids=optional_product_ids)
# options have all time the same quantity
for option_id in optional_product_ids:
order._cart_update(product_id=option_id,
set_qty=value.get('quantity'),
linked_line_id=value.get('line_id'))
return str(order.cart_quantity)
@http.route(['/shop/modal'], type='json', auth="public", methods=['POST'], website=True)
def modal(self, product_id, **kw):
cr, uid, context, pool = request.cr, request.uid, request.context, request.registry
if not context.get('pricelist'):
context['pricelist'] = int(self.get_pricelist())
product = pool['product.product'].browse(cr, uid, int(product_id), context=context)
return request.website._render("website_sale_options.modal", {
'product': product,
'get_attribute_value_ids': self.get_attribute_value_ids,
})

View File

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="1">
<!-- add Warranty to ipad -->
<record id="product_attribute_1" model="product.attribute">
<field name="name">Duration</field>
<field name="type">hidden</field>
</record>
<record id="product_attribute_value_1" model="product.attribute.value">
<field name="name">1 year</field>
<field name="attribute_id" ref="product_attribute_1"/>
</record>
<record id="product_attribute_value_2" model="product.attribute.value">
<field name="name">2 year</field>
<field name="attribute_id" ref="product_attribute_1"/>
</record>
<record id="product_product_1" model="product.product">
<field name="name">Warranty</field>
<field name="list_price">20.0</field>
<field name="website_sequence">-1</field>
<field name="website_published" eval="True"/>
<field name="type">service</field>
<field name="uom_id" ref="product.product_uom_unit"/>
<field name="uom_po_id" ref="product.product_uom_unit"/>
<field name="description_sale">Warranty, issued to the purchaser of an article by its manufacturer, promising to repair or replace it if necessary within a specified period of time.</field>
<field name="default_code">W0001</field>
<field name="attribute_value_ids" eval="[(6,0,[ref('product_attribute_value_1')])]"/>
</record>
<record id="product_product_1b" model="product.product">
<field name="default_code">W0002</field>
<field name="product_tmpl_id" ref="product_product_1_product_template"/>
<field name="attribute_value_ids" eval="[(6,0,[ref('product_attribute_value_2')])]"/>
</record>
<record id="product_attribute_line_1" model="product.attribute.line">
<field name="product_tmpl_id" ref="product_product_1_product_template"/>
<field name="attribute_id" ref="product_attribute_1"/>
<field name="value_ids" eval="[(6,0,[ref('product_attribute_value_1'), ref('product_attribute_value_2')])]"/>
</record>
<record id="product_product_1_product_template" model="product.template">
<field name="attribute_line_ids" eval="[(6,0,[ref('product_attribute_line_1')])]"/>
</record>
<record id="product.product_product_4_product_template" model="product.template">
<field name="optional_product_ids" eval="[(6,0,[ref('product_product_1_product_template')])]"/>
</record>
<record id="product_attribute_price_1" model="product.attribute.price">
<field name="product_tmpl_id" ref="product_product_1_product_template"/>
<field name="value_id" ref="product_attribute_value_2"/>
<field name="price_extra">18.00</field>
</record>
<!-- -->
</data>
</openerp>

View File

@ -0,0 +1,2 @@
import product
import sale_order

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
from openerp import tools
from openerp.osv import osv, fields
class product_template(osv.Model):
_inherit = "product.template"
_columns = {
'optional_product_ids': fields.many2many('product.template','product_optional_rel','src_id','dest_id',string='Optional Products', help="Products to propose when add to cart."),
}

View File

@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
from openerp import SUPERUSER_ID
from openerp.osv import osv, orm, fields
from openerp.tools.translate import _
class sale_order_line(osv.Model):
_inherit = "sale.order.line"
_columns = {
'linked_line_id': fields.many2one('sale.order.line', 'Linked Order Line', domain="[('order_id','!=',order_id)]", ondelete='cascade'),
'option_line_ids': fields.one2many('sale.order.line', 'linked_line_id', string='Options Linked'),
}
class sale_order(osv.Model):
_inherit = "sale.order"
def _cart_find_product_line(self, cr, uid, ids, product_id=None, line_id=None, context=None, **kwargs):
line_ids = super(sale_order, self)._cart_find_product_line(cr, uid, ids, product_id, line_id, context=context)
if line_id:
return line_ids
linked_line_id = kwargs.get('linked_line_id')
optional_product_ids = kwargs.get('optional_product_ids')
for so in self.browse(cr, uid, ids, context=context):
domain = [('id', 'in', line_ids)]
domain += linked_line_id and [('linked_line_id', '=', linked_line_id)] or [('linked_line_id', '=', False)]
if optional_product_ids:
domain += [('option_line_ids.product_id', '=', pid) for pid in optional_product_ids]
else:
domain += [('option_line_ids', '=', False)]
return self.pool.get('sale.order.line').search(cr, SUPERUSER_ID, domain, context=context)
def _cart_update(self, cr, uid, ids, product_id=None, line_id=None, add_qty=0, set_qty=0, context=None, **kwargs):
""" Add or set product quantity, add_qty can be negative """
value = super(sale_order, self)._cart_update(cr, uid, ids, product_id, line_id, add_qty, set_qty, context=context, **kwargs)
linked_line_id = kwargs.get('linked_line_id')
sol = self.pool.get('sale.order.line')
line = sol.browse(cr, SUPERUSER_ID, value.get('line_id'), context=context)
for so in self.browse(cr, uid, ids, context=context):
if linked_line_id and linked_line_id in map(int,so.order_line):
linked = sol.browse(cr, SUPERUSER_ID, linked_line_id, context=context)
line.write({
"name": _("%s\nOption for: %s") % (line.name, linked.product_id.name_get()[0][1]),
"linked_line_id": linked_line_id
}, context=context)
# select linked product
option_ids = [l.id for l in so.order_line if l.linked_line_id.id == line.id]
if option_ids:
# update line
sol.write(cr, SUPERUSER_ID, option_ids, {
'product_uom_qty': value.get('quantity')
}, context=context)
value['option_ids'] = option_ids
return value

View File

@ -0,0 +1,2 @@
sass:
sass -t expanded --unix-newlines --watch website_sale.sass:website_sale.css&

View File

@ -0,0 +1,3 @@
.css_not_available.js_product > *:nth-child(5) > * {
display: none;
}

View File

@ -0,0 +1,3 @@
.css_not_available.js_product
> *:nth-child(5) > *
display: none

View File

@ -0,0 +1,66 @@
$(document).ready(function () {
$('.oe_website_sale #add_to_cart, .oe_website_sale #products_grid .a-submit')
.off('click')
.removeClass('a-submit')
.click(function (event) {
var $form = $(this).closest('form');
var quantity = parseFloat($form.find('input[name="add_qty"]').val() || 1);
event.preventDefault();
openerp.jsonRpc("/shop/modal", 'call', {
'product_id': parseInt($form.find('input[name="product_id"]').val(),10),
kwargs: {
context: openerp.website.get_context()
},
}).then(function (modal) {
var $modal = $(modal);
$modal.appendTo($form)
.modal()
.on('hidden.bs.modal', function () {
$(this).remove();
});
$modal.on('click', '.a-submit', function () {
var $a = $(this);
$form.ajaxSubmit({
url: '/shop/cart/update_option',
success: function (quantity) {
if (!$a.hasClass('js_goto_shop')) {
window.location.href = window.location.href.replace(/shop([\/?].*)?$/, "shop/cart");
}
var $q = $(".my_cart_quantity");
$q.parent().parent().removeClass("hidden", !quantity);
$q.html(quantity).hide().fadeIn(600);
}
});
$modal.modal('hide');
});
$modal.on("click", "a.js_add, a.js_remove", function (event) {
event.preventDefault();
var $parent = $(this).parents('.js_product:first');
$parent.find("a.js_add, span.js_remove").toggleClass("hidden");
$parent.find("input.js_optional_same_quantity").val( $(this).hasClass("js_add") ? 1 : 0 );
var $remove = $parent.find(".js_remove");
});
$modal.on("change", "input.js_quantity", function (event) {
var qty = parseFloat($(this).val());
if (qty === 1) {
$(".js_remove .js_items").addClass("hidden");
$(".js_remove .js_item").removeClass("hidden");
} else {
$(".js_remove .js_items").removeClass("hidden").text($(".js_remove .js_items").text().replace(/[0-9.,]+/, qty));
$(".js_remove .js_item").addClass("hidden");
}
});
$modal.find('input[name="add_qty"]').val(quantity).change();
$('ul.js_add_cart_variants').each(function () {
$('input.js_variant_change, select.js_variant_change', this).first().trigger('change');
});
});
return false;
});
});

View File

@ -0,0 +1,95 @@
(function () {
'use strict';
var steps = openerp.Tour.tours.shop_buy_product.steps;
for (var k=0; k<steps.length; k++) {
if (steps[k].title === "click on add to cart") {
steps.splice(k+1, 0, {
title: "click in modal on 'Proceed to checkout' button",
element: '.modal a:contains("Proceed to checkout")',
});
break;
}
}
openerp.Tour.register({
id: 'shop_customize',
name: "Customize the page and search a product",
path: '/shop',
mode: 'test',
steps: [
{
title: "open customize menu",
element: '#customize-menu-button',
},
{
title: "click on 'Product Attribute's Filters'",
element: "#customize-menu a:contains(Product Attribute's Filters)",
},
{
title: "select product attribute memory 16 Go",
waitNot: '#customize-menu:visible',
element: 'form.js_attributes label:contains(16 Go) input:not(:checked)',
},
{
title: "check the selection",
waitFor: 'form.js_attributes label:contains(16 Go) input:checked',
},
{
title: "select iPad",
waitNot: '.oe_website_sale .oe_product_cart:eq(2)',
element: '.oe_product_cart a:contains("iPad")',
},
{
title: "click on 'Add to Cart' button",
waitNot: '#customize-menu:visible',
element: "a:contains(Add to Cart)",
},
{
title: "add an optional Warranty",
element: ".js_product:contains(Warranty) a:contains(Add to Cart)",
},
{
title: "click in modal on 'Proceed to checkout' button",
waitFor: '.js_product:contains(Warranty) a:contains(Add to Cart):hidden',
element: '.modal a:contains("Proceed to checkout")',
},
{
title: "check quantity",
waitFor: '.my_cart_quantity:containsExact(2)',
},
{
title: "check optional product",
waitFor: '.optional_product',
},
{
title: "remove iPad from cart",
element: '#cart_products a.js_add_cart_json:first',
},
{
title: "check optional product is removed",
waitNot: '.optional_product',
},
{
title: "click on shop",
element: "a:contains(Shop)",
waitNot: '#products_grid_before .js_attributes',
},
{
title: "open customize menu bis",
waitFor: '#products_grid_before .js_attributes',
element: '#customize-menu-button',
},
{
title: "remove 'Product Attribute's Filters'",
element: "#customize-menu a:contains(Product Attribute's Filters):has(.fa-check-square-o)",
},
{
title: "finish",
waitNot: '#products_grid_before .js_attributes',
waitFor: 'li:has(.my_cart_quantity):hidden',
},
]
});
}());

View File

@ -0,0 +1 @@
import customize_test

View File

@ -0,0 +1,13 @@
import os
import openerp.tests
inject = [
("openerp.Tour", os.path.join(os.path.dirname(__file__), '../../web/static/src/js/tour.js')),
]
@openerp.tests.common.at_install(False)
@openerp.tests.common.post_install(True)
class TestUi(openerp.tests.HttpCase):
def test_01_admin_shop_tour(self):
self.phantom_js("/", "openerp.Tour.run('shop_customize', 'test')", "openerp.Tour.tours.shop_customize", login="admin", inject=inject)

View File

@ -0,0 +1,149 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<template id="assets_frontend" inherit_id="website.assets_frontend" name="Shop Product Options">
<xpath expr="." position="inside">
<script type="text/javascript" src="/website_sale_options/static/src/js/website_sale.test.js"></script>
<script type="text/javascript" src="/website_sale_options/static/src/js/website_sale.js"></script>
<link rel='stylesheet' href='/website_sale_options/static/src/css/website_sale.css'/>
</xpath>
</template>
<template id="optional_products" inherit_id="website_sale.cart" name="Optional products">
<xpath expr="//table[@id='cart_products']/tbody//td[last()]/div" position="attributes">
<attribute name="t-if">not line.linked_line_id</attribute>
</xpath>
<xpath expr="//table[@id='cart_products']/tbody//tr" position="attributes">
<attribute name="t-att-class">'optional_product info' if line.linked_line_id else ''</attribute>
</xpath>
<xpath expr="//table[@id='cart_products']/tbody//td[last()]" position="inside">
<t t-if="line.linked_line_id">
<span class="js_quantity text-muted" t-att-data-line-id="line.id" t-esc="int(line.product_uom_qty)"/>
</t>
</xpath>
</template>
<template id="modal" name="Optional Products">
<div id="modal_optional_products" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<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">Product to add in your shopping cart</h4>
</div>
<div class="modal-body">
<table class="table table-striped table-condensed">
<thead>
<tr>
<th colspan="2">Product</th>
<th style="min-width: 140px;"> </th>
<th width="100">Price</th>
<th width="120">Quantity</th>
</tr>
</thead>
<tbody>
<tr id="product_confirmation">
<td width="100">
<span t-field="product.image_medium" t-field-options='{"widget": "image" }'/>
</td>
<td colspan="2">
<strong t-field="product.name"/>
<div class="text-muted">
<div t-field="product.description_sale"/>
<div class="js_attributes"/>
</div>
</td>
<td>
<span t-attf-class="text-danger oe_default_price" t-att-style="'' if (website.compute_curency(product.lst_price) - product.price) &gt; 0.1 else 'display: none;'" style="text-decoration: line-through; white-space: nowrap;"
t-field="product.lst_price"
t-field-options='{
"widget": "monetary",
"from_currency": "website.currency_id",
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
}'/><br/>
<span class="oe_price" style="white-space: nowrap;"
t-field="product.price"
t-field-options='{
"widget": "monetary",
"from_currency": "website.currency_id",
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
}'/>
</td>
<td>
<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" data-min="1" 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>
</td>
</tr>
<tr t-if="product.optional_product_ids"><td colspan="5"><h4>Select Your Options:</h4></td></tr>
<t t-set="option_inc" t-value="0"/>
<tr class="js_product" t-foreach="product.optional_product_ids" t-as="product">
<td width="100">
<input type="hidden" class="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'"/>
<span t-field="product.image_small" t-field-options='{"widget": "image"}'/>
</td>
<td>
<div class="pull-left">
<strong class="media-heading" t-field="product.name"/>
<div class="text-muted" t-field="product.description_sale"/>
</div>
</td>
<td>
<div class="pull-right">
<t t-call="website_sale.variants"/>
</div>
</td>
<td>
<t t-if="(website.compute_curency(product.lst_price) - product.price) &gt; 0.1">
<span class="text-danger" style="text-decoration: line-through; white-space: nowrap;"
t-field="product.lst_price"
t-field-options='{
"widget": "monetary",
"from_currency": "website.currency_id",
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
}'/><br/>
</t>
<span class="oe_price" style="white-space: nowrap;"
t-field="product.price"
t-field-options='{
"widget": "monetary",
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
}'/>
<p class="css_not_available_msg bg-danger" style="position:absolute; padding: 15px;">Product not available</p>
</td>
<td>
<input type="hidden" class="js_optional_same_quantity" t-attf-name="optional-add-#{option_inc}" value="0"/>
<a href="#" class="js_add"><strong>Add to Cart</strong></a>
<span class="js_remove hidden">
<span class="js_item">1 Item</span><span class="js_items hidden">5 Items</span><br/>
<a href="#" class="js_remove"><small>Remove from cart</small></a>
</span>
</td>
</tr>
</tbody>
</table>
</div>
<div class="modal-footer">
<a class="btn btn-default a-submit js_goto_shop"><i class="fa fa-chevron-left"></i> Continue shopping</a>
<a class="btn btn-primary pull-right a-submit"><i class="fa fa-shopping-cart fa-fw"></i> Proceed to checkout</a>
</div>
</div>
</div>
</div>
</template>
</data>
</openerp>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record model="ir.ui.view" id="product_template_form_view">
<field name="name">product.template.product.website.form</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="website_sale.product_template_form_view"/>
<field name="arch" type="xml">
<field name="accessory_product_ids" position="after">
<field name="optional_product_ids" widget="many2many_tags"/>
</field>
</field>
</record>
</data>
</openerp>

View File

@ -761,7 +761,7 @@ class MonetaryConverter(osv.AbstractModel):
if context is None:
context = {}
Currency = self.pool['res.currency']
display = self.display_currency(cr, uid, options)
display_currency = self.display_currency(cr, uid, options['display_currency'], options)
# lang.format mandates a sprintf-style format. These formats are non-
# minimal (they have a default fixed precision instead), and
@ -772,17 +772,23 @@ class MonetaryConverter(osv.AbstractModel):
# The log10 of the rounding should be the number of digits involved if
# negative, if positive clamp to 0 digits and call it a day.
# nb: int() ~ floor(), we want nearest rounding instead
precision = int(round(math.log10(display.rounding)))
precision = int(round(math.log10(display_currency.rounding)))
fmt = "%.{0}f".format(-precision if precision < 0 else 0)
from_amount = record[field_name]
if options.get('from_currency'):
from_currency = self.display_currency(cr, uid, options['from_currency'], options)
from_amount = Currency.compute(cr, uid, from_currency.id, display_currency.id, from_amount)
lang_code = context.get('lang') or 'en_US'
lang = self.pool['res.lang']
formatted_amount = lang.format(cr, uid, [lang_code],
fmt, Currency.round(cr, uid, display, record[field_name]),
fmt, Currency.round(cr, uid, display_currency, from_amount),
grouping=True, monetary=True)
pre = post = u''
if display.position == 'before':
if display_currency.position == 'before':
pre = u'{symbol} '
else:
post = u' {symbol}'
@ -791,12 +797,12 @@ class MonetaryConverter(osv.AbstractModel):
formatted_amount,
pre=pre, post=post,
).format(
symbol=display.symbol,
symbol=display_currency.symbol,
))
def display_currency(self, cr, uid, options):
def display_currency(self, cr, uid, currency, options):
return self.qweb_object().eval_object(
options['display_currency'], options['_qweb_context'])
currency, options['_qweb_context'])
TIMEDELTA_UNITS = (
('year', 3600 * 24 * 365),