-
+
+
diff --git a/addons/auth_signup/controllers/main.py b/addons/auth_signup/controllers/main.py
index 6ec7436a9cf..68c996c82db 100644
--- a/addons/auth_signup/controllers/main.py
+++ b/addons/auth_signup/controllers/main.py
@@ -43,7 +43,7 @@ class AuthSignupHome(Home):
lang=user_sudo.lang,
auth_login=werkzeug.url_encode({'auth_login': user_sudo.email}),
).send_mail(user_sudo.id, force_send=True)
- return super(AuthSignupHome, self).web_login(*args, **kw)
+ return self.web_login(*args, **kw)
except UserError as e:
qcontext['error'] = e.name or e.value
except (SignupError, AssertionError) as e:
@@ -68,7 +68,7 @@ class AuthSignupHome(Home):
try:
if qcontext.get('token'):
self.do_signup(qcontext)
- return super(AuthSignupHome, self).web_login(*args, **kw)
+ return self.web_login(*args, **kw)
else:
login = qcontext.get('login')
assert login, _("No login provided.")
diff --git a/addons/base_import_module/models/ir_module.py b/addons/base_import_module/models/ir_module.py
index df7ad832d86..2c72ad42ff4 100644
--- a/addons/base_import_module/models/ir_module.py
+++ b/addons/base_import_module/models/ir_module.py
@@ -38,6 +38,8 @@ class IrModule(models.Model):
installed_mods = [m.name for m in known_mods if m.state == 'installed']
terp = load_information_from_description_file(module, mod_path=path)
+ if not terp:
+ return False
values = self.get_values_from_terp(terp)
if 'version' in terp:
values['latest_version'] = terp['version']
@@ -133,8 +135,8 @@ class IrModule(models.Model):
try:
# assert mod_name.startswith('theme_')
path = opj(module_dir, mod_name)
- self._import_module(mod_name, path, force=force)
- success.append(mod_name)
+ if self._import_module(mod_name, path, force=force):
+ success.append(mod_name)
except Exception as e:
_logger.exception('Error while importing module')
errors[mod_name] = exception_to_unicode(e)
diff --git a/addons/l10n_ch/models/account_invoice.py b/addons/l10n_ch/models/account_invoice.py
index 4daf5ebef10..efe7df92634 100644
--- a/addons/l10n_ch/models/account_invoice.py
+++ b/addons/l10n_ch/models/account_invoice.py
@@ -12,6 +12,7 @@ from odoo.tools.misc import mod10r
l10n_ch_ISR_NUMBER_LENGTH = 27
l10n_ch_ISR_NUMBER_ISSUER_LENGTH = 12
+
class AccountInvoice(models.Model):
_inherit = 'account.invoice'
@@ -25,7 +26,7 @@ class AccountInvoice(models.Model):
l10n_ch_isr_valid = fields.Boolean(compute='_compute_l10n_ch_isr_valid', help='Boolean value. True iff all the data required to generate the ISR are present')
- l10n_ch_isr_sent = fields.Boolean(defaut=False, help="Boolean value telling whether or not the ISR corresponding to this invoice has already been printed or sent by mail.")
+ l10n_ch_isr_sent = fields.Boolean(default=False, help="Boolean value telling whether or not the ISR corresponding to this invoice has already been printed or sent by mail.")
l10n_ch_currency_name = fields.Char(related='currency_id.name', help="The name of this invoice's currency") #This field is used in the "invisible" condition field of the 'Print ISR' button.
@api.depends('partner_bank_id.bank_id.l10n_ch_postal_eur', 'partner_bank_id.bank_id.l10n_ch_postal_chf')
diff --git a/addons/l10n_fr_pos_cert/models/__init__.py b/addons/l10n_fr_pos_cert/models/__init__.py
index 76de40a9e62..33657992e1c 100644
--- a/addons/l10n_fr_pos_cert/models/__init__.py
+++ b/addons/l10n_fr_pos_cert/models/__init__.py
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import account_bank_statement
+from . import account_fiscal_position
from . import res_company
from . import pos
diff --git a/addons/l10n_fr_pos_cert/models/account_fiscal_position.py b/addons/l10n_fr_pos_cert/models/account_fiscal_position.py
new file mode 100644
index 00000000000..71b4b12155a
--- /dev/null
+++ b/addons/l10n_fr_pos_cert/models/account_fiscal_position.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from odoo import _, models
+from odoo.exceptions import UserError
+
+
+class AccountFiscalPosition(models.Model):
+ _inherit = "account.fiscal.position"
+
+ def write(self, vals):
+ if "tax_ids" in vals:
+ if self.env["pos.order"].sudo().search_count([("fiscal_position_id", "in", self.ids)]):
+ raise UserError(
+ _(
+ "You cannot modify a fiscal position used in a POS order. "
+ + "You should archive it and create a new one."
+ )
+ )
+ return super(AccountFiscalPosition, self).write(vals)
diff --git a/addons/point_of_sale/data/point_of_sale_data.xml b/addons/point_of_sale/data/point_of_sale_data.xml
index 13059b39a0c..b20bc74e2f0 100644
--- a/addons/point_of_sale/data/point_of_sale_data.xml
+++ b/addons/point_of_sale/data/point_of_sale_data.xml
@@ -1,13 +1,14 @@
-
+
Picking POS
POS
5
-
+
+
PoS Orders
diff --git a/addons/website_event_sale/models/sale_order.py b/addons/website_event_sale/models/sale_order.py
index 3cf2d253182..58c56bc9441 100644
--- a/addons/website_event_sale/models/sale_order.py
+++ b/addons/website_event_sale/models/sale_order.py
@@ -41,7 +41,7 @@ class SaleOrder(models.Model):
values['product_id'] = ticket.product_id.id
values['event_id'] = ticket.event_id.id
values['event_ticket_id'] = ticket.id
- values['price_unit'] = ticket.price_reduce or ticket.price
+ values['price_unit'] = ticket.price_reduce
values['name'] = "%s\n%s" % (ticket.event_id.display_name, ticket.name)
# avoid writing related values that end up locking the product record
diff --git a/addons/website_quote/controllers/main.py b/addons/website_quote/controllers/main.py
index 28759b64108..ae849bcad52 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 exceptions, fields, http, _
+from odoo.tools import formatLang
from odoo.http import request
from odoo.addons.portal.controllers.portal import get_records_pager
from odoo.addons.sale.controllers.portal import CustomerPortal
@@ -127,8 +129,17 @@ class sale_quote(http.Controller):
_message_post_helper(message=message, res_id=order_id, res_model='sale.order', **{'token': 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')
@@ -138,10 +149,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/"], 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 5c78c9d1363..9b5f0897928 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){
- window.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/sale_order_views.xml b/addons/website_quote/views/sale_order_views.xml
index 5202baf5324..dba08fcab01 100644
--- a/addons/website_quote/views/sale_order_views.xml
+++ b/addons/website_quote/views/sale_order_views.xml
@@ -10,6 +10,12 @@
class="o_open_quotation" string="Preview"
attrs="{'invisible': [('template_id', '=', False)]}"/>
+
+
+
+
+
+
diff --git a/addons/website_quote/views/website_quote_templates.xml b/addons/website_quote/views/website_quote_templates.xml
index d43fe3ebed4..fa3e9840f55 100644
--- a/addons/website_quote/views/website_quote_templates.xml
+++ b/addons/website_quote/views/website_quote_templates.xml
@@ -73,11 +73,11 @@
@@ -116,7 +116,7 @@