From 4730a884b488536b5d149c294ac08cc1e23365c5 Mon Sep 17 00:00:00 2001 From: "Lucas Perais (lpe)" <lpe@odoo.com> Date: Tue, 16 Apr 2019 11:04:14 +0000 Subject: [PATCH] [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: Lucas Perais (lpe) <lpe@odoo.com> --- addons/website_quote/controllers/main.py | 34 +++- .../static/src/js/website_quotation.js | 168 ++++++++++++++++-- .../views/website_quote_templates.xml | 8 +- 3 files changed, 185 insertions(+), 25 deletions(-) diff --git a/addons/website_quote/controllers/main.py b/addons/website_quote/controllers/main.py index 0fcb9485d76d..2cb66869051e 100644 --- a/addons/website_quote/controllers/main.py +++ b/addons/website_quote/controllers/main.py @@ -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): diff --git a/addons/website_quote/static/src/js/website_quotation.js b/addons/website_quote/static/src/js/website_quotation.js index 04705686f325..6985625c378e 100644 --- a/addons/website_quote/static/src/js/website_quotation.js +++ b/addons/website_quote/static/src/js/website_quotation.js @@ -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 = []; diff --git a/addons/website_quote/views/website_quote_templates.xml b/addons/website_quote/views/website_quote_templates.xml index 2aa63f2f7308..1b4c906a5004 100644 --- a/addons/website_quote/views/website_quote_templates.xml +++ b/addons/website_quote/views/website_quote_templates.xml @@ -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> -- GitLab