[FIX] sale_management: show correct order details to the customer

Currently, when the quantity of an optional SOline is modified on the
portal, only the quantity and order/line amounts are updated.

This raises two problematic situations when the pricelist is
configured to give discount when a certain number of products is
reached:
1) If the pricelist is configured to include the discount in the price,
the price is not refreshed, which means you see a line total which is
not the result of unit price * qty
2) If the pricelist is configured to show the discount, you won't see
it even if there is one (and you won't see the column if there was
no discount in the SO before your qty modification).

This commit makes sure the order details are refreshed when the quantity
of an option is modified.  To do so, we simply need to use the previous
code used to refresh the content when an option was added to the order.

Since we now do a full refresh of the order details whenever the quantity
is updated, the old code updating part of the order details (qty & amounts)
is unnecessary and can be safely removed.

While this code & logic is modified, a cleanup & some documentation of
the code was also made.

task-2180180

Part-of: odoo/odoo#88366
Co-authored-by: Victor Feyens <vfe@odoo.com>
This commit is contained in:
Prakash Prajapati
2022-05-05 17:33:49 +02:00
committed by Victor Feyens
co-authored by Victor Feyens
parent 6e61544e17
commit f0f14bf25a
2 changed files with 105 additions and 177 deletions
+42 -46
View File
@@ -1,41 +1,43 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from functools import partial
from odoo import http
from odoo.tools import formatLang
from odoo.exceptions import AccessError, MissingError
from odoo.http import request
from odoo.http import request, route
from odoo.addons.sale.controllers import portal
class CustomerPortal(portal.CustomerPortal):
def _get_portal_order_details(self, order_sudo, order_line=False):
currency = order_sudo.currency_id
format_price = partial(formatLang, request.env, digits=currency.decimal_places)
results = {
'order_amount_total': format_price(order_sudo.amount_total),
'order_amount_untaxed': format_price(order_sudo.amount_untaxed),
'order_amount_tax': format_price(order_sudo.amount_tax),
'order_amount_undiscounted': format_price(order_sudo.amount_undiscounted),
def _get_order_portal_content(self, order_sudo):
""" Return the order portal details.
:return: rendered html of the order portal details
:rtype: dict
"""
return {
'sale_template': request.env['ir.ui.view']._render_template(
'sale.sale_order_portal_content', {
'sale_order': order_sudo,
'report_type': 'html',
},
),
}
if order_line:
results.update({
'order_line_product_uom_qty': str(order_line.product_uom_qty),
'order_line_price_total': format_price(order_line.price_total),
'order_line_price_subtotal': format_price(order_line.price_subtotal)
})
try:
results['order_totals_table'] = request.env['ir.ui.view']._render_template('sale.sale_order_portal_content_totals_table', {'sale_order': order_sudo})
except ValueError:
pass
return results
@route(['/my/orders/<int:order_id>/update_line_dict'], type='json', auth="public", website=True)
def portal_quote_option_update(self, order_id, line_id, access_token=None, remove=False, unlink=False, input_quantity=False, **kwargs):
""" Update the quantity or Remove an optional SOline from a SO.
@http.route(['/my/orders/<int:order_id>/update_line_dict'], type='json', auth="public", website=True)
def update_line_dict(self, line_id, remove=False, unlink=False, order_id=None, access_token=None, input_quantity=False, **kwargs):
:param int order_id: `sale.order` id
:param int line_id: `sale.order.line` id
:param str access_token: portal access_token of the specified order
:param bool remove: if true, 1 unit will be removed from the line
:param bool unlink: if true, the option will be removed from the SO
:param float input_quantity: if specified, will be set as new line qty
:param dict kwargs: unused parameters
:return: New order details (as html content)
:rtype: dict
"""
try:
order_sudo = self._document_check_access('sale.order', order_id, access_token=access_token)
except (AccessError, MissingError):
@@ -55,23 +57,22 @@ class CustomerPortal(portal.CustomerPortal):
if unlink or quantity <= 0:
order_line.unlink()
results = self._get_portal_order_details(order_sudo)
results.update({
'unlink': True,
'sale_template': request.env['ir.ui.view']._render_template('sale.sale_order_portal_content', {
'sale_order': order_sudo,
'report_type': "html"
}),
})
return results
else:
order_line.product_uom_qty = quantity
order_line.write({'product_uom_qty': quantity})
results = self._get_portal_order_details(order_sudo, order_line)
return self._get_order_portal_content(order_sudo)
return results
@route(["/my/orders/<int:order_id>/add_option/<int:option_id>"], type='json', auth="public", website=True)
def portal_quote_add_option(self, order_id, option_id, access_token=None, **kwargs):
""" Add the specified option to the specified order.
@http.route(["/my/orders/<int:order_id>/add_option/<int:option_id>"], type='json', auth="public", website=True)
def add(self, order_id, option_id, access_token=None, **post):
:param int order_id: `sale.order` id
:param int option_id: `sale.order.option` id
:param str access_token: portal access_token of the specified order
:param dict kwargs: unused parameters
:return: New order details (as html content)
:rtype: dict
"""
try:
order_sudo = self._document_check_access('sale.order', order_id, access_token=access_token)
except (AccessError, MissingError):
@@ -83,9 +84,4 @@ class CustomerPortal(portal.CustomerPortal):
return request.redirect(order_sudo.get_portal_url())
option_sudo.add_option_to_order()
results = self._get_portal_order_details(order_sudo)
results['sale_template'] = request.env['ir.ui.view']._render_template("sale.sale_order_portal_content", {
'sale_order': option_sudo.order_id,
'report_type': "html"
})
return results
return self._get_order_portal_content(order_sudo)
@@ -6,87 +6,19 @@ var publicWidget = require('web.public.widget');
publicWidget.registry.SaleUpdateLineButton = publicWidget.Widget.extend({
selector: '.o_portal_sale_sidebar',
events: {
'click a.js_update_line_json': '_onClick',
'click a.js_add_optional_products': '_onClickOptionalProduct',
'change .js_quantity': '_onChangeQuantity'
'click a.js_update_line_json': '_onClickOptionQuantityButton',
'click a.js_add_optional_products': '_onClickAddOptionalProduct',
'change .js_quantity': '_onChangeOptionQuantity',
},
/**
* @override
*/
async start() {
await this._super(...arguments);
this.orderDetail = this.$el.find('table#sales_order_table').data();
this.elems = this._getUpdatableElements();
},
/**
* Process the change in line quantity
*
* @private
* @param {Event} ev
*/
_onChangeQuantity(ev) {
ev.preventDefault();
let self = this,
$target = $(ev.currentTarget),
quantity = parseInt($target.val());
this._callUpdateLineRoute(self.orderDetail.orderId, {
'line_id': $target.data('lineId'),
'input_quantity': quantity >= 0 ? quantity : false,
'access_token': self.orderDetail.token
}).then((data) => {
self._updateOrderLineValues($target.closest('tr'), data);
self._updateOrderValues(data);
});
},
/**
* Reacts to the click on the -/+ buttons
*
* @param {Event} ev
*/
_onClick(ev) {
ev.preventDefault();
let self = this,
$target = $(ev.currentTarget);
this._callUpdateLineRoute(self.orderDetail.orderId, {
'line_id': $target.data('lineId'),
'remove': $target.data('remove'),
'unlink': $target.data('unlink'),
'access_token': self.orderDetail.token
}).then((data) => {
var $saleTemplate = $(data['sale_template']);
if ($saleTemplate.length && data['unlink']) {
self.$('#portal_sale_content').html($saleTemplate);
self.elems = self._getUpdatableElements();
}
self._updateOrderLineValues($target.closest('tr'), data);
self._updateOrderValues(data);
});
},
/**
* trigger when optional product added to order from portal.
*
* @private
* @param {Event} ev
*/
_onClickOptionalProduct(ev) {
ev.preventDefault();
let self = this,
$target = $(ev.currentTarget);
// to avoid double click on link with href.
$target.css('pointer-events', 'none');
this._rpc({
route: "/my/orders/" + self.orderDetail.orderId + "/add_option/" + $target.data('optionId'),
params: {access_token: self.orderDetail.token}
}).then((data) => {
if (data) {
self.$('#portal_sale_content').html($(data['sale_template']));
self.elems = self._getUpdatableElements();
self._updateOrderValues(data);
}
});
},
/**
* Calls the route to get updated values of the line and order
* when the quantity of a product has changed
@@ -96,87 +28,87 @@ publicWidget.registry.SaleUpdateLineButton = publicWidget.Widget.extend({
* @param {Object} params
* @return {Deferred}
*/
_callUpdateLineRoute(order_id, params) {
_callUpdateLineRoute(order_id, params) {
return this._rpc({
route: "/my/orders/" + order_id + "/update_line_dict",
params: params,
});
},
/**
* Processes data from the server to update the orderline UI
* Refresh the UI of the order details
*
* @private
* @param {Element} $orderLine: orderline element to update
* @param {Object} data: contains order and line updated values
* @param {Object} data: contains order html details
*/
_updateOrderLineValues($orderLine, data) {
let linePriceTotal = data.order_line_price_total,
linePriceSubTotal = data.order_line_price_subtotal,
$linePriceTotal = $orderLine.find('.oe_order_line_price_total .oe_currency_value'),
$linePriceSubTotal = $orderLine.find('.oe_order_line_price_subtotal .oe_currency_value');
if (!$linePriceTotal.length && !$linePriceSubTotal.length) {
$linePriceTotal = $linePriceSubTotal = $orderLine.find('.oe_currency_value').last();
}
$orderLine.find('.js_quantity').val(data.order_line_product_uom_qty);
if ($linePriceTotal.length && linePriceTotal !== undefined) {
$linePriceTotal.text(linePriceTotal);
}
if ($linePriceSubTotal.length && linePriceSubTotal !== undefined) {
$linePriceSubTotal.text(linePriceSubTotal);
_refreshOrderUI(data){
const $saleTemplate = $(data['sale_template']);
if ($saleTemplate.length) {
this.$('#portal_sale_content').html($saleTemplate);
}
},
/**
* Processes data from the server to update the UI
* Process the change in line quantity
*
* @private
* @param {Object} data: contains order and line updated values
* @param {Event} ev
*/
_updateOrderValues(data) {
let orderAmountTotal = data.order_amount_total,
orderAmountUntaxed = data.order_amount_untaxed,
orderAmountUndiscounted = data.order_amount_undiscounted,
$orderTotalsTable = $(data.order_totals_table);
if (orderAmountUntaxed !== undefined) {
this.elems.$orderAmountUntaxed.text(orderAmountUntaxed);
}
async _onChangeOptionQuantity(ev) {
ev.preventDefault();
let self = this,
$target = $(ev.currentTarget),
quantity = parseInt($target.val());
if (orderAmountTotal !== undefined) {
this.elems.$orderAmountTotal.text(orderAmountTotal);
}
if (orderAmountUndiscounted !== undefined) {
this.elems.$orderAmountUndiscounted.text(orderAmountUndiscounted);
}
if ($orderTotalsTable.length) {
this.elems.$orderTotalsTable.find('table').replaceWith($orderTotalsTable);
}
const result = await this._callUpdateLineRoute(self.orderDetail.orderId, {
'line_id': $target.data('lineId'),
'input_quantity': quantity >= 0 ? quantity : false,
'access_token': self.orderDetail.token
});
this._refreshOrderUI(result);
},
/**
* 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
* Reacts to the click on the -/+ buttons
*
* @private
* @return {Object}: Jquery elements to update
* @param {Event} ev
*/
_getUpdatableElements() {
let $orderAmountUntaxed = $('[data-id="total_untaxed"]').find('span, b'),
$orderAmountTotal = $('[data-id="total_amount"]').find('span, b'),
$orderAmountUndiscounted = $('[data-id="amount_undiscounted"]').find('span, b');
async _onClickOptionQuantityButton(ev) {
ev.preventDefault();
let self = this,
$target = $(ev.currentTarget);
if (!$orderAmountUntaxed.length) {
$orderAmountUntaxed = $orderAmountTotal.eq(1);
$orderAmountTotal = $orderAmountTotal.eq(0).add($orderAmountTotal.eq(2));
}
const result = await this._callUpdateLineRoute(self.orderDetail.orderId, {
'line_id': $target.data('lineId'),
'remove': $target.data('remove'),
'unlink': $target.data('unlink'),
'access_token': self.orderDetail.token
});
this._refreshOrderUI(result);
},
/**
* Triggered when optional product added to order from portal.
*
* @private
* @param {Event} ev
*/
_onClickAddOptionalProduct(ev) {
ev.preventDefault();
let self = this,
$target = $(ev.currentTarget);
// to avoid double click on link with href.
$target.css('pointer-events', 'none');
this._rpc({
route: "/my/orders/" + self.orderDetail.orderId + "/add_option/" + $target.data('optionId'),
params: {access_token: self.orderDetail.token}
}).then((data) => {
this._refreshOrderUI(data);
});
},
return {
$orderAmountUntaxed: $orderAmountUntaxed,
$orderAmountTotal: $orderAmountTotal,
$orderTotalsTable: $('#total'),
$orderAmountUndiscounted: $orderAmountUndiscounted,
};
}
});
});