From f5e197570bafef79cfaea0ee36180013e4fae9ae Mon Sep 17 00:00:00 2001 From: xmo-odoo Date: Fri, 21 Apr 2017 14:46:05 +0200 Subject: [PATCH 1/9] [FIX] translation --- addons/l10n_be/i18n/fr.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/l10n_be/i18n/fr.po b/addons/l10n_be/i18n/fr.po index 0707b259f49..cdcb2fa046c 100644 --- a/addons/l10n_be/i18n/fr.po +++ b/addons/l10n_be/i18n/fr.po @@ -553,7 +553,7 @@ msgstr "Montant de la TVA" #: code:addons/l10n_be/wizard/l10n_be_vat_intra.py:246 #, python-format msgid "No vat number defined for %s." -msgstr "Aucun numéro de TVA défini pour %." +msgstr "Aucun numéro de TVA défini pour %s." #. module: l10n_be #: model:account.financial.report,name:l10n_be.account_financial_report_bnficepertereporte2 From 5b502841276dda0dbc0161fd0fa080d1790086bb Mon Sep 17 00:00:00 2001 From: Goffin Simon Date: Tue, 18 Apr 2017 12:00:14 +0200 Subject: [PATCH 2/9] [FIX] point_of_sale: fiscal position with included taxes in POS To substract the included tax from the unit price when it's mapped by a fiscal position in the POS. To keep the same behavior introduced by this commit odoo@503820a opw:710437 --- addons/point_of_sale/static/src/js/models.js | 32 ++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index fc9a877f3b9..b0abc797f08 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -1345,7 +1345,7 @@ exports.Orderline = Backbone.Model.extend({ } return false; }, - compute_all: function(taxes, price_unit, quantity, currency_rounding) { + compute_all: function(taxes, price_unit, quantity, currency_rounding, no_map_tax) { var self = this; var list_taxes = []; var currency_rounding_bak = currency_rounding; @@ -1356,7 +1356,9 @@ exports.Orderline = Backbone.Model.extend({ var total_included = total_excluded; var base = total_excluded; _(taxes).each(function(tax) { - tax = self._map_tax_fiscal_position(tax); + if (!no_map_tax){ + tax = self._map_tax_fiscal_position(tax); + } if (tax.amount_type === 'group'){ var ret = self.compute_all(tax.children_tax_ids, price_unit, quantity, currency_rounding); total_excluded = ret.total_excluded; @@ -1813,6 +1815,27 @@ exports.Order = Backbone.Model.extend({ this.orderlines.remove(line); this.select_orderline(this.get_last_orderline()); }, + + fix_tax_included_price: function(line){ + if(this.fiscal_position){ + var unit_price = line.price; + var taxes = line.get_taxes(); + var mapped_included_taxes = []; + _(taxes).each(function(tax) { + var line_tax = line._map_tax_fiscal_position(tax); + if(tax.price_include && tax.id != line_tax.id){ + + mapped_included_taxes.push(tax); + } + }) + + unit_price = line.compute_all(mapped_included_taxes, unit_price, 1, this.pos.currency.rounding, true).total_excluded; + + line.set_unit_price(unit_price); + } + + }, + add_product: function(product, options){ if(this._printed){ this.destroy(); @@ -1828,9 +1851,14 @@ exports.Order = Backbone.Model.extend({ if(options.quantity !== undefined){ line.set_quantity(options.quantity); } + if(options.price !== undefined){ line.set_unit_price(options.price); } + + //To substract from the unit price the included taxes mapped by the fiscal position + this.fix_tax_included_price(line); + if(options.discount !== undefined){ line.set_discount(options.discount); } From 577d9863919d5b1a9d8a21db81123f90949e1342 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Fri, 21 Apr 2017 17:21:21 +0200 Subject: [PATCH 3/9] [FIX] account: subtract refund amount from "awaiting payments" total Currently the amounts of all invoices (linked to sale or purchase journals) is summed regardless of these invoices being bills or refunds, this means e.g. if you create an invoice for 1000 then a refund for 500 on this invoice, the dashboard will display an "awaiting payment" total of 1500 which while technically correct is not really logical or helpful from a user's POV. Fix summation to take in account the invoice type (invoice or refund) and remove refund amount from the total amount. Intent validated by @lap-odoo, also not ignoring or removing refunds from count to avoid situations like "0 Awaiting Payments [for] $-1200" which would be really strange. OPW-728860 --- .../models/account_journal_dashboard.py | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/addons/account/models/account_journal_dashboard.py b/addons/account/models/account_journal_dashboard.py index 8fc73d1a3ca..505442c36dd 100644 --- a/addons/account/models/account_journal_dashboard.py +++ b/addons/account/models/account_journal_dashboard.py @@ -138,7 +138,8 @@ class account_journal(models.Model): number_to_reconcile = last_balance = account_sum = 0 ac_bnk_stmt = [] title = '' - number_draft = number_waiting = number_late = sum_draft = sum_waiting = sum_late = 0 + number_draft = number_waiting = number_late = 0 + sum_draft = sum_waiting = sum_late = 0.0 if self.type in ['bank', 'cash']: last_bank_stmt = self.env['account.bank.statement'].search([('journal_id', 'in', self.ids)], order="date desc, id desc", limit=1) last_balance = last_bank_stmt and last_bank_stmt[0].balance_end or 0 @@ -171,30 +172,33 @@ class account_journal(models.Model): elif self.type in ['sale', 'purchase']: title = _('Bills to pay') if self.type == 'purchase' else _('Invoices owed to you') # optimization to find total and sum of invoice that are in draft, open state - query = """SELECT state, amount_total, currency_id AS currency FROM account_invoice WHERE journal_id = %s AND state NOT IN ('paid', 'cancel');""" + query = """SELECT state, amount_total, currency_id AS currency, type FROM account_invoice WHERE journal_id = %s AND state NOT IN ('paid', 'cancel');""" self.env.cr.execute(query, (self.id,)) query_results = self.env.cr.dictfetchall() today = datetime.today() - query = """SELECT amount_total, currency_id AS currency FROM account_invoice WHERE journal_id = %s AND date < %s AND state = 'open';""" + query = """SELECT amount_total, currency_id AS currency, type FROM account_invoice WHERE journal_id = %s AND date < %s AND state = 'open';""" self.env.cr.execute(query, (self.id, today)) late_query_results = self.env.cr.dictfetchall() - sum_draft = 0.0 - number_draft = 0 - number_waiting = 0 for result in query_results: + if result['type'] in ['in_refund', 'out_refund']: + factor = -1 + else: + factor = 1 cur = self.env['res.currency'].browse(result.get('currency')) if result.get('state') in ['draft', 'proforma', 'proforma2']: number_draft += 1 - sum_draft += cur.compute(result.get('amount_total'), currency) + sum_draft += cur.compute(result.get('amount_total'), currency) * factor elif result.get('state') == 'open': number_waiting += 1 - sum_waiting += cur.compute(result.get('amount_total'), currency) - sum_late = 0.0 - number_late = 0 + sum_waiting += cur.compute(result.get('amount_total'), currency) * factor for result in late_query_results: + if result['type'] in ['in_refund', 'out_refund']: + factor = -1 + else: + factor = 1 cur = self.env['res.currency'].browse(result.get('currency')) number_late += 1 - sum_late += cur.compute(result.get('amount_total'), currency) + sum_late += cur.compute(result.get('amount_total'), currency) * factor return { 'number_to_reconcile': number_to_reconcile, From ec8699db1773381e3e5d6e999d4dee038d2b993c Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Fri, 14 Apr 2017 16:58:49 +0200 Subject: [PATCH 4/9] [FIX] account: performance in issued total When a partner has a lot of contacts, the computation of the issue total takes a lot of time. Indeed, it performs the computation for the partner, but also for all the children, despite the fact that the amount is not displayed. opw-726355 --- addons/account/models/partner.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/addons/account/models/partner.py b/addons/account/models/partner.py index bb78919c767..8e18962ab00 100644 --- a/addons/account/models/partner.py +++ b/addons/account/models/partner.py @@ -349,12 +349,9 @@ class ResPartner(models.Model): def _compute_issued_total(self): """ Returns the issued total as will be displayed on partner view """ today = fields.Date.context_today(self) - for partner in self: - domain = partner.get_followup_lines_domain(today, overdue_only=True) - issued_total = 0 - for aml in self.env['account.move.line'].search(domain): - issued_total += aml.amount_residual - partner.issued_total = issued_total + domain = self.get_followup_lines_domain(today, overdue_only=True) + for aml in self.env['account.move.line'].search(domain): + aml.partner_id.issued_total += aml.amount_residual @api.one def _compute_has_unreconciled_entries(self): From 863f46ec71a61f26a03959443db2c42bbc17c01e Mon Sep 17 00:00:00 2001 From: Olivier Colson Date: Tue, 25 Apr 2017 11:01:33 +0200 Subject: [PATCH 5/9] [FIX] resource: conditions on id Don't check ids with "id is None" statements, as it won't match if id is False. This caused some bugs with MRP when confirming a MO with no calendar defined, if mrp_operations was installed. --- addons/resource/resource.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/resource/resource.py b/addons/resource/resource.py index 09a5c2f3add..5a2b3559497 100644 --- a/addons/resource/resource.py +++ b/addons/resource/resource.py @@ -179,7 +179,7 @@ class resource_calendar(osv.osv): def get_weekdays(self, cr, uid, id, default_weekdays=None, context=None): """ Return the list of weekdays that contain at least one working interval. If no id is given (no calendar), return default weekdays. """ - if id is None: + if not id: return default_weekdays if default_weekdays is not None else [0, 1, 2, 3, 4] calendar = self.browse(cr, uid, id, context=None) weekdays = set() @@ -329,7 +329,7 @@ class resource_calendar(osv.osv): work_dt = start_dt.replace(hour=0, minute=0, second=0) # no calendar: try to use the default_interval, then return directly - if id is None: + if not id: working_interval = [] if default_interval: working_interval = (start_dt.replace(hour=default_interval[0], minute=0, second=0), start_dt.replace(hour=default_interval[1], minute=0, second=0)) @@ -448,7 +448,7 @@ class resource_calendar(osv.osv): working_intervals = self.get_working_intervals_of_day(cr, uid, id, **call_args) - if id is None and not working_intervals: # no calendar -> consider working 8 hours + if not id and not working_intervals: # no calendar -> consider working 8 hours remaining_hours -= 8.0 elif working_intervals: if backwards: @@ -541,7 +541,7 @@ class resource_calendar(osv.osv): compute_leaves=compute_leaves, resource_id=resource_id, default_interval=default_interval, context=context) - if id is None or working_intervals: # no calendar -> no working hours, but day is considered as worked + if not id or working_intervals: # no calendar -> no working hours, but day is considered as worked planned_days += 1 intervals += working_intervals # get next day From cd2b07ea44a58f576ce8ca95acca499267afdbbf Mon Sep 17 00:00:00 2001 From: Nicolas Lempereur Date: Tue, 21 Mar 2017 16:04:56 +0100 Subject: [PATCH 6/9] [FIX] pad: remove wrong warning at pad creation When creating a pad before this change these were the steps: - modify pad URL for new pad URL - end of record loading This caused an issue because modifying the record at this point would mark it as dirty before displaying it (which would result in a erroneous warning "The record has been modified, your changes will be discarded. Are you sure you want to leave this page ?"). With this fix: - modifying the pad URL is done after the record is ready - deferred have been unified (to remove possible race conditions) closes #15239 opw-702797 forward port note: this change is needed up to saas-15, over saas-15 only a small part is needed (because x2many views are loaded with the root view and there is one widget instance per record). code note: it would be better to modify load_record of form view and delay `record_loaded` upto after x2many views are loaded, but possible impacts have been judged too risky so this is done only in pad widget. --- addons/pad/static/src/js/pad.js | 61 ++++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 16 deletions(-) diff --git a/addons/pad/static/src/js/pad.js b/addons/pad/static/src/js/pad.js index 30c89d82819..cebe92055a7 100644 --- a/addons/pad/static/src/js/pad.js +++ b/addons/pad/static/src/js/pad.js @@ -17,7 +17,9 @@ var FieldPad = form_common.AbstractField.extend(form_common.ReinitializeWidgetMi event.preventDefault(); self.set("configured", true); }); - this.pad_loading_request = null; + // CHANGES ONLY NEEDED UNTIL SAAS-15 + // deferred for request getting pad content (readonly) or new pad url (edit) + this._pad_loading_deferred = null; }, initialize_content: function() { var self = this; @@ -36,14 +38,25 @@ var FieldPad = form_common.AbstractField.extend(form_common.ReinitializeWidgetMi }, render_value: function() { var self = this; - $.when(this._configured_deferred, this.pad_loading_request).always(function() { + $.when(this._configured_deferred).always(function() { if (!self.get('configured')){ return; } + + // reject previously ongoing _pad_loading_deferred + if (self._pad_loading_deferred !== null) { + self._pad_loading_deferred.reject(); + self.$('.oe_pad_content').removeClass('oe_pad_loading').html(''); + } + self._pad_loading_deferred = $.Deferred(); + // keep reference to current _pad_loading_deferred + var loading_def = self._pad_loading_deferred; + var value = self.get('value'); if (self.get('effective_readonly')) { if (_.str.startsWith(value, 'http')) { - self.pad_loading_request = self.view.dataset.call('pad_get_content', {url: value}).done(function(data) { + self.view.dataset.call('pad_get_content', {url: value}).then(loading_def.resolve, loading_def.reject); + loading_def.done(function(data) { self.$('.oe_pad_content').removeClass('oe_pad_loading').html('
'); self.$('.oe_pad_readonly').html(data); }).fail(function() { @@ -54,21 +67,37 @@ var FieldPad = form_common.AbstractField.extend(form_common.ReinitializeWidgetMi } } else { - var def = $.when(); + var def = $.Deferred(); if (! value || !_.str.startsWith(value, 'http')) { - def = self.view.dataset.call('pad_generate_url', { - context: { - model: self.view.model, - field_name: self.name, - object_id: self.view.datarecord.id - }, - }).then(function(data) { - if (! data.url) { - self.set("configured", false); - } else { - self.internal_set_value(data.url); - } + var deferreds = [ + self.view.dataset.call('pad_generate_url', { + context: { + model: self.view.model, + field_name: self.name, + object_id: self.view.datarecord.id + } + }), + // change record only after record_loaded and its call stack is finished + self.view.record_loaded.then(function() { + var call_stack_ended = $.Deferred(); + _.defer(call_stack_ended.resolve); + return call_stack_ended; + }) + ]; + // delay onchange after x2many views are loaded + deferreds = deferreds.concat(_.compact(_.pluck(self.view.fields, 'is_loaded'))); + $.when.apply($, deferreds).then(function(data) { + // update value only if loading_def has not been previously rejected + loading_def.resolve().done(function(){ + if (! data.url) { + self.set("configured", false); + } else { + self.internal_set_value(data.url); + } + }).then(def.resolve, def.reject); }); + } else { + def.resolve(); } def.then(function() { value = self.get('value'); From 551719a81e97807f8114c5f09a9115f7fbd8d4e6 Mon Sep 17 00:00:00 2001 From: Nicolas Lempereur Date: Wed, 5 Apr 2017 11:39:29 +0200 Subject: [PATCH 7/9] [FIX] mass_mailing: mail template not in english Mass mailing `mail templates` when selected in a mass mailing are always used in the english version. But when another language is involved the edition of mass mailing `templates` had a number of issue. For example saving the edited english would only modify the user current language. These issues are cause because the widget with snippets used for mass mailing templates edition is not compatible. Several fixes were proposed, what was choosen finally was to use the correct widget with the following behavior changes: - the snippets are not showed anymore in mass mailing templates, - the current language can be edited and is saved rightly, The shortcoming this give mass mailing templates can be partly compensated by: - editing the template back (if someone want the old behavior), - duplicating mass mailings which keep the snippets and translations, - doing a customization to improve the situation closes #16255 fixes #16165 opw-726746 opw-727232 note: this fix is only needed for 9.0 and saas-11. --- addons/mass_mailing/views/email_template.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/mass_mailing/views/email_template.xml b/addons/mass_mailing/views/email_template.xml index 4f179c60983..570130efd9b 100644 --- a/addons/mass_mailing/views/email_template.xml +++ b/addons/mass_mailing/views/email_template.xml @@ -27,7 +27,7 @@ - + From 2a65b8156d79902ae95013622f55780fbfff3ad4 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Wed, 26 Apr 2017 19:36:10 +0200 Subject: [PATCH 8/9] [FIX] base: run defined modifiers tests --- openerp/addons/base/tests/test_views.py | 5 +++++ openerp/osv/orm.py | 8 ++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/openerp/addons/base/tests/test_views.py b/openerp/addons/base/tests/test_views.py index bd297d2789c..027a2d9262c 100644 --- a/openerp/addons/base/tests/test_views.py +++ b/openerp/addons/base/tests/test_views.py @@ -9,6 +9,7 @@ from lxml.builder import E from psycopg2 import IntegrityError +from openerp.osv.orm import modifiers_tests from openerp.exceptions import ValidationError from openerp.tests import common import openerp.tools @@ -854,6 +855,10 @@ class test_views(ViewCase): string="Replacement title", version="7.0" )) + def test_modifiers(self): + # implemeted elsewhere... + modifiers_tests() + class ViewModeField(ViewCase): """ This should probably, eventually, be folded back into other test case diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 835e29da7f9..be5c69d9a01 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -118,13 +118,13 @@ def test_modifiers(what, expected): node = etree.fromstring(what) transfer_node_to_modifiers(node, modifiers) simplify_modifiers(modifiers) - json = simplejson.dumps(modifiers) - assert json == expected, "%s != %s" % (json, expected) + dumped = simplejson.dumps(modifiers) + assert dumped == expected, "%s != %s" % (dumped, expected) elif isinstance(what, dict): transfer_field_to_modifiers(what, modifiers) simplify_modifiers(modifiers) - json = simplejson.dumps(modifiers) - assert json == expected, "%s != %s" % (json, expected) + dumped = simplejson.dumps(modifiers) + assert dumped == expected, "%s != %s" % (dumped, expected) # To use this test: From 11f8a2157df0a7be459571f44db0d2391d59b759 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Wed, 26 Apr 2017 19:45:27 +0200 Subject: [PATCH 9/9] [FIX] base: res.lang: do not redefine `_` variable --- openerp/addons/base/res/res_lang.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/addons/base/res/res_lang.py b/openerp/addons/base/res/res_lang.py index fc45d18e420..75d72c8a363 100644 --- a/openerp/addons/base/res/res_lang.py +++ b/openerp/addons/base/res/res_lang.py @@ -269,7 +269,7 @@ class Lang(models.Model): if percent[-1] in 'eEfFgG': parts = formatted.split('.') - parts[0], _ = intersperse(parts[0], eval_lang_grouping, thousands_sep) + parts[0] = intersperse(parts[0], eval_lang_grouping, thousands_sep)[0] formatted = decimal_point.join(parts)