diff --git a/addons/account/views/report_overdue.xml b/addons/account/views/report_overdue.xml index 002ce58ef9b..66fcc84a5a3 100644 --- a/addons/account/views/report_overdue.xml +++ b/addons/account/views/report_overdue.xml @@ -5,7 +5,8 @@
-
+ +
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 @@
- + - + @@ -107,7 +107,7 @@ Subtotal: - + @@ -116,7 +116,7 @@ Taxes: - +