[ADD] website_sale: checkout process

Change checkout process.

- Billing address is always the connected partner_id or neww partner_id.
- Shipping address is contact of current commercial_partner_id
- New checkout design for address selection
- Add delete a line in my cart
- Fix input quantity size
- Refactor/Reindent xml template from website_sale
- Improve the modularity for checkout (preprocess, posprocess, required fields, ...)
- Allow to use form builder in extra step for OEE (No edit mode)
- Add route to have states according country selected (need to be overridable for website_sale_delivery)
- ...
This commit is contained in:
Jeremy Kersten
2016-07-04 15:48:15 +02:00
parent bb7bfd7382
commit e4a317a176
11 changed files with 2187 additions and 2037 deletions
@@ -100,7 +100,7 @@ odoo.define('website_form.animation', function (require) {
}
// Post form and handle result
ajax.post(this.$target.attr('action') + this.$target.data('model_name'), form_values)
ajax.post(this.$target.attr('action') + (this.$target.data('force_action')||this.$target.data('model_name')), form_values)
.then(function(result_data) {
result_data = $.parseJSON(result_data);
if(!result_data.id) {
+1 -1
View File
@@ -10,7 +10,7 @@ Odoo E-Commerce
==================
""",
'depends': ['website', 'sale', 'payment', 'website_payment', 'website_portal_sale', 'website_mail', 'rating'],
'depends': ['website', 'sale', 'payment', 'website_payment', 'website_portal_sale', 'website_mail', 'website_form', 'rating'],
'data': [
'data/data.xml',
'data/web_planner_data.xml',
+229 -229
View File
@@ -1,15 +1,23 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import werkzeug
import json
import logging
from werkzeug.exceptions import Forbidden
from odoo import http, tools, _
from odoo.http import request
from odoo.addons.base.ir.ir_qweb.fields import nl2br
from odoo.addons.website.models.website import slug
from odoo.addons.website.controllers.main import QueryURL
from odoo.exceptions import ValidationError
from odoo.addons.website_form.controllers.main import WebsiteForm
_logger = logging.getLogger(__name__)
PPG = 20 # Products Per Page
PPR = 4 # Products Per Row
class TableCompute(object):
def __init__(self):
@@ -81,9 +89,38 @@ class TableCompute(object):
# TODO keep with input type hidden
class WebsiteSaleForm(WebsiteForm):
@http.route('/website_form/shop.sale.order', type='http', auth="public", methods=['POST'], website=True)
def website_form_saleorder(self, **kwargs):
model_record = request.env.ref('sale.model_sale_order')
try:
data = self.extract_data(model_record, **kwargs)
except ValidationError, e:
return json.dumps({'error_fields': e.args[0]})
order = request.website.sale_get_order()
if data['record']:
order.write(data['record'])
if data['custom']:
values = {
'body': nl2br(data['custom']),
'model': 'sale.order',
'message_type': 'comment',
'no_auto_thread': False,
'res_id': order.id,
}
request.env['mail.message'].sudo().create(values)
if data['attachments']:
self.insert_attachment(model_record, order.id, data['attachments'])
return json.dumps({'id': order.id})
class WebsiteSale(http.Controller):
def get_attribute_value_ids(self, product):
""" list of selectable attributes of a product
@@ -109,13 +146,6 @@ class WebsiteSale(http.Controller):
# id is added to be sure that order is a unique sort key
return 'website_published desc,%s , id desc' % post.get('order', 'website_sequence desc')
@http.route(['/shop/change_pricelist/<model("product.pricelist"):pl_id>'], type='http', auth="public", website=True)
def pricelist_change(self, pl_id, **post):
if request.website.is_pricelist_available(pl_id.id):
request.session['website_sale_current_pl'] = pl_id.id
request.website.sale_get_order(force_pricelist=pl_id.id)
return request.redirect(request.httprequest.referrer or '/shop')
def _get_search_domain(self, search, category, attrib_values):
domain = request.website.sale_product_domain()
if search:
@@ -279,6 +309,13 @@ class WebsiteSale(http.Controller):
}
return request.website.render("website_sale.product", values)
@http.route(['/shop/change_pricelist/<model("product.pricelist"):pl_id>'], type='http', auth="public", website=True)
def pricelist_change(self, pl_id, **post):
if request.website.is_pricelist_available(pl_id.id):
request.session['website_sale_current_pl'] = pl_id.id
request.website.sale_get_order(force_pricelist=pl_id.id)
return request.redirect(request.httprequest.referrer or '/shop')
@http.route(['/shop/pricelist'], type='http', auth="public", website=True)
def pricelist(self, promo, **post):
pricelist = request.env['product.pricelist'].sudo().search([('code', '=', promo)], limit=1)
@@ -317,12 +354,12 @@ class WebsiteSale(http.Controller):
return request.website.render("website_sale.cart", values)
@http.route(['/shop/cart/update'], type='http', auth="public", methods=['POST'], website=True)
@http.route(['/shop/cart/update'], type='http', auth="public", methods=['POST'], website=True, csrf=False)
def cart_update(self, product_id, add_qty=1, set_qty=0, **kw):
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)
@http.route(['/shop/cart/update_json'], type='json', auth="public", methods=['POST'], website=True, csrf=False)
def cart_update_json(self, product_id, line_id=None, add_qty=None, set_qty=None, display=True):
order = request.website.sale_get_order(force_create=1)
if order.state != 'draft':
@@ -341,10 +378,10 @@ class WebsiteSale(http.Controller):
from_currency = order.company_id.currency_id
to_currency = order.pricelist_id.currency_id
value['website_sale.cart_lines'] = request.website._render("website_sale.cart_lines", {
'website_sale_order': order,
'compute_currency': lambda price: from_currency.compute(price, to_currency),
'suggested_products': order.with_context(pricelist=order.pricelist_id.id)._cart_accessories()
})
'website_sale_order': order,
'compute_currency': lambda price: from_currency.compute(price, to_currency),
'suggested_products': order.with_context(pricelist=order.pricelist_id.id)._cart_accessories()
})
return value
# ------------------------------------------------------
@@ -352,7 +389,6 @@ class WebsiteSale(http.Controller):
# ------------------------------------------------------
def checkout_redirection(self, order):
# must have a draft sale order with lines at this point, otherwise reset
if not order or order.state != 'draft':
request.session['sale_order_id'] = None
@@ -364,138 +400,56 @@ class WebsiteSale(http.Controller):
if tx and tx.state != 'draft':
return request.redirect('/shop/payment/confirmation/%s' % order.id)
def checkout_values(self, data=None):
countries = request.env['res.country'].sudo().search([])
states = request.env['res.country.state'].sudo().search([])
partner = request.env.user.partner_id
order = None
shipping_id = data and data.get('shipping_id') or None
shippings = request.env['res.partner']
checkout = {}
if not data:
if request.env.uid != request.website.user_id.id:
checkout.update(self.checkout_parse("billing", partner))
shippings |= request.env['res.partner'].sudo().search([("parent_id", "=", partner.id), ('type', "=", 'delivery')])
else:
order = request.website.sale_get_order(force_create=1)
if order.partner_id:
domain = [("partner_id", "=", order.partner_id.id)]
user_ids = request.env['res.users'].with_context(active_test=False).sudo().search(domain).ids
if not user_ids or request.website.user_id.id not in user_ids:
checkout.update( self.checkout_parse("billing", order.partner_id) )
else:
checkout = self.checkout_parse('billing', data)
try:
shipping_id = int(shipping_id)
except (ValueError, TypeError):
pass
if shipping_id == -1:
checkout.update(self.checkout_parse('shipping', data))
if shipping_id is None:
if not order:
order = request.website.sale_get_order()
if order and order.partner_shipping_id:
shipping_id = order.partner_shipping_id.id
shippings -= partner
if shipping_id == partner.id:
shipping_id = 0
elif shipping_id > 0 and shipping_id not in shippings.ids:
shippings |= request.env['res.partner'].browse(shipping_id)
elif shipping_id is None and shippings:
shipping_id = shippings[0].id
if shipping_id > 0:
shipping = request.env['res.partner'].with_context(show_address=1).sudo().browse(shipping_id)
checkout.update(self.checkout_parse("shipping", shipping))
checkout['shipping_id'] = shipping_id
# Default search by user country
if not checkout.get('country_id'):
country_code = request.session['geoip'].get('country_code')
if country_code:
checkout['country_id'] = request.env['res.country'].search([('code', '=', country_code)], limit=1).id
def checkout_values(self, **kw):
order = request.website.sale_get_order(force_create=1)
shippings = []
if order.partner_id != request.website.user_id.sudo().partner_id:
Partner = order.partner_id.with_context(show_address=1).sudo()
shippings = Partner.search([
("id", "child_of", order.partner_id.commercial_partner_id.ids),
'|', ("type", "=", "delivery"), ("id", "=", order.partner_id.commercial_partner_id.id)
], order='id desc')
if shippings:
if kw.get('partner_id'):
partner_id = int(kw.get('partner_id'))
if partner_id in shippings.mapped('id'):
order.partner_shipping_id = int(kw.get('partner_id'))
elif not order.partner_shipping_id:
last_order = request.env['sale.order'].sudo().search([("partner_id", "=", order.partner_id.id)], order='id desc', limit=1)
order.partner_shipping_id = last_order and last_order.id
values = {
'countries': countries,
'states': states,
'checkout': checkout,
'shipping_id': partner.id != shipping_id and shipping_id or 0,
'shippings': shippings.with_context(show_address=1).sudo(),
'error': {},
'has_check_vat': hasattr(request.env['res.partner'], 'check_vat'),
'order': order,
'shippings': shippings,
'only_services': order and order.only_services or False
}
return values
mandatory_billing_fields = ["name", "phone", "email", "street2", "city", "country_id"]
optional_billing_fields = ["street", "state_id", "vat", "zip"]
mandatory_shipping_fields = ["name", "phone", "street", "city", "country_id"]
optional_shipping_fields = ["state_id", "zip"]
def _get_mandatory_billing_fields(self):
return self.mandatory_billing_fields
def _get_optional_billing_fields(self):
return self.optional_billing_fields
return ["name", "email", "street", "city", "country_id"]
def _get_mandatory_shipping_fields(self):
return self.mandatory_shipping_fields
def _get_optional_shipping_fields(self):
return self.optional_shipping_fields
def _post_prepare_query(self, query, data, address_type):
return query
def checkout_parse(self, address_type, data, remove_prefix=False):
""" data is a dict OR a partner browse record
"""
# set mandatory and optional fields
assert address_type in ('billing', 'shipping')
if address_type == 'billing':
all_fields = self._get_mandatory_billing_fields() + self._get_optional_billing_fields()
prefix = ''
else:
all_fields = self._get_mandatory_shipping_fields() + self._get_optional_shipping_fields()
prefix = 'shipping_'
# set data
if isinstance(data, dict):
query = dict((prefix + field_name, data[prefix + field_name])
for field_name in all_fields if prefix + field_name in data)
else:
query = dict((prefix + field_name, getattr(data, field_name))
for field_name in all_fields if getattr(data, field_name))
if address_type == 'billing' and data.parent_id:
query[prefix + 'street'] = data.parent_id.name
if query.get(prefix + 'state_id'):
query[prefix + 'state_id'] = int(query[prefix + 'state_id'])
if query.get(prefix + 'country_id'):
query[prefix + 'country_id'] = int(query[prefix + 'country_id'])
query = self._post_prepare_query(query, data, address_type)
if not remove_prefix:
return query
return dict((field_name, data[prefix + field_name]) for field_name in all_fields if prefix + field_name in data)
def checkout_form_validate(self, data):
Partner = request.env['res.partner']
return ["name", "street", "city", "country_id"]
def checkout_form_validate(self, mode, all_form_values, data):
# mode: tuple ('new|edit', 'billing|shipping')
# all_form_values: all values before preprocess
# data: values after preprocess
error = dict()
error_message = []
# Validation
for field_name in self._get_mandatory_billing_fields():
# Required fields from form
required_fields = filter(None, (all_form_values.get('field_required') or '').split(','))
# Required fields from mandatory field function
required_fields += mode[1] == 'shipping' and self._get_mandatory_shipping_fields() or self._get_mandatory_billing_fields()
# Check if state required
if data.get('country_id'):
country = request.env['res.country'].browse(int(data.get('country_id')))
if 'state_code' in country.get_address_fields() and country.state_ids:
required_fields += ['state_id']
# error message for empty required fields
for field_name in required_fields:
if not data.get(field_name):
error[field_name] = 'missing'
@@ -505,122 +459,166 @@ class WebsiteSale(http.Controller):
error_message.append(_('Invalid Email! Please enter a valid email address.'))
# vat validation
Partner = request.env['res.partner']
if data.get("vat") and hasattr(Partner, "check_vat"):
if request.website.company_id.vat_check_vies:
# force full VIES online check
check_func = Partner.vies_vat_check
else:
# quick and partial off-line checksum validation
check_func = Partner.simple_vat_check
check_func = request.website.company_id.vat_check_vies and Partner.vies_vat_check or Partner.simple_vat_check
vat_country, vat_number = Partner._split_vat(data.get("vat"))
if not check_func(vat_country, vat_number): # simple_vat_check
if not check_func(vat_country, vat_number):
error["vat"] = 'error'
if data.get("shipping_id") == -1:
for field_name in self._get_mandatory_shipping_fields():
field_name = 'shipping_' + field_name
if not data.get(field_name):
error[field_name] = 'missing'
# error message for empty required fields
if [err for err in error.values() if err == 'missing']:
error_message.append(_('Some required fields are empty.'))
return error, error_message
def _get_shipping_info(self, checkout):
shipping_info = {}
shipping_info.update(self.checkout_parse('shipping', checkout, True))
shipping_info['type'] = 'delivery'
return shipping_info
def checkout_form_save(self, checkout):
order = request.website.sale_get_order(force_create=1)
def _checkout_form_save(self, mode, checkout, all_values):
Partner = request.env['res.partner']
partner_lang = request.lang if request.lang in request.website.mapped('language_ids.code') else None
if mode[0] == 'new':
partner_id = Partner.sudo().create(checkout)
elif mode[0] == 'edit':
partner_id = int(all_values.get('partner_id', 0))
if partner_id:
# double check
order = request.website.sale_get_order()
shippings = Partner.sudo().search([("id", "child_of", order.partner_id.commercial_partner_id.ids)])
if partner_id not in shippings.mapped('id') and partner_id != order.partner_id.id:
return Forbidden()
billing_info = {'customer': True}
if partner_lang:
billing_info['lang'] = partner_lang
billing_info.update(self.checkout_parse('billing', checkout, True))
Partner.browse(partner_id).sudo().update(checkout)
return partner_id
# set partner_id
partner = None
if request.uid != request.website.user_id.id:
partner = request.env.user.sudo().partner_id
elif order.partner_id:
user_ids = request.env['res.users'].with_context(active_test=False).sudo().search([("partner_id", "=", order.partner_id.id)]).ids
if not user_ids or request.website.user_id.id not in user_ids:
partner = order.partner_id
def values_preprocess(self, order, mode, values):
return values
# save partner informations
if partner and request.website.partner_id.id != partner.id:
partner.write(billing_info)
else:
# create partner
billing_info['team_id'] = request.website.salesteam_id.id
partner = Partner.sudo().create(billing_info)
order.write({'partner_id': partner.id})
order.onchange_partner_id()
order.write({'partner_invoice_id': partner.id})
def values_postprocess(self, order, mode, values, errors, error_msg):
new_values = {}
authorized_fields = request.env['ir.model'].sudo().search([('model', '=', 'res.partner')])._get_form_writable_fields()
for k, v in values.items():
# don't drop empty value, it could be a field to reset
if k in authorized_fields and v is not None:
new_values[k] = v
else: # DEBUG ONLY
if k not in ('field_required', 'partner_id', 'callback', 'submitted'): # classic case
_logger.debug("website_sale postprocess: %s value has been dropped (empty or not writable)" % k)
# create a new shipping partner
if checkout.get('shipping_id') == -1:
shipping_info = self._get_shipping_info(checkout)
if partner_lang:
shipping_info['lang'] = partner_lang
shipping_info['parent_id'] = partner.id
checkout['shipping_id'] = Partner.sudo().create(shipping_info).id
if checkout.get('shipping_id'):
order.write({'partner_shipping_id': checkout['shipping_id']})
new_values['customer'] = True
new_values['team_id'] = request.website.salesteam_id and request.website.salesteam_id.id
order.onchange_partner_shipping_id()
order.order_line._compute_tax_id()
lang = request.lang if request.lang in request.website.mapped('language_ids.code') else None
if lang:
new_values['lang'] = lang
order_info = {
'message_partner_ids': [(4, partner.id), (3, request.website.partner_id.id)],
}
order.write(order_info)
if mode[1] == 'shipping':
new_values['parent_id'] = order.partner_id.commercial_partner_id.id
new_values['type'] = 'delivery'
@http.route(['/shop/checkout'], type='http', auth="public", website=True)
def checkout(self, **post):
return new_values, errors, error_msg
@http.route(['/shop/address'], type='http', methods=['GET', 'POST'], auth="public", website=True)
def address(self, **kw):
Partner = request.env['res.partner'].with_context(show_address=1).sudo()
order = request.website.sale_get_order(force_create=1)
mode, def_country_id = False, False
values, errors = {}, {}
partner_id = int(kw.get('partner_id', -1))
redirection = self.checkout_redirection(order)
if redirection:
return redirection
values = self.checkout_values()
# IF PUBLIC ORDER
if order.partner_id.id == request.website.user_id.sudo().partner_id.id:
mode = ('new', 'billing')
country_code = request.session['geoip'].get('country_code')
if country_code:
def_country_id = request.env['res.country'].search([('code', '=', country_code)], limit=1)
else:
def_country_id = request.website.user_id.sudo().country_id
# IF ORDER LINKED TO A PARTNER
else:
def_country_id = order.partner_id.country_id
if partner_id > 0:
if partner_id == order.partner_id.id:
mode = ('edit', 'billing')
else:
shippings = Partner.search([('id', 'child_of', order.partner_id.commercial_partner_id.ids)])
if partner_id in shippings.mapped('id'):
mode = ('edit', 'shipping')
else:
return Forbidden()
if mode:
values = Partner.browse(partner_id)
elif partner_id == -1:
mode = ('new', 'shipping')
else: # no mode - refresh without post?
return request.redirect('/shop/checkout')
# IF POSTED
if 'submitted' in kw:
pre_values = self.values_preprocess(order, mode, kw)
errors, error_msg = self.checkout_form_validate(mode, kw, pre_values)
post, errors, error_msg = self.values_postprocess(order, mode, pre_values, errors, error_msg)
if errors:
errors['error_message'] = error_msg
values = kw
else:
partner_id = self._checkout_form_save(mode, post, kw)
if mode[1] == 'billing':
order.partner_id = partner_id
order.onchange_partner_id()
elif mode[1] == 'shipping':
order.partner_shipping_id = partner_id
order.onchange_partner_shipping_id()
order.order_line._compute_tax_id()
order.message_partner_ids = [(4, partner_id), (3, request.website.partner_id.id)]
if not errors:
return request.redirect(kw.get('callback') or '/shop/checkout')
country = 'country_id' in values and values['country_id'] != '' and request.env['res.country'].browse(int(values['country_id']))
render_values = {
'partner_id': partner_id,
'mode': mode,
'checkout': values,
'country': country and country.exists() or def_country_id,
'error': errors,
'callback': kw.get('callback'),
}
return request.website.render("website_sale.address", render_values)
@http.route(['/shop/checkout'], type='http', auth="public", website=True)
def checkout(self, **post):
order = request.website.sale_get_order()
redirection = self.checkout_redirection(order)
if redirection:
return redirection
if order.partner_id.id == request.website.user_id.sudo().partner_id.id:
return request.redirect('/shop/address')
values = self.checkout_values(**post)
# Avoid useless rendering if called in ajax
if post.get('xhr'):
return 'ok'
return request.website.render("website_sale.checkout", values)
@http.route(['/shop/confirm_order'], type='http', auth="public", website=True)
def confirm_order(self, **post):
order = request.website.sale_get_order()
if not order:
return request.redirect("/shop")
redirection = self.checkout_redirection(order)
if redirection:
return redirection
values = self.checkout_values(post)
values["error"], values["error_message"] = self.checkout_form_validate(values["checkout"])
if values["error"]:
return request.website.render("website_sale.checkout", values)
self.checkout_form_save(values["checkout"])
if not int(post.get('shipping_id', 0)):
order.partner_shipping_id = order.partner_invoice_id
request.session['sale_last_order_id'] = order.id
request.website.sale_get_order(update_pricelist=True)
extra_step = request.env.ref('website_sale.extra_info_option')
if extra_step.active:
return request.redirect("/shop/extra_info")
@@ -655,7 +653,9 @@ class WebsiteSale(http.Controller):
return request.redirect("/shop/payment")
values = {
'website_sale_order': order
'website_sale_order': order,
'post': post,
'escape': lambda x: x.replace("'", r"\'")
}
values.update(request.env['sale.order']._get_website_data(order))
@@ -868,6 +868,21 @@ class WebsiteSale(http.Controller):
else:
return request.redirect('/shop')
@http.route(['/shop/tracking_last_order'], type='json', auth="public")
def tracking_cart(self, **post):
""" return data about order in JSON needed for google analytics"""
ret = {}
sale_order_id = request.session.get('sale_last_order_id')
if sale_order_id:
order = request.env['sale.order'].sudo().browse(sale_order_id)
ret = self.order_2_return_dict(order)
return ret
@http.route(['/shop/get_unit_price'], type='json', auth="public", methods=['POST'], website=True)
def get_unit_price(self, product_ids, add_qty, **kw):
products = request.env['product.product'].browse(product_ids)
return {product.id: request.website.get_product_price(product, qty=add_qty) / add_qty for product in products}
# ------------------------------------------------------
# Edit
# ------------------------------------------------------
@@ -947,18 +962,3 @@ class WebsiteSale(http.Controller):
},
'lines': self.order_lines_2_google_api(order.order_line)
}
@http.route(['/shop/tracking_last_order'], type='json', auth="public")
def tracking_cart(self, **post):
""" return data about order in JSON needed for google analytics"""
ret = {}
sale_order_id = request.session.get('sale_last_order_id')
if sale_order_id:
order = request.env['sale.order'].sudo().browse(sale_order_id)
ret = self.order_2_return_dict(order)
return ret
@http.route(['/shop/get_unit_price'], type='json', auth="public", methods=['POST'], website=True)
def get_unit_price(self, product_ids, add_qty, **kw):
products = request.env['product.product'].browse(product_ids)
return {product.id: request.website.get_product_price(product, qty=add_qty) / add_qty for product in products}
+28 -3
View File
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1">
<odoo>
<data noupdate="1">
<record id="menu_shop" model="website.menu">
<field name="name">Shop</field>
@@ -39,8 +40,32 @@
<field name="selectable" eval="True" />
<field name="website_id" ref="website.default_website" />
<field name="pricelist_id" ref='product.list0' />
<field name="country_group_ids" eval="[(6,0,[ref('base.europe')])]" />
<field name="country_group_ids" eval="[(6, 0, [ref('base.europe')])]" />
</record>
</data>
<data>
<record id="sale.model_sale_order" model="ir.model">
<field name="website_form_access" eval="True"/>
<field name="website_form_label">Sale Order</field>
</record>
<function model="ir.model.fields" name="formbuilder_whitelist">
<value>sale.order</value>
<value eval="[
'client_order_ref',
]"/>
</function>
<record id="base.model_res_partner" model="ir.model">
<field name="website_form_access">True</field>
<field name="website_form_label">Customer</field>
</record>
<function model="ir.model.fields" name="formbuilder_whitelist">
<value>res.partner</value>
<value eval="[
'name', 'phone', 'email',
'city', 'zip', 'street', 'state_id', 'country_id',
'vat', 'company_name'
]"/>
</function>
</data>
</odoo>
@@ -1,4 +1,3 @@
@charset "utf-8";
/* ---- Default Styles ---- */
.oe_product {
border: 1px solid rgba(100, 100, 100, 0.2);
@@ -412,6 +411,13 @@
display: block;
}
.css_quantity {
max-width: 125px;
}
.css_quantity input[name="add_qty"] {
text-align: center;
}
option.css_not_available {
color: #ccc;
}
@@ -557,3 +563,22 @@ ul.wizard li:first-child {
.mycart-popover .cart_line {
border-bottom: 1px #EEE solid;
}
td.noborder {
border: none !important;
}
.border_primary {
border: 1px solid #337ab7;
}
.js_change_shipping {
cursor: pointer;
}
a.no-decoration {
cursor: pointer;
text-decoration: none !important;
}
/*# sourceMappingURL=website_sale.css.map */
@@ -348,6 +348,11 @@
.css_not_available_msg
display: block
.css_quantity
max-width: 125px
input[name="add_qty"]
text-align: center
option.css_not_available
color: #ccc
label.css_not_available
@@ -473,3 +478,16 @@ ul.wizard li:first-child
.cart_line
border-bottom: 1px #EEE solid
td.noborder
border: none !important
.border_primary
border: 1px solid #337ab7
.js_change_shipping
cursor: pointer
a.no-decoration
cursor: pointer
text-decoration: none !important
+356 -321
View File
@@ -1,356 +1,391 @@
odoo.define('website_sale.cart', function (require) {
"use strict";
"use strict";
var base = require('web_editor.base');
var core = require('web.core');
var _t = core._t;
var base = require('web_editor.base');
var core = require('web.core');
var _t = core._t;
var shopping_cart_link = $('ul#top_menu li a[href$="/shop/cart"]');
var shopping_cart_link_counter;
shopping_cart_link.popover({
trigger: 'manual',
animation: true,
html: true,
title: function () {
return _t("My Cart");
},
container: 'body',
placement: 'auto',
template: '<div class="popover mycart-popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
}).on("mouseenter",function () {
var self = this;
clearTimeout(shopping_cart_link_counter);
shopping_cart_link.not(self).popover('hide');
shopping_cart_link_counter = setTimeout(function(){
if($(self).is(':hover') && !$(".mycart-popover:visible").length)
{
$.get("/shop/cart", {'type': 'popover'})
.then(function (data) {
$(self).data("bs.popover").options.content = data;
$(self).popover("show");
$(".popover").on("mouseleave", function () {
$(self).trigger('mouseleave');
});
});
}
}, 100);
}).on("mouseleave", function () {
var self = this;
setTimeout(function () {
if (!$(".popover:hover").length) {
if(!$(self).is(':hover'))
var shopping_cart_link = $('ul#top_menu li a[href$="/shop/cart"]');
var shopping_cart_link_counter;
shopping_cart_link.popover({
trigger: 'manual',
animation: true,
html: true,
title: function () {
return _t("My Cart");
},
container: 'body',
placement: 'auto',
template: '<div class="popover mycart-popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
}).on("mouseenter",function () {
var self = this;
clearTimeout(shopping_cart_link_counter);
shopping_cart_link.not(self).popover('hide');
shopping_cart_link_counter = setTimeout(function(){
if($(self).is(':hover') && !$(".mycart-popover:visible").length)
{
$(self).popover('hide');
$.get("/shop/cart", {'type': 'popover'})
.then(function (data) {
$(self).data("bs.popover").options.content = data;
$(self).popover("show");
$(".popover").on("mouseleave", function () {
$(self).trigger('mouseleave');
});
});
}
}
}, 1000);
}, 100);
}).on("mouseleave", function () {
var self = this;
setTimeout(function () {
if (!$(".popover:hover").length) {
if(!$(self).is(':hover')) {
$(self).popover('hide');
}
}
}, 1000);
});
});
odoo.define('website_sale.website_sale_category', function (require) {
"use strict";
var base = require('web_editor.base');
if(!$('#o_shop_collapse_category').length) {
return $.Deferred().reject("DOM doesn't contain '#o_shop_collapse_category'");
}
$('#o_shop_collapse_category').on('click', '.fa-chevron-right',function(){
$(this).parent().siblings().find('.fa-chevron-down:first').click();
$(this).parents('li').find('ul:first').show('normal');
$(this).toggleClass('fa-chevron-down fa-chevron-right');
});
$('#o_shop_collapse_category').on('click', '.fa-chevron-down',function(){
$(this).parent().find('ul:first').hide('normal');
$(this).toggleClass('fa-chevron-down fa-chevron-right');
});
});
odoo.define('website_sale.website_sale', function (require) {
"use strict";
"use strict";
var ajax = require('web.ajax');
var core = require('web.core');
var _t = core._t;
var base = require('web_editor.base');
var base = require('web_editor.base');
var ajax = require('web.ajax');
if(!$('#o_shop_collapse_category, .oe_website_sale').length) {
return $.Deferred().reject("DOM doesn't contain '#o_shop_collapse_category, .oe_website_sale'");
}
function update_product_image(event_source, product_id) {
var $img = $(event_source).closest('tr.js_product, .oe_website_sale').find('span[data-oe-model^="product."][data-oe-type="image"] img:first, img.product_detail_img');
$img.attr("src", "/web/image/product.product/" + product_id + "/image");
$img.parent().attr('data-oe-model', 'product.product').attr('data-oe-id', product_id)
.data('oe-model', 'product.product').data('oe-id', product_id);
}
$('#o_shop_collapse_category').on('click', '.fa-chevron-right',function(){
$(this).parent().siblings().find('.fa-chevron-down:first').click();
$(this).parents('li').find('ul:first').show('normal');
$(this).toggleClass('fa-chevron-down fa-chevron-right');
});
$('#o_shop_collapse_category').on('click', '.fa-chevron-down',function(){
$(this).parent().find('ul:first').hide('normal');
$(this).toggleClass('fa-chevron-down fa-chevron-right');
});
$('.oe_website_sale').each(function () {
var oe_website_sale = this;
var $shippingDifferent = $("select[name='shipping_id']", oe_website_sale);
$shippingDifferent.change(function () {
var value = +$shippingDifferent.val();
var data = $shippingDifferent.find("option:selected").data();
var $snipping = $(".js_shipping", oe_website_sale);
var $inputs = $snipping.find("input");
var $selects = $snipping.find("select");
$snipping.toggle(!!value);
$inputs.attr("readonly", value <= 0 ? null : "readonly" ).prop("readonly", value <= 0 ? null : "readonly" );
$selects.attr("disabled", value <= 0 ? null : "disabled" ).prop("disabled", value <= 0 ? null : "disabled" );
$inputs.each(function () {
$(this).val( data[$(this).attr("name")] || "" );
});
});
$(oe_website_sale).on("change", 'input[name="add_qty"]', function (event) {
var product_ids = [];
var product_dom = $(".js_product .js_add_cart_variants[data-attribute_value_ids]").last();
if (!product_dom.length) {
return;
}
_.each(product_dom.data("attribute_value_ids"), function(entry) {
product_ids.push(entry[0]);});
var qty = $(event.target).closest('form').find('input[name="add_qty"]').val();
if ($("#product_detail").length) {
// display the reduction from the pricelist in function of the quantity
ajax.jsonRpc("/shop/get_unit_price", 'call', {'product_ids': product_ids,'add_qty': parseInt(qty)})
.then(function (data) {
var current = product_dom.data("attribute_value_ids");
for(var j=0; j < current.length; j++){
current[j][2] = data[current[j][0]];
}
product_dom.attr("data-attribute_value_ids", JSON.stringify(current)).trigger("change");
});
}
});
// change for css
$(oe_website_sale).on('mouseup touchend', '.js_publish', function (ev) {
$(ev.currentTarget).parents(".thumbnail").toggleClass("disabled");
});
var clickwatch = (function(){
var timer = 0;
return function(callback, ms){
clearTimeout(timer);
timer = setTimeout(callback, ms);
};
})();
$(oe_website_sale).on("change", ".oe_cart input.js_quantity[data-product-id]", function () {
var $input = $(this);
if ($input.data('update_change')) {
return;
}
var value = parseInt($input.val(), 10);
var $dom = $(this).closest('tr');
var default_price = parseFloat($dom.find('.text-danger > span.oe_currency_value').text());
var $dom_optional = $dom.nextUntil(':not(.optional_product.info)');
var line_id = parseInt($input.data('line-id'),10);
var product_id = parseInt($input.data('product-id'),10);
var product_ids = [product_id];
clickwatch(function(){
$dom_optional.each(function(){
$(this).find('.js_quantity').text(value);
product_ids.push($(this).find('span[data-product-id]').data('product-id'));
});
$input.data('update_change', true);
ajax.jsonRpc("/shop/cart/update_json", 'call', {
'line_id': line_id,
'product_id': parseInt($input.data('product-id'),10),
'set_qty': value})
.then(function (data) {
$input.data('update_change', false);
if (value !== parseInt($input.val(), 10)) {
$input.trigger('change');
return;
}
var $q = $(".my_cart_quantity");
if (data.cart_quantity) {
$q.parent().parent().removeClass("hidden");
}
else {
$q.parent().parent().addClass("hidden");
$('a[href^="/shop/checkout"]').addClass("hidden")
}
$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);
$(".js_cart_lines").first().before(data['website_sale.cart_lines']).end().remove();
if (data.warning) {
var cart_alert = $('.oe_cart').parent().find('#data_warning');
if (cart_alert.length === 0) {
$('.oe_cart').prepend('<div class="alert alert-danger alert-dismissable" role="alert" id="data_warning">'+
'<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button> ' + data.warning + '</div>');
}
else {
cart_alert.html('<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button> ' + data.warning);
}
$input.val(data.quantity);
}
});
}, 500);
});
$(oe_website_sale).on("click", ".oe_cart a.js_add_suggested_products", function () {
$(this).prev('input').val(1).trigger('change');
});
// hack to add and rome from cart with json
$(oe_website_sale).on('click', 'a.js_add_cart_json', function (ev) {
ev.preventDefault();
var $link = $(ev.currentTarget);
var $input = $link.parent().find("input");
var product_id = +$input.closest('*:has(input[name="product_id"])').find('input[name="product_id"]').val();
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);
// if they are more of one input for this product (eg: option modal)
$('input[name="'+$input.attr("name")+'"]').add($input).filter(function () {
var $prod = $(this).closest('*:has(input[name="product_id"])');
return !$prod.length || +$prod.find('input[name="product_id"]').val() === product_id;
}).val(quantity > min ? (quantity < max ? quantity : max) : min);
$input.change();
return false;
});
$('.oe_website_sale .a-submit, #comment .a-submit').off('click').on('click', function (event) {
if (!event.isDefaultPrevented() && !$(this).is(".disabled")) {
$(this).closest('form').submit();
}
});
$('form.js_attributes input, form.js_attributes select', oe_website_sale).on('change', function (event) {
if (!event.isDefaultPrevented()) {
$(this).closest("form").submit();
}
});
// change price when they are variants
$('form.js_add_cart_json label', oe_website_sale).on('mouseup touchend', function () {
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()));
}
var value = $price.data("price") + parseFloat($label.find(".badge span").text() || 0);
var dec = value % 1;
$price.html(value + (dec < 0.01 ? ".00" : (dec < 1 ? "0" : "") ));
});
// hightlight selected color
$('.css_attribute_color input', oe_website_sale).on('change', function () {
$('.css_attribute_color').removeClass("active");
$('.css_attribute_color:has(input:checked)').addClass("active");
});
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');
if(!$('.oe_website_sale').length) {
return $.Deferred().reject("DOM doesn't contain '.oe_website_sale'");
}
$(oe_website_sale).on('change', 'input.js_product_change', function () {
var $parent = $(this).closest('.js_product');
$parent.find(".oe_default_price:first .oe_currency_value").html( price_to_str(+$(this).data('lst_price')) );
$parent.find(".oe_price:first .oe_currency_value").html(price_to_str(+$(this).data('price')) );
update_product_image(this, +$(this).val());
});
$('.oe_website_sale').each(function () {
var oe_website_sale = this;
$(oe_website_sale).on('change', 'input.js_variant_change, select.js_variant_change, ul[data-attribute_value_ids]', function (ev) {
var $ul = $(ev.target).closest('.js_add_cart_variants');
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 $optional_price = $parent.find(".oe_optional: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());
$(oe_website_sale).on("change", 'input[name="add_qty"]', function (event) {
var product_ids = [];
var product_dom = $(".js_product .js_add_cart_variants[data-attribute_value_ids]").last();
if (!product_dom.length) {
return;
}
_.each(product_dom.data("attribute_value_ids"), function(entry) {
product_ids.push(entry[0]);});
var qty = $(event.target).closest('form').find('input[name="add_qty"]').val();
if ($("#product_detail").length) {
// display the reduction from the pricelist in function of the quantity
ajax.jsonRpc("/shop/get_unit_price", 'call', {'product_ids': product_ids,'add_qty': parseInt(qty)})
.then(function (data) {
var current = product_dom.data("attribute_value_ids");
for(var j=0; j < current.length; j++){
current[j][2] = data[current[j][0]];
}
product_dom.attr("data-attribute_value_ids", JSON.stringify(current)).trigger("change");
});
}
});
$parent.find("label").removeClass("text-muted css_not_available");
// change for css
$(oe_website_sale).on('mouseup touchend', '.js_publish', function (ev) {
$(ev.currentTarget).parents(".thumbnail").toggleClass("disabled");
});
var product_id = false;
for (var k in variant_ids) {
if (_.isEmpty(_.difference(variant_ids[k][1], values))) {
$price.html(price_to_str(variant_ids[k][2]));
$default_price.html(price_to_str(variant_ids[k][3]));
if (variant_ids[k][3]-variant_ids[k][2]>0.2) {
$default_price.closest('.oe_website_sale').addClass("discount");
$optional_price.closest('.oe_optional').show().css('text-decoration', 'line-through');
} else {
$default_price.closest('.oe_website_sale').removeClass("discount");
$optional_price.closest('.oe_optional').hide();
}
product_id = variant_ids[k][0];
break;
var clickwatch = (function(){
var timer = 0;
return function(callback, ms){
clearTimeout(timer);
timer = setTimeout(callback, ms);
};
})();
$(oe_website_sale).on("change", ".oe_cart input.js_quantity[data-product-id]", function () {
var $input = $(this);
if ($input.data('update_change')) {
return;
}
var value = parseInt($input.val(), 10);
var $dom = $(this).closest('tr');
//var default_price = parseFloat($dom.find('.text-danger > span.oe_currency_value').text());
var $dom_optional = $dom.nextUntil(':not(.optional_product.info)');
var line_id = parseInt($input.data('line-id'),10);
var product_ids = [parseInt($input.data('product-id'),10)];
clickwatch(function(){
$dom_optional.each(function(){
$(this).find('.js_quantity').text(value);
product_ids.push($(this).find('span[data-product-id]').data('product-id'));
});
$input.data('update_change', true);
ajax.jsonRpc("/shop/cart/update_json", 'call', {
'line_id': line_id,
'product_id': parseInt($input.data('product-id'), 10),
'set_qty': value
}).then(function (data) {
$input.data('update_change', false);
if (value !== parseInt($input.val(), 10)) {
$input.trigger('change');
return;
}
var $q = $(".my_cart_quantity");
if (data.cart_quantity) {
$q.parents('li:first').removeClass("hidden");
}
else {
$q.parents('li:first').addClass("hidden");
$('a[href^="/shop/checkout"]').addClass("hidden");
}
$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);
$(".js_cart_lines").first().before(data['website_sale.cart_lines']).end().remove();
if (data.warning) {
var cart_alert = $('.oe_cart').parent().find('#data_warning');
if (cart_alert.length === 0) {
$('.oe_cart').prepend('<div class="alert alert-danger alert-dismissable" role="alert" id="data_warning">'+
'<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button> ' + data.warning + '</div>');
}
else {
cart_alert.html('<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button> ' + data.warning);
}
$input.val(data.quantity);
}
});
}, 500);
});
$(oe_website_sale).on("click", ".oe_cart a.js_add_suggested_products", function () {
$(this).prev('input').val(1).trigger('change');
});
// hack to add and remove from cart with json
$(oe_website_sale).on('click', 'a.js_add_cart_json', function (ev) {
ev.preventDefault();
var $link = $(ev.currentTarget);
var $input = $link.parent().find("input");
var product_id = +$input.closest('*:has(input[name="product_id"])').find('input[name="product_id"]').val();
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);
// if they are more of one input for this product (eg: option modal)
$('input[name="'+$input.attr("name")+'"]').add($input).filter(function () {
var $prod = $(this).closest('*:has(input[name="product_id"])');
return !$prod.length || +$prod.find('input[name="product_id"]').val() === product_id;
}).val(quantity > min ? (quantity < max ? quantity : max) : min);
$input.change();
return false;
});
$('.oe_website_sale .a-submit, #comment .a-submit').off('click').on('click', function (event) {
if (!event.isDefaultPrevented() && !$(this).is(".disabled")) {
$(this).closest('form').submit();
}
});
$('form.js_attributes input, form.js_attributes select', oe_website_sale).on('change', function (event) {
if (!event.isDefaultPrevented()) {
$(this).closest("form").submit();
}
});
// change price when they are variants
$('form.js_add_cart_json label', oe_website_sale).on('mouseup touchend', function () {
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()));
}
var value = $price.data("price") + parseFloat($label.find(".badge span").text() || 0);
var dec = value % 1;
$price.html(value + (dec < 0.01 ? ".00" : (dec < 1 ? "0" : "") ));
});
// hightlight selected color
$('.css_attribute_color input', oe_website_sale).on('change', function () {
$('.css_attribute_color').removeClass("active");
$('.css_attribute_color:has(input:checked)').addClass("active");
});
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');
}
if (product_id) {
update_product_image(this, product_id);
function update_product_image(event_source, product_id) {
var $img = $(event_source).closest('tr.js_product, .oe_website_sale').find('span[data-oe-model^="product."][data-oe-type="image"] img:first, img.product_detail_img');
$img.attr("src", "/web/image/product.product/" + product_id + "/image");
$img.parent().attr('data-oe-model', 'product.product').attr('data-oe-id', product_id)
.data('oe-model', 'product.product').data('oe-id', product_id);
}
$parent.find("input.js_variant_change:radio, select.js_variant_change").each(function () {
var $input = $(this);
var id = +$input.val();
var values = [id];
$(oe_website_sale).on('change', 'input.js_product_change', function () {
var $parent = $(this).closest('.js_product');
$parent.find(".oe_default_price:first .oe_currency_value").html( price_to_str(+$(this).data('lst_price')) );
$parent.find(".oe_price:first .oe_currency_value").html(price_to_str(+$(this).data('price')) );
update_product_image(this, +$(this).val());
});
$parent.find("ul:not(:has(input.js_variant_change[value='" + id + "'])) input.js_variant_change:checked, select").each(function () {
$(oe_website_sale).on('change', 'input.js_variant_change, select.js_variant_change, ul[data-attribute_value_ids]', function (ev) {
var $ul = $(ev.target).closest('.js_add_cart_variants');
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 $optional_price = $parent.find(".oe_optional: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 (!_.difference(values, variant_ids[k][1]).length) {
return;
if (_.isEmpty(_.difference(variant_ids[k][1], values))) {
$price.html(price_to_str(variant_ids[k][2]));
$default_price.html(price_to_str(variant_ids[k][3]));
if (variant_ids[k][3]-variant_ids[k][2]>0.2) {
$default_price.closest('.oe_website_sale').addClass("discount");
$optional_price.closest('.oe_optional').show().css('text-decoration', 'line-through');
} else {
$default_price.closest('.oe_website_sale').removeClass("discount");
$optional_price.closest('.oe_optional').hide();
}
product_id = variant_ids[k][0];
update_product_image(this, product_id);
break;
}
}
$input.closest("label").addClass("css_not_available");
$input.find("option[value='" + id + "']").addClass("css_not_available");
$parent.find("input.js_variant_change:radio, select.js_variant_change").each(function () {
var $input = $(this);
var id = +$input.val();
var values = [id];
$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;
}
}
$input.closest("label").addClass("css_not_available");
$input.find("option[value='" + id + "']").addClass("css_not_available");
});
if (product_id) {
$parent.removeClass("css_not_available");
$product_id.val(product_id);
$parent.find("#add_to_cart").removeClass("disabled");
} else {
$parent.addClass("css_not_available");
$product_id.val(0);
$parent.find("#add_to_cart").addClass("disabled");
}
});
if (product_id) {
$parent.removeClass("css_not_available");
$product_id.val(product_id);
$parent.find("#add_to_cart").removeClass("disabled");
} else {
$parent.addClass("css_not_available");
$product_id.val(0);
$parent.find("#add_to_cart").addClass("disabled");
$('div.js_product', oe_website_sale).each(function () {
$('input.js_product_change', this).first().trigger('change');
});
$('.js_add_cart_variants', oe_website_sale).each(function () {
$('input.js_variant_change, select.js_variant_change', this).first().trigger('change');
});
$('.oe_cart').on('click', '.js_change_shipping', function() {
var $old = $('.all_shipping').find('.panel.border_primary');
$old.find('.btn-ship').toggle();
$old.addClass('js_change_shipping');
$old.removeClass('border_primary');
var $new = $(this).parent('div.one_kanban').find('.panel');
$new.find('.btn-ship').toggle();
$new.removeClass('js_change_shipping');
$new.addClass('border_primary');
var $form = $(this).parent('div.one_kanban').find('form.hide');
$.post($form.attr('action'), $form.serialize()+'&xhr=1');
});
$('.oe_cart').on('click', '.js_edit_address', function() {
$(this).parent('div.one_kanban').find('form.hide').attr('action', '/shop/address').submit();
});
$('.oe_cart').on('click', '.js_delete_product', function(e) {
$(this).closest('tr').find('.js_quantity').val(0).trigger('change');
});
if ($(".checkout_autoformat").length) {
$(oe_website_sale).on('change', "select[name='country_id']", function () {
clickwatch(function() {
if ($("#country_id").val()) {
ajax.jsonRpc("/website/country_infos/" + $("#country_id").val(), 'call', {}).then(
function(data) {
// placeholder phone_code
//$("input[name='phone']").attr('placeholder', data.phone_code !== 0 ? '+'+ data.phone_code : '');
// populate states and display
var selectStates = $("select[name='state_id']");
// dont reload state at first loading (done in qweb)
if (selectStates.data('init')===1) {
selectStates.data('init', 0);
}
else {
if (data.states.length) {
selectStates.html('');
_.each(data.states, function(x) {
var opt = $('<option>').text(x[1])
.attr('value', x[0])
.attr('data-code', x[2]);
selectStates.append(opt);
});
selectStates.parent('div').show();
}
else{
selectStates.val('').parent('div').hide();
}
}
// manage fields order / visibility
if (data.fields) {
if ($.inArray('zip', data.fields) > $.inArray('city', data.fields)){
$(".div_zip").before($(".div_city"));
}
else {
$(".div_zip").after($(".div_city"));
}
var all_fields = ["street", "zip", "city", "country_name"]; // "state_code"];
_.each(all_fields, function(field) {
$(".checkout_autoformat .div_" + field.split('_')[0]).toggle($.inArray(field, data.fields)>=0);
});
}
}
);
}
}, 500);
});
}
$("select[name='country_id']").change();
});
$('div.js_product', oe_website_sale).each(function () {
$('input.js_product_change', this).first().trigger('change');
});
$('.js_add_cart_variants', oe_website_sale).each(function () {
$('input.js_variant_change, select.js_variant_change', this).first().trigger('change');
});
var state_options = $("select[name='state_id']:enabled option:not(:first)");
$(oe_website_sale).on('change', "select[name='country_id']", function () {
var select = $("select[name='state_id']");
state_options.detach();
var displayed_state = state_options.filter("[data-country_id="+($(this).val() || 0)+"]");
var nb = displayed_state.appendTo(select).show().size();
select.parent().toggle(nb>=1);
});
$(oe_website_sale).find("select[name='country_id']").change();
var shipping_state_options = $("select[name='shipping_state_id']:enabled option:not(:first)");
$(oe_website_sale).on('change', "select[name='shipping_country_id']", function () {
var select = $("select[name='shipping_state_id']");
shipping_state_options.detach();
var displayed_state = shipping_state_options.filter("[data-country_id="+($(this).val() || 0)+"]");
var nb = displayed_state.appendTo(select).show().size();
select.parent().toggle(nb>=1);
});
$(oe_website_sale).find("select[name='shipping_country_id']").change();
});
});
File diff suppressed because it is too large Load Diff
@@ -3,7 +3,7 @@
<data>
<template id="cart_delivery" name="Delivery Costs" inherit_id="website_sale.total">
<xpath expr="//div[@id='order_total_taxes']" position="after">
<xpath expr="//div[@id='order_total_untaxed']" position="before">
<div class="row" id="order_delivery" t-if="website_sale_order and website_sale_order.has_delivery">
<span class="col-xs-6 text-right text-muted" title="Delivery will be updated after choosing a new delivery method"> Delivery:</span>
<span class="col-xs-6 text-left text-muted">
+5 -2
View File
@@ -238,7 +238,6 @@ class IrQWeb(models.AbstractModel, QWeb):
return (attributes, content, inherit_branding or translate)
# formating methods
def _format_func_monetary(self, value, display_currency=None, from_currency=None):
env = display_currency.env
precision = int(round(math.log10(display_currency.rounding)))
@@ -254,8 +253,12 @@ class IrQWeb(models.AbstractModel, QWeb):
formatted_amount, pre=pre, post=post
).format(symbol=display_currency.symbol,)
# compile expression add safe_eval
def _format_func_htmlcontact(self, value, options, values=None):
return self.pool['ir.qweb.field.contact'].record_to_html(
self.env.cr, self.env.uid, dict(contact=value), 'contact', dict(options or {}), context=self.env.context
)
# compile expression add safe_eval
def _compile_expr(self, expr):
""" Compiles a purported Python expression to ast, verifies that it's safe
(according to safe_eval's semantics) and alter its variable references to
+3 -4
View File
@@ -150,10 +150,9 @@
<field name="parent_id"
placeholder="Company"
domain="[('is_company', '=', True)]" context="{'default_is_company': True, 'default_supplier': supplier, 'default_customer': customer}"
attrs="{'invisible': ['|', '&amp;', ('is_company','=', True),('parent_id', '=', False),('display_company_name', '!=', False),('display_company_name', '!=', '')]}"/>
<field name="display_company_name" attrs="{'invisible': ['|', '|', ('display_company_name', '=', False), ('display_company_name', '=', ''), ('is_company', '=', True)]}"/>
<button name="create_company" type="object" string="Create company" class="btn btn-sm oe_edit_only fa fa-external-link btn btn-link " attrs="{'invisible': ['|', '|', ('is_company','=', True), ('display_company_name', '=', ''), ('display_company_name', '=', False)]}"/>
attrs="{'invisible': ['|', '&amp;', ('is_company','=', True),('parent_id', '=', False),('company_name', '!=', False),('company_name', '!=', '')]}"/>
<field name="company_name" attrs="{'invisible': ['|', '|', ('company_name', '=', False), ('company_name', '=', ''), ('is_company', '=', True)]}"/>
<button name="create_company" type="object" string="Create company" class="btn btn-sm oe_edit_only fa fa-external-link btn btn-link " attrs="{'invisible': ['|', '|', ('is_company','=', True), ('company_name', '=', ''), ('company_name', '=', False)]}"/>
</div>
</div>