diff --git a/README.md b/README.md index 95fd20c854b..8a566fb8a8c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -[![Build Status](http://runbot.odoo.com/runbot/badge/flat/1/master.svg)](http://runbot.odoo.com/runbot) -[![Tech Doc](http://img.shields.io/badge/master-docs-8f8f8f.svg?style=flat)](http://www.odoo.com/documentation/master) -[![Help](http://img.shields.io/badge/master-help-8f8f8f.svg?style=flat)](https://www.odoo.com/forum/help-1) -[![Nightly Builds](http://img.shields.io/badge/master-nightly-8f8f8f.svg?style=flat)](http://nightly.odoo.com/) +[![Build Status](http://runbot.odoo.com/runbot/badge/flat/1/9.0.svg)](http://runbot.odoo.com/runbot) +[![Tech Doc](http://img.shields.io/badge/9.0-docs-8f8f8f.svg?style=flat)](http://www.odoo.com/documentation/9.0) +[![Help](http://img.shields.io/badge/9.0-help-8f8f8f.svg?style=flat)](https://www.odoo.com/forum/help-1) +[![Nightly Builds](http://img.shields.io/badge/9.0-nightly-8f8f8f.svg?style=flat)](http://nightly.odoo.com/) Odoo ---- diff --git a/addons/account/models/account.py b/addons/account/models/account.py index ae06e71f0cf..5c6fea97a5c 100644 --- a/addons/account/models/account.py +++ b/addons/account/models/account.py @@ -653,25 +653,24 @@ class AccountTax(models.Model): else: tax_amount = currency.round(tax_amount) - if tax_amount: - if tax.price_include: - total_excluded -= tax_amount - base -= tax_amount - else: - total_included += tax_amount + if tax.price_include: + total_excluded -= tax_amount + base -= tax_amount + else: + total_included += tax_amount - if tax.include_base_amount: - base += tax_amount + if tax.include_base_amount: + base += tax_amount - taxes.append({ - 'id': tax.id, - 'name': tax.name, - 'amount': tax_amount, - 'sequence': tax.sequence, - 'account_id': tax.account_id.id, - 'refund_account_id': tax.refund_account_id.id, - 'analytic': tax.analytic, - }) + taxes.append({ + 'id': tax.id, + 'name': tax.name, + 'amount': tax_amount, + 'sequence': tax.sequence, + 'account_id': tax.account_id.id, + 'refund_account_id': tax.refund_account_id.id, + 'analytic': tax.analytic, + }) return { 'taxes': sorted(taxes, key=lambda k: k['sequence']), diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index 19b305babd4..7a82962f6ec 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -623,16 +623,17 @@ class AccountInvoice(models.Model): def tax_line_move_line_get(self): res = [] for tax_line in self.tax_line_ids: - res.append({ - 'tax_line_id': tax_line.tax_id.id, - 'type': 'tax', - 'name': tax_line.name, - 'price_unit': tax_line.amount, - 'quantity': 1, - 'price': tax_line.amount, - 'account_id': tax_line.account_id.id, - 'account_analytic_id': tax_line.account_analytic_id.id, - }) + if tax_line.amount: + res.append({ + 'tax_line_id': tax_line.tax_id.id, + 'type': 'tax', + 'name': tax_line.name, + 'price_unit': tax_line.amount, + 'quantity': 1, + 'price': tax_line.amount, + 'account_id': tax_line.account_id.id, + 'account_analytic_id': tax_line.account_analytic_id.id, + }) return res def inv_line_characteristic_hashcode(self, invoice_line): diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index c6d43c5ce96..9ecc153a498 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -535,7 +535,7 @@ class AccountMoveLine(models.Model): # Return lines formatted if len(pairs) > 0: - target_currency = self.currency_id or self.company_id.currency_id + target_currency = (self.currency_id and self.amount_currency) and self.currency_id or self.company_id.currency_id lines = self.browse(list(pairs[0])) return lines.prepare_move_lines_for_reconciliation_widget(target_currency=target_currency) return [] @@ -630,7 +630,7 @@ class AccountMoveLine(models.Model): 'journal_name': line.journal_id.name, 'partner_id': line.partner_id.id, 'partner_name': line.partner_id.name, - 'currency_id': line.currency_id.id or False, + 'currency_id': (line.currency_id and line.amount_currency) and line.currency_id.id or False, } debit = line.debit @@ -645,7 +645,7 @@ class AccountMoveLine(models.Model): # Get right debit / credit: target_currency = target_currency or company_currency - line_currency = line.currency_id or company_currency + line_currency = (line.currency_id and line.amount_currency) and line.currency_id or company_currency amount_currency_str = "" total_amount_currency_str = "" if line_currency != company_currency: @@ -976,20 +976,21 @@ class AccountMoveLine(models.Model): vals['amount_currency'] = self.env['res.currency'].browse(vals['currency_id']).round(vals['amount_currency'] * (amount / res['total_excluded'])) # Create tax lines for tax_vals in res['taxes']: - account_id = (amount > 0 and tax_vals['account_id'] or tax_vals['refund_account_id']) - if not account_id: account_id = vals['account_id'] - tax_lines_vals.append({ - 'account_id': account_id, - 'name': vals['name'] + ' ' + tax_vals['name'], - 'tax_line_id': tax_vals['id'], - 'move_id': vals['move_id'], - 'date': vals['date'], - 'partner_id': vals.get('partner_id'), - 'ref': vals.get('ref'), - 'statement_id': vals.get('statement_id'), - 'debit': tax_vals['amount'] > 0 and tax_vals['amount'] or 0.0, - 'credit': tax_vals['amount'] < 0 and -tax_vals['amount'] or 0.0, - }) + if tax_vals['amount']: + account_id = (amount > 0 and tax_vals['account_id'] or tax_vals['refund_account_id']) + if not account_id: account_id = vals['account_id'] + tax_lines_vals.append({ + 'account_id': account_id, + 'name': vals['name'] + ' ' + tax_vals['name'], + 'tax_line_id': tax_vals['id'], + 'move_id': vals['move_id'], + 'date': vals['date'], + 'partner_id': vals.get('partner_id'), + 'ref': vals.get('ref'), + 'statement_id': vals.get('statement_id'), + 'debit': tax_vals['amount'] > 0 and tax_vals['amount'] or 0.0, + 'credit': tax_vals['amount'] < 0 and -tax_vals['amount'] or 0.0, + }) new_line = super(AccountMoveLine, self).create(vals) for tax_line_vals in tax_lines_vals: @@ -1220,6 +1221,7 @@ class AccountPartialReconcile(models.Model): 'move_id': move.id, 'currency_id': rec.currency_id.id, 'amount_currency': 0.0, + 'partner_id': rec.debit_move_id.partner_id.id, }) rec.env['account.move.line'].create({ 'name': _('Currency exchange rate difference'), @@ -1228,6 +1230,7 @@ class AccountPartialReconcile(models.Model): 'account_id': amount_diff > 0 and rec.company_id.currency_exchange_journal_id.default_debit_account_id.id or rec.company_id.currency_exchange_journal_id.default_credit_account_id.id, 'move_id': move.id, 'currency_id': rec.currency_id.id, + 'partner_id': rec.debit_move_id.partner_id.id, }) rec.env['account.partial.reconcile'].create({ 'debit_move_id': amount_diff < 0 and line_to_reconcile.id or rec.debit_move_id.id, @@ -1249,7 +1252,28 @@ class AccountPartialReconcile(models.Model): def unlink(self): """ When removing a link between entries, we need to revert the eventual journal entries we created to book the fluctuation of the foreign currency's exchange rate. + We need also to reconcile together the origin currency difference line and its reversal in order to completly + cancel the currency difference entry on the partner account (otherwise it will still appear on the aged balance + for example). """ exchange_rate_entries = self.env['account.move'].search([('rate_diff_partial_rec_id', 'in', self.ids)]) - exchange_rate_entries.reverse_moves() - return super(AccountPartialReconcile, self).unlink() + # revert the currency difference entry + reversed_moves = exchange_rate_entries.reverse_moves() + # find the origin currency difference line on the partner account and its newly created reversal, and store them in a list + pairs_to_rec = [] + for rev_move in self.env['account.move'].browse(reversed_moves): + if not rev_move.rate_diff_partial_rec_id: + continue + origin_move = exchange_rate_entries.filtered(lambda x: x.rate_diff_partial_rec_id == rev_move.rate_diff_partial_rec_id) + for acm_line in rev_move.line_ids: + if acm_line.account_id.reconcile: + for origin_line in origin_move.line_ids: + if origin_line.account_id == acm_line.account_id and origin_line.debit == acm_line.credit and origin_line.credit == acm_line.debit: + to_rec = origin_line + acm_line + pairs_to_rec.append(to_rec) + # the call to super() had to be delayed in order to mark the move lines to reconcile together (to use 'rate_diff_partial_rec_id') + res = super(AccountPartialReconcile, self).unlink() + # now that the origin currency difference line is not reconciled anymore, we can reconcile it with its reversal entry to cancel it completly + for to_rec in pairs_to_rec: + to_rec.reconcile() + return res diff --git a/addons/analytic/models/analytic.py b/addons/analytic/models/analytic.py index aa4cc3a330b..2946740387b 100644 --- a/addons/analytic/models/analytic.py +++ b/addons/analytic/models/analytic.py @@ -77,6 +77,8 @@ class account_analytic_account(models.Model): @api.model def name_search(self, name='', args=None, operator='ilike', limit=100): + if operator not in ('ilike', 'like', '=', '=like', '=ilike'): + return super(account_analytic_account, self).name_search(name, args, operator, limit) args = args or [] domain = ['|', ('code', operator, name), ('name', operator, name)] partners = self.env['res.partner'].search([('name', operator, name)], limit=limit) diff --git a/addons/base_import/models.py b/addons/base_import/models.py index 7cbd0d79e09..22b48967e65 100644 --- a/addons/base_import/models.py +++ b/addons/base_import/models.py @@ -6,6 +6,8 @@ import logging import operator import os +from openerp.tools.mimetypes import guess_mimetype + try: from cStringIO import StringIO except ImportError: @@ -158,16 +160,36 @@ class ir_import(orm.TransientModel): return fields def _read_file(self, file_type, record, options): + # guess mimetype from file content + mimetype = guess_mimetype(record.file) + (file_extension, handler, req) = FILE_TYPE_DICT.get(mimetype, (None, None, None)) + if handler: + try: + return getattr(self, '_read_' + file_extension)(record, options) + except Exception: + _logger.warn("Failed to read file '%s' (transient id %d) using guessed mimetype %s", + record.file_name or '', record.id, mimetype) + + # try reading with user-provided mimetype (file_extension, handler, req) = FILE_TYPE_DICT.get(file_type, (None, None, None)) if handler: - return getattr(self, '_read_' + file_extension)(record, options) + try: + return getattr(self, '_read_' + file_extension)(record, options) + except Exception: + _logger.warn("Failed to read file '%s' (transient id %d) using user-provided mimetype %s", + record.file_name or '', record.id, file_type) + # fallback on file extensions as mime types can be unreliable (e.g. # software setting incorrect mime types, or non-installed software # leading to browser not sending mime types) if record.file_name: p, ext = os.path.splitext(record.file_name) - if ext and EXTENSIONS.get(ext): - return getattr(self, '_read_' + ext[1:])(record, options) + if ext in EXTENSIONS: + try: + return getattr(self, '_read_' + ext[1:])(record, options) + except Exception: + _logger.warn("Failed to read file '%s' (transient id %s) using file extension", + record.file_name, record.id) if req: raise ImportError(_("Unable to load \"{extension}\" file: requires Python module \"{modname}\"").format(extension=file_extension, modname=req)) diff --git a/addons/base_import/tests/test_cases.py b/addons/base_import/tests/test_cases.py index bd9feafb9ab..5a8ad70402b 100644 --- a/addons/base_import/tests/test_cases.py +++ b/addons/base_import/tests/test_cases.py @@ -3,7 +3,7 @@ import unittest from openerp.tests.common import TransactionCase, can_import from openerp.modules.module import get_module_resource - +from openerp.tools import mute_logger from .. import models @@ -197,6 +197,7 @@ class test_preview(TransactionCase): }) return Import, id + @mute_logger('openerp.addons.base_import.models') def test_encoding(self): Import, id = self.make_import() result = Import.parse_preview(self.cr, self.uid, id, { @@ -205,6 +206,7 @@ class test_preview(TransactionCase): }) self.assertTrue('error' in result) + @mute_logger('openerp.addons.base_import.models') def test_csv_errors(self): Import, id = self.make_import() diff --git a/addons/crm/base_partner_merge_view.xml b/addons/crm/base_partner_merge_view.xml index 54632487de7..4585ec0be97 100644 --- a/addons/crm/base_partner_merge_view.xml +++ b/addons/crm/base_partner_merge_view.xml @@ -2,16 +2,14 @@ - + + Deduplicate Contacts + base.partner.merge.automatic.wizard + form + form + new + {'active_test': False} + base.partner.merge.automatic.wizard.form diff --git a/addons/delivery/views/delivery_view.xml b/addons/delivery/views/delivery_view.xml index 915f31fd3f3..de71a72b22c 100644 --- a/addons/delivery/views/delivery_view.xml +++ b/addons/delivery/views/delivery_view.xml @@ -4,6 +4,22 @@ + + delivery.carrier.search + delivery.carrier + + + + + + + + + + + + + delivery.carrier.tree delivery.carrier @@ -23,6 +39,13 @@
+
+ +