Skip to content
Snippets Groups Projects
Commit 4730a884 authored by Lucas Perais (lpe)'s avatar Lucas Perais (lpe)
Browse files

[FIX] website_quote: portal optional products qty change


Have a SO with a quotation template and some optional products set
Display it on the portal
On an optional product, click on the cart icon
to add it to the SO

Modify the quantity of the optional product

Before this commit, the feature was barely working:
- the total price of the optional product did not change
- negative quantities were allowed
- there was no reaction when directly putting a number in the input
- when decrementing the quantity, it crashed
- untaxed and tax amounts were not dynamic

After this commit:
- the total price of the optional product changes as a function of the quantity input
- negative quantities are not allowed
- it is not possible to manually input the quantity with a keyboard
  only +/- buttons are used to change the quantity
- decrementing the quantity works
- untaxed and tax amounts are dynamic

OPW 1947769

closes odoo/odoo#32715

Signed-off-by: default avatarLucas Perais (lpe) <lpe@odoo.com>
parent 5ec4da2e
No related branches found
No related tags found
No related merge requests found
......@@ -2,8 +2,10 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import werkzeug
from functools import partial
from odoo import fields, http, _
from odoo.tools import formatLang
from odoo.http import request
from odoo.addons.website_mail.controllers.main import _message_post_helper
......@@ -104,8 +106,17 @@ class sale_quote(http.Controller):
_message_post_helper(message=message, res_id=order_id, res_model='sale.order', **{'token': token, 'token_field': 'access_token'} if token else {})
return werkzeug.utils.redirect("/quote/%s/%s?message=2" % (order_id, token))
# Deprecated because override opportunities are **really** limited
# In fact it should be removed in master ASAP
@http.route(['/quote/update_line'], type='json', auth="public", website=True)
def update(self, line_id, remove=False, unlink=False, order_id=None, token=None, **post):
values = self.update_line_dict(line_id, remove, unlink, order_id, token, **post)
if values:
return [values['order_line_product_uom_qty'], values['order_amount_total']]
return values
@http.route(['/quote/update_line_dict'], type='json', auth="public", website=True)
def update_line_dict(self, line_id, remove=False, unlink=False, order_id=None, token=None, input_quantity=False, **kwargs):
Order = request.env['sale.order'].sudo().browse(int(order_id))
if token != Order.access_token:
return request.render('website.404')
......@@ -115,10 +126,27 @@ class sale_quote(http.Controller):
if unlink:
OrderLine.unlink()
return False
number = -1 if remove else 1
quantity = OrderLine.product_uom_qty + number
if input_quantity is not False:
quantity = input_quantity
else:
number = -1 if remove else 1
quantity = OrderLine.product_uom_qty + number
if quantity < 0:
quantity = 0.0
OrderLine.write({'product_uom_qty': quantity})
return [str(quantity), str(Order.amount_total)]
currency = Order.currency_id
format_price = partial(formatLang, request.env, digits=currency.decimal_places)
return {
'order_line_product_uom_qty': str(quantity),
'order_line_price_total': format_price(OrderLine.price_total),
'order_line_price_subtotal': format_price(OrderLine.price_subtotal),
'order_amount_total': format_price(Order.amount_total),
'order_amount_untaxed': format_price(Order.amount_untaxed),
'order_amount_tax': format_price(Order.amount_tax),
}
@http.route(["/quote/template/<model('sale.quote.template'):quote>"], type='http', auth="user", website=True)
def template_view(self, quote, **post):
......
......@@ -15,28 +15,160 @@ if(!$('.o_website_quote').length) {
events: {
'click' : 'onClick',
},
onClick: function(ev){
ev.preventDefault();
/**
* @override
*/
start: function () {
var self = this;
var href = this.$el.attr("href");
var order_id = href.match(/order_id=([0-9]+)/);
var line_id = href.match(/update_line\/([0-9]+)/);
var token = href.match(/token=(.*)/);
ajax.jsonRpc("/quote/update_line", 'call', {
'line_id': line_id[1],
'order_id': parseInt(order_id[1]),
'token': token[1],
'remove': self.$el.is('[href*="remove"]'),
'unlink': self.$el.is('[href*="unlink"]')
}).then(function (data) {
if(!data){
location.reload();
}
self.$el.parents('.input-group:first').find('.js_quantity').val(data[0]);
$('[data-id="total_amount"]>span').html(data[1]);
return this._super.apply(this, arguments).then(function () {
self.elems = self._getUpdatableElements();
self.elems.$lineQuantity.change(function (ev) {
var quantity = parseInt(this.value);
self._onChangeQuantity(quantity);
});
});
},
/**
* Process the change in line quantity
*
* @private
* @param {Int} quantity, the new quantity of the line
* If not present it will increment/decrement the existing quantity
*/
_onChangeQuantity: function (quantity) {
var href = this.$el.attr("href");
var order_id = href.match(/order_id=([0-9]+)/)[1];
var line_id = href.match(/update_line(_dict)?\/([0-9]+)/)[2];
var token = href.match(/token=([\w\d-]*)/)[1];
var callParams = {
'line_id': parseInt(line_id),
'order_id': parseInt(order_id),
'token': token,
'remove': this.$el.is('[href*="remove"]'),
'unlink': this.$el.is('[href*="unlink"]'),
'input_quantity': quantity >= 0 ? quantity : false,
};
this._callUpdateLineRoute(callParams).then(this._updateOrderValues.bind(this));
return false;
},
/**
* Reacts to the click on the -/+ buttons
*
* @param {Event} ev
*/
onClick: function (ev) {
ev.preventDefault();
return this._onChangeQuantity();
},
/**
* Calls the route to get updated values of the line and order
* when the quantity of a product has changed
*
* @private
* @param {Object} params
* @return {Deferred}
*/
_callUpdateLineRoute: function (params) {
var def = new $.Deferred();
ajax.jsonRpc("/quote/update_line_dict", 'call', params)
.then(def.resolve.bind(def))
.fail(function () {
// Compatibility: the server may not have been restarted
// So the real route may not exist
delete params.input_quantity;
ajax.jsonRpc("/quote/update_line", 'call', params)
.fail(def.reject.bind(def))
.then(function (data) {
// Data is an array, convert it to a dict
var actualData = data;
if (data) {
actualData = {
order_amount_total: data[1],
order_line_product_uom_qty: data[0],
};
}
def.resolve(actualData);
});
});
return def;
},
/**
* Processes data from the server to update the UI
*
* @private
* @param {Object} data: contains order and line updated values
*/
_updateOrderValues: function (data) {
if (!data) {
window.location.reload();
}
var orderAmountTotal = data.order_amount_total;
var orderAmountUntaxed = data.order_amount_untaxed;
var orderAmountTax = data.order_amount_tax;
var lineProductUomQty = data.order_line_product_uom_qty;
var linePriceTotal = data.order_line_price_total;
var linePriceSubTotal = data.order_line_price_subtotal;
this.elems.$lineQuantity.val(lineProductUomQty)
if (this.elems.$linePriceTotal.length && linePriceTotal !== undefined) {
this.elems.$linePriceTotal.text(linePriceTotal);
}
if (this.elems.$linePriceSubTotal.length && linePriceSubTotal !== undefined) {
this.elems.$linePriceSubTotal.text(linePriceSubTotal);
}
if (orderAmountUntaxed !== undefined) {
this.elems.$orderAmountUntaxed.text(orderAmountUntaxed);
}
if (orderAmountTax !== undefined) {
this.elems.$orderAmountTax.text(orderAmountTax);
}
if (orderAmountTotal !== undefined) {
this.elems.$orderAmountTotal.text(orderAmountTotal);
}
},
/**
* Locate in the DOM the elements to update
* Mostly for compatibility, when the module has not been upgraded
* In that case, we need to fall back to some other elements
*
* @private
* @return {Object}: Jquery elements to update
*/
_getUpdatableElements: function () {
var $parentTr = this.$el.parents('tr:first');
var $linePriceTotal = $parentTr.find('.oe_order_line_price_total .oe_currency_value');
var $linePriceSubTotal = $parentTr.find('.oe_order_line_price_subtotal .oe_currency_value');
if (!$linePriceTotal.length && !$linePriceSubTotal.length) {
$linePriceTotal = $linePriceSubTotal = $parentTr.find('.oe_currency_value').last();
}
var $orderAmountUntaxed = $('[data-id="total_untaxed"]>span');
var $orderAmountTax = $('[data-id="total_taxes"]>span');
var $orderAmountTotal = $('[data-id="total_amount"]>span');
if (!$orderAmountUntaxed.length && !$orderAmountTax.length) {
$orderAmountUntaxed = $orderAmountTotal.eq(1);
$orderAmountTax = $orderAmountTotal.eq(2);
$orderAmountTotal = $orderAmountTotal.eq(0).add($orderAmountTotal.eq(3));
}
return {
$lineQuantity: this.$el.parents('.input-group:first').find('.js_quantity'),
$linePriceSubTotal: $linePriceSubTotal,
$linePriceTotal: $linePriceTotal,
$orderAmountUntaxed: $orderAmountUntaxed,
$orderAmountTax: $orderAmountTax,
$orderAmountTotal: $orderAmountTotal,
}
}
});
var update_button_list = [];
......
......@@ -69,11 +69,11 @@
t-options="{'widget': 'monetary', 'display_currency': quotation.pricelist_id.currency_id}"/>
</div>
</td>
<td class="text-right" groups="sale.group_show_price_subtotal">
<td class="text-right oe_order_line_price_subtotal" groups="sale.group_show_price_subtotal">
<span t-field="line.price_subtotal"
t-options='{"widget": "monetary", "display_currency": quotation.pricelist_id.currency_id}'/>
</td>
<td class="text-right" groups="sale.group_show_price_total">
<td class="text-right oe_order_line_price_total" groups="sale.group_show_price_total">
<span t-field="line.price_total"
t-options='{"widget": "monetary", "display_currency": quotation.pricelist_id.currency_id}'/>
</td>
......@@ -102,7 +102,7 @@
<td></td><td></td><td></td><td></td>
<td class="text-right"><strong>Subtotal:</strong></td>
<td class="text-right">
<strong data-id="total_amount" t-field="quotation.amount_untaxed" t-options='{"widget": "monetary","display_currency": quotation.pricelist_id.currency_id}'/>
<strong data-id="total_untaxed" t-field="quotation.amount_untaxed" t-options='{"widget": "monetary","display_currency": quotation.pricelist_id.currency_id}'/>
</td>
<td></td>
</tr>
......@@ -111,7 +111,7 @@
<td></td><td></td><td></td><td></td>
<td class="text-right">Taxes:</td>
<td class="text-right">
<span data-id="total_amount" t-field="quotation.amount_tax" t-options='{"widget": "monetary","display_currency": quotation.pricelist_id.currency_id}'/>
<span data-id="total_taxes" t-field="quotation.amount_tax" t-options='{"widget": "monetary","display_currency": quotation.pricelist_id.currency_id}'/>
</td>
<td></td>
</tr>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment