[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:
Thibault Libioulle
2022-04-14 14:33:23 +02:00
parent a7f30ce917
commit a0e6ffc7dd
9 changed files with 108 additions and 36 deletions
+16 -7
View File
@@ -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
+20 -4
View File
@@ -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
+8
View File
@@ -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
+5 -1
View File
@@ -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({
+11 -5
View File
@@ -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>