[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:
committed by
Victor Feyens
co-authored by
Victor Feyens
parent
6e61544e17
commit
f0f14bf25a
@@ -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,
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user