From 9785f8bbed1036147b4ee1054c40cdeb91b0d1b3 Mon Sep 17 00:00:00 2001 From: Laurent Smet Date: Thu, 16 Nov 2017 09:08:41 +0100 Subject: [PATCH 1/5] [FIX] account: fix migrate_tags_on_taxes not working on multi-company -opw: see PR https://github.com/odoo/odoo/pull/20792 --- addons/account/models/chart_template.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/addons/account/models/chart_template.py b/addons/account/models/chart_template.py index e54c0c926de..38b638cb4c2 100644 --- a/addons/account/models/chart_template.py +++ b/addons/account/models/chart_template.py @@ -33,8 +33,7 @@ def migrate_tags_on_taxes(cr, registry): tax_template_ids = [x['res_id'] for x in registry['ir.model.data'].read(cr, SUPERUSER_ID, xml_record_ids, ['res_id'])] for tax_template in registry['account.tax.template'].browse(cr, SUPERUSER_ID, tax_template_ids): tax_id = registry['account.tax'].search(cr, SUPERUSER_ID, [('name', '=', tax_template.name), ('type_tax_use', '=', tax_template.type_tax_use), ('description', '=', tax_template.description)]) - if len(tax_id) == 1: - registry['account.tax'].write(cr, SUPERUSER_ID, tax_id, {'tag_ids': [(6,0,[x.id for x in tax_template.tag_ids])]}) + registry['account.tax'].write(cr, SUPERUSER_ID, tax_id, {'tag_ids': [(6,0,[x.id for x in tax_template.tag_ids])]}) def preserve_existing_tags_on_taxes(cr, registry, module): ''' This is a utility function used to preserve existing previous tags during upgrade of the module.''' From b4ec51021ba2a4f6cb9be29e4c3dae9bf2c4a56f Mon Sep 17 00:00:00 2001 From: "Lucas Perais (lpe)" Date: Wed, 15 Nov 2017 09:02:26 +0100 Subject: [PATCH 2/5] [FIX][BACKPORT] calendar: correct search in recurrent event backporting 76b724294aa21c83edd3409d4664e041dd70cd00 OPW 779208 closes #20996 original commit message: Add a 3 use cases with tests that were failing before. datetime filters were not working correctly on stop filters The `_get_recurrent_date_by_event` was only computing recurrency on the start field of an event which can make a difference. A recurring event start=2017-01-01 09:00:00; stop=2017-01-01 10:00:0 was not matched in a search ('stop', '>', '2017-01-01 09:30:00') The filter != was not handled in the recurring use cases. e.g. making the search 'start is set' in the web client is equal to ('start', '!=', False) Searching with only a start filter matching recurring events but not the first ("real") occurence, was returning no result if no stop filter was present. start=2017-01-01 09:00:00; count=5 had no result in a search ('start', '>', '2017-01-02') --- addons/calendar/calendar.py | 63 +++++++++++++++---- .../test_calendar_recurrent_event_case2.yml | 47 ++++++++++++++ 2 files changed, 97 insertions(+), 13 deletions(-) diff --git a/addons/calendar/calendar.py b/addons/calendar/calendar.py index ae0cbe60349..43064b369d6 100644 --- a/addons/calendar/calendar.py +++ b/addons/calendar/calendar.py @@ -16,6 +16,7 @@ from dateutil.relativedelta import relativedelta from openerp import api from openerp import tools, SUPERUSER_ID from openerp.osv import fields, osv +from openerp.fields import Datetime as FieldDatetime from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT from openerp.tools.translate import _ from openerp.http import request @@ -666,9 +667,23 @@ class calendar_event(osv.Model): def do_run_scheduler(self, cr, uid, id, context=None): self.pool['calendar.alarm_manager'].get_next_mail(cr, uid, context=context) + def _get_recurrent_dates_by_event(self, cr, uid, ids, context=None): + """ Get recurrent start and stop dates based on Rule string""" + start_dates = self._get_recurrent_date_by_event(cr, uid, ids, date_field='start') + stop_dates = self._get_recurrent_date_by_event(cr, uid, ids, date_field='stop') + return zip(start_dates, stop_dates) + def get_recurrent_date_by_event(self, cr, uid, event, context=None): + return self._get_recurrent_date_by_event(cr, uid, event, context=None) + + def _get_recurrent_date_by_event(self, cr, uid, event, context=None, date_field='start'): """Get recurrent dates based on Rule string and all event where recurrent_id is child """ + if date_field in event._fields.keys() and event._fields[date_field].type in ('date', 'datetime'): + reference_date = event[date_field] + else: + reference_date = event.start + def todate(date): val = parser.parse(''.join((re.compile('\d')).findall(date))) ## Dates are localized to saved timezone if any, else current timezone. @@ -680,14 +695,14 @@ class calendar_event(osv.Model): context = {} timezone = pytz.timezone(context.get('tz') or 'UTC') - startdate = pytz.UTC.localize(datetime.strptime(event.start, DEFAULT_SERVER_DATETIME_FORMAT)) # Add "+hh:mm" timezone - if not startdate: - startdate = datetime.now() + event_date = pytz.UTC.localize(FieldDatetime.from_string(reference_date)) # Add "+hh:mm" timezone + if not event_date: + event_date = datetime.now() - ## Convert the start date to saved timezone (or context tz) as it'll + ## Convert the event date to saved timezone (or context tz) as it'll ## define the correct hour/day asked by the user to repeat for recurrence. - startdate = startdate.astimezone(timezone) # transform "+hh:mm" timezone - rset1 = rrule.rrulestr(str(event.rrule), dtstart=startdate, forceset=True) + event_date = event_date.astimezone(timezone) # transform "+hh:mm" timezone + rset1 = rrule.rrulestr(str(event.rrule), dtstart=event_date, forceset=True) ids_depending = self.search(cr, uid, [('recurrent_id', '=', event.id), '|', ('active', '=', False), ('active', '=', True)], context=context) all_events = self.browse(cr, uid, ids_depending, context=context) for ev in all_events: @@ -1173,9 +1188,9 @@ class calendar_event(osv.Model): result.append(ev.id) result_data.append(self.get_search_fields(ev, order_fields)) continue - rdates = self.get_recurrent_date_by_event(cr, uid, ev, context=context) + rdates = self._get_recurrent_dates_by_event(cr, uid, ev, context=context) - for r_date in rdates: + for r_start_date, r_stop_date in rdates: # fix domain evaluation # step 1: check date and replace expression by True or False, replace other expressions by True # step 2: evaluation of & and | @@ -1184,16 +1199,26 @@ class calendar_event(osv.Model): ok = True for arg in domain: if str(arg[0]) in ('start', 'stop', 'final_date'): + if str(arg[0]) == 'start': + r_date = r_start_date + else: + r_date = r_stop_date + if arg[2] and len(arg[2]) > len(r_date.strftime(DEFAULT_SERVER_DATE_FORMAT)): + dformat = DEFAULT_SERVER_DATETIME_FORMAT + else: + dformat = DEFAULT_SERVER_DATE_FORMAT if (arg[1] == '='): - ok = r_date.strftime('%Y-%m-%d') == arg[2] + ok = r_date.strftime(dformat) == arg[2] if (arg[1] == '>'): - ok = r_date.strftime('%Y-%m-%d') > arg[2] + ok = r_date.strftime(dformat) > arg[2] if (arg[1] == '<'): - ok = r_date.strftime('%Y-%m-%d') < arg[2] + ok = r_date.strftime(dformat) < arg[2] if (arg[1] == '>='): - ok = r_date.strftime('%Y-%m-%d') >= arg[2] + ok = r_date.strftime(dformat) >= arg[2] if (arg[1] == '<='): - ok = r_date.strftime('%Y-%m-%d') <= arg[2] + ok = r_date.strftime(dformat) <= arg[2] + if (arg[1] == '!='): + ok = r_date.strftime(dformat) != arg[2] pile.append(ok) elif str(arg) == str('&') or str(arg) == str('|'): pile.append(arg) @@ -1529,6 +1554,18 @@ class calendar_event(osv.Model): if not context.get('virtual_id', True): return super(calendar_event, self).search(cr, uid, new_args, offset=offset, limit=limit, order=order, count=count, context=context) + if any(arg[0] == 'start' for arg in args) and \ + not any(arg[0] in ('stop', 'final_date') for arg in args): + # domain with a start filter but with no stop clause should be extended + # e.g. start=2017-01-01, count=5 => virtual occurences must be included in ('start', '>', '2017-01-02') + start_args = new_args + new_args = [] + for arg in start_args: + new_arg = arg + if arg[0] in ('start_date', 'start_datetime', 'start',): + new_args += ['|', '&', ('recurrency', '=', 1), ('final_date', arg[1], arg[2])] + new_args.append(new_arg) + # offset, limit, order and count must be treated separately as we may need to deal with virtual ids res = super(calendar_event, self).search(cr, uid, new_args, offset=0, limit=0, order=None, context=context, count=False) res = self.get_recurrent_ids(cr, uid, res, args, order=order, context=context) diff --git a/addons/calendar/test/test_calendar_recurrent_event_case2.yml b/addons/calendar/test/test_calendar_recurrent_event_case2.yml index 0908edfb2a8..99ff3855c88 100644 --- a/addons/calendar/test/test_calendar_recurrent_event_case2.yml +++ b/addons/calendar/test/test_calendar_recurrent_event_case2.yml @@ -100,3 +100,50 @@ self.write(cr, uid, ids, {'description': DESCRIPTION}, context=context) for i, event in enumerate(self.browse(cr, uid, ids, context)): assert event.description == DESCRIPTION, "Event %d has not been updated" % i +- + !record {model: calendar.event, id: calendar_event_reviewcode0}: + count: 12 + start: '2011-01-22 11:47:00' + stop: '2011-01-22 12:47:00' + day: 0.0 + duration: 1.0 + final_date: 2017-06-30 + end_type: 'end_date' + name: Review code with programmer + fr: true + mo: true + th: true + tu: true + we: true + recurrency: true + rrule_type: weekly +- + !python {model: calendar.event}: | + context = {'virtual_id': True} + + meetings_count = self.search_count(cr, uid, [ + ('start', '<=', '2017-01-24'), ('stop', '>=', '2017-01-24'), ('name', '=', 'Review code with programmer') + ], context=context) + + assert meetings_count == 1, 'Recurrent weekly meetings are not found using date filter !' + + meetings_count = self.search_count(cr, uid, [ + ('start', '<=', '2017-01-24 11:55:00'), ('stop', '>=', '2017-01-24 11:55:00'), ('name', '=', 'Review code with programmer') + ], context=context) + assert meetings_count == 1, 'Recurrent weekly meetings are not found using time filter !' + + meetings_count = self.search_count(cr, uid, [ + ('start', '!=', False), ('stop', '>=', '2017-06-30 11:55:00'), ('name', '=', 'Review code with programmer') + ], context=context) + assert meetings_count == 1, "Last recurrent weekly meetings are not found using 'is set' filter !" + + meetings_count = self.search_count(cr, uid, [ + ('start', '<=', '2017-01-24 11:55:00'), ('stop', '>=', '2017-01-24 15:55:00') + ], context=context) + assert meetings_count == 0, 'Too late recurrent meetings are found using time filter !' + + # I search using a start filter but no stop + meetings_count = self.search_count(cr, uid, [ + ('start', '>=', '2017-06-30 08:00:00'), ('name', '=', 'Review code with programmer') + ], context=context) + assert meetings_count == 1, "Last recurrent weekly meetings are not found without stop filter !" From 5b10f5dbc1182164bde3c3be462125f5338fe63b Mon Sep 17 00:00:00 2001 From: Cedric Snauwaert Date: Thu, 16 Nov 2017 12:23:43 +0100 Subject: [PATCH 3/5] [FIX] account: reconciliation of payment with statement should not take lock date into account If we have a payment in october and a statement in november and the lock date is 1st november, we can't reconcile both because we try to write statement_id on payment but there is the lockdate constraint. This is not correct and we should be able to reconcile both even after lock date as it is the case for other moves. --- addons/account/models/account_bank_statement.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py index e49546e31e7..3f75f605644 100644 --- a/addons/account/models/account_bank_statement.py +++ b/addons/account/models/account_bank_statement.py @@ -878,7 +878,7 @@ class AccountBankStatementLine(models.Model): # Fully reconciled moves are just linked to the bank statement for aml_rec in payment_aml_rec: - aml_rec.write({'statement_id': self.statement_id.id}) + aml_rec.with_context(check_move_validity=False).write({'statement_id': self.statement_id.id}) aml_rec.move_id.write({'statement_line_id': self.id}) counterpart_moves = (counterpart_moves | aml_rec.move_id) From 8d5c4cb30d7f3cf37f6bd8bd396f47aa7f5c9287 Mon Sep 17 00:00:00 2001 From: qsm-odoo Date: Thu, 16 Nov 2017 12:30:19 +0100 Subject: [PATCH 4/5] [FIX] web: adapt JS boot logs to new Firefox version When logging multiple elements in one log call in the new Firefox version, all the elements are kept on one line. So if the second element starts with a line feed, it will not go under the first element but still be at the right of the first element, on a new line. --- addons/web/static/src/js/boot.js | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/addons/web/static/src/js/boot.js b/addons/web/static/src/js/boot.js index 1f830ef254f..aa832711ca6 100644 --- a/addons/web/static/src/js/boot.js +++ b/addons/web/static/src/js/boot.js @@ -10,7 +10,7 @@ * only when the deferred is resolved, and its value is equal to the resolved value. * The module can be rejected (unloaded). This will be logged in the console as info. * - * logs: + * logs: * Missing dependencies: * These modules do not appear in the page. It is possible that the * JavaScript file is not in the page or that the module name is wrong @@ -183,16 +183,27 @@ var failed = odoo.__DEBUG__.get_failed_jobs(); var unloaded = _.filter(debug_jobs, function (job) { return job.missing; }); - var log = [(_.isEmpty(failed) ? (_.isEmpty(unloaded) ? 'info' : 'warning' ) : 'error') + ':', 'Some modules could not be started']; - if (missing.length) log.push('\nMissing dependencies: ', missing); - if (!_.isEmpty(failed)) log.push('\nFailed modules: ', _.pluck(failed, 'name')); - if (!_.isEmpty(rejected)) log.push('\nRejected modules: ', rejected); - if (!_.isEmpty(rejected_linked))log.push('\nRejected linked modules:', rejected_linked); - if (!_.isEmpty(unloaded)) log.push('\nNon loaded modules: ', _.pluck(unloaded, 'name')); - if (odoo.debug && !_.isEmpty(debug_jobs)) log.push('\nDebug: ', debug_jobs); - if (odoo.debug || !_.isEmpty(failed) || !_.isEmpty(unloaded)) { - console[_.isEmpty(failed) || _.isEmpty(unloaded) ? 'info' : 'error'].apply(console, log); + var log = console[_.isEmpty(failed) || _.isEmpty(unloaded) ? 'info' : 'error'].bind(console); + log((_.isEmpty(failed) ? (_.isEmpty(unloaded) ? 'info' : 'warning') : 'error') + ': Some modules could not be started'); + if (missing.length) { + log('Missing dependencies: ', missing); + } + if (!_.isEmpty(failed)) { + log('Failed modules: ', _.pluck(failed, 'name')); + } + if (!_.isEmpty(rejected)) { + log('Rejected modules: ', rejected); + } + if (!_.isEmpty(rejected_linked)) { + log('Rejected linked modules: ', rejected_linked); + } + if (!_.isEmpty(unloaded)) { + log('Non loaded modules: ', _.pluck(unloaded, 'name')); + } + if (odoo.debug && !_.isEmpty(debug_jobs)) { + log('Debug: ', debug_jobs); + } } } }, From bab597bb47159f6b12a1cf8f7e845a3d42a3af8b Mon Sep 17 00:00:00 2001 From: "Lucas Perais (lpe)" Date: Fri, 17 Nov 2017 15:24:49 +0100 Subject: [PATCH 5/5] [FIX] calendar: r_date must be defined before use commit b4ec51021ba2a4f6cb9be29e4c3dae9bf2c4a56f forgot about the case where no domain was supplied or when the conditions on it were not fulfilled. before this commit, a traceback was thrown and no event was shown After this commit, no errors and all the relevant events are shown opw 782985 --- addons/calendar/calendar.py | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/calendar/calendar.py b/addons/calendar/calendar.py index 43064b369d6..b1644c64edd 100644 --- a/addons/calendar/calendar.py +++ b/addons/calendar/calendar.py @@ -1197,6 +1197,7 @@ class calendar_event(osv.Model): # check if there are one False pile = [] ok = True + r_date = None for arg in domain: if str(arg[0]) in ('start', 'stop', 'final_date'): if str(arg[0]) == 'start':