diff --git a/addons/account/account_bank_statement.py b/addons/account/account_bank_statement.py index 6b0da3d7988..0f6701806a4 100644 --- a/addons/account/account_bank_statement.py +++ b/addons/account/account_bank_statement.py @@ -430,7 +430,11 @@ class account_bank_statement(osv.osv): for statement in self.browse(cr, uid, ids, context=context): for st_line in statement.line_ids: if st_line.bank_account_id and st_line.partner_id and st_line.bank_account_id.partner_id.id != st_line.partner_id.id: - self.pool.get('res.partner.bank').write(cr, uid, [st_line.bank_account_id.id], {'partner_id': st_line.partner_id.id}, context=context) + # Update the partner informations of the bank account, possibly overriding existing ones + bank_obj = self.pool.get('res.partner.bank') + bank_vals = bank_obj.onchange_partner_id(cr, uid, [st_line.bank_account_id.id], st_line.partner_id.id, context=context)['value'] + bank_vals.update({'partner_id': st_line.partner_id.id}) + bank_obj.write(cr, uid, [st_line.bank_account_id.id], bank_vals, context=context) class account_bank_statement_line(osv.osv): @@ -659,7 +663,8 @@ class account_bank_statement_line(osv.osv): mv_line_pool = self.pool.get('account.move.line') domain = self._domain_move_lines_for_reconciliation(cr, uid, st_line, excluded_ids=excluded_ids, str=str, additional_domain=additional_domain, context=context) - # Get move lines ; in case of a partial reconciliation, only consider one line + # Get move lines ; in case of a partial reconciliation, only keep one line (the first whose amount is greater than + # the residual amount because it is presumably the invoice, which is the relevant item in this situation) filtered_lines = [] reconcile_partial_ids = [] actual_offset = offset @@ -668,7 +673,9 @@ class account_bank_statement_line(osv.osv): lines = mv_line_pool.browse(cr, uid, line_ids, context=context) make_one_more_loop = False for line in lines: - if line.reconcile_partial_id and line.reconcile_partial_id.id in reconcile_partial_ids: + if line.reconcile_partial_id and \ + (line.reconcile_partial_id.id in reconcile_partial_ids or \ + abs(line.debit - line.credit) < abs(line.amount_residual)): #if we filtered a line because it is partially reconciled with an already selected line, we must do one more loop #in order to get the right number of items in the pager make_one_more_loop = True diff --git a/addons/account/account_invoice.py b/addons/account/account_invoice.py index be1f6cbf91c..4553ec7ed8d 100644 --- a/addons/account/account_invoice.py +++ b/addons/account/account_invoice.py @@ -643,7 +643,7 @@ class account_invoice(models.Model): partner = invoice.partner_id if partner.lang: ctx['lang'] = partner.lang - for taxe in account_invoice_tax.compute(invoice).values(): + for taxe in account_invoice_tax.compute(invoice.with_context(ctx)).values(): account_invoice_tax.create(taxe) # dummy write on self to trigger recomputations return self.with_context(ctx).write({'invoice_line': []}) diff --git a/addons/account/account_move_line.py b/addons/account/account_move_line.py index d91d7696162..d75d4eb3d61 100644 --- a/addons/account/account_move_line.py +++ b/addons/account/account_move_line.py @@ -783,7 +783,7 @@ class account_move_line(osv.osv): ret_line = { 'id': line.id, 'name': line.name != '/' and line.move_id.name + ': ' + line.name or line.move_id.name, - 'ref': line.move_id.ref, + 'ref': line.move_id.ref or '', # For reconciliation between statement transactions and already registered payments (eg. checks) 'already_paid': line.account_id.type == 'liquidity', 'account_code': line.account_id.code, @@ -1378,6 +1378,8 @@ class account_move_line(osv.osv): } self.create(cr, uid, data, context) #create the Tax movement + if not tax['amount']: + continue data = { 'move_id': vals['move_id'], 'name': tools.ustr(vals['name'] or '') + ' ' + tools.ustr(tax['name'] or ''), @@ -1395,7 +1397,8 @@ class account_move_line(osv.osv): self.create(cr, uid, data, context) del vals['account_tax_id'] - if check and not context.get('novalidate') and (context.get('recompute', True) or journal.entry_posted): + recompute = journal.env.recompute and context.get('recompute', True) + if check and not context.get('novalidate') and (recompute or journal.entry_posted): tmp = move_obj.validate(cr, uid, [vals['move_id']], context) if journal.entry_posted and tmp: move_obj.button_validate(cr,uid, [vals['move_id']], context) diff --git a/addons/account/account_view.xml b/addons/account/account_view.xml index 623fef0662f..abc8b896b76 100644 --- a/addons/account/account_view.xml +++ b/addons/account/account_view.xml @@ -615,7 +615,8 @@ - + diff --git a/addons/calendar/calendar.py b/addons/calendar/calendar.py index aa31a536df1..8a2cac738b4 100644 --- a/addons/calendar/calendar.py +++ b/addons/calendar/calendar.py @@ -7,6 +7,7 @@ import openerp import openerp.service.report import uuid import collections +import babel.dates from werkzeug.exceptions import BadRequest from datetime import datetime, timedelta from dateutil import parser @@ -1441,7 +1442,16 @@ class calendar_event(osv.Model): return invitation def get_interval(self, cr, uid, ids, date, interval, tz=None, context=None): - #Function used only in calendar_event_data.xml for email template + ''' Format and localize some dates to be used in email templates + + :param string date: date/time to be formatted + :param string interval: Among 'day', 'month', 'dayname' and 'time' indicating the desired formatting + :param string tz: Timezone indicator (optional) + + :return unicode: Formatted date or time (as unicode string, to prevent jinja2 crash) + + (Function used only in calendar_event_data.xml) ''' + date = openerp.fields.Datetime.from_string(date) if tz: @@ -1449,14 +1459,23 @@ class calendar_event(osv.Model): date = date.replace(tzinfo=pytz.timezone('UTC')).astimezone(timezone) if interval == 'day': - res = str(date.day) + # Day number (1-31) + res = unicode(date.day) + elif interval == 'month': - res = date.strftime('%B') + " " + str(date.year) + # Localized month name and year + res = babel.dates.format_date(date=date, format='MMMM y', locale=context.get('lang', 'en_US')) + elif interval == 'dayname': - res = date.strftime('%A') + # Localized day name + res = babel.dates.format_date(date=date, format='EEEE', locale=context.get('lang', 'en_US')) + elif interval == 'time': + # Localized time + dummy, format_time = self.get_date_formats(cr, uid, context=context) - res = date.strftime(format_time + " %Z") + res = tools.ustr(date.strftime(format_time + " %Z")) + return res def search(self, cr, uid, args, offset=0, limit=0, order=None, context=None, count=False): diff --git a/addons/google_calendar/google_calendar.py b/addons/google_calendar/google_calendar.py index 1482d71b2c7..79be277de09 100644 --- a/addons/google_calendar/google_calendar.py +++ b/addons/google_calendar/google_calendar.py @@ -212,13 +212,20 @@ class google_calendar(osv.AbstractModel): type = 'dateTime' vstype = 'date' attendee_list = [] - for attendee in event.attendee_ids: attendee_list.append({ 'email': attendee.email or 'NoEmail@mail.com', 'displayName': attendee.partner_id.name, 'responseStatus': attendee.state or 'needsAction', }) + + reminders = [] + for alarm in event.alarm_ids: + reminders.append({ + "method": "email" if alarm.type == "email" else "popup", + "minutes": alarm.duration_minutes + }) + data = { "summary": event.name or '', "description": event.description or '', @@ -233,6 +240,10 @@ class google_calendar(osv.AbstractModel): 'timeZone': 'UTC' }, "attendees": attendee_list, + "reminders": { + "overrides": reminders, + "useDefault": "false" + }, "location": event.location or '', "visibility": event['class'] or 'public', } @@ -244,7 +255,6 @@ class google_calendar(osv.AbstractModel): if not self.get_need_synchro_attendee(cr, uid, context=context): data.pop("attendees") - return data def create_an_event(self, cr, uid, event, context=None): @@ -286,7 +296,7 @@ class google_calendar(osv.AbstractModel): registry = openerp.modules.registry.RegistryManager.get(request.session.db) with registry.cursor() as cur: - self.pool['res.users'].write(cur, uid, [uid], {'google_calendar_token': False, 'google_calendar_token_validity': False}, context=context) + self.pool['res.users'].write(cur, SUPERUSER_ID, [uid], {'google_calendar_token': False, 'google_calendar_token_validity': False}, context=context) raise self.pool.get('res.config.settings').get_config_warning(cr, _(error_msg), context=context) raise @@ -400,14 +410,16 @@ class google_calendar(osv.AbstractModel): calendar_event = self.pool['calendar.event'] res_partner_obj = self.pool['res.partner'] calendar_attendee_obj = self.pool['calendar.attendee'] + calendar_alarm_obj = self.pool['calendar.alarm'] user_obj = self.pool['res.users'] myPartnerID = user_obj.browse(cr, uid, uid, context).partner_id.id attendee_record = [] + alarm_record = [] partner_record = [(4, myPartnerID)] result = {} - if single_event_dict.get('attendees', False): - for google_attendee in single_event_dict['attendees']: + if self.get_need_synchro_attendee(cr, uid, context=context): + for google_attendee in single_event_dict.get('attendees', []): if type == "write": for oe_attendee in event['attendee_ids']: if oe_attendee.email == google_attendee['email']: @@ -415,22 +427,41 @@ class google_calendar(osv.AbstractModel): google_attendee['found'] = True continue - if google_attendee.get('found', False): + if google_attendee.get('found'): continue - if self.get_need_synchro_attendee(cr, uid, context=context): - attendee_id = res_partner_obj.search(cr, uid, [('email', '=', google_attendee['email'])], context=context) - if not attendee_id: - data = { - 'email': google_attendee['email'], - 'customer': False, - 'name': google_attendee.get("displayName", False) or google_attendee['email'] - } - attendee_id = [res_partner_obj.create(cr, uid, data, context=context)] - attendee = res_partner_obj.read(cr, uid, attendee_id[0], ['email'], context=context) - partner_record.append((4, attendee.get('id'))) - attendee['partner_id'] = attendee.pop('id') - attendee['state'] = google_attendee['responseStatus'] - attendee_record.append((0, 0, attendee)) + + attendee_id = res_partner_obj.search(cr, uid, [('email', '=', google_attendee['email'])], context=context) + if not attendee_id: + data = { + 'email': google_attendee['email'], + 'customer': False, + 'name': google_attendee.get("displayName", False) or google_attendee['email'] + } + attendee_id = [res_partner_obj.create(cr, uid, data, context=context)] + attendee = res_partner_obj.read(cr, uid, attendee_id[0], ['email'], context=context) + partner_record.append((4, attendee.get('id'))) + attendee['partner_id'] = attendee.pop('id') + attendee['state'] = google_attendee['responseStatus'] + attendee_record.append((0, 0, attendee)) + for google_alarm in single_event_dict.get('reminders', {}).get('overrides', []): + alarm_id = calendar_alarm_obj.search( + cr, + uid, + [ + ('type', '=', google_alarm['method'] if google_alarm['method'] == 'email' else 'notification'), + ('duration_minutes', '=', google_alarm['minutes']) + ], + context=context + ) + if not alarm_id: + data = { + 'type': google_alarm['method'] if google_alarm['method'] == 'email' else 'notification', + 'duration': google_alarm['minutes'], + 'interval': 'minutes', + 'name': "%s minutes - %s" % (google_alarm['minutes'], google_alarm['method']) + } + alarm_id = [calendar_alarm_obj.create(cr, uid, data, context=context)] + alarm_record.append(alarm_id[0]) UTC = pytz.timezone('UTC') if single_event_dict.get('start') and single_event_dict.get('end'): # If not cancelled @@ -458,6 +489,7 @@ class google_calendar(osv.AbstractModel): result.update({ 'attendee_ids': attendee_record, 'partner_ids': list(set(partner_record)), + 'alarm_ids': [(6, 0, alarm_record)], 'name': single_event_dict.get('summary', 'Event'), 'description': single_event_dict.get('description', False), @@ -666,7 +698,7 @@ class google_calendar(osv.AbstractModel): cr.rollback() registry = openerp.modules.registry.RegistryManager.get(request.session.db) with registry.cursor() as cur: - self.pool['res.users'].write(cur, uid, [uid], {'google_calendar_last_sync_date': False}, context=context) + self.pool['res.users'].write(cur, SUPERUSER_ID, [uid], {'google_calendar_last_sync_date': False}, context=context) error_key = simplejson.loads(str(e)) error_key = error_key.get('error', {}).get('message', 'nc') error_msg = "Google is lost... the next synchro will be a full synchro. \n\n %s" % error_key @@ -943,7 +975,7 @@ class calendar_event(osv.Model): _inherit = "calendar.event" def get_fields_need_update_google(self, cr, uid, context=None): - return ['name', 'description', 'allday', 'date', 'date_end', 'stop', 'attendee_ids', 'location', 'class', 'active'] + return ['name', 'description', 'allday', 'date', 'date_end', 'stop', 'attendee_ids', 'alarm_ids', 'location', 'class', 'active'] def write(self, cr, uid, ids, vals, context=None): if context is None: diff --git a/addons/hr_payroll/hr_payroll.py b/addons/hr_payroll/hr_payroll.py index aa052d40738..fde5c9132d8 100644 --- a/addons/hr_payroll/hr_payroll.py +++ b/addons/hr_payroll/hr_payroll.py @@ -847,18 +847,20 @@ result = rules.NET > categories.NET * 0.10''', rule = self.browse(cr, uid, rule_id, context=context) if rule.amount_select == 'fix': try: - return rule.amount_fix, eval(rule.quantity, localdict), 100.0 + return rule.amount_fix, float(eval(rule.quantity, localdict)), 100.0 except: raise UserError(_('Wrong quantity defined for salary rule %s (%s).') % (rule.name, rule.code)) elif rule.amount_select == 'percentage': try: - return eval(rule.amount_percentage_base, localdict), eval(rule.quantity, localdict), rule.amount_percentage + return (float(eval(rule.amount_percentage_base, localdict)), + float(eval(rule.quantity, localdict)), + rule.amount_percentage) except: raise UserError(_('Wrong percentage base or quantity defined for salary rule %s (%s).') % (rule.name, rule.code)) else: try: eval(rule.amount_python_compute, localdict, mode='exec', nocopy=True) - return localdict['result'], 'result_qty' in localdict and localdict['result_qty'] or 1.0, 'result_rate' in localdict and localdict['result_rate'] or 100.0 + return float(localdict['result']), 'result_qty' in localdict and localdict['result_qty'] or 1.0, 'result_rate' in localdict and localdict['result_rate'] or 100.0 except: raise UserError(_('Wrong python code defined for salary rule %s (%s).') % (rule.name, rule.code)) diff --git a/addons/hr_timesheet_sheet/static/src/xml/timesheet.xml b/addons/hr_timesheet_sheet/static/src/xml/timesheet.xml index 90d6c858232..710e68d13d9 100644 --- a/addons/hr_timesheet_sheet/static/src/xml/timesheet.xml +++ b/addons/hr_timesheet_sheet/static/src/xml/timesheet.xml @@ -36,7 +36,7 @@
-
+
Total
diff --git a/addons/im_livechat/views/im_livechat.xml b/addons/im_livechat/views/im_livechat.xml index 76c97d57212..d861caa7dfd 100644 --- a/addons/im_livechat/views/im_livechat.xml +++ b/addons/im_livechat/views/im_livechat.xml @@ -84,7 +84,7 @@