[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>
<!-- 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>
@@ -428,7 +428,7 @@
<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">
<tr>
<th class="text-left">Products</th>
@@ -519,48 +519,7 @@
<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">
<!-- Should be replaced in master by 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>
<t t-call="sale.sale_order_portal_content_totals_table"/>
</div>
</div>
</section>
+41 -30
View File
@@ -12,12 +12,27 @@ from odoo.addons.sale.controllers.portal import CustomerPortal
class CustomerPortal(CustomerPortal):
@http.route(['/my/orders/<int:order_id>/update_line'], type='json', auth="public", website=True)
def update(self, line_id, remove=False, unlink=False, order_id=None, access_token=None, **post):
values = self.update_line_dict(line_id, remove, unlink, order_id, access_token, **post)
if values:
return [values['order_line_product_uom_qty'], values['order_amount_total']]
return values
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),
}
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)
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))
if order_line.order_id != order_sudo:
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:
quantity = input_quantity
@@ -41,29 +53,24 @@ class CustomerPortal(CustomerPortal):
number = -1 if remove else 1
quantity = order_line.product_uom_qty + number
if quantity < 0:
quantity = 0.0
order_line.write({'product_uom_qty': quantity})
currency = order_sudo.currency_id
format_price = partial(formatLang, request.env, digits=currency.decimal_places)
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
results = {
'order_line_product_uom_qty': str(quantity),
'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
order_line.write({'product_uom_qty': quantity})
results = self._get_portal_order_details(order_sudo, order_line)
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):
try:
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())
option_sudo.add_option_to_order()
return request.redirect(option_sudo.order_id.get_portal_url(anchor='details'))
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
@@ -4,56 +4,88 @@ odoo.define('sale_management.sale_management', function (require) {
var publicWidget = require('web.public.widget');
publicWidget.registry.SaleUpdateLineButton = publicWidget.Widget.extend({
selector: '.o_portal_sale_sidebar a.js_update_line_json',
selector: '.o_portal_sale_sidebar',
events: {
'click': '_onClick',
'click a.js_update_line_json': '_onClick',
'click a.js_add_optional_products': '_onClickOptionalProduct',
'change .js_quantity': '_onChangeQuantity'
},
/**
* @override
*/
start: function () {
var self = this;
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);
});
});
},
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 {Int} quantity, the new quantity of the line
* If not present it will increment/decrement the existing quantity
* @param {Event} ev
*/
_onChangeQuantity: function (quantity) {
var href = this.$el.attr("href");
var orderID = href.match(/my\/orders\/([0-9]+)/);
var lineID = href.match(/update_line\/([0-9]+)/);
var params = {
'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;
}
_onChangeQuantity(ev) {
ev.preventDefault();
let self = this,
$target = $(ev.currentTarget),
quantity = parseInt($target.val());
orderID = parseInt(orderID[1]);
this._callUpdateLineRoute(orderID, params).then(this._updateOrderValues.bind(this));
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: function (ev) {
_onClick(ev) {
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
@@ -64,43 +96,48 @@ publicWidget.registry.SaleUpdateLineButton = publicWidget.Widget.extend({
* @param {Object} params
* @return {Deferred}
*/
_callUpdateLineRoute: function (order_id, params) {
var url = "/my/orders/" + order_id + "/update_line_dict";
_callUpdateLineRoute(order_id, params) {
return this._rpc({
route: url,
route: "/my/orders/" + order_id + "/update_line_dict",
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
*
* @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 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);
}
_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);
}
@@ -112,8 +149,8 @@ publicWidget.registry.SaleUpdateLineButton = publicWidget.Widget.extend({
if (orderAmountUndiscounted !== undefined) {
this.elems.$orderAmountUndiscounted.text(orderAmountUndiscounted);
}
if (orderTotalsTable) {
this.elems.$orderTotalsTable.find('table').replaceWith(orderTotalsTable);
if ($orderTotalsTable.length) {
this.elems.$orderTotalsTable.find('table').replaceWith($orderTotalsTable);
}
},
/**
@@ -124,18 +161,10 @@ publicWidget.registry.SaleUpdateLineButton = publicWidget.Widget.extend({
* @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"]').find('span, b');
var $orderAmountTotal = $('[data-id="total_amount"]').find('span, b');
var $orderAmountUndiscounted = $('[data-id="amount_undiscounted"]').find('span, b');
_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');
if (!$orderAmountUntaxed.length) {
$orderAmountUntaxed = $orderAmountTotal.eq(1);
@@ -143,9 +172,6 @@ publicWidget.registry.SaleUpdateLineButton = publicWidget.Widget.extend({
}
return {
$lineQuantity: this.$el.closest('.input-group').find('.js_quantity'),
$linePriceSubTotal: $linePriceSubTotal,
$linePriceTotal: $linePriceTotal,
$orderAmountUntaxed: $orderAmountUntaxed,
$orderAmountTotal: $orderAmountTotal,
$orderTotalsTable: $('#total'),
@@ -21,7 +21,7 @@
<xpath expr="//section[@id='details']//t[@t-if='not line.display_type']" position="inside">
<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>
</a>
</td>
@@ -65,8 +65,8 @@
</strong>
</td>
<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">
<span class="fa fa-lg fa-shopping-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-shopping-cart"/>
</a>
</td>
</t>
@@ -83,16 +83,16 @@
<div class="input-group-prepend d-print-none">
<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"/>
</a>
</span>
</div>
<!-- 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">
<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"/>
</a>
</span>