From 1ab61efeb68d4746f4e5a65f76532f8e233a7199 Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Tue, 16 Apr 2019 06:07:55 +0000 Subject: [PATCH 1/8] [FIX] l10n_fr_pos_cert: fpos settings The computation of hash integrity depends on the field `tax_ids_after_fiscal_position`. However, this field is computed, but not stored. It means that if one modifies a fiscal position used in the POS, the computation of `tax_ids_after_fiscal_position` will change. Consequently, the hash computation of the order will be modified, and the hash integrity of the journal will be corrupted. We prevent the modification of the taxes implied in a fiscal position if any POS order use it. Closes #32665 opw-1969009 closes odoo/odoo#32701 Signed-off-by: Nicolas Martinelli (nim) --- .../i18n/l10n_fr_pos_cert.pot | 13 ++++++++++++- addons/l10n_fr_pos_cert/models/__init__.py | 3 ++- .../models/account_fiscal_position.py | 19 +++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 addons/l10n_fr_pos_cert/models/account_fiscal_position.py diff --git a/addons/l10n_fr_pos_cert/i18n/l10n_fr_pos_cert.pot b/addons/l10n_fr_pos_cert/i18n/l10n_fr_pos_cert.pot index 4e1507eb9f0..ca60d0c4779 100644 --- a/addons/l10n_fr_pos_cert/i18n/l10n_fr_pos_cert.pot +++ b/addons/l10n_fr_pos_cert/i18n/l10n_fr_pos_cert.pot @@ -66,6 +66,11 @@ msgstr "" msgid "Corrupted data on point of sale order with id %s." msgstr "" +#. module: l10n_fr_pos_cert +#: model:ir.model,name:l10n_fr_pos_cert.model_account_fiscal_position +msgid "Fiscal Position" +msgstr "" + #. module: l10n_fr_pos_cert #: model:ir.ui.menu,name:l10n_fr_pos_cert.pos_fr_statements_menu msgid "French Statements" @@ -137,7 +142,13 @@ msgid "This session has been opened another day. To comply with the French law, msgstr "" #. module: l10n_fr_pos_cert -#: code:addons/l10n_fr_pos_cert/models/account_bank_statement.py:11 +#: code:addons/l10n_fr_pos_cert/models/account_fiscal_position.py:14 +#, python-format +msgid "You cannot modify a fiscal position used in a POS order. You should archive it and create a new one." +msgstr "" + +#. module: l10n_fr_pos_cert +#: code:addons/l10n_fr_pos_cert/models/account_bank_statement.py:12 #, python-format msgid "You cannot modify anything on a bank statement (name: %s) that was created by point of sale operations." msgstr "" diff --git a/addons/l10n_fr_pos_cert/models/__init__.py b/addons/l10n_fr_pos_cert/models/__init__.py index e56d2fcd33c..92fba1e252d 100644 --- a/addons/l10n_fr_pos_cert/models/__init__.py +++ b/addons/l10n_fr_pos_cert/models/__init__.py @@ -1,3 +1,4 @@ import account_bank_statement +from . import account_fiscal_position import res_company -import pos \ No newline at end of file +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) From 10b342ca6e18a3ffecd23ff24830939956d9b538 Mon Sep 17 00:00:00 2001 From: Julien Castiaux Date: Thu, 18 Apr 2019 12:07:48 +0000 Subject: [PATCH 2/8] [FIX] website_event_sale: free event `price_reduce` is a computed field that depends on `price`. opw-1961576 closes odoo/odoo#32800 Signed-off-by: Nicolas Martinelli (nim) --- addons/website_event_sale/models/sale_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/website_event_sale/models/sale_order.py b/addons/website_event_sale/models/sale_order.py index edfb30e7e62..8b42076861e 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 From 0229ef4c4a3960c447040ef186fa28f929bf54e1 Mon Sep 17 00:00:00 2001 From: Joseph Caburnay Date: Tue, 16 Apr 2019 14:24:16 +0000 Subject: [PATCH 3/8] [FIX] auth_signup: portal user redirect to /web after signup To reproduce: 0. Start an odoo v10 instance with --load=saas_worker,web 1. Install ecommerce. 2. Enable "Allow external users to sign up" and "Enable password reset from Login page" from General Settings. 3. Open different session then signup a new user. 4. After successfull signup, the new user will be redirected to the backend (/web). Facts to consider: 1. odoo.addons.auth_signup.controllers.main.AuthSignupHome and odoo.addons.website.controllers.main.Website both inherit odoo.addons.web.controllers.main.Home 2. When instantiating an odoo instance *without* saas_worker,web, the mro is the following: ( , , , , , ) while the mro *with* saas_worker,web loaded is: ( , , , , , ) You can notice that depending on how the instance is instantiated, the order of inheritance is different. The problem occurs when saas_worker is loaded, so this bug can be experienced by saas clients. Explanation of the fix: Notice that the original code calls web_login of its super in its web_auth_signup method. This is technique is used normally during optimization (according to RCO). If website is installed, the portal user should be redirected to '/' instead of '/web' and this is defined in web_login of website. However, the web_login of "website" is not called after signup because "website" is not super of "auth_signup" when saas_worker is loaded (see the mro above). Calling self.web_login will make sure that web_login is called from top to bottom, and regardless of the order of website and auth_signup, web_login of "website" will be called and makes sure that the new portal user is redirected to the '/' and not to '/web'. opw-1956980 closes odoo/odoo#32741 Signed-off-by: Nicolas Martinelli (nim) --- addons/auth_signup/controllers/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/auth_signup/controllers/main.py b/addons/auth_signup/controllers/main.py index 5b45e068289..dc09b8bbdee 100644 --- a/addons/auth_signup/controllers/main.py +++ b/addons/auth_signup/controllers/main.py @@ -32,7 +32,7 @@ class AuthSignupHome(Home): if 'error' not in qcontext and request.httprequest.method == 'POST': try: self.do_signup(qcontext) - return super(AuthSignupHome, self).web_login(*args, **kw) + return self.web_login(*args, **kw) except (SignupError, AssertionError), e: if request.env["res.users"].sudo().search([("login", "=", qcontext.get("login"))]): qcontext["error"] = _("Another user is already registered using this email address.") @@ -53,7 +53,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." From 0106fcd59c8c97b1a1e39a263f305650978c0606 Mon Sep 17 00:00:00 2001 From: "Lucas Perais (lpe)" Date: Fri, 19 Apr 2019 09:08:14 +0000 Subject: [PATCH 4/8] [FIX] website_quote: change website_description when the template id changes Have a SO with a partner in another language Apply a quote template to a SO Customize the portal view to show website_description of a product on the template Translate it Show the SO on the portal Before this commit, the description of the product was not translated This was because the field was not included in the onchange of template_id causing that field to never have changed, that is, it kept the description done injected with the first write After this commit, the description is changed according to the partner's lang OPW 1960977 closes odoo/odoo#32825 Signed-off-by: Lucas Perais (lpe) --- addons/website_quote/views/sale_order_views.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/addons/website_quote/views/sale_order_views.xml b/addons/website_quote/views/sale_order_views.xml index 4c679196d12..2052f5d804c 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)]}"/> + + + + + + From f9ce7d1fc8f901efc9db397ec4786d8710aabd53 Mon Sep 17 00:00:00 2001 From: "Lucas Perais (lpe)" Date: Fri, 19 Apr 2019 12:40:11 +0000 Subject: [PATCH 5/8] [FIX] account: report overdue: name of company duplicated Have a partner which is only a company Print "Due Payments" report Before this commit, the name of the company was shown in double this was because both `name` and `contact_address` are shown in that report but in the case of a company, or child of a company, the company's name is included in contact_address After this commit, the name appears only once OPW 1970581 closes odoo/odoo#32838 Signed-off-by: Lucas Perais (lpe) --- addons/account/views/report_overdue.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/account/views/report_overdue.xml b/addons/account/views/report_overdue.xml index a413e77d0d3..9357917e370 100644 --- a/addons/account/views/report_overdue.xml +++ b/addons/account/views/report_overdue.xml @@ -5,7 +5,8 @@
-
+ +
From d3c980e92e5743a5d7c10006c255ac19de3c22b5 Mon Sep 17 00:00:00 2001 From: David Date: Mon, 1 Apr 2019 12:33:13 +0000 Subject: [PATCH 6/8] [FIX] point_of_sale: put PoS picking sequence in noupdate The default PoS picking sequence should not be reset. Losing custom configuration can lead to errors. For instance: - A customer initially uses the default sequence until POS09999 - he changes it to another one of his own taste starting the sequence over, like: WH/POS/00001 - In a module update, the sequence is reset and then we get the POS prefix again. - In the next picking, we could have a duplicated name, and an error would raise in the Point of Sale. opw 1962302 closes #32299 closes odoo/odoo#32842 Signed-off-by: Nans Lefebvre (len) --- addons/point_of_sale/data/point_of_sale_data.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 f2c82edc99a..998299a643d 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 From 5ec4da2ee251a1ec422378632b6c4a3e10b92da1 Mon Sep 17 00:00:00 2001 From: xmo-odoo Date: Wed, 6 Mar 2019 15:02:38 +0000 Subject: [PATCH 7/8] [IMP] base_module_import: ignore garbage directories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the zipfile contains some garbage directories (e.g. leftover empty directories from git, `__MACOSX` metadata folder, …) it seems unnecessary to log an error, just skip the directory and don't mark it as a proper / successful module. closes odoo/odoo#31639 Signed-off-by: Xavier Morel (xmo) --- addons/base_import_module/models/ir_module.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/addons/base_import_module/models/ir_module.py b/addons/base_import_module/models/ir_module.py index e0b18dd355a..ff55205bdf2 100644 --- a/addons/base_import_module/models/ir_module.py +++ b/addons/base_import_module/models/ir_module.py @@ -28,6 +28,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) unmet_dependencies = set(terp['depends']).difference(installed_mods) @@ -110,8 +112,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, e: _logger.exception('Error while importing module') errors[mod_name] = exception_to_unicode(e) From 4730a884b488536b5d149c294ac08cc1e23365c5 Mon Sep 17 00:00:00 2001 From: "Lucas Perais (lpe)" Date: Tue, 16 Apr 2019 11:04:14 +0000 Subject: [PATCH 8/8] [FIX] website_quote: portal optional products qty change Have a SO with a quotation template and some optional products set Display it on the portal On an optional product, click on the cart icon to add it to the SO Modify the quantity of the optional product Before this commit, the feature was barely working: - the total price of the optional product did not change - negative quantities were allowed - there was no reaction when directly putting a number in the input - when decrementing the quantity, it crashed - untaxed and tax amounts were not dynamic After this commit: - the total price of the optional product changes as a function of the quantity input - negative quantities are not allowed - it is not possible to manually input the quantity with a keyboard only +/- buttons are used to change the quantity - decrementing the quantity works - untaxed and tax amounts are dynamic OPW 1947769 closes odoo/odoo#32715 Signed-off-by: Lucas Perais (lpe) --- addons/website_quote/controllers/main.py | 34 +++- .../static/src/js/website_quotation.js | 168 ++++++++++++++++-- .../views/website_quote_templates.xml | 8 +- 3 files changed, 185 insertions(+), 25 deletions(-) diff --git a/addons/website_quote/controllers/main.py b/addons/website_quote/controllers/main.py index 0fcb9485d76..2cb66869051 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 fields, http, _ +from odoo.tools import formatLang from odoo.http import request from odoo.addons.website_mail.controllers.main import _message_post_helper @@ -104,8 +106,17 @@ class sale_quote(http.Controller): _message_post_helper(message=message, res_id=order_id, res_model='sale.order', **{'token': token, 'token_field': 'access_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') @@ -115,10 +126,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 04705686f32..6985625c378 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){ - 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/website_quote_templates.xml b/addons/website_quote/views/website_quote_templates.xml index 2aa63f2f730..1b4c906a500 100644 --- a/addons/website_quote/views/website_quote_templates.xml +++ b/addons/website_quote/views/website_quote_templates.xml @@ -69,11 +69,11 @@ t-options="{'widget': 'monetary', 'display_currency': quotation.pricelist_id.currency_id}"/>
- + - + @@ -102,7 +102,7 @@ Subtotal: - + @@ -111,7 +111,7 @@ Taxes: - +