From e4a317a17698df842e4cbdbf094a38b774f28941 Mon Sep 17 00:00:00 2001 From: Jeremy Kersten Date: Wed, 29 Jun 2016 19:46:25 +0200 Subject: [PATCH] [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) - ... --- .../static/src/js/website_form.js | 2 +- addons/website_sale/__openerp__.py | 2 +- addons/website_sale/controllers/main.py | 458 +-- addons/website_sale/data/data.xml | 31 +- .../static/src/css/website_sale.css | 27 +- .../static/src/css/website_sale.sass | 18 + .../static/src/js/website_sale.js | 677 ++-- addons/website_sale/views/templates.xml | 2993 +++++++++-------- .../views/website_sale_delivery.xml | 2 +- openerp/addons/base/ir/ir_qweb/ir_qweb.py | 7 +- openerp/addons/base/res/res_partner_view.xml | 7 +- 11 files changed, 2187 insertions(+), 2037 deletions(-) diff --git a/addons/website_form/static/src/js/website_form.js b/addons/website_form/static/src/js/website_form.js index ad5dcaa5cb7..001bc5e44c0 100644 --- a/addons/website_form/static/src/js/website_form.js +++ b/addons/website_form/static/src/js/website_form.js @@ -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) { diff --git a/addons/website_sale/__openerp__.py b/addons/website_sale/__openerp__.py index 1edcd27e63e..367d64ec1d4 100644 --- a/addons/website_sale/__openerp__.py +++ b/addons/website_sale/__openerp__.py @@ -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', diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py index 6b0ca54a049..cef8238edf2 100644 --- a/addons/website_sale/controllers/main.py +++ b/addons/website_sale/controllers/main.py @@ -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/'], 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/'], 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} diff --git a/addons/website_sale/data/data.xml b/addons/website_sale/data/data.xml index e9f6c15df79..1d631a708ab 100644 --- a/addons/website_sale/data/data.xml +++ b/addons/website_sale/data/data.xml @@ -1,5 +1,6 @@ - + + Shop @@ -39,8 +40,32 @@ - - + + + + + + Sale Order + + + sale.order + + + + True + Customer + + + res.partner + + + diff --git a/addons/website_sale/static/src/css/website_sale.css b/addons/website_sale/static/src/css/website_sale.css index 1a6a148f6fa..4486a957362 100644 --- a/addons/website_sale/static/src/css/website_sale.css +++ b/addons/website_sale/static/src/css/website_sale.css @@ -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 */ diff --git a/addons/website_sale/static/src/css/website_sale.sass b/addons/website_sale/static/src/css/website_sale.sass index f8179bd6bc0..4c4ce73c45c 100644 --- a/addons/website_sale/static/src/css/website_sale.sass +++ b/addons/website_sale/static/src/css/website_sale.sass @@ -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 \ No newline at end of file diff --git a/addons/website_sale/static/src/js/website_sale.js b/addons/website_sale/static/src/js/website_sale.js index 5d01e05759e..6f1ee64f25a 100644 --- a/addons/website_sale/static/src/js/website_sale.js +++ b/addons/website_sale/static/src/js/website_sale.js @@ -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: '' -}).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: '' + }).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(''); - } - else { - cart_alert.html(' ' + 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(''); + } + else { + cart_alert.html(' ' + 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 = $('