[IMP] website_sale: Add prevent_zero_price_sale option

After this commit clients will be able to select whether to display the add to cart button when the computed price of a product is 0.
This option will be available in the settings.

task-2694860

closes odoo/odoo#87952

Related: odoo/enterprise#27594
Signed-off-by: William Braeckman (wbr) <wbr@odoo.com>
This commit is contained in:
Danial Sheikhani
2022-07-18 18:41:06 +02:00
committed by William Braeckman
parent 9765879130
commit 8d22dd8ae9
12 changed files with 116 additions and 28 deletions
@@ -12,7 +12,7 @@
<img class="o_carousel_product_card_img_top card-img-top p-2" loading="lazy" t-att-src="data['image_512']"
t-att-alt="record.display_name"/>
</a>
<i t-if="data.get('_latest_viewed')" class="fa fa-trash o_carousel_product_remove js_remove"></i>
<i t-if="data.get('_latest_viewed')" class="fa fa-trash o_carousel_product_remove js_remove"/>
<div class="o_carousel_product_card_body card-body border-top">
<a t-att-href="record.website_url" class="text-decoration-none">
<div class="h6 card-title mb-0" t-field="record.display_name"/>
@@ -287,10 +287,17 @@
</template>
<template id="price_dynamic_filter_template_product_product" name="Dynamic Product Filter Price">
<span t-esc="record._get_contextual_price()" class="fw-bold"
t-options="{'widget': 'monetary', 'display_currency': website.currency_id}"/>
<del t-if="data.get('has_discounted_price')" class="text-danger ms-1 h6" style="white-space: nowrap;"
t-esc="data['list_price']" t-options="{'widget': 'monetary', 'display_currency': website.currency_id}"/>
<t t-set="record_price" t-value="record._get_contextual_price()"/>
<t t-if="not website.prevent_zero_price_sale or record_price">
<span t-esc="record_price" class="fw-bold"
t-options="{'widget': 'monetary', 'display_currency': website.currency_id}"/>
<del t-if="data.get('has_discounted_price')" class="text-danger ms-1 h6" style="white-space: nowrap;"
t-esc="data['list_price']"
t-options="{'widget': 'monetary', 'display_currency': website.currency_id}"/>
</t>
<t t-else="">
<span t-field="website.prevent_zero_price_sale_text"/>
</t>
</template>
<!-- Assets -->
@@ -88,7 +88,8 @@ class Product(models.Model):
return combination_info['product_type'] == 'product' and combination_info['free_qty'] <= 0
def _website_show_quick_add(self):
return self.sale_ok
website = self.env['website'].get_current_website()
return self.sale_ok and (not website.prevent_zero_price_sale or self._get_contextual_price())
def _is_add_to_cart_allowed(self):
self.ensure_one()
+11 -5
View File
@@ -255,7 +255,7 @@ class ProductTemplate(models.Model):
product, partner
)
has_discounted_price = pricelist.currency_id.compare_amounts(list_price, price) == 1
prevent_zero_price_sale = not price and current_website.prevent_zero_price_sale
combination_info.update(
base_unit_name=product.base_unit_name,
base_unit_price=product.base_unit_price,
@@ -263,6 +263,7 @@ class ProductTemplate(models.Model):
list_price=list_price,
price_extra=price_extra,
has_discounted_price=has_discounted_price,
prevent_zero_price_sale=prevent_zero_price_sale,
)
return combination_info
@@ -491,9 +492,13 @@ class ProductTemplate(models.Model):
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['prevent_zero_price_sale']:
website = self.env['website'].get_current_website()
price = website.prevent_zero_price_sale_text
else:
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
@@ -524,4 +529,5 @@ class ProductTemplate(models.Model):
return pricelist
def _website_show_quick_add(self):
return self.sale_ok
website = self.env['website'].get_current_website()
return self.sale_ok and (not website.prevent_zero_price_sale or self._get_contextual_price())
@@ -59,6 +59,8 @@ class ResConfigSettings(models.TransientModel):
compute="_compute_account_on_checkout",
inverse="_inverse_account_on_checkout",
readonly=False, required=True)
website_sale_prevent_zero_price_sale = fields.Boolean(string="Prevent Sale of Zero Priced Product", related='website_id.prevent_zero_price_sale', readonly=False)
website_sale_contact_us_button_url = fields.Char(string="Button URL", related='website_id.contact_us_button_url', readonly=False)
@api.depends('website_id')
def _compute_terms_url(self):
+3
View File
@@ -132,6 +132,9 @@ class SaleOrder(models.Model):
if not product or not product._is_add_to_cart_allowed():
raise UserError(_("The given product does not exist therefore it cannot be added to cart."))
if product.lst_price == 0 and product.website_id.prevent_zero_price_sale:
raise UserError(_("The given product does not have a price therefore it cannot be added to cart."))
if line_id is not False:
order_line = self._cart_find_product_line(product_id, line_id, **kwargs)[:1]
else:
+5
View File
@@ -101,6 +101,11 @@ class Website(models.Model):
], default='small', required=True,
)
prevent_zero_price_sale = fields.Boolean(string="Hide 'Add To Cart' when price = 0")
prevent_zero_price_sale_text = fields.Char(string="Text to show instead of price", translate=True,
default="Not Available For Sale")
contact_us_button_url = fields.Char(string="Contact Us Button URL", translate=True, default="/contactus")
@api.depends('all_pricelist_ids')
def _compute_pricelist_ids(self):
for website in self:
@@ -39,7 +39,24 @@ VariantMixin._onChangeCombination = function (ev, $parent, combination) {
$product.data('product-tracking-info', combination['product_tracking_info']);
$product.trigger('view_item_event', combination['product_tracking_info']);
}
const addToCart = $parent.find('#add_to_cart_wrap');
const contactUsButton = $parent.find('#contact_us_wrapper');
const productPrice = $parent.find('.product_price');
const quantity = $parent.find('.css_quantity');
const product_unavailable = $parent.find('#product_unavailable');
if (combination.prevent_zero_price_sale) {
productPrice.removeClass('d-inline-block').addClass('d-none');
quantity.removeClass('d-inline-flex').addClass('d-none');
addToCart.removeClass('d-inline-flex').addClass('d-none');
contactUsButton.removeClass('d-none').addClass('d-flex');
product_unavailable.removeClass('d-none').addClass('d-flex')
} else {
productPrice.removeClass('d-none').addClass('d-inline-block');
quantity.removeClass('d-none').addClass('d-inline-flex');
addToCart.removeClass('d-none').addClass('d-inline-flex');
contactUsButton.removeClass('d-flex').addClass('d-none');
product_unavailable.removeClass('d-flex').addClass('d-none')
}
originalOnChangeCombination.apply(this, [ev, $parent, combination]);
};
+1 -1
View File
@@ -191,7 +191,7 @@ class TestUi(HttpCaseWithUserDemo, HttpCaseWithUserPortal):
product_template = self.env['product.template'].create({
'name': 'Dynamic Product',
'website_published': True,
'list_price': 0,
'list_price': 10,
})
# set attribute and attribute values on the template
@@ -216,6 +216,23 @@
</div>
</div>
</div>
<div class="col-12 col-lg-6 o_setting_box" id="hide_add_to_cart_setting">
<div class="o_setting_left_pane">
<field name="website_sale_prevent_zero_price_sale"/>
</div>
<div class="o_setting_right_pane">
<label for="website_sale_prevent_zero_price_sale"/>
<div class="text-muted">
If product price equals 0, replace 'Add to Cart' by 'Contact us'.
</div>
<div class="content-group" attrs="{'invisible': [('website_sale_prevent_zero_price_sale', '=', False)]}">
<div class="row mt16">
<label class="o_light_label col-lg-3" string="Button url" for="website_sale_contact_us_button_url"/>
<field name="website_sale_contact_us_button_url"/>
</div>
</div>
</div>
</div>
</div>
<h2>Shipping</h2>
+20 -6
View File
@@ -194,7 +194,8 @@
<em class="small" t-esc="template_price_vals['base_price']" t-options="{'widget': 'monetary', 'display_currency': website.currency_id}" />
</del>
</t>
<span class="h6 mb-0" t-if="template_price_vals['price_reduce']" t-esc="template_price_vals['price_reduce']" t-options="{'widget': 'monetary', 'display_currency': website.currency_id}"/>
<span class="h6 mb-0" t-if="template_price_vals['price_reduce'] or not website.prevent_zero_price_sale" t-esc="template_price_vals['price_reduce']" t-options="{'widget': 'monetary', 'display_currency': website.currency_id}"/>
<span class="h6 mb-0" t-else="" t-field="website.prevent_zero_price_sale_text"/>
<span itemprop="price" style="display:none;" t-esc="template_price_vals['price_reduce']" />
<span itemprop="priceCurrency" style="display:none;" t-esc="website.currency_id.name" />
</div>
@@ -215,7 +216,7 @@
<xpath expr="//div[hasclass('o_wsale_product_btn')]" position="inside">
<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">
<t t-if="product_variant_id and template_price_vals['price_reduce'] or not website.prevent_zero_price_sale">
<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"/>
@@ -649,7 +650,7 @@
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()" />
<div class="js_product js_main_product mb-3">
<div>
<t t-call="website_sale.product_price" />
<t t-call="website_sale.product_price"/>
<small t-if="combination_info['base_unit_price']"
class="ms-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'/>
@@ -674,7 +675,7 @@
<t t-set="hasBuyNow" t-value="false"/>
<t t-set="ctaSizeBig" t-value="not hasQuantities or not hasBuyNow"/>
<div id="add_to_cart_wrap" class="d-inline-flex align-items-center mb-2 me-auto">
<div id="add_to_cart_wrap" t-attf-class="{{'d-none' if combination_info['prevent_zero_price_sale'] else 'd-inline-flex'}} align-items-center mb-2 me-auto">
<a role="button" id="add_to_cart" t-attf-class="btn btn-primary js_check_product a-submit flex-grow-1 #{'btn-lg' if ctaSizeBig else ''}" href="#">
<i class="fa fa-shopping-cart me-2"/>
<span style="font-weight: bold">ADD TO CART</span>
@@ -682,6 +683,16 @@
</div>
<div id="product_option_block" class="d-flex flex-wrap w-100"/>
</div>
<div id="contact_us_wrapper"
t-attf-class="{{'d-flex' if combination_info['prevent_zero_price_sale'] else 'd-none'}} oe_structure oe_structure_solo #{_div_classes}">
<section class="s_text_block" data-snippet="s_text_block" data-name="Text">
<div class="container">
<a t-att-href="website.contact_us_button_url"
class="btn btn-primary btn_cta">Contact Us
</a>
</div>
</section>
</div>
</div>
</form>
<p t-elif="not product.active" class="alert alert-warning">This product is no longer available.</p>
@@ -807,7 +818,7 @@
<attribute name="t-value" remove="false" add="true" separator=" "/>
</xpath>
<xpath expr="//div[@id='add_to_cart_wrap']" position="before">
<div t-attf-class="css_quantity input-group d-inline-flex me-2 mb-2 align-middle #{'input-group-lg' if ctaSizeBig else ''}" contenteditable="false">
<div t-attf-class="css_quantity input-group {{'d-none' if combination_info['prevent_zero_price_sale'] else 'd-inline-flex'}} me-2 mb-2 align-middle #{'input-group-lg' if ctaSizeBig else ''}" contenteditable="false">
<a t-attf-href="#" class="btn btn-link js_add_cart_json" aria-label="Remove one" title="Remove one">
<i class="fa fa-minus"></i>
</a>
@@ -832,7 +843,7 @@
</template>
<template id="product_price">
<div itemprop="offers" itemscope="itemscope" itemtype="http://schema.org/Offer" class="product_price d-inline-block mt-2 mb-3">
<div itemprop="offers" itemscope="itemscope" itemtype="http://schema.org/Offer" t-attf-class="product_price mt-2 mb-3 {{'d-none' if combination_info['prevent_zero_price_sale'] else 'd-inline-block'}}">
<h3 class="css_editable_mode_hidden">
<span class="oe_price" style="white-space: nowrap;" t-esc="combination_info['price']" t-options="{'widget': 'monetary', 'display_currency': website.currency_id}"/>
<span itemprop="price" style="display:none;" t-esc="combination_info['price']"/>
@@ -866,6 +877,9 @@
</del>
</h3>
</div>
<div id="product_unavailable" t-attf-class="{{'d-flex' if combination_info['prevent_zero_price_sale'] else 'd-none'}}">
<h3 class="font-italic" t-field="website.prevent_zero_price_sale_text"/>
</div>
</template>
<template id="product_variants" inherit_id="website_sale.product" active="False" name="List View of Variants">
@@ -113,20 +113,29 @@
<span t-esc="combination_info['display_name']"></span><br/>
</a>
<span class="o_comparison_price">
<span class="o_comparison_price" t-if="combination_info['prevent_zero_price_sale']">
<strong t-field="website.prevent_zero_price_sale_text"/>
</span>
<span class="o_comparison_price" t-else="">
<strong>Price:</strong>
<del t-attf-class="text-danger mr8 {{'' if combination_info['has_discounted_price'] else 'd-none'}}" style="white-space: nowrap;" t-esc="combination_info['list_price']" t-options="{'widget': 'monetary', 'display_currency': website.currency_id}" />
<span t-esc="combination_info['price']" t-options="{'widget': 'monetary', 'display_currency': website.currency_id}"></span>
<del t-attf-class="text-danger mr8 {{'' if combination_info['has_discounted_price'] else 'd-none'}}"
style="white-space: nowrap;" t-esc="combination_info['list_price']"
t-options="{'widget': 'monetary', 'display_currency': website.currency_id}"/>
<span t-esc="combination_info['price']"
t-options="{'widget': 'monetary', 'display_currency': website.currency_id}"/>
<small class="d-block text-muted" groups="website_sale.group_show_uom_price" t-if="combination_info['base_unit_price']">
<t t-call='website_sale.base_unit_price'/>
</small>
</span>
<form action="/shop/cart/update" method="post" class="text-center">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()" />
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<input name="product_id" t-att-value="product.id" type="hidden"
t-att-data-product-tracking-info="json.dumps(request.env['product.template'].get_google_analytics_data(combination_info))"/>
<a role="button" class="btn btn-primary d-block a-submit" href="#"><i class="fa fa-shopping-cart me-2"></i>Add to Cart</a>
<a t-if="combination_info['prevent_zero_price_sale']" t-att-href="website.contact_us_button_url" class="btn btn-primary btn_cta">Contact Us</a>
<a t-else="" role="button" class="btn btn-primary d-block a-submit" href="#">
<i class="fa fa-shopping-cart me-2"/>Add to Cart
</a>
</form>
</div>
</td>
@@ -166,8 +175,11 @@
<div class="col-8 ps-2">
<h6>
<a t-att-href="product.website_url"><t t-esc="combination_info['display_name']" /></a><br/>
<del t-attf-class="text-danger mr8 {{'' if combination_info['has_discounted_price'] else 'd-none'}}" style="white-space: nowrap;" t-esc="combination_info['list_price']" t-options="{'widget': 'monetary', 'display_currency': website.currency_id}" />
<span t-esc="combination_info['price']" t-options="{'widget': 'monetary', 'display_currency': website.currency_id}"></span>
<div t-attf-class="{{'d-none' if combination_info['prevent_zero_price_sale'] else ''}}">
<del t-attf-class="text-danger mr8 {{'' if combination_info['has_discounted_price'] else 'd-none'}}" style="white-space: nowrap;" t-esc="combination_info['list_price']" t-options="{'widget': 'monetary', 'display_currency': website.currency_id}" />
<span t-esc="combination_info['price']" t-options="{'widget': 'monetary', 'display_currency': website.currency_id}"/>
</div>
<div t-attf-class="{{'' if combination_info['prevent_zero_price_sale'] else 'd-none'}}" t-field="website.prevent_zero_price_sale_text"/>
</h6>
</div>
<div class="col-1 text-end">
@@ -188,7 +188,10 @@
<small class='d-none d-md-block'><p t-field="wish.product_id.description_sale" class="text-muted"/></small>
<button type="button" class="btn btn-link o_wish_rm no-decoration"><small><i class='fa fa-trash-o'></i> Remove</small></button>
</td>
<td class="align-middle o_wish_price">
<td class="align-middle" t-if="combination_info['prevent_zero_price_sale']">
<span t-field="website.prevent_zero_price_sale_text"/>
</td>
<td class="align-middle o_wish_price" t-else="">
<t t-esc="combination_info['price']" t-options="{'widget': 'monetary', 'display_currency': website.pricelist_id.currency_id}"/>
<small t-if="combination_info['base_unit_price']" class="cart_product_base_unit_price d-block text-muted" groups="website_sale.group_show_uom_price">
<t t-call='website_sale.base_unit_price'><t t-set='product' t-value='wish.product_id' /></t>
@@ -196,7 +199,8 @@
</td>
<td class='text-center td-wish-btn align-middle'>
<input name="product_id" t-att-value="wish.product_id.id" type="hidden"/>
<button type="button" role="button" class="btn btn-secondary btn-block o_wish_add mb4" >
<a t-if="combination_info['prevent_zero_price_sale']" t-att-href="website.contact_us_button_url" class="btn btn-primary btn_cta">Contact Us</a>
<button t-else="" type="button" role="button" class="btn btn-secondary btn-block o_wish_add mb4" >
Add <span class='d-none d-md-inline'>to Cart</span>
</button>
</td>