[IMP] sale, sale_management: avoid reload when add optional product added in portal

porpose of the task is to avoid the reload of the web client
when you add an optional product, in the customer portal

this commit will fix the issue of window reload when add optional product and delete
product in customer portal.
removed order line from sale order, when a customer set the quantity of an option line to 0 in the portal

task-1961015
Closes #32551

Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com>
Co-authored-by: mba-odoo <mba@odoo.com>
This commit is contained in:
JPR
2020-03-16 09:02:23 +00:00
committed by jpr-odoo
co-authored by mba-odoo
parent 0b88e862a8
commit 0beebad0ff
4 changed files with 152 additions and 156 deletions
+3 -44
View File
@@ -310,7 +310,7 @@
</div> </div>
<!-- main content --> <!-- main content -->
<div t-attf-class="card #{'pb-5' if report_type == 'html' else ''}"> <div t-attf-class="card #{'pb-5' if report_type == 'html' else ''}" id="portal_sale_content">
<div t-call="sale.sale_order_portal_content"/> <div t-call="sale.sale_order_portal_content"/>
</div> </div>
@@ -428,7 +428,7 @@
<t t-set="display_discount" t-value="True in [line.discount > 0 for line in sale_order.order_line]"/> <t t-set="display_discount" t-value="True in [line.discount > 0 for line in sale_order.order_line]"/>
<table class="table table-sm" id="sales_order_table"> <table t-att-data-order-id="sale_order.id" t-att-data-token="sale_order.access_token" class="table table-sm" id="sales_order_table">
<thead class="bg-100"> <thead class="bg-100">
<tr> <tr>
<th class="text-left">Products</th> <th class="text-left">Products</th>
@@ -519,48 +519,7 @@
<div id="total" class="row" name="total" style="page-break-inside: avoid;"> <div id="total" class="row" name="total" style="page-break-inside: avoid;">
<div t-attf-class="#{'col-4' if report_type != 'html' else 'col-sm-7 col-md-5'} ml-auto"> <div t-attf-class="#{'col-4' if report_type != 'html' else 'col-sm-7 col-md-5'} ml-auto">
<!-- Should be replaced in master by t-call="sale.sale_order_portal_content_totals_table" --> <t t-call="sale.sale_order_portal_content_totals_table"/>
<table class="table table-sm">
<tr class="border-black" style="border-bottom:1px solid #dddddd;">
<td><strong>Subtotal</strong></td>
<td class="text-right">
<span
data-id="total_untaxed"
t-field="sale_order.amount_untaxed"
t-options='{"widget": "monetary","display_currency": sale_order.pricelist_id.currency_id}'
/>
</td>
</tr>
<t t-foreach="sale_order.amount_by_group" t-as="amount_by_group">
<tr style="border-bottom:1px solid #dddddd;">
<t t-if="amount_by_group[3] == 1 and sale_order.amount_untaxed == amount_by_group[2]">
<td>
<span t-esc="amount_by_group[0]"/>
<span>&amp;nbsp;<span>on</span>&amp;nbsp;<t t-esc="amount_by_group[2]" t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'/></span>
</td>
<td class="text-right">
<span t-esc="amount_by_group[1]"
t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'/>
</td>
</t>
<t t-else ="">
<td>
<span t-esc="amount_by_group[0]"/>
</td>
<td class="text-right">
<span t-esc="amount_by_group[1]"
t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'/>
</td>
</t>
</tr>
</t>
<tr class="border-black">
<td><strong>Total</strong></td>
<td class="text-right">
<span data-id="total_amount" t-field="sale_order.amount_total" t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'/>
</td>
</tr>
</table>
</div> </div>
</div> </div>
</section> </section>
+41 -30
View File
@@ -12,12 +12,27 @@ from odoo.addons.sale.controllers.portal import CustomerPortal
class CustomerPortal(CustomerPortal): class CustomerPortal(CustomerPortal):
@http.route(['/my/orders/<int:order_id>/update_line'], type='json', auth="public", website=True) def _get_portal_order_details(self, order_sudo, order_line=False):
def update(self, line_id, remove=False, unlink=False, order_id=None, access_token=None, **post): currency = order_sudo.currency_id
values = self.update_line_dict(line_id, remove, unlink, order_id, access_token, **post) format_price = partial(formatLang, request.env, digits=currency.decimal_places)
if values: results = {
return [values['order_line_product_uom_qty'], values['order_amount_total']] 'order_amount_total': format_price(order_sudo.amount_total),
return values '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),
}
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
@http.route(['/my/orders/<int:order_id>/update_line_dict'], type='json', auth="public", website=True) @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): def update_line_dict(self, line_id, remove=False, unlink=False, order_id=None, access_token=None, input_quantity=False, **kwargs):
@@ -31,9 +46,6 @@ class CustomerPortal(CustomerPortal):
order_line = request.env['sale.order.line'].sudo().browse(int(line_id)) order_line = request.env['sale.order.line'].sudo().browse(int(line_id))
if order_line.order_id != order_sudo: if order_line.order_id != order_sudo:
return False return False
if unlink:
order_line.unlink()
return False # return False to reload the page, the line must move back to options and the JS doesn't handle it
if input_quantity is not False: if input_quantity is not False:
quantity = input_quantity quantity = input_quantity
@@ -41,29 +53,24 @@ class CustomerPortal(CustomerPortal):
number = -1 if remove else 1 number = -1 if remove else 1
quantity = order_line.product_uom_qty + number quantity = order_line.product_uom_qty + number
if quantity < 0: if unlink or quantity <= 0:
quantity = 0.0 order_line.unlink()
order_line.write({'product_uom_qty': quantity}) results = self._get_portal_order_details(order_sudo)
currency = order_sudo.currency_id results.update({
format_price = partial(formatLang, request.env, digits=currency.decimal_places) '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
results = { order_line.write({'product_uom_qty': quantity})
'order_line_product_uom_qty': str(quantity), results = self._get_portal_order_details(order_sudo, order_line)
'order_line_price_total': format_price(order_line.price_total),
'order_line_price_subtotal': format_price(order_line.price_subtotal),
'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),
}
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 return results
@http.route(["/my/orders/<int:order_id>/add_option/<int:option_id>"], type='http', auth="public", website=True) @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): def add(self, order_id, option_id, access_token=None, **post):
try: try:
order_sudo = self._document_check_access('sale.order', order_id, access_token=access_token) order_sudo = self._document_check_access('sale.order', order_id, access_token=access_token)
@@ -76,5 +83,9 @@ class CustomerPortal(CustomerPortal):
return request.redirect(order_sudo.get_portal_url()) return request.redirect(order_sudo.get_portal_url())
option_sudo.add_option_to_order() option_sudo.add_option_to_order()
results = self._get_portal_order_details(order_sudo)
return request.redirect(option_sudo.order_id.get_portal_url(anchor='details')) 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
@@ -4,56 +4,88 @@ odoo.define('sale_management.sale_management', function (require) {
var publicWidget = require('web.public.widget'); var publicWidget = require('web.public.widget');
publicWidget.registry.SaleUpdateLineButton = publicWidget.Widget.extend({ publicWidget.registry.SaleUpdateLineButton = publicWidget.Widget.extend({
selector: '.o_portal_sale_sidebar a.js_update_line_json', selector: '.o_portal_sale_sidebar',
events: { events: {
'click': '_onClick', 'click a.js_update_line_json': '_onClick',
'click a.js_add_optional_products': '_onClickOptionalProduct',
'change .js_quantity': '_onChangeQuantity'
}, },
/** /**
* @override * @override
*/ */
start: function () { async start() {
var self = this; await this._super(...arguments);
return this._super.apply(this, arguments).then(function () { this.orderDetail = this.$el.find('table#sales_order_table').data();
self.elems = self._getUpdatableElements(); this.elems = this._getUpdatableElements();
self.elems.$lineQuantity.change(function (ev) { },
var quantity = parseInt(this.value);
self._onChangeQuantity(quantity);
});
});
},
/** /**
* Process the change in line quantity * Process the change in line quantity
* *
* @private * @private
* @param {Int} quantity, the new quantity of the line * @param {Event} ev
* If not present it will increment/decrement the existing quantity
*/ */
_onChangeQuantity: function (quantity) { _onChangeQuantity(ev) {
var href = this.$el.attr("href"); ev.preventDefault();
var orderID = href.match(/my\/orders\/([0-9]+)/); let self = this,
var lineID = href.match(/update_line\/([0-9]+)/); $target = $(ev.currentTarget),
var params = { quantity = parseInt($target.val());
'line_id': parseInt(lineID[1]),
'remove': this.$el.is('[href*="remove"]'),
'unlink': this.$el.is('[href*="unlink"]'),
'input_quantity': quantity >= 0 ? quantity : false,
};
var token = href.match(/token=([\w\d-]*)/)[1];
if (token) {
params['access_token'] = token;
}
orderID = parseInt(orderID[1]); this._callUpdateLineRoute(self.orderDetail.orderId, {
this._callUpdateLineRoute(orderID, params).then(this._updateOrderValues.bind(this)); '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 * Reacts to the click on the -/+ buttons
* *
* @param {Event} ev * @param {Event} ev
*/ */
_onClick: function (ev) { _onClick(ev) {
ev.preventDefault(); ev.preventDefault();
return this._onChangeQuantity(); 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 * Calls the route to get updated values of the line and order
@@ -64,43 +96,48 @@ publicWidget.registry.SaleUpdateLineButton = publicWidget.Widget.extend({
* @param {Object} params * @param {Object} params
* @return {Deferred} * @return {Deferred}
*/ */
_callUpdateLineRoute: function (order_id, params) { _callUpdateLineRoute(order_id, params) {
var url = "/my/orders/" + order_id + "/update_line_dict";
return this._rpc({ return this._rpc({
route: url, route: "/my/orders/" + order_id + "/update_line_dict",
params: params, params: params,
}); });
}, },
/**
* Processes data from the server to update the orderline UI
*
* @private
* @param {Element} $orderLine: orderline element to update
* @param {Object} data: contains order and line updated values
*/
_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);
}
},
/** /**
* Processes data from the server to update the UI * Processes data from the server to update the UI
* *
* @private * @private
* @param {Object} data: contains order and line updated values * @param {Object} data: contains order and line updated values
*/ */
_updateOrderValues: function (data) { _updateOrderValues(data) {
if (!data) { let orderAmountTotal = data.order_amount_total,
window.location.reload(); orderAmountUntaxed = data.order_amount_untaxed,
} orderAmountUndiscounted = data.order_amount_undiscounted,
$orderTotalsTable = $(data.order_totals_table);
var orderAmountTotal = data.order_amount_total;
var orderAmountUntaxed = data.order_amount_untaxed;
var orderAmountTax = data.order_amount_tax;
var orderAmountUndiscounted = data.order_amount_undiscounted;
var orderTotalsTable = $(data.order_totals_table);
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) { if (orderAmountUntaxed !== undefined) {
this.elems.$orderAmountUntaxed.text(orderAmountUntaxed); this.elems.$orderAmountUntaxed.text(orderAmountUntaxed);
} }
@@ -112,8 +149,8 @@ publicWidget.registry.SaleUpdateLineButton = publicWidget.Widget.extend({
if (orderAmountUndiscounted !== undefined) { if (orderAmountUndiscounted !== undefined) {
this.elems.$orderAmountUndiscounted.text(orderAmountUndiscounted); this.elems.$orderAmountUndiscounted.text(orderAmountUndiscounted);
} }
if (orderTotalsTable) { if ($orderTotalsTable.length) {
this.elems.$orderTotalsTable.find('table').replaceWith(orderTotalsTable); this.elems.$orderTotalsTable.find('table').replaceWith($orderTotalsTable);
} }
}, },
/** /**
@@ -124,18 +161,10 @@ publicWidget.registry.SaleUpdateLineButton = publicWidget.Widget.extend({
* @private * @private
* @return {Object}: Jquery elements to update * @return {Object}: Jquery elements to update
*/ */
_getUpdatableElements: function () { _getUpdatableElements() {
var $parentTr = this.$el.parents('tr:first'); let $orderAmountUntaxed = $('[data-id="total_untaxed"]').find('span, b'),
var $linePriceTotal = $parentTr.find('.oe_order_line_price_total .oe_currency_value'); $orderAmountTotal = $('[data-id="total_amount"]').find('span, b'),
var $linePriceSubTotal = $parentTr.find('.oe_order_line_price_subtotal .oe_currency_value'); $orderAmountUndiscounted = $('[data-id="amount_undiscounted"]').find('span, b');
if (!$linePriceTotal.length && !$linePriceSubTotal.length) {
$linePriceTotal = $linePriceSubTotal = $parentTr.find('.oe_currency_value').last();
}
var $orderAmountUntaxed = $('[data-id="total_untaxed"]').find('span, b');
var $orderAmountTotal = $('[data-id="total_amount"]').find('span, b');
var $orderAmountUndiscounted = $('[data-id="amount_undiscounted"]').find('span, b');
if (!$orderAmountUntaxed.length) { if (!$orderAmountUntaxed.length) {
$orderAmountUntaxed = $orderAmountTotal.eq(1); $orderAmountUntaxed = $orderAmountTotal.eq(1);
@@ -143,9 +172,6 @@ publicWidget.registry.SaleUpdateLineButton = publicWidget.Widget.extend({
} }
return { return {
$lineQuantity: this.$el.closest('.input-group').find('.js_quantity'),
$linePriceSubTotal: $linePriceSubTotal,
$linePriceTotal: $linePriceTotal,
$orderAmountUntaxed: $orderAmountUntaxed, $orderAmountUntaxed: $orderAmountUntaxed,
$orderAmountTotal: $orderAmountTotal, $orderAmountTotal: $orderAmountTotal,
$orderTotalsTable: $('#total'), $orderTotalsTable: $('#total'),
@@ -21,7 +21,7 @@
<xpath expr="//section[@id='details']//t[@t-if='not line.display_type']" position="inside"> <xpath expr="//section[@id='details']//t[@t-if='not line.display_type']" position="inside">
<td class="text-center" t-if="display_remove"> <td class="text-center" t-if="display_remove">
<a t-att-href="sale_order.get_portal_url(suffix='/update_line/%s' % line.id, query_string='&amp;unlink=True')" class="mb8 js_update_line_json d-print-none" t-if="sale_order.state in ('draft', 'sent') and line.sale_order_option_ids" aria-label="Remove" title="Remove"> <a t-att-data-line-id="line.id" t-att-data-unlink="True" href="#" class="mb8 js_update_line_json d-print-none" t-if="sale_order.state in ('draft', 'sent') and line.sale_order_option_ids" aria-label="Remove" title="Remove">
<span class="fa fa-trash-o"></span> <span class="fa fa-trash-o"></span>
</a> </a>
</td> </td>
@@ -65,8 +65,8 @@
</strong> </strong>
</td> </td>
<td class="text-center" t-if="sale_order.state in ['draft', 'sent'] and report_type == 'html'"> <td class="text-center" t-if="sale_order.state in ['draft', 'sent'] and report_type == 'html'">
<a t-att-href="sale_order.get_portal_url(suffix='/add_option/%s' % option.id)" class="mb8 d-print-none" aria-label="Add to cart" title="Add to cart"> <a t-att-data-option-id="option.id" href="#" class="mb8 js_add_optional_products d-print-none" aria-label="Add to cart" title="Add to cart">
<span class="fa fa-lg fa-shopping-cart"/> <span class="fa fa-shopping-cart"/>
</a> </a>
</td> </td>
</t> </t>
@@ -83,16 +83,16 @@
<div class="input-group-prepend d-print-none"> <div class="input-group-prepend d-print-none">
<span class="input-group-text"> <span class="input-group-text">
<a t-att-href="sale_order.get_portal_url(suffix='/update_line/%s' % line.id, query_string='&amp;remove=True')" class="js_update_line_json" aria-label="Remove one" title="Remove one"> <a t-att-data-line-id="line.id" t-att-data-remove="True" href="#" class="js_update_line_json" aria-label="Remove one" title="Remove one">
<span class="fa fa-minus"/> <span class="fa fa-minus"/>
</a> </a>
</span> </span>
</div> </div>
<!-- TODO add uom in this case too --> <!-- TODO add uom in this case too -->
<input type="text" class="js_quantity form-control" t-att-data-id="line.id" t-att-value="line.product_uom_qty"/> <input type="text" class="js_quantity form-control" t-att-data-line-id="line.id" t-att-value="line.product_uom_qty"/>
<div class="input-group-append d-print-none"> <div class="input-group-append d-print-none">
<span class="input-group-text"> <span class="input-group-text">
<a t-att-href="sale_order.get_portal_url(suffix='/update_line/%s' % line.id)" class="js_update_line_json" aria-label="Add one" title="Add one"> <a t-att-data-line-id="line.id" href="#" class="js_update_line_json" aria-label="Add one" title="Add one">
<span class="fa fa-plus"/> <span class="fa fa-plus"/>
</a> </a>
</span> </span>