diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index 4da13596b88..b7c1985719f 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -503,16 +503,19 @@ class AccountInvoice(models.Model): """ res = super(AccountInvoice, self).default_get(default_fields) - if not res.get('type', False) == 'out_invoice' or not 'company_id' in res: + if res.get('type', False) not in ('out_invoice', 'in_refund') or not 'company_id' in res: return res - company = self.env['res.company'].browse(res['company_id']) - if company.partner_id: - partner_bank_result = self.env['res.partner.bank'].search([('partner_id', '=', company.partner_id.id)], limit=1) - if partner_bank_result: - res['partner_bank_id'] = partner_bank_result.id + partner_bank_result = self._get_partner_bank_id(res['company_id']) + if partner_bank_result: + res['partner_bank_id'] = partner_bank_result.id return res + def _get_partner_bank_id(self, company_id): + company = self.env['res.company'].browse(company_id) + if company.partner_id: + return self.env['res.partner.bank'].search([('partner_id', '=', company.partner_id.id)], limit=1) + @api.model def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False): def get_view_id(xid, name): @@ -1369,6 +1372,11 @@ class AccountInvoice(models.Model): values['payment_term_id'] = False values['refund_invoice_id'] = invoice.id + if values['type'] == 'in_refund': + partner_bank_result = self._get_partner_bank_id(values['company_id']) + if partner_bank_result: + values['partner_bank_id'] = partner_bank_result.id + if date: values['date'] = date if description: diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index d61b2aa60f2..2c29947ec6c 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -143,8 +143,9 @@ class AccountMove(models.Model): def post(self): invoice = self._context.get('invoice', False) self._post_validate() + # Create the analytic lines in batch is faster as it leads to less cache invalidation. + self.mapped('line_ids').create_analytic_lines() for move in self: - move.line_ids.create_analytic_lines() if move.name == '/': new_name = False journal = move.journal_id diff --git a/addons/account/tests/test_account_supplier_invoice.py b/addons/account/tests/test_account_supplier_invoice.py index d1a6ef46902..04baf780631 100644 --- a/addons/account/tests/test_account_supplier_invoice.py +++ b/addons/account/tests/test_account_supplier_invoice.py @@ -108,3 +108,39 @@ class TestAccountSupplierInvoice(AccountingTestCase): #I cancel the account move which is in posted state and verifies that it gives warning message with self.assertRaises(Warning): invoice.move_id.button_cancel() + + def test_vendor_bill_refund(self): + invoice_account = self.env['account.account'].search( + [('user_type_id', '=', self.env.ref('account.data_account_type_receivable').id)], limit=1) + invoice_line_account = self.env['account.account'].search( + [('user_type_id', '=', self.env.ref('account.data_account_type_expenses').id)], limit=1) + + if self.env.ref('base.main_partner').bank_account_count > 0: + bank = self.env['res.partner.bank'].search([('partner_id', '=', self.env.ref('base.main_partner').id)], limit=1) + + else: + bank = self.env['res.partner.bank'].create({ + 'acc_number': '12345678910', + 'partner_id': self.env.ref('base.main_partner').id, + }) + + invoice_id = self.env['account.invoice'].create({ + 'name': 'invoice test refund', + 'reference_type': 'none', + 'partner_id': self.env.ref("base.res_partner_2").id, + 'account_id': invoice_account.id, + 'currency_id': self.env.ref('base.USD').id, + 'type': 'in_invoice', + }) + self.env['account.invoice.line'].create({ + 'product_id': self.env.ref("product.product_product_4").id, + 'quantity': 1, + 'price_unit': 15.0, + 'invoice_id': invoice_id.id, + 'name': 'something', + 'account_id': invoice_line_account.id, + }) + + refund_invoices = invoice_id.refund() + + self.assertEqual(refund_invoices.partner_bank_id, bank) diff --git a/addons/account_asset/models/account.py b/addons/account_asset/models/account.py index 7a2a3bc4968..1ad55554061 100644 --- a/addons/account_asset/models/account.py +++ b/addons/account_asset/models/account.py @@ -18,7 +18,5 @@ class AccountMove(models.Model): @api.multi def post(self): - for move in self: - for depreciation_line in move.asset_depreciation_ids: - depreciation_line.post_lines_and_close_asset() + self.mapped('asset_depreciation_ids').post_lines_and_close_asset() return super(AccountMove, self).post() diff --git a/addons/account_asset/models/account_asset.py b/addons/account_asset/models/account_asset.py index 85c2c659119..7abf9acd7cd 100644 --- a/addons/account_asset/models/account_asset.py +++ b/addons/account_asset/models/account_asset.py @@ -500,8 +500,12 @@ class AccountAssetDepreciationLine(models.Model): @api.multi def create_move(self, post_move=True): created_moves = self.env['account.move'] + # `line.move_id` was invalidated from the cache at each iteration + # To prevent to refetch `move_id` of all lines at each iteration just to check a UserError, + # we use an intermediar dict which stores the information the UserError check requires. + line_moves = {line: line.move_id for line in self} for line in self: - if line.move_id: + if line_moves[line]: raise UserError(_('This depreciation is already linked to a journal entry! Please post or delete it.')) move_vals = self._prepare_move(line) move = self.env['account.move'].create(move_vals) @@ -547,6 +551,7 @@ class AccountAssetDepreciationLine(models.Model): 'line_ids': [(0, 0, move_line_1), (0, 0, move_line_2)], } return move_vals + line_moves[line] = move def _prepare_move_grouped(self): category_id = self[0].asset_id.category_id # we can suppose that all lines have the same category @@ -602,12 +607,19 @@ class AccountAssetDepreciationLine(models.Model): @api.multi def post_lines_and_close_asset(self): # we re-evaluate the assets to determine whether we can close them + # `message_post` invalidates the (whole) cache + # preprocess the assets and lines in which a message should be posted, + # and then post in batch will prevent the re-fetch of the same data over and over. + assets_to_close = self.env['account.asset.asset'] for line in self: - line.log_message_when_posted() asset = line.asset_id if asset.currency_id.is_zero(asset.value_residual): - asset.message_post(body=_("Document closed.")) - asset.write({'state': 'close'}) + assets_to_close |= asset + self.log_message_when_posted() + assets_to_close.write({'state': 'close'}) + for asset in assets_to_close: + asset.message_post(body=_("Document closed.")) + @api.multi def log_message_when_posted(self): @@ -620,6 +632,10 @@ class AccountAssetDepreciationLine(models.Model): message += '%s' % values return message + # `message_post` invalidates the (whole) cache + # preprocess the assets in which messages should be posted, + # and then post in batch will prevent the re-fetch of the same data over and over. + assets_to_post = {} for line in self: if line.move_id and line.move_id.state == 'draft': partner_name = line.asset_id.partner_id.name @@ -628,7 +644,10 @@ class AccountAssetDepreciationLine(models.Model): if partner_name: msg_values[_('Partner')] = partner_name msg = _format_message(_('Depreciation line posted.'), msg_values) - line.asset_id.message_post(body=msg) + assets_to_post.setdefault(line.asset_id, []).append(msg) + for asset, messages in assets_to_post.items(): + for msg in messages: + asset.message_post(body=msg) @api.multi def unlink(self): diff --git a/addons/barcodes/static/src/js/barcode_events.js b/addons/barcodes/static/src/js/barcode_events.js index 0f2849525de..70463cd590e 100644 --- a/addons/barcodes/static/src/js/barcode_events.js +++ b/addons/barcodes/static/src/js/barcode_events.js @@ -56,20 +56,18 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, { this.isChromeMobile = isMobile && navigator.userAgent.match(/Chrome/i); // Creates an input who will receive the barcode scanner value. - if (this.isChromeMobile) { - this.$barcodeInput = $('', { - name: 'barcode', - type: 'text', - css: { - 'position': 'fixed', - 'top': '50%', - 'transform': 'translateY(-50%)', - 'z-index': '-1', - }, - }); - // Avoid to show autocomplete for a non appearing input - this.$barcodeInput.attr('autocomplete', 'off'); - } + this.$barcodeInput = $('', { + name: 'barcode', + type: 'text', + css: { + 'position': 'fixed', + 'top': '50%', + 'transform': 'translateY(-50%)', + 'z-index': '-1', + }, + }); + // Avoid to show autocomplete for a non appearing input + this.$barcodeInput.attr('autocomplete', 'off'); this.__blurBarcodeInput = _.debounce(this._blurBarcodeInput, this.inputTimeOut); }, @@ -220,7 +218,8 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, { * @param {jQuery.Event} e keydown event */ _listenBarcodeScanner: function (e) { - if (!$('input:text:focus, textarea:focus, [contenteditable]:focus').length) { + if ($(document.activeElement).not('input:text, textarea, [contenteditable], ' + + '[type="email"], [type="number"], [type="password"], [type="tel"]').length) { $('body').append(this.$barcodeInput); this.$barcodeInput.focus(); } @@ -263,11 +262,9 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, { * @private */ _blurBarcodeInput: function () { - if (this.$barcodeInput) { - // Close the virtual keyboard on mobile browsers - // FIXME: actually we can't prevent keyboard from opening - this.$barcodeInput.val('').blur(); - } + // Close the virtual keyboard on mobile browsers + // FIXME: actually we can't prevent keyboard from opening + this.$barcodeInput.val('').blur(); }, start: function(prevent_key_repeat){ diff --git a/addons/barcodes/static/tests/barcode_mobile_tests.js b/addons/barcodes/static/tests/barcode_mobile_tests.js new file mode 100644 index 00000000000..cb26da64c74 --- /dev/null +++ b/addons/barcodes/static/tests/barcode_mobile_tests.js @@ -0,0 +1,74 @@ +odoo.define('barcodes.barcode_mobile_tests', function () { + "use strict"; + + QUnit.module('Barcodes', {}, function () { + + QUnit.module('Barcodes Mobile'); + + QUnit.test('barcode field automatically focus behavior', function (assert) { + assert.expect(10); + + // Mock Chrome mobile environment + var barcodeEvents = odoo.__DEBUG__.services["barcodes.BarcodeEvents"].BarcodeEvents; + var __isChromeMobile = barcodeEvents.isChromeMobile; + barcodeEvents.isChromeMobile = true; + // Rebind keyboard events + barcodeEvents.stop(); + barcodeEvents.start(); + + var $form = $( + '
'); + $('#qunit-fixture').append($form); + + // Some elements doesn't need to keep the focus + $('body').keydown(); + assert.strictEqual(document.activeElement.name, 'barcode', + "hidden barcode input should have the focus"); + + var $element = $form.find('select'); + $element.focus().keydown(); + assert.strictEqual(document.activeElement.name, 'barcode', + "hidden barcode input should have the focus"); + + // Those elements absolutely need to keep the focus: + // inputs elements: + var keepFocusedElements = ['email', 'number', 'password', 'tel', + 'text', 'explicit_text']; + for (var i = 0; i < keepFocusedElements.length; ++i) { + $element = $form.find('input[name=' + keepFocusedElements[i] + ']'); + $element.focus().keydown(); + assert.strictEqual(document.activeElement, $element[0], + "input " + keepFocusedElements[i] + " should keep focus"); + } + // textarea element + $element = $form.find('textarea'); + $element.focus().keydown(); + assert.strictEqual(document.activeElement, $element[0], + "textarea should keep focus"); + // contenteditable elements + $element = $form.find('[contenteditable=true]'); + $element.focus().keydown(); + assert.strictEqual(document.activeElement, $element[0], + "contenteditable should keep focus"); + + $('#qunit-fixture').empty(); + barcodeEvents.isChromeMobile = __isChromeMobile; + // Rebind keyboard events + barcodeEvents.stop(); + barcodeEvents.start(); + }); + }); + }); diff --git a/addons/barcodes/views/barcodes_templates.xml b/addons/barcodes/views/barcodes_templates.xml index cb16a40c178..45b77bd3301 100644 --- a/addons/barcodes/views/barcodes_templates.xml +++ b/addons/barcodes/views/barcodes_templates.xml @@ -16,4 +16,10 @@ + +