[IMP] website_sale: improve inheritability on cart updates
This commit adds hooks to give more flexibility on cart updates. It allows to add parameters when a product is added to a cart. It gives a method to disable the quick add and multiple hooks for further smooths developments. task-2382027
This commit is contained in:
@@ -420,7 +420,7 @@ class WebsiteSale(http.Controller):
|
||||
'categories': ProductCategory.search([('parent_id', '=', False)]),
|
||||
'main_object': product,
|
||||
'product': product,
|
||||
'add_qty': 1.0,
|
||||
'add_qty': 1,
|
||||
'view_track': view_track,
|
||||
}
|
||||
|
||||
@@ -540,7 +540,8 @@ class WebsiteSale(http.Controller):
|
||||
add_qty=add_qty,
|
||||
set_qty=set_qty,
|
||||
product_custom_attribute_values=product_custom_attribute_values,
|
||||
no_variant_attribute_values=no_variant_attribute_values
|
||||
no_variant_attribute_values=no_variant_attribute_values,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
if express:
|
||||
@@ -549,7 +550,10 @@ class WebsiteSale(http.Controller):
|
||||
return request.redirect("/shop/cart")
|
||||
|
||||
@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, **kw):
|
||||
def cart_update_json(
|
||||
self, product_id, line_id=None, add_qty=None, set_qty=None, display=True,
|
||||
product_custom_attribute_values=None, no_variant_attribute_values=None, **kw
|
||||
):
|
||||
"""
|
||||
This route is called :
|
||||
- When changing quantity from the cart.
|
||||
@@ -564,15 +568,20 @@ class WebsiteSale(http.Controller):
|
||||
else:
|
||||
return {}
|
||||
|
||||
pcav = kw.get('product_custom_attribute_values')
|
||||
nvav = kw.get('no_variant_attribute_values')
|
||||
if product_custom_attribute_values:
|
||||
product_custom_attribute_values = json_scriptsafe.loads(product_custom_attribute_values)
|
||||
|
||||
if no_variant_attribute_values:
|
||||
no_variant_attribute_values = json_scriptsafe.loads(no_variant_attribute_values)
|
||||
|
||||
values = order._cart_update(
|
||||
product_id=product_id,
|
||||
line_id=line_id,
|
||||
add_qty=add_qty,
|
||||
set_qty=set_qty,
|
||||
product_custom_attribute_values=json_scriptsafe.loads(pcav) if pcav else None,
|
||||
no_variant_attribute_values=json_scriptsafe.loads(nvav) if nvav else None
|
||||
product_custom_attribute_values=product_custom_attribute_values,
|
||||
no_variant_attribute_values=no_variant_attribute_values,
|
||||
**kw
|
||||
)
|
||||
|
||||
if not order.cart_quantity:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<data>
|
||||
<!-- Templates for Dynamic Snippet -->
|
||||
<template id="dynamic_filter_template_product_product_add_to_cart" name="Classic Card">
|
||||
<t t-foreach="records" t-as="data">
|
||||
@@ -28,7 +28,8 @@
|
||||
<div>
|
||||
<t t-call="website_sale.price_dynamic_filter_template_product_product"/>
|
||||
</div>
|
||||
<button type="button" role="button" class="btn btn-primary js_add_cart ml-auto" title="Add to Cart">
|
||||
<button t-if="record._website_show_quick_add()"
|
||||
type="button" role="button" class="btn btn-primary js_add_cart ml-auto" title="Add to Cart">
|
||||
<i class="fa fa-fw fa-shopping-cart"/>
|
||||
</button>
|
||||
</div>
|
||||
@@ -198,8 +199,9 @@
|
||||
</t>
|
||||
<div class="card-title h6 w-100 mt-2" t-field="record.display_name"/>
|
||||
</div>
|
||||
<div class="text-right o_dynamic_snippet_btn_wrapper">
|
||||
<button type="button" role="button" class="btn btn-primary js_add_cart" title="Add to Cart">
|
||||
<div class="text-right o_dynamic_snippet_btn_wrapper" t-if="record._website_show_quick_add()">
|
||||
<button t-if="record._website_show_quick_add()"
|
||||
type="button" role="button" class="btn btn-primary js_add_cart" title="Add to Cart">
|
||||
Add to Cart <i class="fa fa-fw fa-shopping-cart"/>
|
||||
</button>
|
||||
</div>
|
||||
@@ -235,7 +237,8 @@
|
||||
<a class="btn btn-outline-secondary mr-1 mt-1" t-att-href="record.website_url">
|
||||
View Product
|
||||
</a>
|
||||
<button type="button" role="button" class="btn btn-primary js_add_cart mt-1" title="Add to Cart">
|
||||
<button t-if="record._website_show_quick_add()"
|
||||
type="button" role="button" class="btn btn-primary js_add_cart mt-1" title="Add to Cart">
|
||||
Add to Cart <i class="fa fa-fw fa-shopping-cart"/>
|
||||
</button>
|
||||
</div>
|
||||
@@ -271,7 +274,8 @@
|
||||
<div class="d-flex justify-content-between align-items-center flex-wrap">
|
||||
<t t-call="website_sale.price_dynamic_filter_template_product_product"/>
|
||||
<div class="o_dynamic_snippet_btn_wrapper">
|
||||
<button type="button" role="button" class="btn btn-primary js_add_cart" title="Add to Cart">
|
||||
<button t-if="record._website_show_quick_add()"
|
||||
type="button" role="button" class="btn btn-primary js_add_cart" title="Add to Cart">
|
||||
<i class="fa fa-fw fa-shopping-cart"/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -86,3 +86,6 @@ class Product(models.Model):
|
||||
def _is_sold_out(self):
|
||||
combination_info = self.with_context(website_sale_stock_get_quantity=True).product_tmpl_id._get_combination_info(product_id=self.id)
|
||||
return combination_info['product_type'] == 'product' and combination_info['free_qty'] <= 0
|
||||
|
||||
def _website_show_quick_add(self):
|
||||
return self.sale_ok
|
||||
|
||||
@@ -453,10 +453,11 @@ class ProductTemplate(models.Model):
|
||||
for product, data in zip(self, results_data):
|
||||
if with_price:
|
||||
combination_info = product._get_combination_info(only_template=True)
|
||||
monetary_options = {'display_currency': mapping['detail']['display_currency']}
|
||||
data['price'] = self.env['ir.qweb.field.monetary'].value_to_html(combination_info['price'], monetary_options)
|
||||
if combination_info['has_discounted_price']:
|
||||
data['list_price'] = self.env['ir.qweb.field.monetary'].value_to_html(combination_info['list_price'], monetary_options)
|
||||
data['price'], list_price = self._search_render_results_prices(
|
||||
mapping, combination_info
|
||||
)
|
||||
if list_price:
|
||||
data['list_price'] = list_price
|
||||
if with_image:
|
||||
data['image_url'] = '/web/image/product.template/%s/image_128' % data['id']
|
||||
if with_category and product.public_categ_ids:
|
||||
@@ -466,6 +467,18 @@ class ProductTemplate(models.Model):
|
||||
)
|
||||
return results_data
|
||||
|
||||
def _search_render_results_prices(self, mapping, combination_info):
|
||||
monetary_options = {'display_currency': mapping['detail']['display_currency']}
|
||||
price = self.env['ir.qweb.field.monetary'].value_to_html(
|
||||
combination_info['price'], monetary_options
|
||||
)
|
||||
if combination_info['has_discounted_price']:
|
||||
list_price = self.env['ir.qweb.field.monetary'].value_to_html(
|
||||
combination_info['list_price'], monetary_options
|
||||
)
|
||||
|
||||
return price, list_price if combination_info['has_discounted_price'] else None
|
||||
|
||||
@api.model
|
||||
def get_google_analytics_data(self, combination):
|
||||
product = self.env['product.product'].browse(combination['product_id'])
|
||||
@@ -487,3 +500,6 @@ class ProductTemplate(models.Model):
|
||||
if website:
|
||||
return website.get_current_pricelist()
|
||||
return pricelist
|
||||
|
||||
def _website_show_quick_add(self):
|
||||
return self.sale_ok
|
||||
|
||||
@@ -26,6 +26,7 @@ class SaleOrder(models.Model):
|
||||
cart_recovery_email_sent = fields.Boolean('Cart recovery email already sent')
|
||||
website_id = fields.Many2one('website', string='Website', readonly=True,
|
||||
help='Website through which this order was placed.')
|
||||
shop_warning = fields.Char('Warning')
|
||||
|
||||
def _compute_user_id(self):
|
||||
"""Do not assign self.env.user as salesman for e-commerce orders
|
||||
@@ -382,3 +383,10 @@ class SaleOrder(models.Model):
|
||||
if not order.transaction_ids and not order.amount_total and self._context.get('send_email'):
|
||||
order._send_order_confirmation_mail()
|
||||
return res
|
||||
|
||||
def _get_shop_warning(self, clear=True):
|
||||
self.ensure_one()
|
||||
warn = self.shop_warning
|
||||
if clear:
|
||||
self.shop_warning = ''
|
||||
return warn
|
||||
|
||||
@@ -12,6 +12,8 @@ class SaleOrderLine(models.Model):
|
||||
|
||||
name_short = fields.Char(compute="_compute_name_short")
|
||||
|
||||
shop_warning = fields.Char('Warning')
|
||||
|
||||
#=== COMPUTE METHODS ===#
|
||||
|
||||
@api.depends('linked_line_id', 'option_line_ids')
|
||||
@@ -45,3 +47,10 @@ class SaleOrderLine(models.Model):
|
||||
|
||||
def get_description_following_lines(self):
|
||||
return self.name.splitlines()[1:]
|
||||
|
||||
def _get_shop_warning(self, clear=True):
|
||||
self.ensure_one()
|
||||
warn = self.shop_warning
|
||||
if clear:
|
||||
self.shop_warning = ''
|
||||
return warn
|
||||
|
||||
@@ -6,6 +6,7 @@ import logging
|
||||
from odoo import api, fields, models, tools, SUPERUSER_ID, _
|
||||
|
||||
from odoo.http import request
|
||||
from odoo.osv import expression
|
||||
from odoo.addons.http_routing.models.ir_http import url_for
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
@@ -254,7 +255,10 @@ class Website(models.Model):
|
||||
return pricelist
|
||||
|
||||
def sale_product_domain(self):
|
||||
return [("sale_ok", "=", True)] + self.get_current_website().website_domain()
|
||||
return expression.AND([self._product_domain(), self.get_current_website().website_domain()])
|
||||
|
||||
def _product_domain(self):
|
||||
return [('sale_ok', '=', True)]
|
||||
|
||||
def sale_get_order(self, force_create=False, update_pricelist=False):
|
||||
""" Return the current sales order after mofications specified by params.
|
||||
|
||||
@@ -555,15 +555,7 @@ publicWidget.registry.WebsiteSale = publicWidget.Widget.extend(VariantMixin, car
|
||||
|
||||
return productReady.then(function (productId) {
|
||||
$form.find(productSelector.join(', ')).val(productId);
|
||||
|
||||
self.rootProduct = {
|
||||
product_id: productId,
|
||||
quantity: parseFloat($form.find('input[name="add_qty"]').val() || 1),
|
||||
product_custom_attribute_values: self.getCustomVariantValues($form.find('.js_product')),
|
||||
variant_values: self.getSelectedVariantValues($form.find('.js_product')),
|
||||
no_variant_attribute_values: self.getNoVariantAttributeValues($form.find('.js_product'))
|
||||
};
|
||||
|
||||
self._updateRootProduct($form, productId);
|
||||
return self._onProductReady();
|
||||
});
|
||||
},
|
||||
@@ -593,13 +585,14 @@ publicWidget.registry.WebsiteSale = publicWidget.Widget.extend(VariantMixin, car
|
||||
params.add_qty = params.quantity;
|
||||
params.product_custom_attribute_values = JSON.stringify(params.product_custom_attribute_values);
|
||||
params.no_variant_attribute_values = JSON.stringify(params.no_variant_attribute_values);
|
||||
delete params.quantity;
|
||||
return this.addToCart(params);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onClickAddCartJSON: function (ev){
|
||||
_onClickAddCartJSON: function (ev) {
|
||||
this.onClickAddCartJSON(ev);
|
||||
},
|
||||
/**
|
||||
@@ -656,13 +649,13 @@ publicWidget.registry.WebsiteSale = publicWidget.Widget.extend(VariantMixin, car
|
||||
ev.preventDefault();
|
||||
$aSubmit.closest('form').submit();
|
||||
}
|
||||
if ($aSubmit.hasClass('a-submit-disable')){
|
||||
if ($aSubmit.hasClass('a-submit-disable')) {
|
||||
$aSubmit.addClass("disabled");
|
||||
}
|
||||
if ($aSubmit.hasClass('a-submit-loading')){
|
||||
if ($aSubmit.hasClass('a-submit-loading')) {
|
||||
var loading = '<span class="fa fa-cog fa-spin"/>';
|
||||
var fa_span = $aSubmit.find('span[class*="fa"]');
|
||||
if (fa_span.length){
|
||||
if (fa_span.length) {
|
||||
fa_span.replaceWith(loading);
|
||||
} else {
|
||||
$aSubmit.append(loading);
|
||||
@@ -802,6 +795,26 @@ publicWidget.registry.WebsiteSale = publicWidget.Widget.extend(VariantMixin, car
|
||||
_onClickReviewsLink: function () {
|
||||
$('#o_product_page_reviews_content').collapse('show');
|
||||
},
|
||||
|
||||
// -------------------------------------
|
||||
// Utils
|
||||
// -------------------------------------
|
||||
/**
|
||||
* Update the root product during an Add process.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} $form
|
||||
* @param {Number} productId
|
||||
*/
|
||||
_updateRootProduct($form, productId) {
|
||||
this.rootProduct = {
|
||||
product_id: productId,
|
||||
quantity: parseFloat($form.find('input[name="add_qty"]').val() || 1),
|
||||
product_custom_attribute_values: this.getCustomVariantValues($form.find('.js_product')),
|
||||
variant_values: this.getSelectedVariantValues($form.find('.js_product')),
|
||||
no_variant_attribute_values: this.getNoVariantAttributeValues($form.find('.js_product'))
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
publicWidget.registry.WebsiteSaleLayout = publicWidget.Widget.extend({
|
||||
|
||||
@@ -214,7 +214,8 @@
|
||||
<t t-set="product_variant_id" t-value="product._get_first_possible_variant_id()"/>
|
||||
<input name="product_id" t-att-value="product_variant_id" type="hidden"/>
|
||||
<t t-if="product_variant_id">
|
||||
<a href="#" role="button" class="btn btn-primary a-submit" aria-label="Shopping cart" title="Shopping cart">
|
||||
<a t-if="product._website_show_quick_add()"
|
||||
href="#" role="button" class="btn btn-primary a-submit" aria-label="Shopping cart" title="Shopping cart">
|
||||
<span class="fa fa-shopping-cart"/>
|
||||
</a>
|
||||
</t>
|
||||
@@ -589,8 +590,10 @@
|
||||
</template>
|
||||
|
||||
<template id="base_unit_price" name="Product Bas eunit price">
|
||||
(<span class="o_base_unit_price" t-field="product.base_unit_price" t-options="{'display_currency': website.currency_id}"/>
|
||||
/ <span class="oe_custom_base_unit" t-field="product.base_unit_name"/>)
|
||||
<t>
|
||||
(<span class="o_base_unit_price" t-field="product.base_unit_price" t-options="{'display_currency': website.currency_id}"/>
|
||||
/ <span class="oe_custom_base_unit" t-field="product.base_unit_name"/>)
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="product" name="Product" track="1">
|
||||
@@ -655,7 +658,8 @@
|
||||
<div class="js_product js_main_product mb-3">
|
||||
<div>
|
||||
<t t-call="website_sale.product_price" />
|
||||
<small class="ml-1 text-muted o_base_unit_price_wrapper d-none" groups="website_sale.group_show_uom_price">
|
||||
<small t-if="combination_info['base_unit_price']"
|
||||
class="ml-1 text-muted o_base_unit_price_wrapper d-none" groups="website_sale.group_show_uom_price">
|
||||
<t t-call='website_sale.base_unit_price'/>
|
||||
</small>
|
||||
</div>
|
||||
@@ -846,6 +850,7 @@
|
||||
<span itemprop="priceCurrency" style="display:none;" t-esc="website.currency_id.name"/>
|
||||
<span t-attf-class="text-danger oe_default_price ml-1 h5 {{'' if combination_info['has_discounted_price'] else 'd-none'}}" style="text-decoration: line-through; white-space: nowrap;"
|
||||
t-esc="combination_info['list_price']" t-options="{'widget': 'monetary', 'display_currency': website.currency_id}"
|
||||
itemprop="listPrice"
|
||||
/>
|
||||
<del t-if="product.compare_list_price">
|
||||
<span t-field="product.compare_list_price"
|
||||
@@ -1284,7 +1289,8 @@
|
||||
</td>
|
||||
<td class="w-25 text-center">
|
||||
<input class="js_quantity" name="product_id" t-att-data-product-id="product.id" type="hidden" />
|
||||
<a role="button" class="btn btn-link js_add_suggested_products">
|
||||
<a t-if="product._website_show_quick_add()"
|
||||
role="button" class="btn btn-link js_add_suggested_products">
|
||||
<strong>Add to Cart</strong>
|
||||
</a>
|
||||
</td>
|
||||
|
||||
Reference in New Issue
Block a user