[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:
@@ -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) {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
@@ -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">×</button> ' + data.warning + '</div>');
|
||||
}
|
||||
else {
|
||||
cart_alert.html('<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</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">×</button> ' + data.warning + '</div>');
|
||||
}
|
||||
else {
|
||||
cart_alert.html('<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</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();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
+1519
-1474
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">
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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': ['|', '&', ('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': ['|', '&', ('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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user